Exemple #1
0
 def receive(self, timeout=None):
     try:
         if timeout is not None:
             ready = select([self.sock], [], [], timeout)
             if self.shutdown_signal:
                 return False
             if ready[0]:
                 data = self.sock.recv(2048)
             else:
                 print_warning('Timed out on port {}', self.port)
                 return False
         else:
             data = self.sock.recv(2048)
         if self.shutdown_signal:
             return False
         if self.analyze:
             print_data("Raw response", data.hex())
         if not data:
             print_error("Got 0 bytes from socket")
             # self.close()
             return False
         else:
             self.decode(data)
             if self.analyze:
                 self.rpc_walk(consume_incoming=False, verbose=False)
             self.find_responses()
             return True
     except ConnectionResetError:
         print_error('Server hung up during receive')
         return False
Exemple #2
0
    def rpc_walk(self, consume_incoming=True, verbose=True):
        # Each section should correspond to a RPC request/reply,
        # with 2 outgoing protobufs and 2 (or more for propertyget/multipart) incoming,
        # or an RPC event, with 2 (or more?) incoming, and no outgoing

        if verbose or len(self.incoming_sections) != 0:
            print_info('Walking {}', self.name)
            print_data('=' * 32)
        if not self.rpc_server_is_phantom:
            print_warning('RPC server appears to be Spark here')

        if consume_incoming:
            for i in range(len(self.incoming_sections)):
                print_data('-' * 16)
                incoming_section = self.incoming_sections.popleft()
                self.rpc_walk_one_section(incoming_section)

            # Useless?
            if len(self.incoming_sections) != 0:
                print_error('There were {} incoming sections remaining', len(self.incoming_sections))

        else:
            for incoming_section in self.incoming_sections:
                print_data('-' * 16)
                self.rpc_walk_one_section(incoming_section)

        if len(self.outgoing_sections) != 0:
            print_error('There were {} outgoing sections remaining', len(self.outgoing_sections))
Exemple #3
0
    def __init__(self, raw_section, time=None):
        self.warnings = []
        self.raw_protobufs = []
        self.raw_fields = raw_section
        self.time = time

        # Sanity Checks
        try:
            self.magic = raw_section[0]['firstbyte']
            # Delimiter, always empty
            if len(raw_section[0]['data']) != 0:
                self.warnings.append('Field 0 of length {} instead of 0'.format(
                    len(raw_section[0]['data'])))

            i = 1
            # Extra field (+ delimiter) that doesnt decode into protobuf, some kind of uid?
            if self.magic == 0xC3:
                self.uid = raw_section[i]['data']
                i += 2
            else:
                self.uid = b''

            # Remaining fields should all be protobufs
            for raw_field in raw_section[i:]:
                self.raw_protobufs.append(raw_field['data'])
                if raw_field['firstbyte'] != self.magic:
                    self.warnings.append('Magic mismatch {} != {}'.format(
                        raw_field['firstbyte'], self.magic))
            if self.warnings:
                print_warning(self.warnings)
        except Exception as e:
            print_error('in Section decode: {} {}', type(e), e)
            pass
Exemple #4
0
 def close(self, arg=None):
     if self.sock is not None:
         print_warning('Closing Connection to {} on port {}', self.addr,
                       self.port)
         self.sock.shutdown(2)
         self.sock.close()
         self.file.close()
         self.sock = None
         self.file = None
