def xor(cls, data: Data, factor: Data) -> Optional[Data]: if data.get_byte(0) != 0: return None data_buffer = data._buffer data_offset = data._offset data_length = data._length fact_buffer = factor._buffer fact_offset = factor._offset fact_length = factor._length assert data_length == 8 or data_length == 20, 'address error: %s' % data assert fact_length == 16, 'factor should be the "magic code" + "(96-bits) transaction ID": %s' % factor array = bytearray(data_length) # family array[1] = data_buffer[data_offset + 1] # X-Port array[2] = data_buffer[data_offset + 2] ^ fact_buffer[fact_offset + 0] array[3] = data_buffer[data_offset + 3] ^ fact_buffer[fact_offset + 1] # X-Address a_pos = 4 f_pos = 0 while a_pos < data_length: array[a_pos] = data_buffer[data_offset + a_pos] ^ fact_buffer[fact_offset + f_pos] a_pos += 1 f_pos += 1 return Data(data=array)
def parse(cls, data: Data): if data.length < 2 or (data.get_byte(index=1) & 0x03) != 0: # format: xxxx xxxx, xxxx xx00 return None elif data.length > 2: data = data.slice(end=2) return cls(data=data)
def data_to_ipv4(cls, address: Data) -> str: # IPv4 assert address.length == 4, 'IPv4 data error: %s' % address return '.'.join([ str(address.get_byte(index=0)), str(address.get_byte(index=1)), str(address.get_byte(index=2)), str(address.get_byte(index=3)), ])
def parse(cls, data: Data): if data.length < 2: return None elif data.length > 2: data = data.slice(end=2) value = data.get_uint16_value() t = cls.__attribute_types.get(value) if t is None: return cls(value=value, data=data) else: return t
def parse(cls, data: Data): if data.length < 2 or (data.get_byte(index=0) & 0xC0) != 0: # format: 00xx xxxx, xxxx xxxx return None elif data.length > 2: data = data.slice(end=2) value = data.get_uint16_value() t = cls.__message_types.get(value) if t is None: # name = 'MessageType-0x%04X' % value # t = cls(value=value, data=data, name=name) raise LookupError('msg type error: %d' % value) return t
def parse(cls, data: Data, tag: AttributeType, length: AttributeLength = None): # checking head byte if data.get_byte(index=0) != 0: raise ValueError('mapped-address error: %s' % data) family = data.get_byte(index=1) if family == cls.family_ipv4: # IPv4 if length.value == 8: port = data.get_uint16_value(2) ip = cls.data_to_ipv4(address=data.slice(start=4)) return cls(data=data, ip=ip, port=port, family=family)
def send_msg(self, msg: str): data = msg.encode('utf-8') data = Data(data=data) address = self.server_address print('sending msg (%d bytes): "%s" to %s' % (data.length, msg, address)) self.send_message(msg=data, destination=address)
def send_cmd(self, cmd: str): data = cmd.encode('utf-8') data = Data(data=data) address = self.server_address print('sending cmd (%d bytes): "%s" to %s' % (data.length, cmd, address)) self.send_command(cmd=data, destination=address)
def parse(cls, data: Data): if data.length < 16: # raise ValueError('transaction ID length error: %d' % data.length) return None elif data.length > 16: data = data.slice(end=16) # assert data.get_bytes(end=4) == MagicCookie, 'transaction ID not starts with magic cookie' return cls(data=data)
def parse(cls, data: Data): # get STUN head head = Header.parse(data=data) if head is None: # not a STUN message? return None # check message length head_len = head.length pack_len = head_len + head.msg_length.value data_len = data.length if data_len < pack_len: # raise ValueError('STUN package length error: %d, %d' % (data_len, pack_len)) return None elif data_len > pack_len: data = data.slice(end=pack_len) # get attributes body body = data.slice(start=head_len) return cls(data=data, head=head, body=body)
def parse(cls, data: Data, tag: Tag, length: Length = None): # check length if length.value != 4: raise ValueError('Change-Request value error: %s' % length) # get value value = data.get_uint32_value() if value == ChangeIPAndPort.value: return ChangeIPAndPort elif value == ChangeIP.value: return ChangeIP elif value == ChangePort.value: return ChangePort
def parse(cls, data: Data): # get message type _type = MessageType.parse(data=data) if _type is None: return None pos = _type.length # get message length _len = MessageLength.parse(data=data.slice(start=pos)) if _len is None: return None pos += _len.length # get transaction ID _id = TransactionID.parse(data=data.slice(start=pos)) if _id is None: return None pos += _id.length assert pos == 20, 'header length error: %d' % pos if data.length > pos: data = data.slice(end=pos) # create return cls(data=data, msg_type=_type, msg_length=_len, trans_id=_id)
def new(cls, msg_type: MessageType, trans_id: TransactionID = None, body: Union[Data, bytes, bytearray] = None): if body is None: body = Data.ZERO elif not isinstance(body, Data): # bytes or bytearray body = Data(data=body) msg_len = MessageLength(value=body.length) head = Header(msg_type=msg_type, msg_length=msg_len, trans_id=trans_id) return cls(head=head, body=body)
def send(self, data: Data, destination: tuple, source: Union[tuple, int] = None) -> int: """ Send data to remote address :param data: :param destination: remote address :param source: local address :return: count of sent bytes """ try: if source is None: source = self.source_address elif isinstance(source, int): source = (self.source_address[0], source) return self.hub.send(data=data.get_bytes(), destination=destination, source=source) except socket.error: return -1
def __bind_request(self, remote_host: str, remote_port: int, body: Data) -> Optional[dict]: # 1. create STUN message package req = Package.new(msg_type=BindRequest, body=body) trans_id = req.head.trans_id # 2. send and get response count = 0 while True: size = self.send(data=req, destination=(remote_host, remote_port)) if size != req.length: # failed to send data return None cargo = self.receive() if cargo is None: if count < self.retries: count += 1 self.info('(%d/%d) receive nothing' % (count, self.retries)) else: # failed to receive data return None else: self.info('received %d bytes from %s' % (len(cargo.data), cargo.source)) break # 3. parse response context = { 'trans_id': trans_id, } data = Data(data=cargo.data) if not self.parse_data(data=data, context=context): return None head = context.get('head') if head is None or head.type != BindResponse or head.trans_id != trans_id: # received package error return None return context
def parse(cls, data: Data, tag: Tag, length: Length = None): # get string desc = data.get_bytes().decode('utf-8').rstrip('\0') return cls(data=data, description=desc)
def received_error(self, data: Data, source: tuple, destination: tuple): self.info('received error (%d bytes) from %s to %s: %s' % (data.length, source, destination, data.get_bytes()))
def received_message(self, msg: Data, source: tuple, destination: tuple) -> bool: self.info('received msg (%d bytes) from %s to %s: %s' % (msg.length, source, destination, msg.get_bytes())) return True
def data_received(self, data: bytes, source: tuple, destination: tuple) -> Optional[bytes]: task = Arrival(payload=Data(data=data), source=source, destination=destination) self.pool.append_arrival(task=task) return None
def send_data(self, data: Data, destination: tuple, source: tuple) -> int: return self.send(data=data.get_bytes(), destination=destination, source=source)
def received_command(self, cmd: Data, source: tuple, destination: tuple) -> bool: self.info('received cmd (%d bytes) from %s to %s: %s' % (cmd.length, source, destination, cmd.get_bytes())) return True