def SizedUTF16MultiSz(size_func): return ExprAdapter( FixedSized(size_func, GreedyRange(CString("UTF_16_le"))), lambda obj, ctx: list( obj), # last element is already removed by GreedyRange lambda obj, ctx: obj + [''])
from construct import Struct, Const, SymmetricAdapter, Int32ul, Int64ul, PaddedString, Bytes, Array, PrefixedArray, \ Pointer, Tell, Switch, Error, Int16ul, Byte, Computed, ExprAdapter, Adapter, Rebuild, Pass, Enum from procmon_parser.construct_helper import FixedNullTerminatedUTF16String, OriginalEnumAdapter, Filetime, \ ListAdapter, PVoid, Duration, CheckCustom from procmon_parser.logs_details_format import NetworkDetails, RegistryDetails, FilesystemDetails, ProcessDetails from procmon_parser.consts import EventClass, ProcessOperation, RegistryOperation, NetworkOperation, ProfilingOperation, \ FilesystemOperation from procmon_parser.logs import Process, Event EventClassType = OriginalEnumAdapter(Int32ul, EventClass) ProcessOperationType = Enum(Int16ul, ProcessOperation) RegistryOperationType = Enum(Int16ul, RegistryOperation) NetworkOperationType = Enum(Int16ul, NetworkOperation) ProfilingOperationType = Enum(Int16ul, ProfilingOperation) FilesystemOperationType = Enum(Int16ul, FilesystemOperation) StringIndex = ExprAdapter(Struct("string_index" / Int32ul), lambda obj, ctx: ctx._.strings_table[obj.string_index], lambda obj, ctx: ctx._.strings_table.index(obj)) ProcessIndex = ExprAdapter(Struct("process_index" / Int32ul), lambda obj, ctx: ctx._.process_table[obj.process_index], lambda obj, ctx: ctx._.process_table.index(obj)) SUPPORTED_VERSIONS = [9] class PMLVersionNumberValidator(SymmetricAdapter): def _decode(self, obj, context, path): if obj not in SUPPORTED_VERSIONS: raise RuntimeError("PML is version {} while only versions {} are supported", obj, SUPPORTED_VERSIONS) Header = """ The header of the PML file. """ * Struct(
"_not_used2" / BitsInteger(6), "bell_output_overload_trouble" / Flag, "bell_output_disconnected_trouble" / Flag, "_not_used3" / BitsInteger(2), "computer_fail_to_communicate_trouble" / Flag, "voice_fail_to_communicate_trouble" / Flag, "pager_fail_to_communicate_trouble" / Flag, "central_2_reporting_ftc_indicator_trouble" / Flag, "central_1_reporting_ftc_indicator_trouble" / Flag, "telephone_line" / Flag, ), "system" / Struct( "date" / Struct("time" / DateAdapter(Bytes(6))), "power" / Struct( "vdc" / ExprAdapter( Byte, lambda obj, ctx: round(obj * (20.3 - 1.4) / 255.0 + 1.4, 1), 0), "battery" / ExprAdapter( Byte, lambda obj, ctx: round(obj * 22.8 / 255.0, 1), 0), "dc" / ExprAdapter( Byte, lambda obj, ctx: round(obj * 22.8 / 255.0, 1), 0), ), "rf" / Struct("noise_floor" / Int8ub, ), ), "zone_open" / StatusAdapter(Bytes(4)), "zone_tamper" / StatusAdapter(Bytes(4)), "pgm_tamper" / StatusAdapter(Bytes(2)), "module_tamper" / StatusAdapter(Bytes(2)), "zone_fire" / StatusAdapter(Bytes(4)), "_not_used0" / Int8ub, ),
def create_control_record_struct(self): little_endian = self.endianness == "little" min_mod = self.min_modulus control_len = self.control_len # Data types for integer values Int16s, Int16u, Int32s, Int32u = { "big": (Int16sb, Int16ub, Int32sb, Int32ub), "little": (Int16sl, Int16ul, Int32sl, Int32ul), }[self.endianness] # The 16 bytes that used to have the legacy MFTYPE field # has both the actual MFTYPE and the MSTXL, # and their order is affected by the endianness mftype = "mftype" / Default(Byte, 0) mstxl = "mstxl" / Rebuild( Byte, lambda this: this.mstxl if "mstxl" in this and this.mstxl is not None else this.get( "shift", this.modulus.bit_length() - 1 if "modulus" in this else self.default_shift, ), ) # MFCXX2 and MFCXX3 are used for locking the file on updating if self.lockable: mfcxx2 = "_mfcxx2" / Union( 0, "mfcxx2" / Default(Int32s, 0), "delock_count" / Int32u, # Data entry lock ) mfcxx3 = "_mfcxx3" / Union( 0, "mfcxx3" / Default(Int32s, 0), "ewlock" / ExprAdapter( Int32u, # Exclusive write lock lambda obj, ctx: bool(obj), lambda obj, ctx: int(obj)), ) else: mfcxx2 = "mfcxx2" / Default(Int32s, 0) mfcxx3 = "mfcxx3" / Default(Int32s, 0) # Legacy shift replacement if self.shift4is3: shift = "shift" / Computed(lambda this: 3 if this.mstxl == 4 else this.mstxl) else: shift = "shift" / Computed(lambda this: this.mstxl) # The control record struct for all cases nested_unpadded_struct = Struct( # First 4 fields, including information about the whole file "mfn" / Default(Int32s, 0), # CTLMFN Check(lambda this: this.mfn == 0), "next_mfn" / Default(Int32s, 1), # NXTMFN "next_block" / Default(Int32s, 1), # NXTMFB "next_offset" / Default(Int16u, self.control_len), # NXTMFP *([mftype, mstxl] if little_endian else [mstxl, mftype]), # Get the MST (modulus) and XRF (shift) alignment/shift values # from the MSTXL field, fixing the "legacy" replacements shift, "modulus" / Computed(lambda this: max(min_mod, 1 << this.shift)), Check(lambda this: control_len % this.modulus == 0), # Fields used for "statistics during backup/restore", # where the last two fields are also used for multi-user locking "reccnt" / Default(Int32s, 0), "mfcxx1" / Default(Int32s, 0), mfcxx2, mfcxx3, ) return Padded( length=control_len, subcon=Unnest(["_mfcxx2", "_mfcxx3"], nested_unpadded_struct) if self.lockable else nested_unpadded_struct, pattern=self.control_filler, )
def __setup_constructors(self): '''Set endianness and create transport-specific constructors.''' # Set endianness of constructors before using them. self._set_endian('little') self.__Length = Int32ul self.__Type = Enum( Int16ul, default=Pass, Undefined=0x0000, Command=0x0001, Data=0x0002, Response=0x0003, Event=0x0004, ) # This is just a convenience constructor to get the size of a header. self.__Code = Int16ul self.__Header = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'Code' / self.__Code, 'TransactionID' / self._TransactionID, ) # These are the actual constructors for parsing and building. self.__CommandHeader = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'OperationCode' / self._OperationCode, 'TransactionID' / self._TransactionID, ) self.__ResponseHeader = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'ResponseCode' / self._ResponseCode, 'TransactionID' / self._TransactionID, ) self.__EventHeader = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'EventCode' / self._EventCode, 'TransactionID' / self._TransactionID, ) # Apparently nobody uses the SessionID field. Even though it is # specified in ISO15740:2013(E), no device respects it and the session # number is implicit over USB. self.__Param = Range(0, 5, self._Parameter) self.__CommandTransactionBase = Struct( Embedded(self.__CommandHeader), 'Payload' / Bytes( lambda ctx, h=self.__Header: ctx.Length - h.sizeof() ) ) self.__CommandTransaction = ExprAdapter( self.__CommandTransactionBase, encoder=lambda obj, ctx, h=self.__Header: Container( Length=len(obj.Payload) + h.sizeof(), **obj ), decoder=lambda obj, ctx: obj, ) self.__ResponseTransactionBase = Struct( Embedded(self.__ResponseHeader), 'Payload' / Bytes( lambda ctx, h=self.__Header: ctx.Length - h.sizeof()) ) self.__ResponseTransaction = ExprAdapter( self.__ResponseTransactionBase, encoder=lambda obj, ctx, h=self.__Header: Container( Length=len(obj.Payload) + h.sizeof(), **obj ), decoder=lambda obj, ctx: obj, )
) TaggedData = Struct( 'tag'/Int16ul, 'value'/Switch(this.tag, { 3: Int32sl, 5: Float64l, 7: Int64ul, # timestamp? 8: VBString, 9: SomeKindOfEnumMaybe, 11: Const(b'\x00' * 2), # null? 35: Const(b'\x00' * 6), # EOF }) ) # Specialization for loading float data faster TaggedFloat64 = ExprAdapter( Struct(Const('tag'/Int16ul, 5), 'value'/Float64l), encoder=lambda obj, ctx: Container(tag=obj.tag, value=obj.value), decoder=lambda obj, ctx: obj.value) DataList = Struct( 'size'/Int64ul, OnDemand(Array(lambda ctx: ctx.size, TaggedFloat64)), Const('\xc0\xff\xee\x01') # XXX: probably useful ) # XXX: hacks bad_strings = ('\xc0\xff\xee\x01\x00\x00', '\x01#Eg\x00\x00') Property = Struct( 'peek'/Peek(String(6)), Embedded(IfThenElse( this.peek in bad_strings, Padding(6), Struct('label'/VBString, 'TaggedData'/TaggedData))) )
except ValueError: return -1 def PacketLength(subcons): return Rebuild( subcons, lambda x: x._root._subcons.fields.sizeof() + x._root._subcons.checksum.sizeof(), ) def PacketChecksum(subcons): return Checksum(subcons, lambda data: calculate_checksum(data), this.fields.data) HexInt = ExprAdapter(Int8ub, _hex_to_int, lambda obj, path: int(str(obj), 16)) ProductIdEnum = Enum( Int8ub, DIGIPLEX_v13=0, DIGIPLEX_v2=1, DIGIPLEX_NE=2, DIGIPLEX_EVO_48=3, DIGIPLEX_EVO_96=4, DIGIPLEX_EVO_192=5, DIGIPLEX_EVO_HD=7, SPECTRA_SP5500=21, SPECTRA_SP6000=22, SPECTRA_SP7000=23, SPECTRA_SP4000=26, SPECTRA_SP65=27,
def __setup_constructors(self): '''Set endianness and create transport-specific constructors.''' # Set endianness of constructors before using them. self._set_endian('little') self.__Length = Int32ul self.__Type = Enum( Int32ul, Undefined=0x00000000, InitCommand=0x00000001, InitCommandAck=0x00000002, InitEvent=0x00000003, InitEventAck=0x00000004, InitFail=0x00000005, Command=0x00000006, Response=0x00000007, Event=0x00000008, StartData=0x00000009, Data=0x0000000A, Cancel=0x0000000B, EndData=0x0000000C, Ping=0x0000000D, Pong=0x0000000E, ) self.__Header = Struct( 'Length' / self.__Length, 'Type' / self.__Type, ) self.__Param = Range(0, 5, self._Parameter) self.__EventParam = Range(0, 3, self._Parameter) self.__PacketBase = Struct( Embedded(self.__Header), 'Payload' / Bytes( lambda ctx, h=self.__Header: ctx.Length - h.sizeof()), ) self.__Packet = ExprAdapter( self.__PacketBase, encoder=lambda obj, ctx, h=self.__Header: Container( Length=len(obj.Payload) + h.sizeof(), **obj ), decoder=lambda obj, ctx: obj, ) # Yet another arbitrary string type. Max-length CString utf8-encoded self.__PTPIPString = ExprAdapter( RepeatUntil( lambda obj, ctx, lst: six.unichr(obj) in '\x00' or len(lst) == 40, Int16ul ), encoder=lambda obj, ctx: [] if len(obj) == 0 else[ord(c) for c in six.text_type(obj)]+[0], decoder=lambda obj, ctx: u''.join( [six.unichr(o) for o in obj] ).split('\x00')[0], ) # PTP/IP packets # Command self.__ProtocolVersion = Struct( 'Major' / Int16ul, 'Minor' / Int16ul, ) self.__InitCommand = Embedded(Struct( 'InitiatorGUID' / Array(16, Int8ul), 'InitiatorFriendlyName' / self.__PTPIPString, 'InitiatorProtocolVersion' / self.__ProtocolVersion, )) self.__InitCommandACK = Embedded(Struct( 'ConnectionNumber' / Int32ul, 'ResponderGUID' / Array(16, Int8ul), 'ResponderFriendlyName' / self.__PTPIPString, 'ResponderProtocolVersion' / self.__ProtocolVersion, )) # Event self.__InitEvent = Embedded(Struct( 'ConnectionNumber' / Int32ul, )) # Common to Events and Command requests self.__Reason = Enum( # TODO: Verify these codes... Int32ul, Undefined=0x0000, RejectedInitiator=0x0001, Busy=0x0002, Unspecified=0x0003, ) self.__InitFail = Embedded(Struct( 'Reason' / self.__Reason, )) self.__DataphaseInfo = Enum( Int32ul, Undefined=0x00000000, In=0x00000001, Out=0x00000002, ) self.__Command = Embedded(Struct( 'DataphaseInfo' / self.__DataphaseInfo, 'OperationCode' / self._OperationCode, 'TransactionID' / self._TransactionID, 'Parameter' / self.__Param, )) self.__Response = Embedded(Struct( 'ResponseCode' / self._ResponseCode, 'TransactionID' / self._TransactionID, 'Parameter' / self.__Param, )) self.__Event = Embedded(Struct( 'EventCode' / self._EventCode, 'TransactionID' / self._TransactionID, 'Parameter' / self.__EventParam, )) self.__StartData = Embedded(Struct( 'TransactionID' / self._TransactionID, 'TotalDataLength' / Int64ul, )) # TODO: Fix packing and unpacking dataphase data self.__Data = Embedded(Struct( 'TransactionID' / self._TransactionID, 'Data' / Bytes( lambda ctx: ctx._.Length - self.__Header.sizeof() - self._TransactionID.sizeof() ), )) self.__EndData = Embedded(Struct( 'TransactionID' / self._TransactionID, 'Data' / Bytes( lambda ctx: ctx._.Length - self.__Header.sizeof() - self._TransactionID.sizeof() ), )) self.__Cancel = Embedded(Struct( 'TransactionID' / self._TransactionID, )) # Convenience construct for parsing packets self.__PacketPayload = Debugger(Struct( 'Header' / Embedded(self.__Header), 'Payload' / Embedded(Switch( lambda ctx: ctx.Type, { 'InitCommand': self.__InitCommand, 'InitCommandAck': self.__InitCommandACK, 'InitEvent': self.__InitEvent, 'InitFail': self.__InitFail, 'Command': self.__Command, 'Response': self.__Response, 'Event': self.__Event, 'StartData': self.__StartData, 'Data': self.__Data, 'EndData': self.__EndData, }, default=Pass, )) ))
Binary : 109, long : 111, Fun : 112, MFA : 113, map : 116, BitBinary : 77, } if obj == []: return 106 return mapping[obj.__class__] # Recurrent term term_ = LazyBound(lambda: term) atom_cache_ref = ExprAdapter(Int8ub, encoder=lambda obj, ctx: obj.index, decoder=lambda obj, ctx: AtomCacheReference(obj)) small_integer = Int8ub integer = Int32sb float_ = ExprAdapter(PaddedString(31, encoding="ascii"), encoder=lambda obj, ctx: u"{:.20e} ".format(obj), decoder=lambda obj, ctx: float(obj)) atom = PascalString(lengthfield=Int16ub, encoding="latin1") reference = ExprAdapter(Sequence("node" / term_, "id" / Int32ub, "creation" / Int8ub), encoder=lambda obj, ctx: (obj.node, obj.id, obj.creation), decoder=lambda obj, ctx: Reference(*obj)) port = ExprAdapter(Sequence("node" / term_, "id" / Int32ub, "creation" / Int8ub),
DISABLE=0, ENABLE=1, ), 'swing' / Enum( Byte, SWING_50=0, SWING_55=1, SWING_57=2, SWING_59=3, SWING_61=4, SWING_64=5, ), 'taps' / Byte, 'tempo' / ExprAdapter( Int16ub, # 7bit stuffed - each byte max 0x7F ((obj_ & 0x7f) + ((obj_ & 0x7f00) >> 1)), ((obj_ & 0x7f) + ((obj_ & 0x3f80) << 1)), ), 'octaves' / Enum( Byte, OCT_1=0, OCT_2=1, OCT_3=2, OCT_4=3, ), ) Joy = Struct( # Default values allow Mk1->Mk2 conversion 'axis_x' / Default(Enum( Byte, PBEND=0,
ProvisioningInvite = Struct("attention" / Int8ub, ) ProvisioningCapabilities = Struct( "num_elements" / Int8ub, "algorithms" / BitList(2, reversed=True), "public_key_type" / BitList(1, reversed=True), "static_oob_type" / BitList(1, reversed=True), "output_oob_size" / Int8ub, "output_oob_action" / BitList(2, reversed=True), "input_oob_size" / Int8ub, "input_oob_action" / BitList(2, reversed=True), ) ProvisioningStart = Struct( "algorithm" / EnumAdapter(Int8ub, ProvisioningAlgorithm), "public_key" / ExprAdapter(Int8ub, lambda obj, ctx: bool(obj), lambda obj, ctx: 1 if obj else 0), "authentication_method" / EnumAdapter(Int8ub, ProvisioningAuthenticationMethod), "authentication_action" / Switch( this.authentication_method, { ProvisioningAuthenticationMethod.NONE: Const(0, Int8ub), ProvisioningAuthenticationMethod.STATIC: Const(0, Int8ub), ProvisioningAuthenticationMethod.OUTPUT: EnumAdapter(Int8ub, ProvisioningOutputOOBAction), ProvisioningAuthenticationMethod.INPUT: EnumAdapter(Int8ub, ProvisioningInputOOBAction), }, ),
class USBTransport(object): '''Implement USB transport.''' def __init__(self, dev=None): '''Instantiate the first available PTP device over USB''' self.__setup_constructors() # If no device is specified, find all devices claiming to be Cameras # and get the USB endpoints for the first one that works. cameras = find_usb_cameras() devs = [dev] if (dev is not None) else cameras for dev in devs: if self.__setup_device(dev): break else: raise PTPError('No USB PTP device found.') if self.__dev.is_kernel_driver_active(self.__intf.bInterfaceNumber): try: self.__dev.detach_kernel_driver(self.__intf.bInterfaceNumber) usb.util.claim_interface(self.__dev, self.__intf) except usb.core.USBError: raise PTPError('Could not detach kernel driver.\n' 'Maybe the camera is mounted?') usb.util.claim_interface(self.__dev, self.__intf) self.__event_queue = Queue() self.__event_shutdown = Event() self.__event_proc = Thread(target=self.__poll_events) self.__event_proc.daemon = False atexit.register(self._shutdown) self.__event_proc.start() def _shutdown(self): self.__event_shutdown.set() # Free USB resource on shutdown. # Only join a running thread. if self.__event_proc.is_alive(): self.__event_proc.join(2) usb.util.release_interface(self.__dev, self.__intf) # Helper methods. # --------------------- def __setup_device(self, dev): '''Get endpoints for a device. True on success.''' self.__inep = None self.__outep = None self.__intep = None self.__cfg = None self.__dev = None self.__intf = None # Attempt to find the USB in, out and interrupt endpoints for a PTP # interface. for cfg in dev: for intf in cfg: if intf.bInterfaceClass == PTP_USB_CLASS: for ep in intf: ep_type = endpoint_type(ep.bmAttributes) ep_dir = endpoint_direction(ep.bEndpointAddress) if ep_type == ENDPOINT_TYPE_BULK: if ep_dir == ENDPOINT_IN: self.__inep = ep elif ep_dir == ENDPOINT_OUT: self.__outep = ep elif ((ep_type == ENDPOINT_TYPE_INTR) and (ep_dir == ENDPOINT_IN)): self.__intep = ep if not (self.__inep and self.__outep and self.__intep): self.__inep = None self.__outep = None self.__intep = None else: self.__cfg = cfg self.__dev = dev self.__intf = intf return True return False def __setup_constructors(self): '''Set endianness and create transport-specific constructors.''' # Set endianness of constructors before using them. self._set_endian('little') self.__Length = Int32ul self.__Type = Enum( Int16ul, default=Pass, Undefined=0x0000, Command=0x0001, Data=0x0002, Response=0x0003, Event=0x0004, ) # This is just a convenience constructor to get the size of a header. self.__Code = Int16ul self.__Header = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'Code' / self.__Code, 'TransactionID' / self._TransactionID, ) # These are the actual constructors for parsing and building. self.__CommandHeader = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'OperationCode' / self._OperationCode, 'TransactionID' / self._TransactionID, ) self.__ResponseHeader = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'ResponseCode' / self._ResponseCode, 'TransactionID' / self._TransactionID, ) self.__EventHeader = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'EventCode' / self._EventCode, 'TransactionID' / self._TransactionID, ) # Apparently nobody uses the SessionID field. Even though it is # specified in ISO15740:2013(E), no device respects it and the session # number is implicit over USB. self.__Param = Range(0, 5, self._Parameter) self.__FullParam = Struct(Array(5, self._Parameter)) self.__FullEventParam = Struct(Array(3, self._Parameter)) self.__CommandTransactionBase = Struct( Embedded(self.__CommandHeader), 'Payload' / Bytes(lambda ctx, h=self.__Header: ctx.Length - h.sizeof())) self.__CommandTransaction = ExprAdapter( self.__CommandTransactionBase, encoder=lambda obj, ctx, h=self.__Header: Container( Length=len(obj.Payload) + h.sizeof(), **obj), decoder=lambda obj, ctx: obj, ) self.__ResponseTransactionBase = Struct( Embedded(self.__ResponseHeader), 'Payload' / Bytes(lambda ctx, h=self.__Header: ctx.Length - h.sizeof())) self.__ResponseTransaction = ExprAdapter( self.__ResponseTransactionBase, encoder=lambda obj, ctx, h=self.__Header: Container( Length=len(obj.Payload) + h.sizeof(), **obj), decoder=lambda obj, ctx: obj, ) self.__EventTransactionBase = Struct( Embedded(self.__EventHeader), 'Payload' / Bytes(lambda ctx, h=self.__Header: ctx.Length - h.sizeof()), ) self.__EventTransaction = ExprAdapter( self.__EventTransactionBase, encoder=lambda obj, ctx, h=self.__Header: Container( Length=len(obj.Payload) + h.sizeof(), **obj), decoder=lambda obj, ctx: obj, ) def __parse_response(self, usbdata): '''Helper method for parsing USB data.''' # Build up container with all PTP info. usbdata = bytearray(usbdata) transaction = self.__ResponseTransaction.parse(usbdata) response = Container( SessionID=self.session_id, TransactionID=transaction.TransactionID, ) if transaction.Type == 'Response': response['ResponseCode'] = transaction.ResponseCode response['Parameter'] = self.__Param.parse(transaction.Payload) elif transaction.Type == 'Event': event = self.__EventHeader.parse(usbdata[0:self.__Header.sizeof()]) response['EventCode'] = event.EventCode response['Parameter'] = self.__Param.parse(transaction.Payload) else: command = self.__CommandHeader.parse( usbdata[0:self.__Header.sizeof()]) response['OperationCode'] = command.OperationCode response['Data'] = transaction.Payload return response def __recv(self, event=False, wait=False, raw=False): '''Helper method for receiving non-event data.''' # TODO: clear stalls automatically ep = self.__intep if event else self.__inep try: usbdata = ep.read(ep.wMaxPacketSize, timeout=0 if wait else 5) except usb.core.USBError as e: # Ignore timeout or busy device once. if e.errno == 110 or e.errno == 16: if event: return None else: usbdata = ep.read(ep.wMaxPacketSize, timeout=5000) else: raise e header = self.__ResponseHeader.parse( bytearray(usbdata[0:self.__Header.sizeof()])) if header.Type not in ['Response', 'Data', 'Event']: raise PTPError( 'Unexpected USB transfer type.' 'Expected Response, Event or Data but reveived {}'.format( header.Type)) while len(usbdata) < header.Length: usbdata += ep.read(ep.wMaxPacketSize, timeout=5000) if raw: return usbdata else: return self.__parse_response(usbdata) def __send(self, ptp_container, event=False): '''Helper method for sending data.''' ep = self.__intep if event else self.__outep transaction = self.__CommandTransaction.build(ptp_container) try: ep.write(transaction, timeout=1) except usb.core.USBError as e: # Ignore timeout or busy device once. if e.errno == 110 or e.errno == 16: ep.write(transaction, timeout=5000) def __send_request(self, ptp_container): '''Send PTP request without checking answer.''' # Don't modify original container to keep abstraction barrier. ptp = Container(**ptp_container) # Don't send unused parameters try: while not ptp.Parameter[-1]: ptp.Parameter.pop() if len(ptp.Parameter) == 0: break except IndexError: # The Parameter list is already empty. pass # Send request ptp['Type'] = 'Command' ptp['Payload'] = self.__Param.build(ptp.Parameter) self.__send(ptp) def __send_data(self, ptp_container, data): '''Send data without checking answer.''' # Don't modify original container to keep abstraction barrier. ptp = Container(**ptp_container) # Send data ptp['Type'] = 'Data' ptp['Payload'] = data self.__send(ptp) # Actual implementation # --------------------- def send(self, ptp_container, data): '''Transfer operation with dataphase from initiator to responder''' self.__send_request(ptp_container) self.__send_data(ptp_container, data) # Get response and sneak in implicit SessionID and missing parameters. return self.__recv() def recv(self, ptp_container): '''Transfer operation with dataphase from responder to initiator.''' self.__send_request(ptp_container) dataphase = self.__recv() if hasattr(dataphase, 'Data'): response = self.__recv() if ((ptp_container.OperationCode != dataphase.OperationCode) or (ptp_container.TransactionID != dataphase.TransactionID) or (ptp_container.SessionID != dataphase.SessionID) or (dataphase.TransactionID != response.TransactionID) or (dataphase.SessionID != response.SessionID)): raise PTPError( 'Dataphase does not match with requested operation.') response['Data'] = dataphase.Data return response else: return dataphase def mesg(self, ptp_container): '''Transfer operation without dataphase.''' self.__send_request(ptp_container) # Get response and sneak in implicit SessionID and missing parameters # for FullResponse. return self.__recv() def event(self, wait=False): '''Check event. If `wait` this function is blocking. Otherwise it may return None. ''' evt = None usbdata = None timeout = None if wait else 0.001 if not self.__event_queue.empty(): usbdata = self.__event_queue.get(block=not wait, timeout=timeout) if usbdata is not None: evt = self.__parse_response(usbdata) return evt def __poll_events(self): '''Poll events, adding them to a queue.''' while not self.__event_shutdown.is_set() and _main_thread_alive(): evt = self.__recv(event=True, wait=False, raw=True) if evt is not None: self.__event_queue.put(evt)
Padding(8), "oem_id" / Int16ul, "oem_info" / Int16ul, Padding(20), "coff_header_pointer" / Int32ul, "_assembly_start" / Tell, "code" / OnDemand(HexDump(Bytes(this.coff_header_pointer - this._assembly_start))), ) symbol_table = "symbol_table" / Struct( "name" / String(8, padchar=b"\x00"), "value" / Int32ul, "section_number" / Enum( ExprAdapter( Int16sl, encoder=lambda obj, ctx: obj + 1, decoder=lambda obj, ctx: obj - 1, ), UNDEFINED=-1, ABSOLUTE=-2, DEBUG=-3, _default_=Pass, ), "complex_type" / Enum( Int8ul, NULL=0, POINTER=1, FUNCTION=2, ARRAY=3, ), "base_type" / Enum( Int8ul, NULL=0,
def __setup_constructors(self): '''Set endianness and create transport-specific constructors.''' # Set endianness of constructors before using them. self._set_endian('little') self.__Length = Int32ul self.__Type = Enum( Int16ul, default=Pass, Undefined=0x0000, Command=0x0001, Data=0x0002, Response=0x0003, Event=0x0004, ) # This is just a convenience constructor to get the size of a header. self.__Code = Int16ul self.__Header = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'Code' / self.__Code, 'TransactionID' / self._TransactionID, ) # These are the actual constructors for parsing and building. self.__CommandHeader = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'OperationCode' / self._OperationCode, 'TransactionID' / self._TransactionID, ) self.__ResponseHeader = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'ResponseCode' / self._ResponseCode, 'TransactionID' / self._TransactionID, ) self.__EventHeader = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'EventCode' / self._EventCode, 'TransactionID' / self._TransactionID, ) # Apparently nobody uses the SessionID field. Even though it is # specified in ISO15740:2013(E), no device respects it and the session # number is implicit over USB. self.__Param = Range(0, 5, self._Parameter) self.__CommandTransactionBase = Struct( Embedded(self.__CommandHeader), 'Payload' / Bytes(lambda ctx, h=self.__Header: ctx.Length - h.sizeof())) self.__CommandTransaction = ExprAdapter( self.__CommandTransactionBase, encoder=lambda obj, ctx, h=self.__Header: Container( Length=len(obj.Payload) + h.sizeof(), **obj), decoder=lambda obj, ctx: obj, ) self.__ResponseTransactionBase = Struct( Embedded(self.__ResponseHeader), 'Payload' / Bytes(lambda ctx, h=self.__Header: ctx.Length - h.sizeof())) self.__ResponseTransaction = ExprAdapter( self.__ResponseTransactionBase, encoder=lambda obj, ctx, h=self.__Header: Container( Length=len(obj.Payload) + h.sizeof(), **obj), decoder=lambda obj, ctx: obj, )
class HostnameString(SymmetricAdapter): def __init__(self, is_ipv4_func): super(HostnameString, self).__init__(Bytes(16)) self.is_ipv4_func = is_ipv4_func def _decode(self, obj, context, path): if obj in context.hosts_table: if context.hosts_table[obj]: return context.hosts_table[obj] if self.is_ipv4_func(context): return str(IPv4Address(obj[:4])) return str(IPv6Address(obj)) PortString = ExprAdapter( Struct("port_number" / Int16ul), lambda obj, ctx: ctx.ports_table.get( (obj.port_number, bool(ctx.flags.is_tcp)), str(obj.port_number)), lambda obj, ctx: ctx.ports_table.index(obj)) FilesystemQueryVolumeInformationOperationType = Enum( Int8ul, FilesystemQueryVolumeInformationOperation) FilesystemSetVolumeInformationOperationType = Enum( Int8ul, FilesystemSetVolumeInformationOperation) FilesystemQueryInformationOperationType = Enum( Int8ul, FilesystemQueryInformationOperation) FilesystemDirectoryControlOperationType = Enum( Int8ul, FilesysemDirectoryControlOperation) FilesystemSetInformationOperationType = Enum( Int8ul, FilesystemSetInformationOperation) FilesystemPnpOperationType = Enum(Int8ul, FilesystemPnpOperation) FilesystemLockUnlockOperationType = Enum(Int8ul, FilesystemLockUnlockOperation) NetworkOperationFlags = FlagsEnum(Int16ul,
class USBTransport(object): '''Implement USB transport.''' def __init__(self, *args, **kwargs): device = kwargs.get('device', None) '''Instantiate the first available PTP device over USB''' logger.debug('Init USB') self.__setup_constructors() # If no device is specified, find all devices claiming to be Cameras # and get the USB endpoints for the first one that works. if device is None: logger.debug('No device provided, probing all USB devices.') if isinstance(device, six.string_types): name = device logger.debug( 'Device name provided, probing all USB devices for {}.'.format( name)) device = None else: name = None devs = ([device] if (device is not None) else find_usb_cameras(name=name)) self.__acquire_camera(devs) self.__event_queue = Queue() self.__event_shutdown = Event() # Locks for different end points. self.__inep_lock = RLock() self.__intep_lock = RLock() self.__outep_lock = RLock() # Slightly redundant transaction lock to avoid catching other request's # response self.__transaction_lock = RLock() self.__event_proc = Thread(name='EvtPolling', target=self.__poll_events) self.__event_proc.daemon = False atexit.register(self._shutdown) self.__event_proc.start() def __available_cameras(self, devs): for dev in devs: if self.__setup_device(dev): logger.debug('Found USB PTP device {}'.format(dev)) yield else: message = 'No USB PTP device found.' logger.error(message) raise PTPError(message) def __acquire_camera(self, devs): '''From the cameras given, get the first one that does not fail''' for _ in self.__available_cameras(devs): try: if self.__dev.is_kernel_driver_active( self.__intf.bInterfaceNumber): try: self.__dev.detach_kernel_driver( self.__intf.bInterfaceNumber) usb.util.claim_interface(self.__dev, self.__intf) except usb.core.USBError: message = ('Could not detach kernel driver. ' 'Maybe the camera is mounted?') logger.error(message) logger.debug('Claiming {}'.format(repr(self.__dev))) usb.util.claim_interface(self.__dev, self.__intf) except Exception as e: logger.debug('{}'.format(e)) continue break else: message = ('Could acquire any camera.') logger.error(message) raise PTPError(message) def _shutdown(self): logger.debug('Shutdown request') self.__event_shutdown.set() # Free USB resource on shutdown. # Only join a running thread. if self.__event_proc.is_alive(): self.__event_proc.join(2) logger.debug('Release {}'.format(repr(self.__dev))) usb.util.release_interface(self.__dev, self.__intf) # Helper methods. # --------------------- def __setup_device(self, dev): '''Get endpoints for a device. True on success.''' self.__inep = None self.__outep = None self.__intep = None self.__cfg = None self.__dev = None self.__intf = None # Attempt to find the USB in, out and interrupt endpoints for a PTP # interface. for cfg in dev: for intf in cfg: if intf.bInterfaceClass == PTP_USB_CLASS: for ep in intf: ep_type = endpoint_type(ep.bmAttributes) ep_dir = endpoint_direction(ep.bEndpointAddress) if ep_type == ENDPOINT_TYPE_BULK: if ep_dir == ENDPOINT_IN: self.__inep = ep elif ep_dir == ENDPOINT_OUT: self.__outep = ep elif ((ep_type == ENDPOINT_TYPE_INTR) and (ep_dir == ENDPOINT_IN)): self.__intep = ep if not (self.__inep and self.__outep and self.__intep): self.__inep = None self.__outep = None self.__intep = None else: logger.debug('Found {}'.format(repr(self.__inep))) logger.debug('Found {}'.format(repr(self.__outep))) logger.debug('Found {}'.format(repr(self.__intep))) self.__cfg = cfg self.__dev = dev self.__intf = intf return True return False def __setup_constructors(self): '''Set endianness and create transport-specific constructors.''' # Set endianness of constructors before using them. self._set_endian('little') self.__Length = Int32ul self.__Type = Enum( Int16ul, default=Pass, Undefined=0x0000, Command=0x0001, Data=0x0002, Response=0x0003, Event=0x0004, ) # This is just a convenience constructor to get the size of a header. self.__Code = Int16ul self.__Header = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'Code' / self.__Code, 'TransactionID' / self._TransactionID, ) # These are the actual constructors for parsing and building. self.__CommandHeader = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'OperationCode' / self._OperationCode, 'TransactionID' / self._TransactionID, ) self.__ResponseHeader = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'ResponseCode' / self._ResponseCode, 'TransactionID' / self._TransactionID, ) self.__EventHeader = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'EventCode' / self._EventCode, 'TransactionID' / self._TransactionID, ) # Apparently nobody uses the SessionID field. Even though it is # specified in ISO15740:2013(E), no device respects it and the session # number is implicit over USB. self.__Param = Range(0, 5, self._Parameter) self.__CommandTransactionBase = Struct( Embedded(self.__CommandHeader), 'Payload' / Bytes(lambda ctx, h=self.__Header: ctx.Length - h.sizeof())) self.__CommandTransaction = ExprAdapter( self.__CommandTransactionBase, encoder=lambda obj, ctx, h=self.__Header: Container( Length=len(obj.Payload) + h.sizeof(), **obj), decoder=lambda obj, ctx: obj, ) self.__ResponseTransactionBase = Struct( Embedded(self.__ResponseHeader), 'Payload' / Bytes(lambda ctx, h=self.__Header: ctx.Length - h.sizeof())) self.__ResponseTransaction = ExprAdapter( self.__ResponseTransactionBase, encoder=lambda obj, ctx, h=self.__Header: Container( Length=len(obj.Payload) + h.sizeof(), **obj), decoder=lambda obj, ctx: obj, ) def __parse_response(self, usbdata): '''Helper method for parsing USB data.''' # Build up container with all PTP info. logger.debug('Transaction:') usbdata = bytearray(usbdata) if logger.isEnabledFor(logging.DEBUG): for l in hexdump(six.binary_type(usbdata), result='generator'): logger.debug(l) transaction = self.__ResponseTransaction.parse(usbdata) response = Container( SessionID=self.session_id, TransactionID=transaction.TransactionID, ) logger.debug('Interpreting {} transaction'.format(transaction.Type)) if transaction.Type == 'Response': response['ResponseCode'] = transaction.ResponseCode response['Parameter'] = self.__Param.parse(transaction.Payload) elif transaction.Type == 'Event': event = self.__EventHeader.parse(usbdata[0:self.__Header.sizeof()]) response['EventCode'] = event.EventCode response['Parameter'] = self.__Param.parse(transaction.Payload) else: command = self.__CommandHeader.parse( usbdata[0:self.__Header.sizeof()]) response['OperationCode'] = command.OperationCode response['Data'] = transaction.Payload return response def __recv(self, event=False, wait=False, raw=False): '''Helper method for receiving data.''' # TODO: clear stalls automatically ep = self.__intep if event else self.__inep lock = self.__intep_lock if event else self.__inep_lock usbdata = array.array('B', []) with lock: try: usbdata += ep.read(ep.wMaxPacketSize, timeout=0 if wait else 5) if len(usbdata) < self.__Header.sizeof(): logger.debug('Initial read smaller than a header') tries = 5 for _ in range(tries): usbdata += ep.read(ep.wMaxPacketSize, timeout=5000 / tries) except usb.core.USBError as e: # Ignore timeout or busy device once. if e.errno == 110 or e.errno == 16: if event: return None else: usbdata += ep.read(ep.wMaxPacketSize, timeout=5000) else: raise e if len(usbdata) == 0: if event: return None else: raise PTPError('Empty USB read') if (logger.isEnabledFor(logging.DEBUG) and (len(usbdata) < self.__Header.sizeof())): logger.debug('Incomplete packet') for l in hexdump(six.binary_type(bytearray(usbdata)), result='generator'): logger.debug(l) header = self.__ResponseHeader.parse( bytearray(usbdata[0:self.__Header.sizeof()])) if header.Type not in ['Response', 'Data', 'Event']: raise PTPError( 'Unexpected USB transfer type.' 'Expected Response, Event or Data but received {}'.format( header.Type)) while len(usbdata) < header.Length: usbdata += ep.read(ep.wMaxPacketSize, timeout=5000) if raw: return usbdata else: return self.__parse_response(usbdata) def __send(self, ptp_container, event=False): '''Helper method for sending data.''' ep = self.__intep if event else self.__outep lock = self.__intep_lock if event else self.__outep_lock transaction = self.__CommandTransaction.build(ptp_container) with lock: try: ep.write(transaction, timeout=1) except usb.core.USBError as e: # Ignore timeout or busy device once. if e.errno == 110 or e.errno == 16: ep.write(transaction, timeout=5000) def __send_request(self, ptp_container): '''Send PTP request without checking answer.''' # Don't modify original container to keep abstraction barrier. ptp = Container(**ptp_container) # Don't send unused parameters try: while not ptp.Parameter[-1]: ptp.Parameter.pop() if len(ptp.Parameter) == 0: break except IndexError: # The Parameter list is already empty. pass # Send request ptp['Type'] = 'Command' ptp['Payload'] = self.__Param.build(ptp.Parameter) self.__send(ptp) def __send_data(self, ptp_container, data): '''Send data without checking answer.''' # Don't modify original container to keep abstraction barrier. ptp = Container(**ptp_container) # Send data ptp['Type'] = 'Data' ptp['Payload'] = data self.__send(ptp) # Actual implementation # --------------------- def send(self, ptp_container, data): '''Transfer operation with dataphase from initiator to responder''' datalen = len(data) logger.debug('SEND {} {} bytes{}'.format( ptp_container.OperationCode, datalen, ' ' + str(list(map(hex, ptp_container.Parameter))) if ptp_container.Parameter else '', )) with self.__transaction_lock: self.__send_request(ptp_container) self.__send_data(ptp_container, data) # Get response and sneak in implicit SessionID and missing # parameters. response = self.__recv() logger.debug('SEND {} {} bytes {}{}'.format( ptp_container.OperationCode, datalen, response.ResponseCode, ' ' + str(list(map(hex, response.Parameter))) if ptp_container.Parameter else '', )) return response def recv(self, ptp_container): '''Transfer operation with dataphase from responder to initiator.''' logger.debug('RECV {}{}'.format( ptp_container.OperationCode, ' ' + str(list(map(hex, ptp_container.Parameter))) if ptp_container.Parameter else '', )) with self.__transaction_lock: self.__send_request(ptp_container) dataphase = self.__recv() if hasattr(dataphase, 'Data'): response = self.__recv() if ((ptp_container.OperationCode != dataphase.OperationCode) or (ptp_container.TransactionID != dataphase.TransactionID) or (ptp_container.SessionID != dataphase.SessionID) or (dataphase.TransactionID != response.TransactionID) or (dataphase.SessionID != response.SessionID)): raise PTPError( 'Dataphase does not match with requested operation.') response['Data'] = dataphase.Data else: response = dataphase logger.debug('RECV {} {}{}{}'.format( ptp_container.OperationCode, response.ResponseCode, ' {} bytes'.format(len(response.Data)) if hasattr( response, 'Data') else '', ' ' + str(list(map(hex, response.Parameter))) if response.Parameter else '', )) return response def mesg(self, ptp_container): '''Transfer operation without dataphase.''' logger.debug('MESG {}{}'.format( ptp_container.OperationCode, ' ' + str(list(map(hex, ptp_container.Parameter))) if ptp_container.Parameter else '', )) with self.__transaction_lock: self.__send_request(ptp_container) # Get response and sneak in implicit SessionID and missing # parameters for FullResponse. response = self.__recv() logger.debug('MESG {} {}{}'.format( ptp_container.OperationCode, response.ResponseCode, ' ' + str(list(map(hex, response.Parameter))) if response.Parameter else '', )) return response def event(self, wait=False): '''Check event. If `wait` this function is blocking. Otherwise it may return None. ''' evt = None usbdata = None timeout = None if wait else 0.001 if not self.__event_queue.empty(): usbdata = self.__event_queue.get(block=not wait, timeout=timeout) if usbdata is not None: evt = self.__parse_response(usbdata) return evt def __poll_events(self): '''Poll events, adding them to a queue.''' while not self.__event_shutdown.is_set() and _main_thread_alive(): try: evt = self.__recv(event=True, wait=False, raw=True) if evt is not None: logger.debug('Event queued') self.__event_queue.put(evt) except Exception as e: logger.error(e)
"module_tlm_trouble" / Flag, "module_fail_to_com_trouble" / Flag, "module_printer_trouble" / Flag, "module_ac_trouble" / Flag, "module_battery_fail" / Flag, "module_aux_trouble" / Flag, "missing_keypad_trouble" / Flag, "missing_module_trouble" / Flag, "_future_use_4" / Flag, "_future_use_5" / Flag, "safety_mismatch_trouble" / Flag, "bus_global_fail" / Flag, "bus_overload_trouble" / Flag, "mdl_com_error" / Flag), "time" / DateAdapter(Bytes(7)), "vdc" / ExprAdapter(Byte, obj_ * (20.3 - 1.4) / 255.0 + 1.4, 0), "battery" / ExprAdapter(Byte, obj_ * 22.8 / 255.0, 0), "dc" / ExprAdapter(Byte, obj_ * 22.8 / 255.0, 0), "zone_open" / StatusAdapter(Bytes(12)), "zone_tamper" / StatusAdapter(Bytes(12)), "zone_low_battery" / StatusAdapter(Bytes(12))), 2: Struct("zone_status" / ZoneFlags(64)), 3: Struct( "zone_status" / ZoneFlags(32, start_index_from=65), "partition_status" / PartitionStatus(Bytes(32)), ), 4: Struct( "partition_status" / PartitionStatus(Bytes(16)), "_panel_status" /
class IPTransport(object): '''Implement IP transport.''' def __init__(self, device=None): '''Instantiate the first available PTP device over IP''' self.__setup_constructors() logger.debug('Init IP') self.__dev = device if device is None: raise NotImplementedError( 'IP discovery not implemented. Please provide a device.' ) self.__device = device # Signal usable implicit session self.__implicit_session_open = Event() # Signal implicit session is shutting down self.__implicit_session_shutdown = Event() self.__check_session_lock = Lock() self.__transaction_lock = Lock() self.__event_queue = Queue() atexit.register(self._shutdown) def _shutdown(self): try: self.__close_implicit_session() except Exception as e: logger.error(e) @contextmanager def __implicit_session(self): '''Manage implicit sessions with responder''' # There is now an implicit session self.__check_session_lock.acquire() if not self.__implicit_session_open.is_set(): try: self.__open_implicit_session() self.__check_session_lock.release() yield except Exception as e: logger.error(e) raise PTPError('Failed to open PTP/IP implicit session') finally: if self.__implicit_session_open.is_set(): self.__close_implicit_session() if self.__check_session_lock.locked(): self.__check_session_lock.release() else: self.__check_session_lock.release() yield def __open_implicit_session(self): '''Establish implicit session with responder''' self.__implicit_session_shutdown.clear() # Establish Command and Event connections if type(self.__device) is tuple: host, port = self.__device self.__setup_connection(host, port) else: self.__setup_connection(self.__device) self.__implicit_session_open.set() # Prepare Event and Probe threads self.__event_proc = Thread( name='EvtPolling', target=self.__poll_events ) self.__event_proc.daemon = False self.__ping_pong_proc = Thread( name='PingPong', target=self.__ping_pong ) self.__ping_pong_proc.daemon = False # Launch Event and Probe threads self.__event_proc.start() self.__ping_pong_proc.start() def __close_implicit_session(self): '''Terminate implicit session with responder''' self.__implicit_session_shutdown.set() if not self.__implicit_session_open.is_set(): return # Only join running threads. if self.__event_proc.is_alive(): self.__event_proc.join(2) if self.__ping_pong_proc.is_alive(): self.__ping_pong_proc.join(2) logger.debug('Close connections for {}'.format(repr(self.__dev))) try: self.__evtcon.shutdown(socket.SHUT_RDWR) except socket.error as e: if e.errno == 107: pass else: raise e try: self.__cmdcon.shutdown(socket.SHUT_RDWR) except socket.error as e: if e.errno == 107: pass else: raise e self.__evtcon.close() self.__cmdcon.close() self.__implicit_session_open.clear() def __setup_connection(self, host=None, port=15740): '''Establish a PTP/IP session for a given host''' logger.debug( 'Establishing PTP/IP connection with {}:{}' .format(host, port) ) socket.setdefaulttimeout(5) hdrlen = self.__Header.sizeof() # Command Connection Establishment self.__cmdcon = create_connection((host, port)) # Send InitCommand # TODO: Allow users to identify as an arbitrary initiator. init_cmd_req_payload = self.__InitCommand.build( Container( InitiatorGUID=16*[0xFF], InitiatorFriendlyName='PTPy', InitiatorProtocolVersion=Container( Major=100, Minor=000, ), )) init_cmd_req = self.__Packet.build( Container( Type='InitCommand', Payload=init_cmd_req_payload, ) ) actual_socket(self.__cmdcon).sendall(init_cmd_req) # Get ACK/NACK init_cmd_req_rsp = actual_socket(self.__cmdcon).recv(72) init_cmd_rsp_hdr = self.__Header.parse( init_cmd_req_rsp[0:hdrlen] ) if init_cmd_rsp_hdr.Type == 'InitCommandAck': cmd_ack = self.__InitCommandACK.parse(init_cmd_req_rsp[hdrlen:]) logger.debug( 'Command connection ({}) established' .format(cmd_ack.ConnectionNumber) ) elif init_cmd_rsp_hdr.Type == 'InitFail': cmd_nack = self.__InitFail.parse(init_cmd_req_rsp[hdrlen:]) msg = 'InitCommand failed, Reason: {}'.format( cmd_nack ) logger.error(msg) raise PTPError(msg) else: msg = 'Unexpected response Type to InitCommand : {}'.format( init_cmd_rsp_hdr.Type ) logger.error(msg) raise PTPError(msg) # Event Connection Establishment self.__evtcon = create_connection((host, port)) self.__evtcon.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) self.__evtcon.setsockopt(socket.IPPROTO_TCP, socket.SO_KEEPALIVE, 1) # Send InitEvent payload = self.__InitEvent.build(Container( ConnectionNumber=cmd_ack.ConnectionNumber, )) evt_req = self.__Packet.build( Container( Type='InitEvent', Payload=payload, ) ) actual_socket(self.__evtcon).sendall(evt_req) # Get ACK/NACK init_evt_req_rsp = actual_socket(self.__evtcon).recv( hdrlen + self.__InitFail.sizeof() ) init_evt_rsp_hdr = self.__Header.parse( init_evt_req_rsp[0:hdrlen] ) if init_evt_rsp_hdr.Type == 'InitEventAck': logger.debug( 'Event connection ({}) established' .format(cmd_ack.ConnectionNumber) ) elif init_evt_rsp_hdr.Type == 'InitFail': evt_nack = self.__InitFail.parse(init_evt_req_rsp[hdrlen:]) msg = 'InitEvent failed, Reason: {}'.format( evt_nack ) logger.error(msg) raise PTPError(msg) else: msg = 'Unexpected response Type to InitEvent : {}'.format( init_evt_rsp_hdr.Type ) logger.error(msg) raise PTPError(msg) # Helper methods. # --------------------- def __setup_constructors(self): '''Set endianness and create transport-specific constructors.''' # Set endianness of constructors before using them. self._set_endian('little') self.__Length = Int32ul self.__Type = Enum( Int32ul, Undefined=0x00000000, InitCommand=0x00000001, InitCommandAck=0x00000002, InitEvent=0x00000003, InitEventAck=0x00000004, InitFail=0x00000005, Command=0x00000006, Response=0x00000007, Event=0x00000008, StartData=0x00000009, Data=0x0000000A, Cancel=0x0000000B, EndData=0x0000000C, Ping=0x0000000D, Pong=0x0000000E, ) self.__Header = Struct( 'Length' / self.__Length, 'Type' / self.__Type, ) self.__Param = Range(0, 5, self._Parameter) self.__EventParam = Range(0, 3, self._Parameter) self.__PacketBase = Struct( Embedded(self.__Header), 'Payload' / Bytes( lambda ctx, h=self.__Header: ctx.Length - h.sizeof()), ) self.__Packet = ExprAdapter( self.__PacketBase, encoder=lambda obj, ctx, h=self.__Header: Container( Length=len(obj.Payload) + h.sizeof(), **obj ), decoder=lambda obj, ctx: obj, ) # Yet another arbitrary string type. Max-length CString utf8-encoded self.__PTPIPString = ExprAdapter( RepeatUntil( lambda obj, ctx, lst: six.unichr(obj) in '\x00' or len(lst) == 40, Int16ul ), encoder=lambda obj, ctx: [] if len(obj) == 0 else[ord(c) for c in six.text_type(obj)]+[0], decoder=lambda obj, ctx: u''.join( [six.unichr(o) for o in obj] ).split('\x00')[0], ) # PTP/IP packets # Command self.__ProtocolVersion = Struct( 'Major' / Int16ul, 'Minor' / Int16ul, ) self.__InitCommand = Embedded(Struct( 'InitiatorGUID' / Array(16, Int8ul), 'InitiatorFriendlyName' / self.__PTPIPString, 'InitiatorProtocolVersion' / self.__ProtocolVersion, )) self.__InitCommandACK = Embedded(Struct( 'ConnectionNumber' / Int32ul, 'ResponderGUID' / Array(16, Int8ul), 'ResponderFriendlyName' / self.__PTPIPString, 'ResponderProtocolVersion' / self.__ProtocolVersion, )) # Event self.__InitEvent = Embedded(Struct( 'ConnectionNumber' / Int32ul, )) # Common to Events and Command requests self.__Reason = Enum( # TODO: Verify these codes... Int32ul, Undefined=0x0000, RejectedInitiator=0x0001, Busy=0x0002, Unspecified=0x0003, ) self.__InitFail = Embedded(Struct( 'Reason' / self.__Reason, )) self.__DataphaseInfo = Enum( Int32ul, Undefined=0x00000000, In=0x00000001, Out=0x00000002, ) self.__Command = Embedded(Struct( 'DataphaseInfo' / self.__DataphaseInfo, 'OperationCode' / self._OperationCode, 'TransactionID' / self._TransactionID, 'Parameter' / self.__Param, )) self.__Response = Embedded(Struct( 'ResponseCode' / self._ResponseCode, 'TransactionID' / self._TransactionID, 'Parameter' / self.__Param, )) self.__Event = Embedded(Struct( 'EventCode' / self._EventCode, 'TransactionID' / self._TransactionID, 'Parameter' / self.__EventParam, )) self.__StartData = Embedded(Struct( 'TransactionID' / self._TransactionID, 'TotalDataLength' / Int64ul, )) # TODO: Fix packing and unpacking dataphase data self.__Data = Embedded(Struct( 'TransactionID' / self._TransactionID, 'Data' / Bytes( lambda ctx: ctx._.Length - self.__Header.sizeof() - self._TransactionID.sizeof() ), )) self.__EndData = Embedded(Struct( 'TransactionID' / self._TransactionID, 'Data' / Bytes( lambda ctx: ctx._.Length - self.__Header.sizeof() - self._TransactionID.sizeof() ), )) self.__Cancel = Embedded(Struct( 'TransactionID' / self._TransactionID, )) # Convenience construct for parsing packets self.__PacketPayload = Debugger(Struct( 'Header' / Embedded(self.__Header), 'Payload' / Embedded(Switch( lambda ctx: ctx.Type, { 'InitCommand': self.__InitCommand, 'InitCommandAck': self.__InitCommandACK, 'InitEvent': self.__InitEvent, 'InitFail': self.__InitFail, 'Command': self.__Command, 'Response': self.__Response, 'Event': self.__Event, 'StartData': self.__StartData, 'Data': self.__Data, 'EndData': self.__EndData, }, default=Pass, )) )) def __parse_response(self, ipdata): '''Helper method for parsing data.''' # Build up container with all PTP info. response = self.__PacketPayload.parse(ipdata) # Sneak in an implicit Session ID response['SessionID'] = self.session_id return response def __recv(self, event=False, wait=False, raw=False): '''Helper method for receiving packets.''' hdrlen = self.__Header.sizeof() with self.__implicit_session(): ip = ( actual_socket(self.__evtcon) if event else actual_socket(self.__cmdcon) ) data = bytes() while True: try: ipdata = ip.recv(hdrlen) except socket.timeout: if event: return None else: ipdata = ip.recv(hdrlen) if len(ipdata) == 0 and not event: raise PTPError('Command connection dropped') elif event: return None # Read a single entire header while len(ipdata) < hdrlen: ipdata += ip.recv(hdrlen - len(ipdata)) header = self.__Header.parse( ipdata[0:hdrlen] ) # Read a single entire packet while len(ipdata) < header.Length: ipdata += ip.recv(header.Length - len(ipdata)) # Run sanity checks. if header.Type not in [ 'Cancel', 'Data', 'Event', 'Response', 'StartData', 'EndData', ]: raise PTPError( 'Unexpected PTP/IP packet type {}' .format(header.Type) ) if header.Type not in ['StartData', 'Data', 'EndData']: break else: response = self.__parse_response(ipdata) if header.Type == 'StartData': expected = response.TotalDataLength current_transaction = response.TransactionID elif ( header.Type == 'Data' and response.TransactionID == current_transaction ): data += response.Data elif ( header.Type == 'EndData' and response.TransactionID == current_transaction ): data += response.Data datalen = len(data) if datalen != expected: logger.warning( '{} data than expected {}/{}' .format( 'More' if datalen > expected else 'Less', datalen, expected ) ) response['Data'] = data response['Type'] = 'Data' return response if raw: # TODO: Deal with raw Data packets?? return ipdata else: return self.__parse_response(ipdata) def __send(self, ptp_container, event=False): '''Helper method for sending packets.''' packet = self.__Packet.build(ptp_container) ip = ( actual_socket(self.__evtcon) if event else actual_socket(self.__cmdcon) ) while ip.sendall(packet) is not None: logger.debug('Failed to send {} packet'.format(ptp_container.Type)) def __send_request(self, ptp_container): '''Send PTP request without checking answer.''' # Don't modify original container to keep abstraction barrier. ptp = Container(**ptp_container) # Send unused parameters always ptp['Parameter'] += [0] * (5 - len(ptp.Parameter)) # Send request ptp['Type'] = 'Command' ptp['DataphaseInfo'] = 'In' ptp['Payload'] = self.__Command.build(ptp) self.__send(ptp) def __send_data(self, ptp_container, data): '''Send data without checking answer.''' # Don't modify original container to keep abstraction barrier. ptp = Container(**ptp_container) # Send data ptp['Type'] = 'Data' ptp['DataphaseInfo'] = 'Out' ptp['Payload'] = data self.__send(ptp) # Actual implementation # --------------------- def send(self, ptp_container, data): '''Transfer operation with dataphase from initiator to responder''' logger.debug('SEND {}{}'.format( ptp_container.OperationCode, ' ' + str(list(map(hex, ptp_container.Parameter))) if ptp_container.Parameter else '', )) with self.__implicit_session(): with self.__transaction_lock: self.__send_request(ptp_container) self.__send_data(ptp_container, data) # Get response and sneak in implicit SessionID and missing # parameters. return self.__recv() def recv(self, ptp_container): '''Transfer operation with dataphase from responder to initiator.''' logger.debug('RECV {}{}'.format( ptp_container.OperationCode, ' ' + str(list(map(hex, ptp_container.Parameter))) if ptp_container.Parameter else '', )) with self.__implicit_session(): with self.__transaction_lock: self.__send_request(ptp_container) dataphase = self.__recv() if hasattr(dataphase, 'Data'): response = self.__recv() if ( (ptp_container.TransactionID != dataphase.TransactionID) or (ptp_container.SessionID != dataphase.SessionID) or (dataphase.TransactionID != response.TransactionID) or (dataphase.SessionID != response.SessionID) ): raise PTPError( 'Dataphase does not match with requested operation' ) response['Data'] = dataphase.Data return response else: return dataphase def mesg(self, ptp_container): '''Transfer operation without dataphase.''' op = ptp_container['OperationCode'] if op == 'OpenSession': self.__open_implicit_session() with self.__implicit_session(): with self.__transaction_lock: self.__send_request(ptp_container) # Get response and sneak in implicit SessionID and missing # parameters for FullResponse. response = self.__recv() rc = response['ResponseCode'] if op == 'OpenSession': if rc != 'OK': self.__close_implicit_session() elif op == 'CloseSession': if rc == 'OK': self.__close_implicit_session() return response def event(self, wait=False): '''Check event. If `wait` this function is blocking. Otherwise it may return None. ''' evt = None ipdata = None timeout = None if wait else 0.001 if not self.__event_queue.empty(): ipdata = self.__event_queue.get(block=not wait, timeout=timeout) if ipdata is not None: evt = self.__parse_response(ipdata) return evt def __poll_events(self): '''Poll events, adding them to a queue.''' logger.debug('Start') while ( not self.__implicit_session_shutdown.is_set() and self.__implicit_session_open.is_set() and _main_thread_alive() ): try: evt = self.__recv(event=True, wait=False, raw=True) except OSError as e: if e.errno == 9 and not self.__implicit_session_open.is_set(): break else: raise e if evt is not None: logger.debug('Event queued') self.__event_queue.put(evt) sleep(5e-3) logger.debug('Stop') def __ping_pong(self): '''Poll events, adding them to a queue.''' logger.debug('Start') last = time() while ( not self.__implicit_session_shutdown.is_set() and self.__implicit_session_open.is_set() and _main_thread_alive() ): if time() - last > 10: logger.debug('PING') # TODO: implement Ping Pong last = time() sleep(0.10) logger.debug('Stop')
DBField = Struct( "type" / DBFieldType, "value" / Switch( this.type, { "int8": Int8ub, "int16": Int16ub, "int32": Int32ub, "string": FocusedSeq( 0, PascalString(ExprAdapter(Int32ub, encoder=lambda obj, ctx: obj // 2 + 1, decoder=lambda obj, ctx: (obj - 1) * 2), encoding="utf-16-be"), Padding(2)), "binary": Prefixed(Int32ub, GreedyBytes) # parses to byte string })) class DBFieldFixedAdapter(Adapter): def __init__(self, subcon, ftype): self.ftype = ftype super().__init__(subcon) def _encode(self, obj, context): return {"type": self.ftype, "value": obj}
class USBTransport(object): '''Implement USB transport.''' def __init__(self, *args, **kwargs): device = kwargs.get('device', None) '''Instantiate the first available PTP device over USB''' logger.debug('Init USB') self.__setup_constructors() # If no device is specified, find all devices claiming to be Cameras # and get the USB endpoints for the first one that works. if device is None: logger.debug('No device provided, probing all USB devices.') if isinstance(device, six.string_types): name = device logger.debug( 'Device name provided, probing all USB devices for {}.' .format(name) ) device = None else: name = None devs = ( [device] if (device is not None) else find_usb_cameras(name=name) ) self.__claimed = False self.__acquire_camera(devs) self.__event_queue = Queue() self.__event_shutdown = Event() # Locks for different end points. self.__inep_lock = RLock() self.__intep_lock = RLock() self.__outep_lock = RLock() # Slightly redundant transaction lock to avoid catching other request's # response self.__transaction_lock = RLock() self.__event_proc = Thread( name='EvtPolling', target=self.__poll_events ) self.__event_proc.daemon = False atexit.register(self._shutdown) self.__event_proc.start() def __available_cameras(self, devs): for dev in devs: if self.__setup_device(dev): logger.debug('Found USB PTP device {}'.format(dev)) yield else: message = 'No USB PTP device found.' logger.error(message) raise PTPError(message) def __acquire_camera(self, devs): '''From the cameras given, get the first one that does not fail''' for _ in self.__available_cameras(devs): # Stop system drivers try: if self.__dev.is_kernel_driver_active( self.__intf.bInterfaceNumber): try: self.__dev.detach_kernel_driver( self.__intf.bInterfaceNumber) except usb.core.USBError: message = ( 'Could not detach kernel driver. ' 'Maybe the camera is mounted?' ) logger.error(message) except NotImplementedError as e: logger.debug('Ignoring unimplemented function: {}'.format(e)) # Claim camera try: logger.debug('Claiming {}'.format(repr(self.__dev))) usb.util.claim_interface(self.__dev, self.__intf) self.__claimed = True except Exception as e: logger.warn('Failed to claim PTP device: {}'.format(e)) continue self.__dev.reset() break else: message = ( 'Could not acquire any camera.' ) logger.error(message) raise PTPError(message) def _shutdown(self): logger.debug('Shutdown request') self.__event_shutdown.set() # Free USB resource on shutdown. # Only join a running thread. if self.__event_proc.is_alive(): self.__event_proc.join(2) try: if self.__claimed: logger.debug('Release {}'.format(repr(self.__dev))) usb.util.release_interface(self.__dev, self.__intf) except Exception as e: logger.warn(e) # Helper methods. # --------------------- def __setup_device(self, dev): '''Get endpoints for a device. True on success.''' self.__inep = None self.__outep = None self.__intep = None self.__cfg = None self.__dev = None self.__intf = None # Attempt to find the USB in, out and interrupt endpoints for a PTP # interface. for cfg in dev: for intf in cfg: if intf.bInterfaceClass == PTP_USB_CLASS: for ep in intf: ep_type = endpoint_type(ep.bmAttributes) ep_dir = endpoint_direction(ep.bEndpointAddress) if ep_type == ENDPOINT_TYPE_BULK: if ep_dir == ENDPOINT_IN: self.__inep = ep elif ep_dir == ENDPOINT_OUT: self.__outep = ep elif ((ep_type == ENDPOINT_TYPE_INTR) and (ep_dir == ENDPOINT_IN)): self.__intep = ep if not (self.__inep and self.__outep and self.__intep): self.__inep = None self.__outep = None self.__intep = None else: logger.debug('Found {}'.format(repr(self.__inep))) logger.debug('Found {}'.format(repr(self.__outep))) logger.debug('Found {}'.format(repr(self.__intep))) self.__cfg = cfg self.__dev = dev self.__intf = intf return True return False def __setup_constructors(self): '''Set endianness and create transport-specific constructors.''' # Set endianness of constructors before using them. self._set_endian('little') self.__Length = Int32ul self.__Type = Enum( Int16ul, default=Pass, Undefined=0x0000, Command=0x0001, Data=0x0002, Response=0x0003, Event=0x0004, ) # This is just a convenience constructor to get the size of a header. self.__Code = Int16ul self.__Header = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'Code' / self.__Code, 'TransactionID' / self._TransactionID, ) # These are the actual constructors for parsing and building. self.__CommandHeader = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'OperationCode' / self._OperationCode, 'TransactionID' / self._TransactionID, ) self.__ResponseHeader = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'ResponseCode' / self._ResponseCode, 'TransactionID' / self._TransactionID, ) self.__EventHeader = Struct( 'Length' / self.__Length, 'Type' / self.__Type, 'EventCode' / self._EventCode, 'TransactionID' / self._TransactionID, ) # Apparently nobody uses the SessionID field. Even though it is # specified in ISO15740:2013(E), no device respects it and the session # number is implicit over USB. self.__Param = Range(0, 5, self._Parameter) self.__CommandTransactionBase = Struct( Embedded(self.__CommandHeader), 'Payload' / Bytes( lambda ctx, h=self.__Header: ctx.Length - h.sizeof() ) ) self.__CommandTransaction = ExprAdapter( self.__CommandTransactionBase, encoder=lambda obj, ctx, h=self.__Header: Container( Length=len(obj.Payload) + h.sizeof(), **obj ), decoder=lambda obj, ctx: obj, ) self.__ResponseTransactionBase = Struct( Embedded(self.__ResponseHeader), 'Payload' / Bytes( lambda ctx, h=self.__Header: ctx.Length - h.sizeof()) ) self.__ResponseTransaction = ExprAdapter( self.__ResponseTransactionBase, encoder=lambda obj, ctx, h=self.__Header: Container( Length=len(obj.Payload) + h.sizeof(), **obj ), decoder=lambda obj, ctx: obj, ) def __parse_response(self, usbdata): '''Helper method for parsing USB data.''' # Build up container with all PTP info. logger.debug('Transaction:') usbdata = bytearray(usbdata) if logger.isEnabledFor(logging.DEBUG): for l in hexdump( six.binary_type(usbdata[:512]), result='generator' ): logger.debug(l) transaction = self.__ResponseTransaction.parse(usbdata) response = Container( SessionID=self.session_id, TransactionID=transaction.TransactionID, ) logger.debug('Interpreting {} transaction'.format(transaction.Type)) if transaction.Type == 'Response': response['ResponseCode'] = transaction.ResponseCode response['Parameter'] = self.__Param.parse(transaction.Payload) elif transaction.Type == 'Event': event = self.__EventHeader.parse( usbdata[0:self.__Header.sizeof()] ) response['EventCode'] = event.EventCode response['Parameter'] = self.__Param.parse(transaction.Payload) else: command = self.__CommandHeader.parse( usbdata[0:self.__Header.sizeof()] ) response['OperationCode'] = command.OperationCode response['Data'] = transaction.Payload return response def __recv(self, event=False, wait=False, raw=False): '''Helper method for receiving data.''' # TODO: clear stalls automatically ep = self.__intep if event else self.__inep lock = self.__intep_lock if event else self.__inep_lock usbdata = array.array('B', []) with lock: tries = 0 # Attempt to read a header while len(usbdata) < self.__Header.sizeof() and tries < 5: if tries > 0: logger.debug('Data smaller than a header') logger.debug( 'Requesting {} bytes of data' .format(ep.wMaxPacketSize) ) try: usbdata += ep.read( ep.wMaxPacketSize ) except usb.core.USBError as e: # Return None on timeout or busy for events if ( (e.errno is None and ('timeout' in e.strerror.decode() or 'busy' in e.strerror.decode())) or (e.errno == 110 or e.errno == 16 or e.errno == 5) ): if event: return None else: logger.warning('Ignored exception: {}'.format(e)) else: logger.error(e) raise e tries += 1 logger.debug('Read {} bytes of data'.format(len(usbdata))) if len(usbdata) == 0: if event: return None else: raise PTPError('Empty USB read') if ( logger.isEnabledFor(logging.DEBUG) and len(usbdata) < self.__Header.sizeof() ): logger.debug('Incomplete header') for l in hexdump( six.binary_type(bytearray(usbdata)), result='generator' ): logger.debug(l) header = self.__ResponseHeader.parse( bytearray(usbdata[0:self.__Header.sizeof()]) ) if header.Type not in ['Response', 'Data', 'Event']: raise PTPError( 'Unexpected USB transfer type. ' 'Expected Response, Event or Data but received {}' .format(header.Type) ) while len(usbdata) < header.Length: usbdata += ep.read( min( header.Length - len(usbdata), # Up to 64kB 64 * 2**10 ) ) if raw: return usbdata else: return self.__parse_response(usbdata) def __send(self, ptp_container, event=False): '''Helper method for sending data.''' ep = self.__intep if event else self.__outep lock = self.__intep_lock if event else self.__outep_lock transaction = self.__CommandTransaction.build(ptp_container) with lock: try: sent = 0 while sent < len(transaction): sent = ep.write( # Up to 64kB transaction[sent:(sent + 64*2**10)] ) except usb.core.USBError as e: # Ignore timeout or busy device once. if ( (e.errno is None and ('timeout' in e.strerror.decode() or 'busy' in e.strerror.decode())) or (e.errno == 110 or e.errno == 16 or e.errno == 5) ): logger.warning('Ignored USBError {}'.format(e.errno)) ep.write(transaction) def __send_request(self, ptp_container): '''Send PTP request without checking answer.''' # Don't modify original container to keep abstraction barrier. ptp = Container(**ptp_container) # Don't send unused parameters try: while not ptp.Parameter[-1]: ptp.Parameter.pop() if len(ptp.Parameter) == 0: break except IndexError: # The Parameter list is already empty. pass # Send request ptp['Type'] = 'Command' ptp['Payload'] = self.__Param.build(ptp.Parameter) self.__send(ptp) def __send_data(self, ptp_container, data): '''Send data without checking answer.''' # Don't modify original container to keep abstraction barrier. ptp = Container(**ptp_container) # Send data ptp['Type'] = 'Data' ptp['Payload'] = data self.__send(ptp) @property def _dev(self): return None if self.__event_shutdown.is_set() else self.__dev @_dev.setter def _dev(self, value): raise ValueError('Read-only property') # Actual implementation # --------------------- def send(self, ptp_container, data): '''Transfer operation with dataphase from initiator to responder''' datalen = len(data) logger.debug('SEND {} {} bytes{}'.format( ptp_container.OperationCode, datalen, ' ' + str(list(map(hex, ptp_container.Parameter))) if ptp_container.Parameter else '', )) with self.__transaction_lock: self.__send_request(ptp_container) self.__send_data(ptp_container, data) # Get response and sneak in implicit SessionID and missing # parameters. response = self.__recv() logger.debug('SEND {} {} bytes {}{}'.format( ptp_container.OperationCode, datalen, response.ResponseCode, ' ' + str(list(map(hex, response.Parameter))) if ptp_container.Parameter else '', )) return response def recv(self, ptp_container): '''Transfer operation with dataphase from responder to initiator.''' logger.debug('RECV {}{}'.format( ptp_container.OperationCode, ' ' + str(list(map(hex, ptp_container.Parameter))) if ptp_container.Parameter else '', )) with self.__transaction_lock: self.__send_request(ptp_container) dataphase = self.__recv() if hasattr(dataphase, 'Data'): response = self.__recv() if not (ptp_container.SessionID == dataphase.SessionID == response.SessionID): self.__dev.reset() raise PTPError( 'Dataphase session ID missmatch: {}, {}, {}.' .format( ptp_container.SessionID, dataphase.SessionID, response.SessionID ) ) if not (ptp_container.TransactionID == dataphase.TransactionID == response.TransactionID): self.__dev.reset() raise PTPError( 'Dataphase transaction ID missmatch: {}, {}, {}.' .format( ptp_container.TransactionID, dataphase.TransactionID, response.TransactionID ) ) if not (ptp_container.OperationCode == dataphase.OperationCode): self.__dev.reset() raise PTPError( 'Dataphase operation code missmatch: {}, {}.'. format( ptp_container.OperationCode, dataphase.OperationCode ) ) response['Data'] = dataphase.Data else: response = dataphase logger.debug('RECV {} {}{}{}'.format( ptp_container.OperationCode, response.ResponseCode, ' {} bytes'.format(len(response.Data)) if hasattr(response, 'Data') else '', ' ' + str(list(map(hex, response.Parameter))) if response.Parameter else '', )) return response def mesg(self, ptp_container): '''Transfer operation without dataphase.''' logger.debug('MESG {}{}'.format( ptp_container.OperationCode, ' ' + str(list(map(hex, ptp_container.Parameter))) if ptp_container.Parameter else '', )) with self.__transaction_lock: self.__send_request(ptp_container) # Get response and sneak in implicit SessionID and missing # parameters for FullResponse. response = self.__recv() logger.debug('MESG {} {}{}'.format( ptp_container.OperationCode, response.ResponseCode, ' ' + str(list(map(hex, response.Parameter))) if response.Parameter else '', )) return response def event(self, wait=False): '''Check event. If `wait` this function is blocking. Otherwise it may return None. ''' evt = None usbdata = None if wait: usbdata = self.__event_queue.get(block=True) elif not self.__event_queue.empty(): usbdata = self.__event_queue.get(block=False) if usbdata is not None: evt = self.__parse_response(usbdata) return evt def __poll_events(self): '''Poll events, adding them to a queue.''' while not self.__event_shutdown.is_set() and _main_thread_alive(): try: evt = self.__recv(event=True, wait=False, raw=True) if evt is not None: logger.debug('Event queued') self.__event_queue.put(evt) except usb.core.USBError as e: logger.error( '{} polling exception: {}'.format(repr(self.__dev), e) ) # check if disconnected if e.errno == 19: break except Exception as e: logger.error( '{} polling exception: {}'.format(repr(self.__dev), e) )
def _ExposureBiasCompensation(self): return ExprAdapter( self._UInt16, encode=lambda x: x * 1000, decode=lambda x: x / 1000., )
def adapter(subcon: Construct, decoder: Decoder, encoder: Encoder) -> Adapter: return ExprAdapter(subcon, decoder, encoder)
class DurationAdapter(Adapter): def _decode(self, obj, context, path): secs = obj // (10**7) nanosecs = (obj - secs * (10**7)) * 100 return timedelta64(secs, 's') + timedelta64(nanosecs, 'ns') def _encode(self, obj, context, path): return int(uint64(obj.astype('O'))) Duration = DurationAdapter(Int64ul) PVoid = IfThenElse(lambda ctx: ctx.is_64bit, Int64ul, Int32ul) UTF16MultiSz = ExprAdapter( RepeatUntil(lambda x, lst, ctx: not x, CString("UTF_16_le")), lambda obj, ctx: list(obj[:-1]), # last element is the null lambda obj, ctx: obj + ['']) def SizedUTF16MultiSz(size_func): return ExprAdapter( FixedSized(size_func, GreedyRange(CString("UTF_16_le"))), lambda obj, ctx: list( obj), # last element is already removed by GreedyRange lambda obj, ctx: obj + ['']) class CheckCustom(Check): def __init__(self, func, exc_type, msg): super(CheckCustom, self).__init__(func) self.exc_type = exc_type
InliningStruct( 'signature' / signature, '__raw_cert__' @ AttributeRawCopy( Aligned( 0x40, InlineStruct( 'issuer' / PaddedString(0x40, 'ascii'), 'key_type' / EnumConvert(Int32ub, SignatureAlgorithm), 'name' / PaddedString(0x40, 'ascii'), '_unk1' / Int32ub, # might be a timestamp? 'key' / SwitchNoDefault( this.key_type, {t: t.key_construct for t in SignatureAlgorithm})))))) TitleID = ExprAdapter(Bytes(8), lambda data, _: ids.TitleID(data), lambda title_id, _: title_id.to_bytes()) class PlatformSpecific: _3ds: Construct wiiu: Construct # expected signature: `func(is_wiiu: bool) -> Construct` def __init__(self, func: Callable[[bool], Construct]): self._3ds = func(False) self.wiiu = func(True) def get(self, platform: ids.AnyPlatform) -> Construct: return { ids.TitlePlatform._3DS: self._3ds, ids.ContentPlatform._3DS: self._3ds,
if not skip: print("%08x *" % (i + st)) skip = True else: print("%08x %s" % (i + st, hexdump32(val, ' '))) last = val skip = False def unhex(s): s = re.sub(r"/\*.*?\*/", "", s) return bytes.fromhex(s.replace(" ", "").replace("\n", "")) FourCC = ExprAdapter(Int32ul, lambda d, ctx: d.to_bytes(4, "big").decode("latin-1"), lambda d, ctx: int.from_bytes(d.encode("latin-1"), "big")) class SafeGreedyRange(GreedyRange): def __init__(self, subcon, discard=False): super().__init__(subcon) self.discard = discard def _parse(self, stream, context, path): discard = self.discard obj = ListContainer() try: for i in itertools.count(): context._index = i e = self.subcon._parsereport(stream, context, path)