1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.components.devtools_bridge;
7 import java.nio.ByteBuffer;
8 import java.util.ArrayList;
9 import java.util.HashMap;
10 import java.util.List;
14 * Base class for ServerSession and ClientSession. Both opens a control channel and a default
15 * tunnel. Control channel designated to exchange messages defined in SessionControlMessages.
17 * Signaling communication between client and server works in request/response manner. It's more
18 * restrictive than traditional bidirectional signaling channel but give more freedom in
19 * implementing signaling. Main motivation is that GCD provides API what works in that way.
21 * Session is initiated by a client. It creates an offer and sends it along with RTC configuration.
22 * Server sends an answer in response. Once session negotiated client starts ICE candidates
23 * exchange. It periodically sends own candidates and peeks server's ones. Periodic ICE exchange
24 * stops when control channel opens. It resumes if connections state turns to DISCONNECTED (because
25 * server may generate ICE candidates to recover connectivity but may not notify through
26 * control channel). ICE exchange in CONNECTED state designated to let improve connection
27 * when network configuration changed.
29 * If session is not started (or resumed) after mAutoCloseTimeoutMs it closes itself.
31 * Only default tunnel is supported at the moment. It designated for DevTools UNIX socket.
32 * Additional tunnels may be useful for: 1) reverse port forwarding and 2) tunneling
33 * WebView DevTools sockets of other applications. Additional tunnels negotiation should
34 * be implemented by adding new types of control messages. Dynamic tunnel configuration
35 * will need support for session renegotiation.
37 * Session is a single threaded object. Until started owner is responsible to synchronizing access
38 * to it. When started it must be called on the thread of SessionBase.Executor.
39 * All WebRTC callbacks are forwarded on this thread.
41 public abstract class SessionBase {
42 private static final int CONTROL_CHANNEL_ID = 0;
43 private static final int DEFAULT_TUNNEL_CHANNEL_ID = 1;
45 private final Executor mExecutor;
46 private final SessionDependencyFactory mFactory;
47 private AbstractPeerConnection mConnection;
48 private AbstractDataChannel mControlChannel;
49 private List<String> mCandidates = new ArrayList<String>();
50 private boolean mControlChannelOpened = false;
51 private boolean mConnected = false;
52 private Cancellable mAutoCloseTask;
53 private SessionControlMessages.MessageHandler mControlMessageHandler;
54 private final Map<Integer, SocketTunnelBase> mTunnels =
55 new HashMap<Integer, SocketTunnelBase>();
56 private EventListener mEventListener;
58 protected int mAutoCloseTimeoutMs = 30000;
61 * Allows to post tasks on the thread where the sessions lives.
63 public interface Executor {
64 Cancellable postOnSessionThread(int delayMs, Runnable runnable);
65 boolean isCalledOnSessionThread();
69 * Interface for cancelling scheduled tasks.
71 public interface Cancellable {
76 * Representation of server session. All methods are delivered through
77 * signaling channel (except test configurations). Server session is accessible
78 * in request/response manner.
80 public interface ServerSessionInterface {
82 * Starts session with specified RTC configuration and offer.
84 void startSession(RTCConfiguration config,
86 NegotiationCallback callback);
89 * Renegoteates session. Needed when tunnels added/removed on the fly.
91 void renegotiate(String offer, NegotiationCallback callback);
94 * Sends client's ICE candidates to the server and peeks server's ICE candidates.
96 void iceExchange(List<String> clientCandidates, IceExchangeCallback callback);
100 * Base interface for server callbacks.
102 public interface ServerCallback {
103 void onFailure(String errorMessage);
107 * Server's response to startSession and renegotiate methods.
109 public interface NegotiationCallback extends ServerCallback {
110 void onSuccess(String answer);
114 * Server's response on iceExchange method.
116 public interface IceExchangeCallback extends ServerCallback {
117 void onSuccess(List<String> serverCandidates);
121 * Listener of session's events.
123 public interface EventListener {
127 protected SessionBase(SessionDependencyFactory factory,
129 SocketTunnelBase defaultTunnel) {
130 mExecutor = executor;
132 addTunnel(DEFAULT_TUNNEL_CHANNEL_ID, defaultTunnel);
135 public final void dispose() {
136 checkCalledOnSessionThread();
138 if (isStarted()) stop();
141 public void setEventListener(EventListener listener) {
142 checkCalledOnSessionThread();
144 mEventListener = listener;
147 protected AbstractPeerConnection connection() {
151 protected boolean doesTunnelExist(int channelId) {
152 return mTunnels.containsKey(channelId);
155 private final void addTunnel(int channelId, SocketTunnelBase tunnel) {
156 assert !mTunnels.containsKey(channelId);
157 assert !tunnel.isBound();
158 // Tunnel renegotiation not implemented.
159 assert channelId == DEFAULT_TUNNEL_CHANNEL_ID && !isStarted();
161 mTunnels.put(channelId, tunnel);
164 protected void removeTunnel(int channelId) {
165 assert mTunnels.containsKey(channelId);
166 mTunnels.get(channelId).unbind().dispose();
167 mTunnels.remove(channelId);
170 protected final boolean isControlChannelOpened() {
171 return mControlChannelOpened;
174 protected final boolean isConnected() {
178 protected final void postOnSessionThread(Runnable runnable) {
179 postOnSessionThread(0, runnable);
182 protected final Cancellable postOnSessionThread(int delayMs, Runnable runnable) {
183 return mExecutor.postOnSessionThread(delayMs, runnable);
186 protected final void checkCalledOnSessionThread() {
187 assert mExecutor.isCalledOnSessionThread();
190 public final boolean isStarted() {
191 return mConnection != null;
195 * Creates and configures peer connection and sets a control message handler.
197 protected void start(RTCConfiguration config,
198 SessionControlMessages.MessageHandler handler) {
201 mConnection = mFactory.createPeerConnection(config, new ConnectionObserver());
202 mControlChannel = mConnection.createDataChannel(CONTROL_CHANNEL_ID);
203 mControlMessageHandler = handler;
204 mControlChannel.registerObserver(new ControlChannelObserver());
206 for (Map.Entry<Integer, SocketTunnelBase> entry : mTunnels.entrySet()) {
207 int channelId = entry.getKey();
208 SocketTunnelBase tunnel = entry.getValue();
209 tunnel.bind(connection().createDataChannel(channelId));
214 * Disposed objects created in |start|.
217 checkCalledOnSessionThread();
221 stopAutoCloseTimer();
223 for (SocketTunnelBase tunnel : mTunnels.values()) {
224 tunnel.unbind().dispose();
227 AbstractPeerConnection connection = mConnection;
231 mControlChannel.unregisterObserver();
232 mControlMessageHandler = null;
233 mControlChannel.dispose();
234 mControlChannel = null;
236 // Dispose connection after data channels.
237 connection.dispose();
240 protected abstract void onRemoteDescriptionSet();
241 protected abstract void onLocalDescriptionCreatedAndSet(
242 AbstractPeerConnection.SessionDescriptionType type, String description);
243 protected abstract void onControlChannelOpened();
245 protected void onControlChannelClosed() {
249 protected void onIceConnectionChange() {}
251 private void handleFailureOnSignalingThread(final String message) {
252 postOnSessionThread(new Runnable() {
261 protected final void startAutoCloseTimer() {
262 assert mAutoCloseTask == null;
264 mAutoCloseTask = postOnSessionThread(mAutoCloseTimeoutMs, new Runnable() {
269 mAutoCloseTask = null;
275 protected final void stopAutoCloseTimer() {
276 if (mAutoCloseTask != null) {
277 mAutoCloseTask.cancel();
278 mAutoCloseTask = null;
282 protected void closeSelf() {
284 if (mEventListener != null) {
285 mEventListener.onCloseSelf();
289 // Returns collected candidates (for sending to the remote session) and removes them.
290 protected List<String> takeIceCandidates() {
291 List<String> result = new ArrayList<String>();
292 result.addAll(mCandidates);
297 protected void addIceCandidates(List<String> candidates) {
298 for (String candidate : candidates) {
299 mConnection.addIceCandidate(candidate);
303 protected void onFailure(String message) {
307 protected void onIceCandidate(String candidate) {
308 mCandidates.add(candidate);
312 * Receives callbacks from the peer connection on the signaling thread. Forwards them
313 * on the session thread. All session event handling methods assume session started (prevents
314 * disposed objects). It drops callbacks it closed.
316 private final class ConnectionObserver implements AbstractPeerConnection.Observer {
318 public void onFailure(final String description) {
319 postOnSessionThread(new Runnable() {
322 if (!isStarted()) return;
323 SessionBase.this.onFailure(description);
329 public void onLocalDescriptionCreatedAndSet(
330 final AbstractPeerConnection.SessionDescriptionType type,
331 final String description) {
332 postOnSessionThread(new Runnable() {
335 if (!isStarted()) return;
336 SessionBase.this.onLocalDescriptionCreatedAndSet(type, description);
342 public void onRemoteDescriptionSet() {
343 postOnSessionThread(new Runnable() {
346 if (!isStarted()) return;
347 SessionBase.this.onRemoteDescriptionSet();
353 public void onIceCandidate(final String candidate) {
354 postOnSessionThread(new Runnable() {
357 if (!isStarted()) return;
358 SessionBase.this.onIceCandidate(candidate);
364 public void onIceConnectionChange(final boolean connected) {
365 postOnSessionThread(new Runnable() {
368 if (!isStarted()) return;
369 mConnected = connected;
370 SessionBase.this.onIceConnectionChange();
377 * Receives callbacks from the control channel. Forwards them on the session thread.
379 private final class ControlChannelObserver implements AbstractDataChannel.Observer {
381 public void onStateChange(final AbstractDataChannel.State state) {
382 postOnSessionThread(new Runnable() {
385 if (!isStarted()) return;
386 mControlChannelOpened = state == AbstractDataChannel.State.OPEN;
388 if (mControlChannelOpened) {
389 onControlChannelOpened();
391 onControlChannelClosed();
398 public void onMessage(ByteBuffer message) {
399 final byte[] bytes = new byte[message.remaining()];
401 postOnSessionThread(new Runnable() {
404 if (!isStarted() || mControlMessageHandler == null) return;
407 mControlMessageHandler.readMessage(bytes);
408 } catch (SessionControlMessages.InvalidFormatException e) {
409 // TODO(serya): handle
416 protected void sendControlMessage(SessionControlMessages.Message<?> message) {
417 assert mControlChannelOpened;
419 byte[] bytes = SessionControlMessages.toByteArray(message);
420 ByteBuffer rawMessage = ByteBuffer.allocateDirect(bytes.length);
421 rawMessage.put(bytes);
423 sendControlMessage(rawMessage);
426 private void sendControlMessage(ByteBuffer rawMessage) {
427 rawMessage.limit(rawMessage.position());
428 rawMessage.position(0);
429 mControlChannel.send(rawMessage, AbstractDataChannel.MessageType.TEXT);