Direct IP connect functionality for AppRTC Android demo.

This allows connecting between clients without using external servers, which is useful to OEMs if they are working in a network without internet connection. Implementation uses custom AppRTCClient that replaces WebSocketRTCClient if roomId looks like an IP. Instead of a web socket, this class uses direct TCP connection between peers as a signaling channel.

Review-Url: https://codereview.webrtc.org/1963053002
Cr-Commit-Position: refs/heads/master@{#12789}
This commit is contained in:
sakal
2016-05-18 03:36:41 -07:00
committed by Commit bot
parent 5a216d0489
commit 299ccdee0c
5 changed files with 1038 additions and 3 deletions

View File

@ -0,0 +1,195 @@
/*
* Copyright 2016 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
package org.appspot.apprtc;
import org.appspot.apprtc.util.LooperExecutor;
import org.appspot.apprtc.util.RobolectricLooperExecutor;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class TCPChannelClientTest {
private static final int PORT = 8888;
/**
* How long we wait before trying to connect to the server. Chosen quite arbitrarily and
* could be made smaller if need be.
*/
private static final int SERVER_WAIT = 10;
private static final int CONNECT_TIMEOUT = 100;
private static final int SEND_TIMEOUT = 100;
private static final int DISCONNECT_TIMEOUT = 100;
private static final String TEST_MESSAGE_SERVER = "Hello, Server!";
private static final String TEST_MESSAGE_CLIENT = "Hello, Client!";
@Mock TCPChannelClient.TCPChannelEvents serverEvents;
@Mock TCPChannelClient.TCPChannelEvents clientEvents;
private RobolectricLooperExecutor executor;
private TCPChannelClient server;
private TCPChannelClient client;
@Before
public void setUp() {
ShadowLog.stream = System.out;
MockitoAnnotations.initMocks(this);
executor = new RobolectricLooperExecutor();
executor.requestStart();
}
@After
public void tearDown() {
verifyNoMoreEvents();
executor.executeAndWait(new Runnable() {
@Override
public void run() {
client.disconnect();
server.disconnect();
}
});
// Stop the executor thread
executor.requestStop();
try {
executor.join();
} catch (InterruptedException e) {
fail(e.getMessage());
}
}
@Test
public void testConnectIPv4() {
setUpIPv4Server();
try {
Thread.sleep(SERVER_WAIT);
} catch (InterruptedException e) {
fail(e.getMessage());
}
setUpIPv4Client();
verify(serverEvents, timeout(CONNECT_TIMEOUT)).onTCPConnected(true);
verify(clientEvents, timeout(CONNECT_TIMEOUT)).onTCPConnected(false);
}
@Test
public void testConnectIPv6() {
setUpIPv6Server();
try {
Thread.sleep(SERVER_WAIT);
} catch (InterruptedException e) {
fail(e.getMessage());
}
setUpIPv6Client();
verify(serverEvents, timeout(CONNECT_TIMEOUT)).onTCPConnected(true);
verify(clientEvents, timeout(CONNECT_TIMEOUT)).onTCPConnected(false);
}
@Test
public void testSendData() {
testConnectIPv4();
executor.executeAndWait(new Runnable() {
@Override
public void run() {
client.send(TEST_MESSAGE_SERVER);
server.send(TEST_MESSAGE_CLIENT);
}
});
verify(serverEvents, timeout(SEND_TIMEOUT)).onTCPMessage(TEST_MESSAGE_SERVER);
verify(clientEvents, timeout(SEND_TIMEOUT)).onTCPMessage(TEST_MESSAGE_CLIENT);
}
@Test
public void testDisconnectServer() {
testConnectIPv4();
executor.executeAndWait(new Runnable() {
@Override
public void run() {
server.disconnect();
}
});
verify(serverEvents, timeout(DISCONNECT_TIMEOUT)).onTCPClose();
verify(clientEvents, timeout(DISCONNECT_TIMEOUT)).onTCPClose();
}
@Test
public void testDisconnectClient() {
testConnectIPv4();
executor.executeAndWait(new Runnable() {
@Override
public void run() {
client.disconnect();
}
});
verify(serverEvents, timeout(DISCONNECT_TIMEOUT)).onTCPClose();
verify(clientEvents, timeout(DISCONNECT_TIMEOUT)).onTCPClose();
}
private void setUpIPv4Server() {
setUpServer("0.0.0.0", PORT);
}
private void setUpIPv4Client() {
setUpClient("127.0.0.1", PORT);
}
private void setUpIPv6Server() {
setUpServer("::", PORT);
}
private void setUpIPv6Client() {
setUpClient("::1", PORT);
}
private void setUpServer(String ip, int port) {
server = new TCPChannelClient(executor, serverEvents, ip, port);
}
private void setUpClient(String ip, int port) {
client = new TCPChannelClient(executor, clientEvents, ip, port);
}
/**
* Verifies no more server or client events have been issued
*/
private void verifyNoMoreEvents() {
verifyNoMoreInteractions(serverEvents);
verifyNoMoreInteractions(clientEvents);
}
}

View File

@ -0,0 +1,118 @@
/*
* Copyright 2016 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
package org.appspot.apprtc.util;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import static org.junit.Assert.fail;
/**
* LooperExecutor that doesn't use Looper because its implementation in Robolectric is not suited
* for our needs. Also implements executeAndWait that can be used to wait until the runnable has
* been executed.
*/
public class RobolectricLooperExecutor extends LooperExecutor {
private volatile boolean running = false;
private static final int RUNNABLE_QUEUE_CAPACITY = 256;
private final BlockingQueue<Runnable> runnableQueue
= new ArrayBlockingQueue<>(RUNNABLE_QUEUE_CAPACITY);
private long threadId;
/**
* Executes the runnable passed to the constructor and sets isDone flag afterwards.
*/
private static class ExecuteAndWaitRunnable implements Runnable {
public boolean isDone = false;
private final Runnable runnable;
ExecuteAndWaitRunnable(Runnable runnable) {
this.runnable = runnable;
}
@Override
public void run() {
runnable.run();
synchronized (this) {
isDone = true;
notifyAll();
}
}
}
@Override
public void run() {
threadId = Thread.currentThread().getId();
while (running) {
final Runnable runnable;
try {
runnable = runnableQueue.take();
} catch (InterruptedException e) {
if (running) {
fail(e.getMessage());
}
return;
}
runnable.run();
}
}
@Override
public synchronized void requestStart() {
if (running) {
return;
}
running = true;
start();
}
@Override
public synchronized void requestStop() {
running = false;
interrupt();
}
@Override
public synchronized void execute(Runnable runnable) {
try {
runnableQueue.put(runnable);
} catch (InterruptedException e) {
fail(e.getMessage());
}
}
/**
* Queues runnable to be run and waits for it to be executed by the executor thread
*/
public void executeAndWait(Runnable runnable) {
ExecuteAndWaitRunnable executeAndWaitRunnable = new ExecuteAndWaitRunnable(runnable);
execute(executeAndWaitRunnable);
synchronized (executeAndWaitRunnable) {
while (!executeAndWaitRunnable.isDone) {
try {
executeAndWaitRunnable.wait();
} catch (InterruptedException e) {
fail(e.getMessage());
}
}
}
}
@Override
public boolean checkOnLooperThread() {
return (Thread.currentThread().getId() == threadId);
}
}