Exemple #5
0
    def CallMethod(self,
                   method_descriptor,
                   rpc_controller,
                   request,
                   response_class,
                   done,
                   timeout=2):
        # For event methods, only register callback, don't send anything
        if method_descriptor.GetOptions(
        ).Extensions[dvltMethodOptions].isNotification:
            # For events, response class is always empty, request class is the actual class of the event
            self.event_callbacks[method_descriptor.full_name] = (
                dvlt_pool.messages[method_descriptor.input_type.full_name],
                done)
        else:
            reqUUID = uuid.uuid4().bytes
            serviceId = rpc_controller.parent_service.service_id if hasattr(
                rpc_controller.parent_service,
                'service_id') else self.find_service_id(
                    method_descriptor.containing_service)
            typeId, subTypeId = (
                1,
                0xFFFFFFFF) if method_descriptor.name == 'propertyGet' else (
                    0, method_descriptor.index)
            cmm_request = Devialet.CallMeMaybe.Request(serverId=self.serverId,
                                                       serviceId=serviceId,
                                                       requestId=reqUUID,
                                                       type=typeId,
                                                       subTypeId=subTypeId)
            # First add the callback to the queue...
            self.request_queue[reqUUID] = (method_descriptor, response_class,
                                           rpc_controller,
                                           done if done else self.unblock_call)
            if done is None:
                # Blocking Call
                self.blocked.clear()
            # ...Then send the actual request bytes
            self.write_rpc(cmm_request.SerializeToString(),
                           request.SerializeToString())
            print_info('Calling method {} from service {} ({})...',
                       method_descriptor.name,
                       rpc_controller.parent_service.serviceName, serviceId)
            print_data('... with argument:', request)

            if done is None:
                # Wait for callback to be executed or socket to die
                while self.sock is not None and not self.shutdown_signal and not self.blocked.wait(
                        timeout=timeout):
                    # Wait for blocked event to be unset
                    print_warning('Still blocked on method {}...',
                                  method_descriptor.full_name)
                if self.blocking_response is None:
                    # self.close()
                    print_error("Server hung up before response")
                return self.blocking_response
    def close_client(self, addrport):
        sock, file, c = self.clientsocks.pop(addrport)
        print_warning('Closing Connection to Client {} on port {}',
                      addrport[0], addrport[1])
        try:
            sock.shutdown(2)
        except OSError as e:
            print_error('Error in socket shutdown: {}, already closed?', e)
        try:
            sock.close()
        except OSError as e:
            print_error('Error in socket clse: {}, already closed?', e)

        file.close()
Exemple #7
0
    def run(self):
        print_info('Searching for Devialet Device...')
        while not self.shutdown_signal:
            ready = select([self.sock], [], [], self.period)
            if self.shutdown_signal:
                break
            if ready[0]:
                data, (sender_addr, sender_port) = self.sock.recvfrom(1024)

                if len(data) >= 12:
                    magic, serial_len = struct.unpack('>8sI', data[:12])
                    serial = data[12:]
                    if magic == b'DVL\x01HERE' and len(
                            data) == 12 + serial_len:
                        if serial not in self.database or self.database[
                                serial] != sender_addr:
                            self.database[serial] = sender_addr
                            print_info(
                                'Found Devialet Device with serial {} at address {}',
                                serial, sender_addr)
                            self.queue.put((serial, sender_addr))
                            if self.callback is not None:
                                self.callback(serial, sender_addr)
                    elif magic == b'DVL\x01BYE!' and len(
                            data) == 12 + serial_len:
                        print_info(
                            'Device with serial {} at address {} exited',
                            serial, sender_addr)
                        self.database.pop(serial)
                    else:
                        print_warning('Unknown data: {}', data.hex())
                        # Warning: Unknown data: 44564c014259452100000010066787a949a24d37a99c26d3ff193a5a
                elif data == b'DVL\x01WHO?':
                    print_info('Got Discovery request')
                else:
                    print_warning('Discovery message too short: {}',
                                  data.hex())
            else:
                if self.advertise:
                    print_info('Advertising...')
                    self.sock.sendto(
                        b'DVL\x01HERE\x00\x00\x00' +
                        bytes([len(self.serial)]) + self.serial,
                        ('255.255.255.255', self.port))
