1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/testing/marionette/transport/marionette_transport/transport.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,106 @@ 1.4 +# This Source Code Form is subject to the terms of the Mozilla Public 1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.7 + 1.8 +import errno 1.9 +import json 1.10 +import socket 1.11 + 1.12 + 1.13 +class MarionetteTransport(object): 1.14 + """ The Marionette socket client. This speaks the same protocol 1.15 + as the remote debugger inside Gecko, in which messages are 1.16 + always preceded by the message length and a colon, e.g., 1.17 + 1.18 + 20:{'command': 'test'} 1.19 + """ 1.20 + 1.21 + max_packet_length = 4096 1.22 + connection_lost_msg = "Connection to Marionette server is lost. Check gecko.log (desktop firefox) or logcat (b2g) for errors." 1.23 + 1.24 + def __init__(self, addr, port): 1.25 + self.addr = addr 1.26 + self.port = port 1.27 + self.sock = None 1.28 + self.traits = None 1.29 + self.applicationType = None 1.30 + self.actor = 'root' 1.31 + 1.32 + def _recv_n_bytes(self, n): 1.33 + """ Convenience method for receiving exactly n bytes from 1.34 + self.sock (assuming it's open and connected). 1.35 + """ 1.36 + data = '' 1.37 + while len(data) < n: 1.38 + chunk = self.sock.recv(n - len(data)) 1.39 + if chunk == '': 1.40 + break 1.41 + data += chunk 1.42 + return data 1.43 + 1.44 + def receive(self): 1.45 + """ Receive the next complete response from the server, and return 1.46 + it as a dict. Each response from the server is prepended by 1.47 + len(message) + ':'. 1.48 + """ 1.49 + assert(self.sock) 1.50 + response = self.sock.recv(10) 1.51 + sep = response.find(':') 1.52 + length = response[0:sep] 1.53 + if length != '': 1.54 + response = response[sep + 1:] 1.55 + response += self._recv_n_bytes(int(length) + 1 + len(length) - 10) 1.56 + return json.loads(response) 1.57 + else: 1.58 + raise IOError(self.connection_lost_msg) 1.59 + 1.60 + def connect(self, timeout=360.0): 1.61 + """ Connect to the server and process the hello message we expect 1.62 + to receive in response. 1.63 + """ 1.64 + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 1.65 + self.sock.settimeout(timeout) 1.66 + try: 1.67 + self.sock.connect((self.addr, self.port)) 1.68 + except: 1.69 + # Unset self.sock so that the next attempt to send will cause 1.70 + # another connection attempt. 1.71 + self.sock = None 1.72 + raise 1.73 + hello = self.receive() 1.74 + self.traits = hello.get('traits') 1.75 + self.applicationType = hello.get('applicationType') 1.76 + 1.77 + # get the marionette actor id 1.78 + response = self.send({'to': 'root', 'name': 'getMarionetteID'}) 1.79 + self.actor = response['id'] 1.80 + 1.81 + def send(self, msg): 1.82 + """ Send a message on the socket, prepending it with len(msg) + ':'. 1.83 + """ 1.84 + if not self.sock: 1.85 + self.connect() 1.86 + if 'to' not in msg: 1.87 + msg['to'] = self.actor 1.88 + data = json.dumps(msg) 1.89 + data = '%s:%s' % (len(data), data) 1.90 + 1.91 + for packet in [data[i:i + self.max_packet_length] for i in 1.92 + range(0, len(data), self.max_packet_length)]: 1.93 + try: 1.94 + self.sock.send(packet) 1.95 + except IOError as e: 1.96 + if e.errno == errno.EPIPE: 1.97 + raise IOError("%s: %s" % (str(e)), self.connection_lost_msg) 1.98 + else: 1.99 + raise e 1.100 + 1.101 + response = self.receive() 1.102 + return response 1.103 + 1.104 + def close(self): 1.105 + """ Close the socket. 1.106 + """ 1.107 + if self.sock: 1.108 + self.sock.close() 1.109 + self.sock = None