Upstream version 11.40.271.0
[platform/framework/web/crosswalk.git] / src / components / devtools_bridge / android / java / src / org / chromium / components / devtools_bridge / SessionBase.java
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.
4
5 package org.chromium.components.devtools_bridge;
6
7 import java.nio.ByteBuffer;
8 import java.util.ArrayList;
9 import java.util.HashMap;
10 import java.util.List;
11 import java.util.Map;
12
13 /**
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.
16  *
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.
20  *
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.
28  *
29  * If session is not started (or resumed) after mAutoCloseTimeoutMs it closes itself.
30  *
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.
36  *
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.
40  */
41 public abstract class SessionBase {
42     private static final int CONTROL_CHANNEL_ID = 0;
43     private static final int DEFAULT_TUNNEL_CHANNEL_ID = 1;
44
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;
57
58     protected int mAutoCloseTimeoutMs = 30000;
59
60     /**
61      * Allows to post tasks on the thread where the sessions lives.
62      */
63     public interface Executor {
64         Cancellable postOnSessionThread(int delayMs, Runnable runnable);
65         boolean isCalledOnSessionThread();
66     }
67
68     /**
69      * Interface for cancelling scheduled tasks.
70      */
71     public interface Cancellable {
72         void cancel();
73     }
74
75     /**
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.
79      */
80     public interface ServerSessionInterface {
81         /**
82          * Starts session with specified RTC configuration and offer.
83          */
84         void startSession(RTCConfiguration config,
85                           String offer,
86                           NegotiationCallback callback);
87
88         /**
89          * Renegoteates session. Needed when tunnels added/removed on the fly.
90          */
91         void renegotiate(String offer, NegotiationCallback callback);
92
93         /**
94          * Sends client's ICE candidates to the server and peeks server's ICE candidates.
95          */
96         void iceExchange(List<String> clientCandidates, IceExchangeCallback callback);
97     }
98
99     /**
100      * Base interface for server callbacks.
101      */
102     public interface ServerCallback {
103         void onFailure(String errorMessage);
104     }
105
106     /**
107      * Server's response to startSession and renegotiate methods.
108      */
109     public interface NegotiationCallback extends ServerCallback {
110         void onSuccess(String answer);
111     }
112
113     /**
114      * Server's response on iceExchange method.
115      */
116     public interface IceExchangeCallback  extends ServerCallback {
117         void onSuccess(List<String> serverCandidates);
118     }
119
120     /**
121      * Listener of session's events.
122      */
123     public interface EventListener {
124         void onCloseSelf();
125     }
126
127     protected SessionBase(SessionDependencyFactory factory,
128                           Executor executor,
129                           SocketTunnelBase defaultTunnel) {
130         mExecutor = executor;
131         mFactory = factory;
132         addTunnel(DEFAULT_TUNNEL_CHANNEL_ID, defaultTunnel);
133     }
134
135     public final void dispose() {
136         checkCalledOnSessionThread();
137
138         if (isStarted()) stop();
139     }
140
141     public void setEventListener(EventListener listener) {
142         checkCalledOnSessionThread();
143
144         mEventListener = listener;
145     }
146
147     protected AbstractPeerConnection connection() {
148         return mConnection;
149     }
150
151     protected boolean doesTunnelExist(int channelId) {
152         return mTunnels.containsKey(channelId);
153     }
154
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();
160
161         mTunnels.put(channelId, tunnel);
162     }
163
164     protected void removeTunnel(int channelId) {
165         assert mTunnels.containsKey(channelId);
166         mTunnels.get(channelId).unbind().dispose();
167         mTunnels.remove(channelId);
168     }
169
170     protected final boolean isControlChannelOpened() {
171         return mControlChannelOpened;
172     }
173
174     protected final boolean isConnected() {
175         return mConnected;
176     }
177
178     protected final void postOnSessionThread(Runnable runnable) {
179         postOnSessionThread(0, runnable);
180     }
181
182     protected final Cancellable postOnSessionThread(int delayMs, Runnable runnable) {
183         return mExecutor.postOnSessionThread(delayMs, runnable);
184     }
185
186     protected final void checkCalledOnSessionThread() {
187         assert mExecutor.isCalledOnSessionThread();
188     }
189
190     public final boolean isStarted() {
191         return mConnection != null;
192     }
193
194     /**
195      * Creates and configures peer connection and sets a control message handler.
196      */
197     protected void start(RTCConfiguration config,
198                          SessionControlMessages.MessageHandler handler) {
199         assert !isStarted();
200
201         mConnection = mFactory.createPeerConnection(config, new ConnectionObserver());
202         mControlChannel = mConnection.createDataChannel(CONTROL_CHANNEL_ID);
203         mControlMessageHandler = handler;
204         mControlChannel.registerObserver(new ControlChannelObserver());
205
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));
210         }
211     }
212
213     /**
214      * Disposed objects created in |start|.
215      */
216     public void stop() {
217         checkCalledOnSessionThread();
218
219         assert isStarted();
220
221         stopAutoCloseTimer();
222
223         for (SocketTunnelBase tunnel : mTunnels.values()) {
224             tunnel.unbind().dispose();
225         }
226
227         AbstractPeerConnection connection = mConnection;
228         mConnection = null;
229         assert !isStarted();
230
231         mControlChannel.unregisterObserver();
232         mControlMessageHandler = null;
233         mControlChannel.dispose();
234         mControlChannel = null;
235
236         // Dispose connection after data channels.
237         connection.dispose();
238     }
239
240     protected abstract void onRemoteDescriptionSet();
241     protected abstract void onLocalDescriptionCreatedAndSet(
242             AbstractPeerConnection.SessionDescriptionType type, String description);
243     protected abstract void onControlChannelOpened();
244
245     protected void onControlChannelClosed() {
246         closeSelf();
247     }
248
249     protected void onIceConnectionChange() {}
250
251     private void handleFailureOnSignalingThread(final String message) {
252         postOnSessionThread(new Runnable() {
253             @Override
254             public void run() {
255                 if (isStarted())
256                     onFailure(message);
257             }
258         });
259     }
260
261     protected final void startAutoCloseTimer() {
262         assert mAutoCloseTask == null;
263         assert isStarted();
264         mAutoCloseTask = postOnSessionThread(mAutoCloseTimeoutMs, new Runnable() {
265             @Override
266             public void run() {
267                 assert isStarted();
268
269                 mAutoCloseTask = null;
270                 closeSelf();
271             }
272         });
273     }
274
275     protected final void stopAutoCloseTimer() {
276         if (mAutoCloseTask != null) {
277             mAutoCloseTask.cancel();
278             mAutoCloseTask = null;
279         }
280     }
281
282     protected void closeSelf() {
283         stop();
284         if (mEventListener != null) {
285             mEventListener.onCloseSelf();
286         }
287     }
288
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);
293         mCandidates.clear();
294         return result;
295     }
296
297     protected void addIceCandidates(List<String> candidates) {
298         for (String candidate : candidates) {
299             mConnection.addIceCandidate(candidate);
300         }
301     }
302
303     protected void onFailure(String message) {
304         closeSelf();
305     }
306
307     protected void onIceCandidate(String candidate) {
308         mCandidates.add(candidate);
309     }
310
311     /**
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.
315      */
316     private final class ConnectionObserver implements AbstractPeerConnection.Observer {
317         @Override
318         public void onFailure(final String description) {
319             postOnSessionThread(new Runnable() {
320                 @Override
321                 public void run() {
322                     if (!isStarted()) return;
323                     SessionBase.this.onFailure(description);
324                 }
325             });
326         }
327
328         @Override
329         public void onLocalDescriptionCreatedAndSet(
330                 final AbstractPeerConnection.SessionDescriptionType type,
331                 final String description) {
332             postOnSessionThread(new Runnable() {
333                 @Override
334                 public void run() {
335                     if (!isStarted()) return;
336                     SessionBase.this.onLocalDescriptionCreatedAndSet(type, description);
337                 }
338             });
339         }
340
341         @Override
342         public void onRemoteDescriptionSet() {
343             postOnSessionThread(new Runnable() {
344                 @Override
345                 public void run() {
346                     if (!isStarted()) return;
347                     SessionBase.this.onRemoteDescriptionSet();
348                 }
349             });
350         }
351
352         @Override
353         public void onIceCandidate(final String candidate) {
354             postOnSessionThread(new Runnable() {
355                 @Override
356                 public void run() {
357                     if (!isStarted()) return;
358                     SessionBase.this.onIceCandidate(candidate);
359                 }
360             });
361         }
362
363         @Override
364         public void onIceConnectionChange(final boolean connected) {
365             postOnSessionThread(new Runnable() {
366                 @Override
367                 public void run() {
368                     if (!isStarted()) return;
369                     mConnected = connected;
370                     SessionBase.this.onIceConnectionChange();
371                 }
372             });
373         }
374     }
375
376     /**
377      * Receives callbacks from the control channel. Forwards them on the session thread.
378      */
379     private final class ControlChannelObserver implements AbstractDataChannel.Observer {
380         @Override
381         public void onStateChange(final AbstractDataChannel.State state) {
382             postOnSessionThread(new Runnable() {
383                 @Override
384                 public void run() {
385                     if (!isStarted()) return;
386                     mControlChannelOpened = state == AbstractDataChannel.State.OPEN;
387
388                     if (mControlChannelOpened) {
389                         onControlChannelOpened();
390                     } else {
391                         onControlChannelClosed();
392                     }
393                 }
394             });
395         }
396
397         @Override
398         public void onMessage(ByteBuffer message) {
399             final byte[] bytes = new byte[message.remaining()];
400             message.get(bytes);
401             postOnSessionThread(new Runnable() {
402                 @Override
403                 public void run() {
404                     if (!isStarted() || mControlMessageHandler == null) return;
405
406                     try {
407                         mControlMessageHandler.readMessage(bytes);
408                     } catch (SessionControlMessages.InvalidFormatException e) {
409                         // TODO(serya): handle
410                     }
411                 }
412             });
413         }
414     }
415
416     protected void sendControlMessage(SessionControlMessages.Message<?> message) {
417         assert mControlChannelOpened;
418
419         byte[] bytes = SessionControlMessages.toByteArray(message);
420         ByteBuffer rawMessage = ByteBuffer.allocateDirect(bytes.length);
421         rawMessage.put(bytes);
422
423         sendControlMessage(rawMessage);
424     }
425
426     private void sendControlMessage(ByteBuffer rawMessage) {
427         rawMessage.limit(rawMessage.position());
428         rawMessage.position(0);
429         mControlChannel.send(rawMessage, AbstractDataChannel.MessageType.TEXT);
430     }
431 }