예제 #1
0
파일: packet.py 프로젝트: lowgoz/dockergate
def decode_packet(data, checksum=True, strict=True, server_packet=True):
    """
	Decodes a packet from or send to the PCI.
	
	Returns a tuple, the packet that was parsed and the remainder that was
	unparsed (in the case of some special commands.
	
	If no packet was able to be parsed, the first element of the tuple will be
	None.  However there may be some circumstances where there is still a
	remainder to be parsed (cancel request).
	
	"""
    data = data.strip()
    if data == '':
        return None, None

    # packets from clients have some special flags which we need to handle.
    if server_packet:
        if data[0] == '+':
            data = data[1:]
            return PowerOnPacket(), data
        elif data[0] == '!':
            # buffer is full / invalid checksum, some requests may be dropped.
            # serial interface guide s4.3.3 p28
            data = data[1:]
            return PCIErrorPacket(), data

        if data[0] in CONFIRMATION_CODES:
            success = data[1] == '.'
            code = data[0]
            data = data[2:]
            return ConfirmationPacket(code, success), data

    else:
        if data == '~~~':
            # reset
            return ResetPacket(), None
        elif data == '|':
            # smart + connect shortcut
            return SmartConnectShortcutPacket(), None
        elif '?' in data:
            # discard data before the ?, and resubmit for processing.
            data = data.split('?')[-1]
            return None, data

        if data[0] == '\\':
            data = data[1:]

        if data[0] == '@':
            # this causes it to be once-off a "basic" mode command.
            data = data[1:]
            checksum = False

        if data[-1] not in HEX_CHARS:
            # then there is a confirmation code at the end.
            confirmation = data[-1]

            if confirmation not in CONFIRMATION_CODES:
                if strict:
                    raise ValueError, "Confirmation code is not a lowercase letter in g - z"
                else:
                    warnings.warn(
                        'Confirmation code is not a lowercase letter in g - z')

            data = data[:-1]
        else:
            confirmation = None

    for c in data:
        if c not in HEX_CHARS:
            raise ValueError, "Non-base16 input: %r in %r" % (c, data)

    # get the checksum, if it's there.
    if checksum:
        # check the checksum
        if not validate_cbus_checksum(data):
            real_checksum = get_real_cbus_checksum(data)
            if strict:
                raise ValueError, "C-Bus checksum incorrect (expected %r) and strict mode is enabled: %r." % (
                    real_checksum, data)
            else:
                warnings.warn(
                    "C-Bus checksum incorrect (expected %r) in data %r" %
                    (real_checksum, data), UserWarning)

        # strip checksum
        data = data[:-2]

    # base16 decode
    data = b16decode(data)

    # flags (serial interface guide s3.4)
    flags = ord(data[0])

    destination_address_type = flags & 0x07
    # "reserved", "must be set to 0"
    rc = (flags & 0x18) >> 3
    dp = (flags & 0x20) == 0x20
    # priority class
    priority_class = (flags & 0xC0) >> 6

    # increment ourselves along
    data = data[1:]

    # handle source address
    if server_packet:
        source_addr = ord(data[0])
        data = data[1:]
    else:
        source_addr = None

    if dp:
        # device management flag set!
        # this is used to set parameters of the PCI
        p = DeviceManagementPacket.decode_packet(data, checksum, flags,
                                                 destination_address_type, rc,
                                                 dp, priority_class)

    elif destination_address_type == DAT_PP:
        # decode as point-to-point packet
        p = PointToPointPacket.decode_packet(data, checksum, flags,
                                             destination_address_type, rc, dp,
                                             priority_class)
        #raise NotImplementedError, 'Point-to-point'
    elif destination_address_type == DAT_PM:
        # decode as point-to-multipoint packet
        p = PointToMultipointPacket.decode_packet(data, checksum, flags,
                                                  destination_address_type, rc,
                                                  dp, priority_class)
    elif destination_address_type == DAT_PPM:
        # decode as point-to-point-to-multipoint packet
        #return PointToPointToMultipointPacket.decode_packet(data, checksum, flags, destination_address_type, rc, dp, priority_class)
        raise NotImplementedError, 'Point-to-point-tomultipoint'

    if not server_packet and confirmation:
        p.confirmation = confirmation
        p.source_address = None
    elif source_addr:
        p.source_address = source_addr
        p.confirmation = None

    return p, None