Exemple #8
0
    def close(self):
        rest_incoming = self.incoming_buf.read()
        rest_outgoing = self.outgoing_buf.read()

        if rest_incoming:
            self.warnings.append('Found garbage at end of incoming flow: {}...'.format(
                ' '.join('{:02x}'.format(x) for x in rest_incoming[:50])))

        if rest_outgoing:
            self.warnings.append('Found garbage at end of outgoing flow: {}...'.format(
                ' '.join('{:02x}'.format(x) for x in rest_outgoing[:50])))

        if self.warnings:
            print_warning(self.warnings)

        print_info('flow {} has {} incoming and {} outgoing sections',
                   self.name, len(self.incoming_sections), len(self.outgoing_sections))

        self.incoming_buf.close()
        self.outgoing_buf.close()
Exemple #9
0
 def try_to_find_service(self, service_name, port=None):
     try:
         ports = self.ports_by_service[service_name]
         if len(ports) > 1:
             print_warning(
                 'More than one possible endpoint match for service {}: {}',
                 service_name, ports)
         if port is None:
             port = ports[0]
         elif port not in ports:
             print_warning('Requested port not in list of matches')
             port = ports[0]
         print_info('Using port {} for service {}', port, service_name)
         svc_class = dvlt_pool.service_by_name[service_name]._concrete_class
         client = DevialetClient(name=service_name + ' client',
                                 port=port,
                                 addr=self.addr)
         return client, svc_class(client)
     except KeyError:
         print_error("Can't find service {} in whatsup services",
                     service_name)
     pass
    def find_requests(self, addrport):
        flow = self.flows[addrport]
        while flow.outgoing_sections:
            outgoing_section = flow.outgoing_sections.popleft()
            outgoing_pb = outgoing_section.raw_protobufs

            # print_warning("Found Request")
            req = Devialet.CallMeMaybe.Request.FromString(outgoing_pb[0])
            if req.serverId != self.serverId and req.serverId != b'\x00' * 16:
                print_warning(
                    'Oops, this request is not for us (unless this is inital conenction request): we are {}, sent to {}',
                    self.serverId.hex(), req.serverId.hex())
            try:
                srv = self.services[req.serviceId]
                if req.type == 0:
                    try:
                        method_desc = srv.methods_by_id[req.subTypeId]
                        if method_desc.full_name in self.request_callbacks:
                            (request_class, callback
                             ) = self.request_callbacks[method_desc.full_name]
                            response = callback(
                                request_class.FromString(outgoing_pb[1]))
                            rep = Devialet.CallMeMaybe.Reply(
                                serverId=self.serverId,
                                serviceId=req.serviceId,
                                requestId=req.requestId,
                                type=0,
                                subTypeId=req.subTypeId,
                                errorCode=0,
                                isMultipart=False)
                            self.write_response_to_addr(
                                addrport, rep.SerializeToString(),
                                response.SerializeToString())
                        else:
                            print_error('Unhandled method {} from {}',
                                        method_desc.full_name, addrport)

                    except KeyError:
                        print_errordata(
                            'Method id {} not in service method dict:'.format(
                                req.subTypeId), {
                                    k: v.full_name
                                    for (k, v) in srv.methods_by_id.items()
                                })
                elif req.type == 1 and req.subTypeId == 0xFFFFFFFF:
                    # PropertyGet special request
                    rep = Devialet.CallMeMaybe.Reply(serverId=self.serverId,
                                                     serviceId=req.serviceId,
                                                     requestId=req.requestId,
                                                     type=1,
                                                     subTypeId=0xFFFFFFFF,
                                                     errorCode=0,
                                                     isMultipart=True)
                    print_info('Got PropertyGet request:')
                    for name, prop in srv.properties.items():
                        print_data(name, prop)
                    self.write_responses_to_addr(
                        addrport, rep.SerializeToString(),
                        [p.SerializeToString() for p in srv.get_properties()])
                elif req.type == 1:
                    # PropertySet special request
                    # TODO
                    print_errordata(
                        'Unhandled propertyset subtypeId={}'.format(
                            req.subTypeId), [x.hex() for x in outgoing_pb])
                    pass
                else:
                    print_error('Unknown request type {}', req.type)
            except KeyError:
                print_error(
                    'Service ID {} not in list {}', req.serviceId,
                    ' '.join(str(self.service_list.services).split('\n')))

        self.flows[addrport].outgoing_sections.clear()
 def close(self, arg=None):
     # if self.sock is not None:
     print_warning('Closing Server Connection on port {}', self.port)
     self.sock.shutdown(2)
     self.sock.close()
