|
1 # This Source Code Form is subject to the terms of the Mozilla Public |
|
2 # License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
4 |
|
5 import errno |
|
6 import json |
|
7 import socket |
|
8 |
|
9 |
|
10 class MarionetteTransport(object): |
|
11 """ The Marionette socket client. This speaks the same protocol |
|
12 as the remote debugger inside Gecko, in which messages are |
|
13 always preceded by the message length and a colon, e.g., |
|
14 |
|
15 20:{'command': 'test'} |
|
16 """ |
|
17 |
|
18 max_packet_length = 4096 |
|
19 connection_lost_msg = "Connection to Marionette server is lost. Check gecko.log (desktop firefox) or logcat (b2g) for errors." |
|
20 |
|
21 def __init__(self, addr, port): |
|
22 self.addr = addr |
|
23 self.port = port |
|
24 self.sock = None |
|
25 self.traits = None |
|
26 self.applicationType = None |
|
27 self.actor = 'root' |
|
28 |
|
29 def _recv_n_bytes(self, n): |
|
30 """ Convenience method for receiving exactly n bytes from |
|
31 self.sock (assuming it's open and connected). |
|
32 """ |
|
33 data = '' |
|
34 while len(data) < n: |
|
35 chunk = self.sock.recv(n - len(data)) |
|
36 if chunk == '': |
|
37 break |
|
38 data += chunk |
|
39 return data |
|
40 |
|
41 def receive(self): |
|
42 """ Receive the next complete response from the server, and return |
|
43 it as a dict. Each response from the server is prepended by |
|
44 len(message) + ':'. |
|
45 """ |
|
46 assert(self.sock) |
|
47 response = self.sock.recv(10) |
|
48 sep = response.find(':') |
|
49 length = response[0:sep] |
|
50 if length != '': |
|
51 response = response[sep + 1:] |
|
52 response += self._recv_n_bytes(int(length) + 1 + len(length) - 10) |
|
53 return json.loads(response) |
|
54 else: |
|
55 raise IOError(self.connection_lost_msg) |
|
56 |
|
57 def connect(self, timeout=360.0): |
|
58 """ Connect to the server and process the hello message we expect |
|
59 to receive in response. |
|
60 """ |
|
61 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
|
62 self.sock.settimeout(timeout) |
|
63 try: |
|
64 self.sock.connect((self.addr, self.port)) |
|
65 except: |
|
66 # Unset self.sock so that the next attempt to send will cause |
|
67 # another connection attempt. |
|
68 self.sock = None |
|
69 raise |
|
70 hello = self.receive() |
|
71 self.traits = hello.get('traits') |
|
72 self.applicationType = hello.get('applicationType') |
|
73 |
|
74 # get the marionette actor id |
|
75 response = self.send({'to': 'root', 'name': 'getMarionetteID'}) |
|
76 self.actor = response['id'] |
|
77 |
|
78 def send(self, msg): |
|
79 """ Send a message on the socket, prepending it with len(msg) + ':'. |
|
80 """ |
|
81 if not self.sock: |
|
82 self.connect() |
|
83 if 'to' not in msg: |
|
84 msg['to'] = self.actor |
|
85 data = json.dumps(msg) |
|
86 data = '%s:%s' % (len(data), data) |
|
87 |
|
88 for packet in [data[i:i + self.max_packet_length] for i in |
|
89 range(0, len(data), self.max_packet_length)]: |
|
90 try: |
|
91 self.sock.send(packet) |
|
92 except IOError as e: |
|
93 if e.errno == errno.EPIPE: |
|
94 raise IOError("%s: %s" % (str(e)), self.connection_lost_msg) |
|
95 else: |
|
96 raise e |
|
97 |
|
98 response = self.receive() |
|
99 return response |
|
100 |
|
101 def close(self): |
|
102 """ Close the socket. |
|
103 """ |
|
104 if self.sock: |
|
105 self.sock.close() |
|
106 self.sock = None |