예제 #2
0
def decode_packet(
        data: bytes,
        checksum: bool = True,
        strict: bool = True,
        from_pci: bool = True) \
        -> Tuple[Union[BasePacket, AnyCAL, None], int]:
    """
    Decodes a single C-Bus Serial Interface packet.

    The return value is a tuple:

    0. The packet that was parsed, or None if there was no packet that could
       be parsed.
    1. The buffer position that we parsed up to. This may be non-zero even if
       the packet was None (eg: Cancel request).

    Note: this decoder does not support unaddressed packets (such as Standard
    Format Status Replies).

    Note: Direct Command Access causes this method to return AnyCAL instead
    of a BasePacket.

    :param data: The data to parse, in encapsulated serial format.
    :param checksum: If True, requires a checksum for all packets
    :param strict: If True, returns InvalidPacket whenever checksum is
        incorrect. Otherwise, only emits a warning.
    :param from_pci: If True, parses the packet as if it were sent from/by a
        PCI -- if your software was sent packets by a PCI, this is
        what you want.

        If False, this parses the packet as if it were sent to a PCI; parsing
        messages that software expecting to communicate with a PCI sends. This
        could be used to build a fake PCI, or analyse the behaviour of other
        C-Bus software.
    """
    confirmation = None
    consumed = 0
    # Serial Interface User Guide s4.2.7
    device_managment_cal = False

    if data == b'':
        return None, 0

    # There are some special transport-layer flags that need to be handled
    # before parsing the rest of the message.
    if from_pci:
        if data.startswith(b'+'):  # +
            return PowerOnPacket(), consumed + 1
        elif data.startswith(b'!'):  # !
            # buffer is full / invalid checksum, some requests may be dropped.
            # serial interface guide s4.3.3 p28
            return PCIErrorPacket(), consumed + 1

        if len(data) < MIN_MESSAGE_SIZE:
            # Not enough data in the buffer to process yet.
            return None, 0

        if data[0] in CONFIRMATION_CODES:
            success = indexbytes(data, 1) == 0x2e  # .
            code = data[:1]
            return ConfirmationPacket(code, success), consumed + 2

        end = data.find(END_RESPONSE)
    else:
        if data.startswith(b'~'):
            # Reset
            # Serial Interface Guide, s4.2.3
            return ResetPacket(), consumed + 1
        elif data.startswith(b'null'):
            # Toolkit is buggy, just ignore it.
            return None, consumed + 4
        elif (data.startswith(b'|' + END_COMMAND)
              or data.startswith(b'||' + END_COMMAND)):
            # SMART + CONNECT shortcut
            consumed += data.find(END_COMMAND) + 1
            return SmartConnectShortcutPacket(), consumed
        else:
            # Check if we need to discard a message
            # Serial interface guide, s4.2.4
            nlp = data.find(END_COMMAND)
            qp = data.find(b'?')
            if -1 < qp < nlp:
                # Discard data before the "?", and continue
                return None, consumed + qp + 1

        end = data.find(END_COMMAND)

    # Look for ending character(s). If there is none, break out now.
    if end == -1:
        return None, consumed

    # Make it so the end of the buffer is where the end of the command is, and
    # consume the command up to and including the ending byte(s).
    data = data[:end]

    if from_pci:
        consumed += end + len(END_RESPONSE)
    else:
        consumed += end + len(END_COMMAND)

    if not data:
        # Empty command, break out!
        return None, consumed

    if not from_pci:
        if data.startswith(b'@'):
            # Once-off BASIC mode command, Serial Interface Guide, s4.2.7
            checksum = False
            device_managment_cal = True
            data = data[1:]
        elif data.startswith(b'\\'):
            data = data[1:]
        else:
            device_managment_cal = True

        if data[-1] not in HEX_CHARS:
            # then there is a confirmation code at the end.
            confirmation = int2byte(indexbytes(data, -1))

            if confirmation not in CONFIRMATION_CODES:
                if strict:
                    return InvalidPacket(
                        payload=data,
                        exception=ValueError(
                            'Confirmation code is not in range g..z')
                    ), consumed
                else:
                    warnings.warn('Confirmation code is not in range g..z')

            # strip confirmation byte
            data = data[:-1]

    for c in data:
        if c not in HEX_CHARS:
            return InvalidPacket(
                payload=data,
                exception=ValueError(
                    f'Non-base16 input: {c:x} in {data}')), consumed

    # base16 decode
    data = b16decode(data)

    # get the checksum, if it's there.
    if checksum:
        # check the checksum
        if not validate_cbus_checksum(data):
            real_checksum = get_real_cbus_checksum(data)
            if strict:
                return InvalidPacket(
                    payload=data,
                    exception=ValueError(
                        f'C-Bus checksum incorrect (expected 0x{real_checksum:x}) '
                        f'and strict mode is enabled: {data}')), consumed
            else:
                warnings.warn(
                    f'C-Bus checksum incorrect (expected 0x{real_checksum:x}) '
                    f'in data {data}', UserWarning)

        # strip checksum
        data = data[:-1]

    # flags (serial interface guide s3.4)
    flags = byte2int(data)

    try:
        address_type = DestinationAddressType(flags & 0x07)
        # "reserved", "must be set to 0"
        # rc = (flags >> 3) & 0x03
        dp = (flags & 0x20) == 0x20
        # priority class
        priority_class = PriorityClass((flags >> 6) & 0x03)

        # increment ourselves along
        data = data[1:]

        # handle source address
        if from_pci:
            source_addr = byte2int(data)
            data = data[1:]
        else:
            source_addr = None

        if dp:
            # device management flag set!
            # this is used to set parameters of the PCI
            p = DeviceManagementPacket.decode_packet(
                data=data, checksum=checksum, priority_class=priority_class)
        elif device_managment_cal:
            cal, cal_len = PointToPointPacket.decode_cal(data)
            return cal, consumed + cal_len

        elif address_type == DestinationAddressType.POINT_TO_POINT:
            # decode as point-to-point packet
            p = PointToPointPacket.decode_packet(data=data,
                                                 checksum=checksum,
                                                 priority_class=priority_class)
        elif address_type == DestinationAddressType.POINT_TO_MULTIPOINT:
            # decode as point-to-multipoint packet
            p = PointToMultipointPacket.decode_packet(
                data=data, checksum=checksum, priority_class=priority_class)
        elif (address_type ==
              DestinationAddressType.POINT_TO_POINT_TO_MULTIPOINT):
            # decode as point-to-point-to-multipoint packet
            # return PointToPointToMultipointPacket.decode_packet(data, checksum,
            # flags, destination_address_type, rc, dp, priority_class)
            raise NotImplementedError('Point-to-point-to-multipoint')
        else:
            raise NotImplementedError(
                f'Destination address type = 0x{address_type:x}')

        if not from_pci:
            p.confirmation = confirmation
            p.source_address = None
        elif source_addr:
            p.source_address = source_addr
            p.confirmation = None
    except Exception as e:
        p = InvalidPacket(payload=data, exception=e)

    return p, consumed