Exemple #12
0
    def service_discovery(self):
        # if truncated_name not in dvlt_pool.service_by_name:
        #     ports_by_unknown_service.setdefault(truncated_name, []).append(port)

        # print_data("Services by port", services_by_port)
        # print_data("Ports by Service", ports_by_service)
        # print_data("Ports by (Unknown) Service", ports_by_unknown_service)

        for port, services in self.services_by_port.items():
            # if [0 for srvname in services if srvname not in dvlt_pool.service_by_name]:
            # for service in services:
            #     if srvname not in dvlt_pool.service_by_name:
            if port not in self.connections:
                new_conn = DevialetClient(name='Port ' + str(port),
                                          addr=self.addr,
                                          port=port,
                                          start_time=datetime.now())
                new_conn.open()
                self.connections[port] = new_conn

                # Connect to unknown service
                for service in new_conn.service_list.services:
                    # Reopen if connection failed
                    if new_conn.sock is None:
                        res = new_conn.reconnect()
                        if not res:
                            break
                    truncated_name = '.'.join(
                        service.name.replace('playthatfunkymusic',
                                             'toomanyflows').split('.')[:-1])
                    while truncated_name not in dvlt_pool.service_by_name and truncated_name.endswith(
                            '-0'):
                        truncated_name = '.'.join(
                            truncated_name.split('.')[:-1])
                        print_warning('truncating {} to {}',
                                      '.'.join(service.name.split('.')[:-1]),
                                      truncated_name)
                    # if truncated_name not in dvlt_pool.service_by_name:  # and truncated_name.endswith('-0'):
                    # if (port, truncated_name) not in self.discovered_services and truncated_name not in dvlt_pool.service_by_name:
                    # print_info('Testing 10 methods from service {}', service.name)
                    # for subTypeId in range(10):
                    #     result = new_conn.CallUnknownMethod(service.id, subTypeId, Devialet.CallMeMaybe.Empty(), None)
                    #     print_data("Response from method id {} from service {}".format(subTypeId, service.name),
                    #                result)
                    #     if result is None:
                    #         break
                    if (port, truncated_name) not in self.discovered_services:
                        if truncated_name in dvlt_pool.service_by_name:
                            srv = dvlt_pool.service_by_name[
                                truncated_name]._concrete_class(new_conn)
                            srv.watch_properties(service_id=service.id,
                                                 service_name=service.name)
                            # srv.service_id = service.id
                            # srv.service_name_unique = service.name
                            # srv.propertyGet(DevialetController(srv), Devialet.CallMeMaybe.Empty(), srv.set_properties)
                            # new_conn.keep_receiving(timeout=2)
                        else:
                            result = new_conn.CallUnknownMethod(
                                service.id,
                                0xFFFFFFFF,
                                Devialet.CallMeMaybe.Empty(),
                                None,
                                typeId=1)
                            print_data(
                                "Unknown properties from service {}".format(
                                    service.name), result)
                            pass
                # new_conn.keep_receiving(timeout=2)
                new_conn.close()
                self.connections.pop(port)
