/**
* 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.BindReply;
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.RemoteSocketServiceError;
import com.google.appengine.api.socket.SocketServicePb.SendReply;
import com.google.appengine.api.socket.SocketServicePb.SendRequest;
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.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.DatagramSocketImpl;
import java.net.DatagramSocketImplFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.net.SocketException;
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 TestDatagramSocketServlet extends HttpServletTest {
private static final String SEND_STRING = "ssssssssss";
private static final String READ_STRING = "rrrrrrrrrr";
/**
* Set up a mock delegate to socket 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 {
testOpenAndClose(response);
testConnectWriteAndRead(response);
testSocketOpt(response);
testSetDatagramSocketImpl(response);
testSocketImplConstructor(response);
} catch (AssertionFailedException e) {
return;
//return the error response
} finally {
ApiProxy.setDelegate(oldDelegate);
}
response.getWriter().print("Success!");
}
private void testOpenAndClose(HttpServletResponse response) throws IOException {
DatagramSocket socket = new DatagramSocket(0);
socket.close();
}
private void testSetDatagramSocketImpl(HttpServletResponse response)
throws IOException, AssertionFailedException {
DatagramSocketImplFactory mockFactory =
new DatagramSocketImplFactory() {
@Override
public DatagramSocketImpl createDatagramSocketImpl() {
return null;
}
};
SocketException caught = null;
try {
DatagramSocket.setDatagramSocketImplFactory(mockFactory);
} catch (SocketException e) {
caught = e;
}
assertNotNull("caught", caught, response);
}
private void testSocketOpt(HttpServletResponse response) throws IOException {
DatagramSocket socket = new DatagramSocket(0);
socket.setSoTimeout(10);
}
private DatagramPacket readSomeData(DatagramSocket socket, int size) throws IOException {
byte[] data = new byte[size];
DatagramPacket packet = new DatagramPacket(data, data.length);
socket.receive(packet);
return packet;
}
private void testConnectWriteAndRead(HttpServletResponse response)
throws IOException, AssertionFailedException {
DatagramSocket socket = new DatagramSocket(9999);
DatagramPacket packet =
new DatagramPacket(SEND_STRING.getBytes(), 0, 10, InetAddress.getByName("10.1.1.1"), 9999);
socket.send(packet);
packet = readSomeData(socket, 10);
assertEquals("socket packet read data", READ_STRING, new String(packet.getData()), response);
assertEquals("socket packet port", 9999, packet.getPort(), response);
assertEquals(
"socket packet address", InetAddress.getByName("10.1.1.1"), packet.getAddress(), response);
}
private void testSocketImplConstructor(HttpServletResponse response)
throws IOException, AssertionFailedException {
MockSocketImpl mockImpl = new MockSocketImpl();
DatagramSocket socket = new DatagramSocket(mockImpl) {}; // Accessing protected constructor.
try {
socket.connect(InetAddress.getByName("10.1.1.1"), 9999);
assertTrue(
"Expected SecurityException when not using AppEngineDatagramSocketImpl.",
false,
response);
} catch (SecurityException e) {
// OK
}
}
/**
* A mock ApiProxy.Delegate specifically for handling Socket calls.
*/
private static class MockDelegate implements ApiProxy.Delegate<ApiProxy.Environment> {
private static final String DESCRIPTOR_PREFIX = "mock-descriptor:";
interface Responder {
byte[] makeResponse(Method method, byte[] request);
}
public enum Method {
Bind(
new Responder() {
@Override
public byte[] makeResponse(Method method, byte[] request) {
return new BindReply().toByteArray();
}
}),
CreateSocket(
new Responder() {
int descriptorCount = 0;
@Override
public byte[] makeResponse(Method method, byte[] request) {
CreateSocketReply response = new CreateSocketReply();
response.setSocketDescriptor(DESCRIPTOR_PREFIX + ++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) {
SendRequest requestPb = new SendRequest();
requestPb.parseFrom(request);
if (!requestPb.getSocketDescriptor().startsWith(DESCRIPTOR_PREFIX)) {
throw new ApiProxy.ApplicationException(
RemoteSocketServiceError.ErrorCode.SYSTEM_ERROR.getValue(),
"Descriptor not set correctly.");
}
if (!new String(requestPb.getDataAsBytes()).equals(SEND_STRING)) {
throw new ApiProxy.ApplicationException(
RemoteSocketServiceError.ErrorCode.SYSTEM_ERROR.getValue(),
"Incorrect data being sent.");
}
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();
response.setDataAsBytes(READ_STRING.getBytes());
AddressPort addressPort = new AddressPort();
addressPort.setPort(9999);
addressPort.setPackedAddressAsBytes(
new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 10, 1, 1, 1});
response.setReceivedFrom(addressPort);
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.
*/
private 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;
}
}
private static class MockSocketImpl extends DatagramSocketImpl {
@Override
public void setOption(int optID, Object value) throws SocketException {}
@Override
public Object getOption(int optID) throws SocketException {
return null;
}
@Override
protected void create() throws SocketException {}
@Override
protected void bind(int lport, InetAddress laddr) throws SocketException {}
@Override
protected void send(DatagramPacket p) throws IOException {}
@Override
protected int peek(InetAddress i) throws IOException {
return 0;
}
@Override
protected int peekData(DatagramPacket p) throws IOException {
return 0;
}
@Override
protected void receive(DatagramPacket p) throws IOException {}
@Override
protected void setTTL(byte ttl) throws IOException {}
@Override
protected byte getTTL() throws IOException {
return 0;
}
@Override
protected void setTimeToLive(int ttl) throws IOException {}
@Override
protected int getTimeToLive() throws IOException {
return 0;
}
@Override
protected void join(InetAddress inetaddr) throws IOException {}
@Override
protected void leave(InetAddress inetaddr) throws IOException {}
@Override
protected void joinGroup(SocketAddress mcastaddr, NetworkInterface netIf) throws IOException {
}
@Override
protected void leaveGroup(SocketAddress mcastaddr, NetworkInterface netIf) throws IOException {}
@Override
protected void close() {}
}
}