Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(272)

Unified Diff: components/devtools_bridge/android/java/src/org/chromium/components/devtools_bridge/SessionBase.java

Issue 537253003: Implementing RTC debugging session objects (client and server parts). (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@tunnel
Patch Set: Created 6 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: components/devtools_bridge/android/java/src/org/chromium/components/devtools_bridge/SessionBase.java
diff --git a/components/devtools_bridge/android/java/src/org/chromium/components/devtools_bridge/SessionBase.java b/components/devtools_bridge/android/java/src/org/chromium/components/devtools_bridge/SessionBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..db793b01a07c9229c333c597760d911fe673dfb9
--- /dev/null
+++ b/components/devtools_bridge/android/java/src/org/chromium/components/devtools_bridge/SessionBase.java
@@ -0,0 +1,431 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.devtools_bridge;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Base class for ServerSession and ClientSession. Both opens a control channel and a default
+ * tunnel. Control channel designated to exchange messages defined in SessionControlMessages.
+ *
+ * Signaling communication between client and server works in request/response manner. It's more
+ * restrictive than traditional bidirectional signaling channel but give more freedom in
+ * implementing signaling. Main motivation is that GCD provides API what works in that way.
+ *
+ * Session is initiated by a client. It creates an offer and sends it along with RTC configuration.
+ * Server sends an answer in response. Once session negotiated client starts ICE candidates
+ * exchange. It periodically sends own candidates and peeks server's ones. Periodic ICE exchange
+ * stops when control channel opens. It resumes if connections state turns to DISCONNECTED (because
+ * server may generate ICE candidates to recover connectivity but may not notify through
+ * control channel). ICE exchange in CONNECTED state designated to let improve connection
+ * when network configuration changed.
+ *
+ * If session is not started (or resumed) after mAutoCloseTimeoutMs it closes itself.
+ *
+ * Only default tunnel is supported at the moment. It designated for DevTools UNIX socket.
+ * Additional tunnels may be useful for: 1) reverse port forwarding and 2) tunneling
+ * WebView DevTools sockets of other applications. Additional tunnels negotiation should
+ * be implemented by adding new types of control messages. Dynamic tunnel configuration
+ * will need support for session renegotiation.
+ *
+ * Session is a single threaded object. Until started owner is responsible to synchronizing access
+ * to it. When started it must be called on the thread of SessionBase.Executor.
+ * All WebRTC callbacks are forwarded on this thread.
+ */
+public abstract class SessionBase {
+ private static final int CONTROL_CHANNEL_ID = 0;
+ private static final int DEFAULT_TUNNEL_CHANNEL_ID = 1;
+
+ private final Executor mExecutor;
+ private final SessionDependencyFactory mFactory;
+ private AbstractPeerConnection mConnection;
+ private AbstractDataChannel mControlChannel;
+ private List<String> mCandidates = new ArrayList<String>();
+ private boolean mControlChannelOpened = false;
+ private boolean mConnected = false;
+ private Cancellable mAutoCloseTask;
+ private SessionControlMessages.MessageHandler mControlMessageHandler;
+ private final Map<Integer, SocketTunnelBase> mTunnels =
+ new HashMap<Integer, SocketTunnelBase>();
+ private EventListener mEventListener;
+
+ protected int mAutoCloseTimeoutMs = 30000;
+
+ /**
+ * Allows to post tasks on the thread where the sessions lives.
+ */
+ public interface Executor {
+ Cancellable postOnSessionThread(int delayMs, Runnable runnable);
+ boolean isCalledOnSessionThread();
+ }
+
+ /**
+ * Interface for cancelling scheduled tasks.
+ */
+ public interface Cancellable {
+ void cancel();
+ }
+
+ /**
+ * Representation of server session. All methods are delivered through
+ * signaling channel (except test configurations). Server session is accessible
+ * in request/response manner.
+ */
+ public interface ServerSessionInterface {
+ /**
+ * Starts session with specified RTC configuration and offer.
+ */
+ void startSession(RTCConfiguration config,
+ String offer,
+ NegotiationCallback callback);
+
+ /**
+ * Renegoteates session. Needed when tunnels added/removed on the fly.
+ */
+ void renegotiate(String offer, NegotiationCallback callback);
+
+ /**
+ * Sends client's ICE candidates to the server and peeks server's ICE candidates.
+ */
+ void iceExchange(List<String> clientCandidates, IceExchangeCallback callback);
+ }
+
+ /**
+ * Base interface for server callbacks.
+ */
+ public interface ServerCallback {
+ void onFailure(String errorMessage);
+ }
+
+ /**
+ * Server's response to startSession and renegotiate methods.
+ */
+ public interface NegotiationCallback extends ServerCallback {
+ void onSuccess(String answer);
+ }
+
+ /**
+ * Server's response on iceExchange method.
+ */
+ public interface IceExchangeCallback extends ServerCallback {
+ void onSuccess(List<String> serverCandidates);
+ }
+
+ /**
+ * Listener of session's events.
+ */
+ public interface EventListener {
+ void onCloseSelf();
+ }
+
+ protected SessionBase(SessionDependencyFactory factory,
+ Executor executor,
+ SocketTunnelBase defaultTunnel) {
+ mExecutor = executor;
+ mFactory = factory;
+ addTunnel(DEFAULT_TUNNEL_CHANNEL_ID, defaultTunnel);
+ }
+
+ public final void dispose() {
+ checkCalledOnSessionThread();
+
+ if (isStarted()) stop();
+ }
+
+ public void setEventListener(EventListener listener) {
+ checkCalledOnSessionThread();
+
+ mEventListener = listener;
+ }
+
+ protected AbstractPeerConnection connection() {
+ return mConnection;
+ }
+
+ protected boolean doesTunnelExist(int channelId) {
+ return mTunnels.containsKey(channelId);
+ }
+
+ private final void addTunnel(int channelId, SocketTunnelBase tunnel) {
+ assert !mTunnels.containsKey(channelId);
+ assert !tunnel.isBound();
+ // Tunnel renegotiation not implemented.
+ assert channelId == DEFAULT_TUNNEL_CHANNEL_ID && !isStarted();
+
+ mTunnels.put(channelId, tunnel);
+ }
+
+ protected void removeTunnel(int channelId) {
+ assert mTunnels.containsKey(channelId);
+ mTunnels.get(channelId).unbind().dispose();
+ mTunnels.remove(channelId);
+ }
+
+ protected final boolean isControlChannelOpened() {
+ return mControlChannelOpened;
+ }
+
+ protected final boolean isConnected() {
+ return mConnected;
+ }
+
+ protected final void postOnSessionThread(Runnable runnable) {
+ postOnSessionThread(0, runnable);
+ }
+
+ protected final Cancellable postOnSessionThread(int delayMs, Runnable runnable) {
+ return mExecutor.postOnSessionThread(delayMs, runnable);
+ }
+
+ protected final void checkCalledOnSessionThread() {
+ assert mExecutor.isCalledOnSessionThread();
+ }
+
+ public final boolean isStarted() {
+ return mConnection != null;
+ }
+
+ /**
+ * Creates and configures peer connection and sets a control message handler.
+ */
+ protected void start(RTCConfiguration config,
+ SessionControlMessages.MessageHandler handler) {
+ assert !isStarted();
+
+ mConnection = mFactory.createPeerConnection(config, new ConnectionObserver());
+ mControlChannel = mConnection.createDataChannel(CONTROL_CHANNEL_ID);
+ mControlMessageHandler = handler;
+ mControlChannel.registerObserver(new ControlChannelObserver());
+
+ for (Map.Entry<Integer, SocketTunnelBase> entry : mTunnels.entrySet()) {
+ int channelId = entry.getKey();
+ SocketTunnelBase tunnel = entry.getValue();
+ tunnel.bind(connection().createDataChannel(channelId));
+ }
+ }
+
+ /**
+ * Disposed objects created in |start|.
+ */
+ public void stop() {
+ checkCalledOnSessionThread();
+
+ assert isStarted();
+
+ stopAutoCloseTimer();
+
+ for (SocketTunnelBase tunnel : mTunnels.values()) {
+ tunnel.unbind().dispose();
+ }
+
+ AbstractPeerConnection connection = mConnection;
+ mConnection = null;
+ assert !isStarted();
+
+ mControlChannel.unregisterObserver();
+ mControlMessageHandler = null;
+ mControlChannel.dispose();
+ mControlChannel = null;
+
+ // Dispose connection after data channels.
+ connection.dispose();
+ }
+
+ protected abstract void onRemoteDescriptionSet();
+ protected abstract void onLocalDescriptionCreatedAndSet(
+ AbstractPeerConnection.SessionDescriptionType type, String description);
+ protected abstract void onControlChannelOpened();
+
+ protected void onControlChannelClosed() {
+ closeSelf();
+ }
+
+ protected void onIceConnectionChange() {}
+
+ private void handleFailureOnSignalingThread(final String message) {
+ postOnSessionThread(new Runnable() {
+ @Override
+ public void run() {
+ if (isStarted())
+ onFailure(message);
+ }
+ });
+ }
+
+ protected final void startAutoCloseTimer() {
+ assert mAutoCloseTask == null;
+ assert isStarted();
+ mAutoCloseTask = postOnSessionThread(mAutoCloseTimeoutMs, new Runnable() {
+ @Override
+ public void run() {
+ assert isStarted();
+
+ mAutoCloseTask = null;
+ closeSelf();
+ }
+ });
+ }
+
+ protected final void stopAutoCloseTimer() {
+ if (mAutoCloseTask != null) {
+ mAutoCloseTask.cancel();
+ mAutoCloseTask = null;
+ }
+ }
+
+ protected void closeSelf() {
+ stop();
+ if (mEventListener != null) {
+ mEventListener.onCloseSelf();
+ }
+ }
+
+ // Returns collected candidates (for sending to the remote session) and removes them.
+ protected List<String> takeIceCandidates() {
+ List<String> result = new ArrayList<String>();
+ result.addAll(mCandidates);
+ mCandidates.clear();
+ return result;
+ }
+
+ protected void addIceCandidates(List<String> candidates) {
+ for (String candidate : candidates) {
+ mConnection.addIceCandidate(candidate);
+ }
+ }
+
+ protected void onFailure(String message) {
+ closeSelf();
+ }
+
+ protected void onIceCandidate(String candidate) {
+ mCandidates.add(candidate);
+ }
+
+ /**
+ * Receives callbacks from the peer connection on the signaling thread. Forwards them
+ * on the session thread. All session event handling methods assume session started (prevents
+ * disposed objects). It drops callbacks it closed.
+ */
+ private final class ConnectionObserver implements AbstractPeerConnection.Observer {
+ @Override
+ public void onFailure(final String description) {
+ postOnSessionThread(new Runnable() {
+ @Override
+ public void run() {
+ if (!isStarted()) return;
+ SessionBase.this.onFailure(description);
+ }
+ });
+ }
+
+ @Override
+ public void onLocalDescriptionCreatedAndSet(
+ final AbstractPeerConnection.SessionDescriptionType type,
+ final String description) {
+ postOnSessionThread(new Runnable() {
+ @Override
+ public void run() {
+ if (!isStarted()) return;
+ SessionBase.this.onLocalDescriptionCreatedAndSet(type, description);
+ }
+ });
+ }
+
+ @Override
+ public void onRemoteDescriptionSet() {
+ postOnSessionThread(new Runnable() {
+ @Override
+ public void run() {
+ if (!isStarted()) return;
+ SessionBase.this.onRemoteDescriptionSet();
+ }
+ });
+ }
+
+ @Override
+ public void onIceCandidate(final String candidate) {
+ postOnSessionThread(new Runnable() {
+ @Override
+ public void run() {
+ if (!isStarted()) return;
+ SessionBase.this.onIceCandidate(candidate);
+ }
+ });
+ }
+
+ @Override
+ public void onIceConnectionChange(final boolean connected) {
+ postOnSessionThread(new Runnable() {
+ @Override
+ public void run() {
+ if (!isStarted()) return;
+ mConnected = connected;
+ SessionBase.this.onIceConnectionChange();
+ }
+ });
+ }
+ }
+
+ /**
+ * Receives callbacks from the control channel. Forwards them on the session thread.
+ */
+ private final class ControlChannelObserver implements AbstractDataChannel.Observer {
+ @Override
+ public void onStateChange(final AbstractDataChannel.State state) {
+ postOnSessionThread(new Runnable() {
+ @Override
+ public void run() {
+ if (!isStarted()) return;
+ mControlChannelOpened = state == AbstractDataChannel.State.OPEN;
+
+ if (mControlChannelOpened) {
+ onControlChannelOpened();
+ } else {
+ onControlChannelClosed();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onMessage(ByteBuffer message) {
+ final byte[] bytes = new byte[message.remaining()];
+ message.get(bytes);
+ postOnSessionThread(new Runnable() {
+ @Override
+ public void run() {
+ if (!isStarted() || mControlMessageHandler == null) return;
+
+ try {
+ mControlMessageHandler.readMessage(bytes);
+ } catch (SessionControlMessages.InvalidFormatException e) {
+ // TODO(serya): handle
+ }
+ }
+ });
+ }
+ }
+
+ protected void sendControlMessage(SessionControlMessages.Message<?> message) {
+ assert mControlChannelOpened;
+
+ byte[] bytes = SessionControlMessages.toByteArray(message);
+ ByteBuffer rawMessage = ByteBuffer.allocateDirect(bytes.length);
+ rawMessage.put(bytes);
+
+ sendControlMessage(rawMessage);
+ }
+
+ private void sendControlMessage(ByteBuffer rawMessage) {
+ rawMessage.limit(rawMessage.position());
+ rawMessage.position(0);
+ mControlChannel.send(rawMessage, AbstractDataChannel.MessageType.TEXT);
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698