Exemple #13
0
    def find_responses(self):
        for i in range(len(self.incoming_sections)):
            incoming_section = self.incoming_sections.popleft()
            incoming_pb = incoming_section.raw_protobufs

            if incoming_section.magic == 0xC3:
                print_warning("Found Event")
                # try:
                evt = Devialet.CallMeMaybe.Event.FromString(incoming_pb[0])
                service_name = self.find_service(evt)

                method, input_pb, outputs_pb = dvlt_pool.process_rpc(
                    service_name,
                    evt,
                    incoming_pb[1],
                    incoming_pb[1:],
                    is_event=True)
                try:
                    (response_class,
                     callback) = self.event_callbacks[method.full_name]
                    if evt.type == 1:
                        # PropertyUpdate special event
                        callback(evt.subTypeId, incoming_pb[1])
                    else:
                        callback(response_class.FromString(incoming_pb[1]))
                except KeyError:
                    print_error('Unhandled event {}', method.full_name)
                    print_errordata('Registered Callbacks',
                                    self.event_callbacks)
                except AttributeError:  # method can be none eg when service name not in db
                    pass

                if incoming_section.uid != self.serverId:
                    print_error('Oops, event from different server Id ?!')
                # except IndexError:
                #     print_error('not enough incoming protos for event ({}) < 2', len(incoming_pb))

            else:
                try:
                    rep = Devialet.CallMeMaybe.Reply.FromString(incoming_pb[0])

                    method_descriptor, response_class, controller, callback = self.request_queue.pop(
                        rep.requestId)

                    if rep.errorCode != 0:
                        # print_warning("Got error code: {} ({})", controller.parent_service.get_error(rep.errorCode), rep.errorCode)
                        controller.SetFailed(rep.errorCode)
                        # use errorEnumName, controller...

                    # None is used for unknown method calls
                    if response_class is not None:
                        # PropertyGet special "method"
                        if rep.subTypeId == 0xFFFFFFFF and rep.type == 1:
                            # response = {}
                            # for i, raw in enumerate(incoming_pb[1:]):
                            #     name, prop = dvlt_pool.get_property(method_descriptor.containing_service, i, raw)
                            #     response[name] = prop
                            response = incoming_pb[1:]
                        else:
                            response = response_class.FromString(
                                incoming_pb[1])
                        # print_data("Found Response", response)
                        callback(response)
                    else:
                        # print_data('Response with unknown message type:',
                        #            dvlt_pool.heuristic_search(incoming_pb[1]))
                        if rep.subTypeId == 0xFFFFFFFF and rep.type == 1:
                            callback([
                                dvlt_pool.heuristic_search(x)
                                for x in incoming_pb[1:]
                            ])
                        else:
                            callback(dvlt_pool.heuristic_search(
                                incoming_pb[1]))

                    if not rep.isMultipart and len(incoming_pb) > 2:
                        print_warning(
                            'we got more incoming protobufs than we should have, not Multipart'
                        )

                    if len(incoming_pb) < 2:
                        print_error('we got less than 2 incoming protobufs')

                    # print_info('out_time:{} in_time:{} req:{}/{}/{:>10d}/{:>12d}, rep:{}/{}/{:>10d}/{:>12d} {}{}{}',
                    #            outgoing_section.time, incoming_section.time,
                    #            req.requestId[:4].hex(), req.type, req.subTypeId, req.serviceId,
                    #            rep.requestId[:4].hex(), rep.type, rep.subTypeId, rep.serviceId,
                    #            'M' if rep.isMultipart else ' ',
                    #            ' ' if method.name == 'ping' else 'C' if method.name == 'openConnection' else '.',
                    #            '<' if rep.requestId != req.requestId else ' ')
                except KeyError:
                    print_error('Response to unknown request id {}',
                                rep.requestId.hex())
                    print_errordata('Request queue', self.request_queue)