예제 #3
0
파일: packet.py 프로젝트: JohnTocher/cbus
def decode_packet(data, checksum=True, strict=True, server_packet=True):
	"""
	Decodes a packet from or send to the PCI.
	
	Returns a tuple, the packet that was parsed and the remainder that was
	unparsed (in the case of some special commands.
	
	If no packet was able to be parsed, the first element of the tuple will be
	None.  However there may be some circumstances where there is still a
	remainder to be parsed (cancel request).
	
	"""
	data = data.strip()
	if data == '':
		return None, None
	
	# packets from clients have some special flags which we need to handle.
	if server_packet:
		if data[0] == '+':
			data = data[1:]
			return PowerOnPacket(), data
		elif data[0] == '!':
			# buffer is full / invalid checksum, some requests may be dropped.
			# serial interface guide s4.3.3 p28
			data = data[1:]
			return PCIErrorPacket(), data
		
		if data[0] in CONFIRMATION_CODES:
			success = data[1] == '.'
			code = data[0]
			data = data[2:]
			return ConfirmationPacket(code, success), data
			
			
			
	else:
		if data == '~~~':
			# reset
			return ResetPacket(), None
		elif data == '|':
			# smart + connect shortcut
			return SmartConnectShortcutPacket(), None
		elif '?' in data:
			# discard data before the ?, and resubmit for processing.
			data = data.split('?')[-1]
			return None, data

		if data[0] == '\\':
			data = data[1:]
		
		if data[0] == '@':
			# this causes it to be once-off a "basic" mode command.
			data = data[1:]
			checksum = False		
			
		if data[-1] not in HEX_CHARS:
			# then there is a confirmation code at the end.
			confirmation = data[-1]
			
			if confirmation not in CONFIRMATION_CODES:
				if strict:
					raise ValueError, "Confirmation code is not a lowercase letter in g - z"
				else:
					warnings.warn('Confirmation code is not a lowercase letter in g - z')
					
			data = data[:-1]
		else:
			confirmation = None
			
			
	for c in data:
		if c not in HEX_CHARS:
			raise ValueError, "Non-base16 input: %r in %r" % (c, data)
			
	# get the checksum, if it's there.
	if checksum:
		# check the checksum
		if not validate_cbus_checksum(data):
			real_checksum = get_real_cbus_checksum(data)
			if strict:
				raise ValueError, "C-Bus checksum incorrect (expected %r) and strict mode is enabled: %r." % (real_checksum, data)
			else:
				warnings.warn("C-Bus checksum incorrect (expected %r) in data %r" % (real_checksum, data), UserWarning)
		
		# strip checksum
		data = data[:-2]
	
	# base16 decode
	data = b16decode(data)
	
	# flags (serial interface guide s3.4)
	flags = ord(data[0])
	
	destination_address_type = flags & 0x07
	# "reserved", "must be set to 0"
	rc = (flags & 0x18) >> 3
	dp = (flags & 0x20) == 0x20
	# priority class
	priority_class = (flags & 0xC0) >> 6
	
	# increment ourselves along
	data = data[1:]
	
	# handle source address
	if server_packet:
		source_addr = ord(data[0])
		data = data[1:]
	else:
		source_addr = None
	
	if dp:
		# device management flag set!
		# this is used to set parameters of the PCI
		p = DeviceManagementPacket.decode_packet(data, checksum, flags, destination_address_type, rc, dp, priority_class)
	
	
	elif destination_address_type == DAT_PP:
		# decode as point-to-point packet
		p = PointToPointPacket.decode_packet(data, checksum, flags, destination_address_type, rc, dp, priority_class)
		#raise NotImplementedError, 'Point-to-point'
	elif destination_address_type == DAT_PM:
		# decode as point-to-multipoint packet
		p = PointToMultipointPacket.decode_packet(data, checksum, flags, destination_address_type, rc, dp, priority_class)
	elif destination_address_type == DAT_PPM:
		# decode as point-to-point-to-multipoint packet
		#return PointToPointToMultipointPacket.decode_packet(data, checksum, flags, destination_address_type, rc, dp, priority_class)
		raise NotImplementedError, 'Point-to-point-tomultipoint'

	if not server_packet and confirmation:
		p.confirmation = confirmation
		p.source_address = None
	elif source_addr:
		p.source_address = source_addr
		p.confirmation = None
	
	return p, None