/** * Copyright 2015 Google Inc. All Rights Reserved. * * 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 com.google.apphosting.tests.usercode.testservlets; import com.google.appengine.api.socket.SocketServicePb.AcceptReply; import com.google.appengine.api.socket.SocketServicePb.AddressPort; import com.google.appengine.api.socket.SocketServicePb.CloseReply; import com.google.appengine.api.socket.SocketServicePb.ConnectReply; import com.google.appengine.api.socket.SocketServicePb.CreateSocketReply; import com.google.appengine.api.socket.SocketServicePb.GetSocketNameReply; import com.google.appengine.api.socket.SocketServicePb.ListenReply; import com.google.appengine.api.socket.SocketServicePb.ReceiveReply; import com.google.appengine.api.socket.SocketServicePb.ReceiveRequest; import com.google.appengine.api.socket.SocketServicePb.SendReply; import com.google.appengine.api.socket.SocketServicePb.SetSocketOptionsReply; import com.google.appengine.api.socket.SocketServicePb.ShutDownReply; import com.google.apphosting.api.ApiProxy; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.net.SocketImpl; import java.net.SocketImplFactory; import java.net.UnknownHostException; import java.util.Arrays; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** */ public class TestSocketServlet extends HttpServletTest { /** * Set up a mock delegate to handle resolve calls. */ private ApiProxy.Delegate setUpMockDelegate() { ApiProxy.Delegate oldDelegate = ApiProxy.getDelegate(); ApiProxy.setDelegate(new MockDelegate()); return oldDelegate; } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ApiProxy.Delegate oldDelegate = setUpMockDelegate(); response.setContentType("text/plain"); try { testConnectWriteAndRead(response); testTimedConnect(response); testSocketOpt(response); testSetSocketImpl(response); testShutDownAndClose(response); testProxyConstructor(response); testSocketImplConstructor(response); } catch (AssertionFailedException e) { return; //return the error response } finally { ApiProxy.setDelegate(oldDelegate); } response.getWriter().print("Success!"); } private void testShutDownAndClose(HttpServletResponse response) throws UnknownHostException, IOException { Socket socket = new Socket("10.1.1.1", 80); socket.shutdownInput(); socket.shutdownOutput(); socket.close(); } private void testSetSocketImpl(HttpServletResponse response) throws IOException, AssertionFailedException { SocketImplFactory mockFactory = new SocketImplFactory() { @Override public SocketImpl createSocketImpl() { return null; } }; SocketException caught = null; try { Socket.setSocketImplFactory(mockFactory); } catch (SocketException e) { caught = e; } assertNotNull("caught", caught, response); } private void testSocketOpt(HttpServletResponse response) throws UnknownHostException, IOException { Socket socket = new Socket("10.1.1.1", 80); socket.setSoTimeout(10); } private byte[] readSomeData(Socket socket, int size) throws IOException { byte[] data = new byte[10]; socket.getInputStream().read(data); return data; } private void testTimedConnect(HttpServletResponse response) throws IOException, AssertionFailedException { Socket socket = new Socket(); socket.connect(new InetSocketAddress(InetAddress.getByName("10.1.1.1"), 80), 10); byte[] data = readSomeData(socket, 10); assertEquals("socket read", "aaaaaaaaaa", new String(data), response); } private void testConnectWriteAndRead(HttpServletResponse response) throws IOException, AssertionFailedException { Socket socket = new Socket("10.1.1.1", 80); byte[] data = readSomeData(socket, 10); assertEquals("socket read", "aaaaaaaaaa", new String(data), response); socket.getOutputStream().write(data); } private void testProxyConstructor(HttpServletResponse response) throws IOException, AssertionFailedException { Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(InetAddress.getByName("10.1.1.1"), 80)); try { Socket socket = new Socket(proxy); assertTrue("Expected SecurityException when creating SOCKS socket.", false, response); } catch (SecurityException e) { // OK } Socket socket = new Socket(Proxy.NO_PROXY); socket.connect(new InetSocketAddress(InetAddress.getByName("10.1.1.1"), 80), 10); } private void testSocketImplConstructor(HttpServletResponse response) throws IOException, AssertionFailedException { MockSocketImpl mockImpl = new MockSocketImpl(); Socket socket = new Socket(mockImpl) {}; // Accessing protected constructor. try { socket.connect(new InetSocketAddress(InetAddress.getByName("10.1.1.1"), 80), 10); assertTrue( "Expected SecurityException when connecting with a non App Engine SocketImpl.", false, response); } catch (SecurityException e) { // OK } } /** * A mock ApiProxy.Delegate specifically for handling Socket calls. */ static class MockDelegate implements ApiProxy.Delegate<ApiProxy.Environment> { interface Responder { byte[] makeResponse(Method method, byte[] request); } public enum Method { CreateSocket( new Responder() { int descriptorCount = 0; @Override public byte[] makeResponse(Method method, byte[] request) { CreateSocketReply response = new CreateSocketReply(); response.setSocketDescriptor("mock-descriptor:" + ++descriptorCount); return response.toByteArray(); } }), Connect( new Responder() { @Override public byte[] makeResponse(Method method, byte[] request) { return new ConnectReply().toByteArray(); } }), Listen( new Responder() { @Override public byte[] makeResponse(Method method, byte[] request) { return new ListenReply().toByteArray(); } }), Accept( new Responder() { @Override public byte[] makeResponse(Method method, byte[] request) { return new AcceptReply().toByteArray(); } }), Send( new Responder() { @Override public byte[] makeResponse(Method method, byte[] request) { return new SendReply().toByteArray(); } }), Receive( new Responder() { @Override public byte[] makeResponse(Method method, byte[] request) { ReceiveRequest requestProto = new ReceiveRequest(); requestProto.parseFrom(request); ReceiveReply response = new ReceiveReply(); byte[] data = new byte[requestProto.getDataSize()]; Arrays.fill(data, (byte) 'a'); response.setDataAsBytes(data); return response.toByteArray(); } }), Close( new Responder() { @Override public byte[] makeResponse(Method method, byte[] request) { return new CloseReply().toByteArray(); } }), ShutDown( new Responder() { @Override public byte[] makeResponse(Method method, byte[] request) { return new ShutDownReply().toByteArray(); } }), SetSocketOptions( new Responder() { @Override public byte[] makeResponse(Method method, byte[] request) { return new SetSocketOptionsReply().toByteArray(); } }), GetSocketName( new Responder() { @Override public byte[] makeResponse(Method method, byte[] request) { GetSocketNameReply reply = new GetSocketNameReply(); AddressPort externalIp = reply.getMutableProxyExternalIp(); externalIp.setPort(551212); externalIp.setPackedAddressAsBytes(new byte[] {127, 77, 88, 99}); return reply.toByteArray(); } }); Responder responder; Method(Responder responder) { this.responder = responder; } /** * Make a response for this method. */ public byte[] makeResponse(byte[] request) { return responder.makeResponse(this, request); } } /** * Returns a response for a given method and request. */ byte[] makeResponse(String methodName, byte[] request) { Method method = Method.valueOf(Method.class, methodName); if (method == null) { throw new UnsupportedOperationException(); } return method.makeResponse(request); } @Override public byte[] makeSyncCall( ApiProxy.Environment environment, String packageName, String methodName, byte[] request) { if (!"remote_socket".equals(packageName)) { throw new UnsupportedOperationException(); } return makeResponse(methodName, request); } @Override public Future<byte[]> makeAsyncCall( ApiProxy.Environment environment, String packageName, String methodName, byte[] request, ApiProxy.ApiConfig apiConfig) { if ("remote_socket".equals(packageName)) { final byte[] response = makeResponse(methodName, request); return new Future<byte[]>() { @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; } @Override public boolean isCancelled() { return false; } @Override public boolean isDone() { return true; } @Override public byte[] get() throws InterruptedException, ExecutionException { return response; } @Override public byte[] get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return response; } }; } throw new UnsupportedOperationException(); } @Override public void log(ApiProxy.Environment environment, ApiProxy.LogRecord record) {} @Override public void flushLogs(ApiProxy.Environment environment) {} @Override public List<Thread> getRequestThreads(ApiProxy.Environment environment) { return null; } } static class MockSocketImpl extends SocketImpl { /* (non-Javadoc) * @see java.net.SocketOptions#setOption(int, java.lang.Object) */ @Override public void setOption(int optID, Object value) throws SocketException {} /* (non-Javadoc) * @see java.net.SocketOptions#getOption(int) */ @Override public Object getOption(int optID) throws SocketException { return null; } /* (non-Javadoc) * @see java.net.SocketImpl#create(boolean) */ @Override protected void create(boolean stream) throws IOException {} /* (non-Javadoc) * @see java.net.SocketImpl#connect(java.lang.String, int) */ @Override protected void connect(String host, int port) throws IOException {} /* (non-Javadoc) * @see java.net.SocketImpl#connect(java.net.InetAddress, int) */ @Override protected void connect(InetAddress address, int port) throws IOException {} /* (non-Javadoc) * @see java.net.SocketImpl#connect(java.net.SocketAddress, int) */ @Override protected void connect(SocketAddress address, int timeout) throws IOException { } /* (non-Javadoc) * @see java.net.SocketImpl#bind(java.net.InetAddress, int) */ @Override protected void bind(InetAddress host, int port) throws IOException {} /* (non-Javadoc) * @see java.net.SocketImpl#listen(int) */ @Override protected void listen(int backlog) throws IOException {} /* (non-Javadoc) * @see java.net.SocketImpl#accept(java.net.SocketImpl) */ @Override protected void accept(SocketImpl s) throws IOException {} /* (non-Javadoc) * @see java.net.SocketImpl#getInputStream() */ @Override protected InputStream getInputStream() throws IOException { return null; } /* (non-Javadoc) * @see java.net.SocketImpl#getOutputStream() */ @Override protected OutputStream getOutputStream() throws IOException { return null; } /* (non-Javadoc) * @see java.net.SocketImpl#available() */ @Override protected int available() throws IOException { return 0; } /* (non-Javadoc) * @see java.net.SocketImpl#close() */ @Override protected void close() throws IOException {} /* (non-Javadoc) * @see java.net.SocketImpl#sendUrgentData(int) */ @Override protected void sendUrgentData(int data) throws IOException {} } }