Exemple #14
0
    def rpc_walk_one_section(self, incoming_section):
        incoming_pb = incoming_section.raw_protobufs

        if incoming_section.magic == 0xC3:
            # Event?
            try:
                evt = dvlt_pool.interpret_as(incoming_pb[0], 'Devialet.CallMeMaybe.Event')
                service_name = self.find_service(evt)

                method, input_pb, outputs_pb = dvlt_pool.process_rpc(service_name, evt, incoming_pb[1], incoming_pb[1:], is_event=True)
                if method.containing_service.full_name == 'Devialet.CallMeMaybe.Connection':
                    if method.name == 'serviceAdded':
                        print_info('Extending list of services')
                        self.service_list.services.add(name=input_pb.name, id=input_pb.id)

                # The 'uid' parameter in the section, when present (== when magic is C3, and only on incoming packets)
                # should be equal to the server ID
                print_info('in_time:{} evt:{} {}/{:>10d}/{:>12d}{:31s} {} E',
                           incoming_section.time,
                           evt.serverId[:4].hex(), evt.type, evt.subTypeId, evt.serviceId,
                           '',
                           incoming_section.uid[:4].hex())
            except IndexError:
                print_error('not enough incoming protos for event ({}) < 2', len(incoming_pb))
            except AttributeError:
                pass

        else:
            try:
                outgoing_section = self.outgoing_sections.popleft()
                outgoing_pb = outgoing_section.raw_protobufs
                req = dvlt_pool.interpret_as(outgoing_pb[0], 'Devialet.CallMeMaybe.Request')
                rep = dvlt_pool.interpret_as(incoming_pb[0], 'Devialet.CallMeMaybe.Reply')
                bad_reqs = deque()

                # print('UUID {}'.format(req.requestId.hex()))

                # (First two bytes (or more) of request id appear to be sequential)
                while rep.requestId != req.requestId:
                    bad_reqs.appendleft(outgoing_section)
                    print_warning('Request id {} out of order', req.requestId.hex())
                    # print_errordata('Full Request:', req)
                    try:
                        outgoing_section = self.outgoing_sections.popleft()
                        outgoing_pb = outgoing_section.raw_protobufs
                        req = dvlt_pool.interpret_as(outgoing_pb[0], 'Devialet.CallMeMaybe.Request')
                    except IndexError:
                        # Reached end of outgoing_sections
                        # print_error("Couldn't find request for reply {}", rep.requestId.hex())
                        break
                self.outgoing_sections.extendleft(bad_reqs)

                if not rep.isMultipart and len(incoming_pb) > 2:
                    print_warning('we got more incoming protobufs than we should have, not Multipart')

                if len(outgoing_pb) > 2:
                    print_error('we got more than 2 outgoing protobufs')

                if len(outgoing_pb) < 2:
                    print_error('we got less than 2 outgoing protobufs')

                if len(incoming_pb) < 2:
                    print_error('we got less than 2 incoming protobufs')

                service_name = self.find_service(rep)
                method, input_pb, outputs_pb = dvlt_pool.process_rpc(
                    service_name, rep, outgoing_pb[1], incoming_pb[1:])

                if method.containing_service.full_name == 'Devialet.CallMeMaybe.Connection':
                    if method.name == 'openConnection':
                        self.service_list = outputs_pb[0]

                print_info('out_time:{} in_time:{} req:{}/{}/{:>10d}/{:>12d}, rep:{}/{}/{:>10d}/{:>12d} {}{}{}',
                           outgoing_section.time, incoming_section.time,
                           req.requestId[:4].hex(), req.type, req.subTypeId, req.serviceId, rep.requestId[:4].hex(), rep.type, rep.subTypeId, rep.serviceId,
                           'M' if rep.isMultipart else ' ',
                           ' ' if method.name == 'ping' else 'C' if method.name == 'openConnection' else '.',
                           '<' if rep.requestId != req.requestId else ' ')

            except IndexError:
                print_error('Stream ended prematurely, missing outgoing section')
            except Exception as e:
                print_error('Unexpected {} {}', type(e), e)
