diff --git a/README.md b/README.md
index 9793fcf90..ed8d161cb 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,36 @@ OkHttp uses the platform's [ProxySelector][2]. Prior to Android 4.0, `ProxySelec
OkHttp's test suite creates an in-process HTTPS server. Prior to Android 2.3, SSL server sockets were broken, and so HTTPS tests will time out when run on such devices.
+Building
+--------
+
+### On the Desktop
+Run OkHttp tests on the desktop with Maven.
+```
+mvn clean test
+```
+SPDY support uses a Deflater API that wasn't available in Java 6. For this reason SPDY tests will fail with this error: `Cannot SPDY; no SYNC_FLUSH available`. All other tests should run fine.
+
+### On the Desktop with NPN
+Using NPN on the desktop uses [Jetty-NPN](http://wiki.eclipse.org/Jetty/Feature/NPN) which requires OpenJDK 7+.
+```
+mvn clean test -Pspdy-tls
+```
+
+### On a Device
+Test on a USB-attached Android using [Vogar](https://code.google.com/p/vogar/). Unfortunately `dx` requires that you build with Java 6, otherwise the test class will be silently omitted from the `.dex` file.
+```
+mvn clean
+mvn package -DskipTests
+vogar \
+ --classpath ~/.m2/repository/org/bouncycastle/bcprov-jdk15on/1.47/bcprov-jdk15on-1.47.jar \
+ --classpath ~/.m2/repository/com/google/mockwebserver/mockwebserver/20121111/mockwebserver-20121111.jar \
+ --classpath target/okhttp-0.8-SNAPSHOT.jar \
+ ./src/test/java/libcore/net/http/URLConnectionTest.java
+```
+Because the OkHttp uses `jarjar` to repackage classes in `libcore`, OkHttp tests that use those classes directly cannot be run on a device.
+
+
License
-------
diff --git a/pom.xml b/pom.xml
index b95912d27..c7381d549 100644
--- a/pom.xml
+++ b/pom.xml
@@ -68,6 +68,7 @@
org.mortbay.jetty.npn
npn-boot
${npn.version}
+ true
com.google.mockwebserver
@@ -125,45 +126,6 @@
-
- org.apache.maven.plugins
- maven-surefire-plugin
- 2.9
-
- -Xbootclasspath/p:${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${npn.version}/npn-boot-${npn.version}.jar
-
-
-
-
- org.apache.maven.plugins
- maven-enforcer-plugin
- 1.1
-
-
- enforce-java
-
- enforce
-
-
-
-
- [1.7.0,)
-
-
-
-
-
-
org.apache.maven.plugins
maven-checkstyle-plugin
@@ -185,5 +147,23 @@
+
+
+
+ spdy-tls
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.9
+
+ -Xbootclasspath/p:${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${npn.version}/npn-boot-${npn.version}.jar
+
+
+
+
+
+
diff --git a/src/main/java/libcore/Platform.java b/src/main/java/libcore/Platform.java
new file mode 100644
index 000000000..5b5ae924c
--- /dev/null
+++ b/src/main/java/libcore/Platform.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2012 Square, Inc.
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package libcore;
+
+import com.squareup.okhttp.OkHttpsConnection;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+import javax.net.ssl.SSLSocket;
+
+/**
+ * Access to Platform-specific features necessary for SPDY and advanced TLS.
+ *
+ * SPDY
+ * SPDY requires a TLS extension called NPN (Next Protocol Negotiation) that's
+ * available in Android 4.1+ and OpenJDK 7+ (with the npn-boot extension). It
+ * also requires a recent version of {@code DeflaterOutputStream} that is
+ * public API in Java 7 and callable via reflection in Android 4.1+.
+ */
+public class Platform {
+ private static final Platform platform = findPlatform();
+
+ private Constructor deflaterConstructor;
+
+ public static Platform get() {
+ return platform;
+ }
+
+ public void makeTlsTolerant(SSLSocket socket, String uriHost, boolean tlsTolerant) {
+ if (!tlsTolerant) {
+ socket.setEnabledProtocols(new String[]{"SSLv3"});
+ }
+ }
+
+ /**
+ * Returns the negotiated protocol, or null if no protocol was negotiated.
+ */
+ public byte[] getNpnSelectedProtocol(SSLSocket socket) {
+ return null;
+ }
+
+ /**
+ * Sets client-supported protocols on a socket to send to a server. The
+ * protocols are only sent if the socket implementation supports NPN.
+ */
+ public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) {
+ }
+
+ /**
+ * Returns a deflater output stream that supports SYNC_FLUSH for SPDY name
+ * value blocks. This throws an {@link UnsupportedOperationException} on
+ * Java 6 and earlier where there is no built-in API to do SYNC_FLUSH.
+ */
+ public OutputStream newDeflaterOutputStream(
+ OutputStream out, Deflater deflater, boolean syncFlush) {
+ try {
+ Constructor constructor = deflaterConstructor;
+ if (constructor == null) {
+ constructor = deflaterConstructor = DeflaterOutputStream.class.getConstructor(
+ OutputStream.class, Deflater.class, boolean.class);
+ }
+ return constructor.newInstance(out, deflater, syncFlush);
+ } catch (NoSuchMethodException e) {
+ throw new UnsupportedOperationException("Cannot SPDY; no SYNC_FLUSH available");
+ } catch (InvocationTargetException e) {
+ throw e.getCause() instanceof RuntimeException
+ ? (RuntimeException) e.getCause()
+ : new RuntimeException(e.getCause());
+ } catch (InstantiationException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Attempt to match the host runtime to a capable Platform implementation.
+ */
+ private static Platform findPlatform() {
+ // Attempt to find Android 2.3+ APIs.
+ Class> openSslSocketClass;
+ Method setUseSessionTickets;
+ Method setHostname;
+ try {
+ openSslSocketClass = Class.forName(
+ "org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl");
+ setUseSessionTickets = openSslSocketClass.getMethod(
+ "setUseSessionTickets", boolean.class);
+ setHostname = openSslSocketClass.getMethod("setHostname", String.class);
+
+ // Attempt to find Android 4.1+ APIs.
+ try {
+ Method setNpnProtocols = openSslSocketClass.getMethod(
+ "setNpnProtocols", byte[].class);
+ Method getNpnSelectedProtocol = openSslSocketClass.getMethod(
+ "getNpnSelectedProtocol");
+ return new Android41(openSslSocketClass, setUseSessionTickets, setHostname,
+ setNpnProtocols, getNpnSelectedProtocol);
+ } catch (NoSuchMethodException ignored) {
+ return new Android23(openSslSocketClass, setUseSessionTickets, setHostname);
+ }
+ } catch (ClassNotFoundException ignored) {
+ // This isn't an Android runtime.
+ } catch (NoSuchMethodException ignored) {
+ // This isn't Android 2.3 or better.
+ }
+
+ // Attempt to find the Jetty's NPN extension for OpenJDK.
+ try {
+ String npnClassName = "org.eclipse.jetty.npn.NextProtoNego";
+ Class> nextProtoNegoClass = Class.forName(npnClassName);
+ Class> providerClass = Class.forName(npnClassName + "$Provider");
+ Class> clientProviderClass = Class.forName(npnClassName + "$ClientProvider");
+ Method putMethod = nextProtoNegoClass.getMethod("put", SSLSocket.class, providerClass);
+ Method getMethod = nextProtoNegoClass.getMethod("get", SSLSocket.class);
+ return new JdkWithJettyNpnPlatform(putMethod, getMethod, clientProviderClass);
+ } catch (ClassNotFoundException ignored) {
+ return new Platform(); // NPN isn't on the classpath.
+ } catch (NoSuchMethodException ignored) {
+ return new Platform(); // The NPN version isn't what we expect.
+ }
+ }
+
+ /**
+ * Android version 2.3 and newer support TLS session tickets and server name
+ * indication (SNI).
+ */
+ private static class Android23 extends Platform {
+ protected final Class> openSslSocketClass;
+ private final Method setUseSessionTickets;
+ private final Method setHostname;
+
+ private Android23(Class> openSslSocketClass, Method setUseSessionTickets,
+ Method setHostname) {
+ this.openSslSocketClass = openSslSocketClass;
+ this.setUseSessionTickets = setUseSessionTickets;
+ this.setHostname = setHostname;
+ }
+
+ @Override public void makeTlsTolerant(
+ SSLSocket socket, String uriHost, boolean tlsTolerant) {
+ super.makeTlsTolerant(socket, uriHost, tlsTolerant);
+ if (tlsTolerant && openSslSocketClass.isInstance(socket)) {
+ // This is Android: use reflection on OpenSslSocketImpl.
+ try {
+ setUseSessionTickets.invoke(socket, true);
+ setHostname.invoke(socket, uriHost);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Android version 4.1 and newer support NPN.
+ */
+ private static class Android41 extends Android23 {
+ private final Method setNpnProtocols;
+ private final Method getNpnSelectedProtocol;
+
+ private Android41(Class> openSslSocketClass, Method setUseSessionTickets,
+ Method setHostname, Method setNpnProtocols, Method getNpnSelectedProtocol) {
+ super(openSslSocketClass, setUseSessionTickets, setHostname);
+ this.setNpnProtocols = setNpnProtocols;
+ this.getNpnSelectedProtocol = getNpnSelectedProtocol;
+ }
+
+ @Override public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) {
+ if (!openSslSocketClass.isInstance(socket)) {
+ return;
+ }
+ try {
+ setNpnProtocols.invoke(socket, new Object[] {npnProtocols});
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override public byte[] getNpnSelectedProtocol(SSLSocket socket) {
+ if (!openSslSocketClass.isInstance(socket)) {
+ return null;
+ }
+ try {
+ return (byte[]) getNpnSelectedProtocol.invoke(socket);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ /**
+ * OpenJDK 7 plus {@code org.mortbay.jetty.npn/npn-boot} on the boot class
+ * path.
+ */
+ private static class JdkWithJettyNpnPlatform extends Platform {
+ private final Method getMethod;
+ private final Method putMethod;
+ private final Class> clientProviderClass;
+
+ public JdkWithJettyNpnPlatform(
+ Method putMethod, Method getMethod, Class> clientProviderClass) {
+ this.putMethod = putMethod;
+ this.getMethod = getMethod;
+ this.clientProviderClass = clientProviderClass;
+ }
+
+ @Override public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) {
+ try {
+ List strings = new ArrayList();
+ for (int i = 0; i < npnProtocols.length;) {
+ int length = npnProtocols[i++];
+ strings.add(new String(npnProtocols, i, length, "US-ASCII"));
+ i += length;
+ }
+ Object provider = Proxy.newProxyInstance(Platform.class.getClassLoader(),
+ new Class[] { clientProviderClass }, new JettyNpnProvider(strings));
+ putMethod.invoke(null, socket, provider);
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError(e);
+ } catch (InvocationTargetException e) {
+ throw new AssertionError(e);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override public byte[] getNpnSelectedProtocol(SSLSocket socket) {
+ try {
+ JettyNpnProvider provider = (JettyNpnProvider) Proxy.getInvocationHandler(
+ getMethod.invoke(null, socket));
+ if (!provider.unsupported && provider.selected == null) {
+ Logger logger = Logger.getLogger(OkHttpsConnection.class.getName());
+ logger.log(Level.INFO, "NPN callback dropped so SPDY is disabled. "
+ + "Is npn-boot on the boot class path?");
+ return null;
+ }
+ return provider.unsupported
+ ? null
+ : provider.selected.getBytes("US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError();
+ } catch (InvocationTargetException e) {
+ throw new AssertionError();
+ } catch (IllegalAccessException e) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ /**
+ * Handle the methods of NextProtoNego's ClientProvider and ServerProvider
+ * without a compile-time dependency on those interfaces.
+ */
+ private static class JettyNpnProvider implements InvocationHandler {
+ private final List clientProtocols;
+ private boolean unsupported;
+ private String selected;
+
+ public JettyNpnProvider(List clientProtocols) {
+ this.clientProtocols = clientProtocols;
+ }
+
+ @Override public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ String methodName = method.getName();
+ Class> returnType = method.getReturnType();
+ if (methodName.equals("supports") && boolean.class == returnType) {
+ return true;
+ } else if (methodName.equals("unsupported") && void.class == returnType) {
+ this.unsupported = true;
+ return null;
+ } else if (methodName.equals("selectProtocol") && String.class == returnType
+ && args.length == 1 && (args[0] == null || args[0] instanceof List)) {
+ // TODO: use OpenSSL's algorithm which uses both lists
+ List> serverProtocols = (List) args[0];
+ System.out.println("CLIENT PROTOCOLS: " + clientProtocols + " SERVER PROTOCOLS: " + serverProtocols);
+ this.selected = clientProtocols.get(0);
+ return selected;
+ } else {
+ return method.invoke(this, args);
+ }
+ }
+ }
+}
diff --git a/src/main/java/libcore/net/http/HttpConnection.java b/src/main/java/libcore/net/http/HttpConnection.java
index dd8b7e79e..2fd27935d 100644
--- a/src/main/java/libcore/net/http/HttpConnection.java
+++ b/src/main/java/libcore/net/http/HttpConnection.java
@@ -33,6 +33,7 @@ import java.util.Arrays;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
+import libcore.Platform;
import libcore.io.IoUtils;
import libcore.net.spdy.SpdyConnection;
import libcore.util.Libcore;
@@ -131,6 +132,8 @@ final class HttpConnection {
* validation.
*/
private void upgradeToTls(TunnelConfig tunnelConfig) throws IOException {
+ Platform platform = Platform.get();
+
// Make an SSL Tunnel on the first message pair of each SSL + proxy connection.
if (requiresTunnel()) {
makeTunnel(tunnelConfig);
@@ -140,10 +143,10 @@ final class HttpConnection {
socket = address.sslSocketFactory.createSocket(
socket, address.uriHost, address.uriPort, true /* autoClose */);
SSLSocket sslSocket = (SSLSocket) socket;
- Libcore.makeTlsTolerant(sslSocket, address.uriHost, tlsMode == TLS_MODE_AGGRESSIVE);
+ platform.makeTlsTolerant(sslSocket, address.uriHost, tlsMode == TLS_MODE_AGGRESSIVE);
if (tlsMode == TLS_MODE_AGGRESSIVE) {
- Libcore.setNpnProtocols(sslSocket, NPN_PROTOCOLS);
+ platform.setNpnProtocols(sslSocket, NPN_PROTOCOLS);
}
// Force handshake. This can throw!
@@ -159,7 +162,7 @@ final class HttpConnection {
byte[] selectedProtocol;
if (tlsMode == TLS_MODE_AGGRESSIVE
- && (selectedProtocol = Libcore.getNpnSelectedProtocol(sslSocket)) != null) {
+ && (selectedProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) {
if (Arrays.equals(selectedProtocol, SPDY2)) {
spdyConnection = new SpdyConnection.Builder(true, in, out).build();
HttpConnectionPool.INSTANCE.share(this);
diff --git a/src/main/java/libcore/net/spdy/SpdyWriter.java b/src/main/java/libcore/net/spdy/SpdyWriter.java
index b62011cec..cc0f66ec0 100644
--- a/src/main/java/libcore/net/spdy/SpdyWriter.java
+++ b/src/main/java/libcore/net/spdy/SpdyWriter.java
@@ -22,7 +22,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.zip.Deflater;
-import java.util.zip.DeflaterOutputStream;
+import libcore.Platform;
/**
* Write version 2 SPDY frames.
@@ -39,7 +39,7 @@ final class SpdyWriter {
deflater.setDictionary(SpdyReader.DICTIONARY);
nameValueBlockBuffer = new ByteArrayOutputStream();
nameValueBlockOut = new DataOutputStream(
- new DeflaterOutputStream(nameValueBlockBuffer, deflater, true));
+ Platform.get().newDeflaterOutputStream(nameValueBlockBuffer, deflater, true));
}
public void synStream(int flags, int streamId, int associatedStreamId, int priority,
diff --git a/src/main/java/libcore/util/Libcore.java b/src/main/java/libcore/util/Libcore.java
index 70531878b..808933bab 100644
--- a/src/main/java/libcore/util/Libcore.java
+++ b/src/main/java/libcore/util/Libcore.java
@@ -18,19 +18,12 @@ package libcore.util;
import java.io.File;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.net.Socket;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.ByteOrder;
-import java.util.ArrayList;
-import java.util.List;
-import javax.net.ssl.SSLSocket;
-import org.eclipse.jetty.npn.NextProtoNego;
/**
* APIs for interacting with Android's core library. This mostly emulates the
@@ -41,143 +34,6 @@ public final class Libcore {
private Libcore() {
}
- private static boolean useAndroidTlsApis;
- private static Class> openSslSocketClass;
- private static Method setUseSessionTickets;
- private static Method setHostname;
- private static boolean android23TlsOptionsAvailable;
- private static Method setNpnProtocols;
- private static Method getNpnSelectedProtocol;
- private static boolean android41TlsOptionsAvailable;
-
- static {
- try {
- openSslSocketClass = Class.forName(
- "org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl");
- useAndroidTlsApis = true;
- setUseSessionTickets = openSslSocketClass.getMethod(
- "setUseSessionTickets", boolean.class);
- setHostname = openSslSocketClass.getMethod("setHostname", String.class);
- android23TlsOptionsAvailable = true;
- setNpnProtocols = openSslSocketClass.getMethod("setNpnProtocols", byte[].class);
- getNpnSelectedProtocol = openSslSocketClass.getMethod("getNpnSelectedProtocol");
- android41TlsOptionsAvailable = true;
- } catch (ClassNotFoundException ignored) {
- // This isn't an Android runtime.
- } catch (NoSuchMethodException ignored) {
- // This Android runtime is missing some optional TLS options.
- }
- }
-
- public static void makeTlsTolerant(SSLSocket socket, String uriHost, boolean tlsTolerant) {
- if (!tlsTolerant) {
- socket.setEnabledProtocols(new String[] {"SSLv3"});
- return;
- }
-
- if (android23TlsOptionsAvailable && openSslSocketClass.isInstance(socket)) {
- // This is Android: use reflection on OpenSslSocketImpl.
- try {
- setUseSessionTickets.invoke(socket, true);
- setHostname.invoke(socket, uriHost);
- } catch (InvocationTargetException e) {
- throw new RuntimeException(e);
- } catch (IllegalAccessException e) {
- throw new AssertionError(e);
- }
- }
- }
-
- /**
- * Returns the negotiated protocol, or null if no protocol was negotiated.
- */
- public static byte[] getNpnSelectedProtocol(SSLSocket socket) {
- if (useAndroidTlsApis) {
- // This is Android: use reflection on OpenSslSocketImpl.
- if (android41TlsOptionsAvailable && openSslSocketClass.isInstance(socket)) {
- try {
- return (byte[]) getNpnSelectedProtocol.invoke(socket);
- } catch (InvocationTargetException e) {
- throw new RuntimeException(e);
- } catch (IllegalAccessException e) {
- throw new AssertionError(e);
- }
- }
- return null;
- } else {
- // This is OpenJDK: use JettyNpnProvider.
- JettyNpnProvider provider = (JettyNpnProvider) NextProtoNego.get(socket);
- if (!provider.unsupported && provider.selected == null) {
- throw new IllegalStateException(
- "No callback received. Is NPN configured properly?");
- }
- try {
- return provider.unsupported
- ? null
- : provider.selected.getBytes("US-ASCII");
- } catch (UnsupportedEncodingException e) {
- throw new AssertionError(e);
- }
- }
- }
-
- public static void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) {
- if (useAndroidTlsApis) {
- // This is Android: use reflection on OpenSslSocketImpl.
- if (android41TlsOptionsAvailable && openSslSocketClass.isInstance(socket)) {
- try {
- setNpnProtocols.invoke(socket, new Object[] {npnProtocols});
- } catch (IllegalAccessException e) {
- throw new AssertionError(e);
- } catch (InvocationTargetException e) {
- throw new RuntimeException(e);
- }
- }
- } else {
- // This is OpenJDK: use JettyNpnProvider.
- try {
- List strings = new ArrayList();
- for (int i = 0; i < npnProtocols.length;) {
- int length = npnProtocols[i++];
- strings.add(new String(npnProtocols, i, length, "US-ASCII"));
- i += length;
- }
- JettyNpnProvider provider = new JettyNpnProvider();
- provider.protocols = strings;
- NextProtoNego.put(socket, provider);
- } catch (UnsupportedEncodingException e) {
- throw new AssertionError(e);
- }
- }
- }
-
- private static class JettyNpnProvider
- implements NextProtoNego.ClientProvider, NextProtoNego.ServerProvider {
- List protocols;
- boolean unsupported;
- String selected;
-
- @Override public boolean supports() {
- return true;
- }
- @Override public List protocols() {
- return protocols;
- }
- @Override public void unsupported() {
- this.unsupported = true;
- }
- @Override public void protocolSelected(String selected) {
- this.selected = selected;
- }
- @Override public String selectProtocol(List strings) {
- // TODO: use OpenSSL's algorithm which uses 2 lists
- System.out.println("CLIENT PROTOCOLS: " + protocols + " SERVER PROTOCOLS: " + strings);
- String selected = protocols.get(0);
- protocolSelected(selected);
- return selected;
- }
- }
-
public static void deleteIfExists(File file) throws IOException {
// okhttp-changed: was Libcore.os.remove() in a try/catch block
file.delete();