Exemple #15
0
        r2.cmd('aaa')
        # print_info('Finished analysis')
        sections = json.loads(r2.cmd('Sj'))
        xrefs = r2.cmd('axF InternalAddGeneratedFile|cut -d" " -f2|sort -u').split()

        def get_paddr(vaddr):
            for section in sections:
                if vaddr >= section['vaddr'] and vaddr < section['vaddr'] + section['size']:
                    return vaddr - section['vaddr'] + section['paddr']
            print_error("Can't find virtual address {}", vaddr)
            return 0

        for xref in xrefs:
            disasm = json.loads(r2.cmd('pdj -2 @ ' + xref))
            if disasm[0]['type'] == 'push' and disasm[1]['type'] == 'push':
                length = disasm[0]['val']
                addr = disasm[1]['val']
                # print_info('Found protobuf of length {:6d} at addr 0x{:8x}', length, addr)
                paddr = get_paddr(addr)
                data = f[paddr:paddr+length]
                try:
                    fdp = FileDescriptorProto.FromString(data)
                    print_info('Found FiledescriptorProto of length {:6d} at addr 0x{:08x}: {}', length, paddr, fdp.name, color='green')
                    outfile = open(os.path.join(out_dir, fdp.name.replace('/', '_')), 'wb')
                    outfile.write(data)
                    # print(fdp)
                except google.protobuf.message.DecodeError:
                    print_error('Error while decoding data at offset 0x{:08x}, length {:6d} as FiledescriptorProto', paddr, length)
            else:
                print_warning('No push in immediate vicinity')
Exemple #16
0
    def __init__(self, filename, spark_addr, phantom_addr, decode_by_flow=False):
        DevialetManyFlows.__init__(self)
        self.filename = filename
        self.spark_addr = spark_addr
        self.phantom_addr = phantom_addr
        self.decode_by_flow = decode_by_flow

        # print_info('Loading file...')
        # capture = scapy.all.rdpcap(filename)
        # self.sessions = capture[TCP].sessions()
        with PcapReader(filename) as pcap_reader:
            for packet in pcap_reader:
                # print(packet)
                if packet.haslayer(TCP):
            # for packet in capture[TCP]:
                    time = datetime.fromtimestamp(packet.time)
                    # print(packet)
                    # if packet.
                    spark_port = -1
                    if packet[IP].src == self.spark_addr and packet[IP].dst == self.phantom_addr:
                        spark_port = packet[TCP].sport
                        phantom_port = packet[TCP].dport
                        phantom_to_spark = False
                    elif packet[IP].src == self.phantom_addr and packet[IP].dst == self.spark_addr:
                        spark_port = packet[TCP].dport
                        phantom_port = packet[TCP].sport
                        phantom_to_spark = True
                    if spark_port >= 0:
                        if (phantom_port, spark_port) in self.flows:
                            flow = self.flows[(phantom_port, spark_port)]
                        else:
                            print_warning('New Flow phantom {}, spark {}, time {}', phantom_port, spark_port, time)
                            flow = DevialetFlow(name='phantom {}, spark {}'.format(phantom_port, spark_port),
                                                phantom_port=phantom_port, spark_port=spark_port, start_time=time)
                            flow.phantom = SeqData()
                            flow.spark = SeqData()
                            # if first packet is from phantom to spark, rpc server is probably spark
                            if phantom_to_spark:
                                print_info('Found flow where Spark appears to be RPC server: phantom {}, spark {}',
                                           phantom_port, spark_port)
                                flow.rpc_server_is_phantom = False
                            self.add_flow(flow)
                        # Reverse direction if rpc server is Spark
                        srv_to_client = phantom_to_spark ^ (not flow.rpc_server_is_phantom)
                        tcplen = packet[IP].len - packet[IP].ihl*4 - packet[TCP].dataofs*4
                        sending = (flow.phantom if phantom_to_spark else flow.spark)
                        receiving = (flow.spark if phantom_to_spark else flow.phantom)

                        if 'S' in packet.sprintf('%TCP.flags%') or 'F' in packet.sprintf('%TCP.flags%'):
                            # if SYN, synchronize sequence numbers
                            print_info('Sp {:6d} {} Ph {:6d} Len {:5d} Seq {:12d} Ack {:12d} Diff {:12d} Flags {}',
                                       spark_port, '<- ' if phantom_to_spark else ' ->', phantom_port, tcplen,
                                       packet[TCP].seq - sending.isn,
                                       packet[TCP].ack - receiving.isn,
                                       0, packet.sprintf('%TCP.flags%'),
                                       color='red')
                            sending.isn = packet[TCP].seq
                            sending.seq = packet[TCP].seq + 1
                            if sending.ood:
                                print_error('{} remaining in {} OOD queue', len(sending.ood),
                                            'Phantom' if phantom_to_spark else 'Spark')
                                sending.ood.clear()
                        else:
                            # print(packet[TCP].load[:12].hex())
                            diff = packet[TCP].seq - sending.seq
                            print_info('Sp {:6d} {} Ph {:6d} Len {:5d} Seq {:12d} Ack {:12d} Diff {:12d} Flags {}',
                                       spark_port, '<- ' if phantom_to_spark else ' ->', phantom_port, tcplen,
                                       packet[TCP].seq - sending.isn,
                                       packet[TCP].ack - receiving.isn,
                                       diff, packet.sprintf('%TCP.flags%'),
                                       color='blue' if phantom_to_spark else 'green',
                                       reverse=(diff != 0))
                            if diff == 0:
                                sending.seq = packet[TCP].seq + tcplen
                                if tcplen:
                                    flow.decode(packet[TCP].load[:tcplen], time=time, incoming=srv_to_client)
                                    if not self.decode_by_flow:
                                        flow.rpc_walk(verbose=False)
                                for p in list(sending.ood):
                                    l = p[IP].len - p[IP].ihl*4 - p[TCP].dataofs*4
                                    d = p[TCP].seq - sending.seq
                                    if d == 0:
                                        sending.seq = p[TCP].seq + l
                                        if l:
                                            flow.decode(p[TCP].load[:l], time=time, incoming=srv_to_client)
                                            if not self.decode_by_flow:
                                                flow.rpc_walk(verbose=False)
                                        sending.ood.remove(p)
                                        print_info('Sp {:6d} {} Ph {:6d} Len {:5d} Seq {:12d} Ack {:12d} Diff {:12d} Flags {:3} Reordered',
                                                   spark_port, '<- ' if phantom_to_spark else ' ->', phantom_port, l,
                                                   p[TCP].seq - sending.isn, p[TCP].ack - receiving.isn,
                                                   d, p.sprintf('%TCP.flags%'), color='magenta')
                                    else:
                                        # print_warning('{:6d} {} Ph {:6d} Len {:5d} Seq {:12d} Ack {:12d} Diff {:12d} Flags {:3} OOD',
                                        #               spark_port, '<- ' if phantom_to_spark else ' ->', phantom_port, l,
                                        #               p[TCP].seq - sending.isn, p[TCP].ack - receiving.isn,
                                        #               d, p.sprintf('%TCP.flags%'))
                                        pass
                            else:
                                sending.ood.append(packet)
from dvlt_client import WhatsUpClient
from dvlt_server import DevialetServer, WhatsUpServer
from dvlt_discovery import DevialetDiscovery
from queue import Queue

# Find a suitable Devialet device
hostUid = b'etincelle-' + bytes(uuid.uuid4().hex, 'ascii')
target_serial = b'J'

discovered = Queue()
dscvr = DevialetDiscovery(discovered, serial=hostUid)
dscvr.start()

serial, dvlt_addr = discovered.get(block=True)
while not serial.startswith(target_serial):
    print_warning('Not who we are looking for')
    serial, dvlt_addr = discovered.get(block=True)

# Open a WhatsUp connection
wu_client = WhatsUpClient(name="WhatsUp", addr=dvlt_addr, port=24242)
wu_client.go()

# Init WhatsUp server
wu_srv = WhatsUpServer(hostUid=hostUid)
wu_srv.open()

# Init AudioSource server
putp_server = DevialetServer(wu_srv, hostUid=hostUid)
putp_service = Devialet.AudioSource.OnlineSourceSession(putp_server)
putp_service.serviceName += '.pickupthepieces'
putp_service.properties = {