def query_node(node: str) -> tuple: """ Query EPMD about the port to the given node. :param node: String with node "name@ip" or "name@hostname" :return: Host and port where the node is, or None :rtype: tuple(str, int) :throws EPMDClientError: if something is wrong with input :throws EPMDConnectionError: if connection went wrong """ # Trim the host name/IP after the @ and resolve the DNS name if "@" not in node: raise EPMDClientError("Node must have @ in it") (r_name, r_ip_or_hostname) = node.split("@") r_ip = socket.gethostbyname(r_ip_or_hostname) port_please2 = bytes([REQ_PORT_PLEASE2]) \ + bytes(r_name, "utf8") # not sure if latin-1 here resp = EPMDClient._fire_forget_query(r_ip, port_please2) # RESP_PORT2 # Response Error structure # 1 1 # 119 Result > 0 if len(resp) < 2 or resp[0] != RESP_PORT2: ERROR("EPMD: PORT_PLEASE2 to %s sent wrong response %s" % (r_ip, resp)) raise EPMDConnectionError("PORT_PLEASE2 wrong response") if resp[1] != 0: ERROR("EPMD: PORT_PLEASE2 to %s: error %d" % (r_ip, resp[1])) raise EPMDConnectionError("PORT_PLEASE2 error %d" % resp[1]) # Response structure # 1 1 2 1 1 2 ... # 119 Result PortNo NodeType Protocol HighestVersion ... # 2 2 Nlen 2 Elen # LowestVersion Nlen NodeName Elen >Extra r_port = util.u16(resp, 2) # node_type = resp[4] # protocol = resp[5] versions = (util.u16(resp, 6), util.u16(resp, 8)) if not dist_protocol.dist_version_check(versions): raise EPMDConnectionError( "Remote node %s supports protocol version %s and we " "support %d" % (node, versions, dist_protocol.DIST_VSN)) # ignore node name and extra return r_ip, r_port
def consume(self, data: bytes) -> Union[bytes, None]: """ Attempt to consume first part of data as a packet :param data: The accumulated data from the socket which we try to partially or fully consume :return: Unconsumed data, incomplete following packet maybe or nothing. Returning None requests to close the connection """ if len(data) < self.packet_len_size_: # Not ready yet, keep reading return data # Dist protocol switches from 2 byte packet length to 4 at some point if self.packet_len_size_ == 2: pkt_size = util.u16(data, 0) offset = 2 else: pkt_size = util.u32(data, 0) offset = 4 if len(data) < self.packet_len_size_ + pkt_size: # Length is already visible but the data is not here yet return data packet = data[offset:(offset + pkt_size)] # LOG('Dist packet:', util.hex_bytes(packet)) if self.on_packet(packet): return data[(offset + pkt_size):] # Protocol error has occured and instead we return None to request # connection close (see util.py) return None
def binary_to_term_2(data: bytes, options: dict = None): """ Proceed decoding after leading tag has been checked and removed. Erlang lists are decoded into ``term.List`` object, whose ``elements_`` field contains the data, ``tail_`` field has the optional tail and a helper function exists to assist with extracting an unicode string. Atoms are decoded into ``Atom``. Pids and refs into ``Pid`` and ``Reference`` respectively. Maps are decoded into Python ``dict``. Binaries and bit strings are decoded into ``bytes`` object and bitstrings into a pair of ``(bytes, last_byte_bits:int)``. :param options: See description on top of the module :param data: Bytes containing encoded term without 131 header :return: Tuple (Value, TailBytes) The function consumes as much data as possible and returns the tail. Tail can be used again to parse another term if there was any. :raises ETFDecodeException: on various errors or on an unsupported tag """ if options is None: options = {} tag = data[0] if tag in [TAG_ATOM_EXT, TAG_ATOM_UTF8_EXT]: len_data = len(data) if len_data < 3: return incomplete_data("decoding length for an atom name") len_expected = util.u16(data, 1) + 3 if len_expected > len_data: return incomplete_data("decoding text for an atom") name = data[3:len_expected] enc = 'latin-1' if tag == TAG_ATOM_EXT else 'utf8' return _bytes_to_atom(name, enc, options), data[len_expected:] if tag in [TAG_SMALL_ATOM_EXT, TAG_SMALL_ATOM_UTF8_EXT]: len_data = len(data) if len_data < 2: return incomplete_data("decoding length for a small-atom name") len_expected = data[1] + 2 name = data[2:len_expected] enc = 'latin-1' if tag == TAG_SMALL_ATOM_EXT else 'utf8' return _bytes_to_atom(name, enc, options), data[len_expected:] if tag == TAG_NIL_EXT: return [], data[1:] if tag == TAG_STRING_EXT: len_data = len(data) if len_data < 3: return incomplete_data("decoding length for a string") len_expected = util.u16(data, 1) + 3 if len_expected > len_data: return incomplete_data() return data[3:len_expected].decode("utf8"), data[len_expected:] if tag == TAG_LIST_EXT: if len(data) < 5: return incomplete_data("decoding length for a list") len_expected = util.u32(data, 1) result_l = [] tail = data[5:] while len_expected > 0: term1, tail = binary_to_term_2(tail) result_l.append(term1) len_expected -= 1 # Read list tail and set it list_tail, tail = binary_to_term_2(tail) if list_tail == NIL: return result_l, tail return (result_l, list_tail), tail if tag == TAG_SMALL_TUPLE_EXT: if len(data) < 2: return incomplete_data("decoding length for a small tuple") len_expected = data[1] result_t = [] tail = data[2:] while len_expected > 0: term1, tail = binary_to_term_2(tail) result_t.append(term1) len_expected -= 1 return tuple(result_t), tail if tag == TAG_LARGE_TUPLE_EXT: if len(data) < 5: return incomplete_data("decoding length for a large tuple") len_expected = util.u32(data, 1) result_lt = [] tail = data[5:] while len_expected > 0: term1, tail = binary_to_term_2(tail) result_lt.append(term1) len_expected -= 1 return tuple(result_lt), tail if tag == TAG_SMALL_INT: if len(data) < 2: return incomplete_data("decoding a 8-bit small uint") return data[1], data[2:] if tag == TAG_INT: if len(data) < 5: return incomplete_data("decoding a 32-bit int") return util.i32(data, 1), data[5:] if tag == TAG_PID_EXT: node, tail = binary_to_term_2(data[1:]) id1 = util.u32(tail, 0) serial = util.u32(tail, 4) creation = tail[8] pid = Pid(node=node, id=id1, serial=serial, creation=creation) return pid, tail[9:] if tag == TAG_NEW_REF_EXT: if len(data) < 2: return incomplete_data("decoding length for a new-ref") term_len = util.u16(data, 1) node, tail = binary_to_term_2(data[3:]) creation = tail[0] id_len = 4 * term_len id1 = tail[1:id_len + 1] ref = reference.Reference(node=node, creation=creation, refid=id1) return ref, tail[id_len + 1:] if tag == TAG_MAP_EXT: if len(data) < 5: return incomplete_data("decoding length for a map") len_expected = util.u32(data, 1) result_m = {} tail = data[5:] while len_expected > 0: term1, tail = binary_to_term_2(tail) term2, tail = binary_to_term_2(tail) result_m[term1] = term2 len_expected -= 1 return result_m, tail if tag == TAG_BINARY_EXT: len_data = len(data) if len_data < 5: return incomplete_data("decoding length for a binary") len_expected = util.u32(data, 1) + 5 if len_expected > len_data: return incomplete_data("decoding data for a binary") # Returned as Python `bytes` return data[5:len_expected], data[len_expected:] if tag == TAG_BIT_BINARY_EXT: len_data = len(data) if len_data < 6: return incomplete_data("decoding length for a bit-binary") len_expected = util.u32(data, 1) + 6 lbb = data[5] if len_expected > len_data: return incomplete_data("decoding data for a bit-binary") # Returned as tuple `(bytes, last_byte_bits:int)` return (data[6:len_expected], lbb), data[len_expected:] if tag == TAG_NEW_FLOAT_EXT: (result_f,) = struct.unpack(">d", data[1:9]) return result_f, data[10:] if tag == TAG_SMALL_BIG_EXT: nbytes = data[1] # Data is encoded little-endian as bytes (least significant first) in_bytes = data[3:(3+nbytes)] # NOTE: int.from_bytes is Python 3.2+ result_bi = int.from_bytes(in_bytes, byteorder='little') if data[2] != 0: result_bi = -result_bi return result_bi, data[3+nbytes:] if tag == TAG_LARGE_BIG_EXT: nbytes = util.u32(data, 1) # Data is encoded little-endian as bytes (least significant first) in_bytes = data[6:(6+nbytes)] # NOTE: int.from_bytes is Python 3.2+ result_lbi = int.from_bytes(in_bytes, byteorder='little') if data[2] != 0: result_lbi = -result_lbi return result_lbi, data[6+nbytes:] if tag == TAG_NEW_FUN_EXT: # size = util.u32(data, 1) arity = data[5] uniq = data[6:22] index = util.u32(data, 22) num_free = util.u32(data, 26) (mod, tail) = binary_to_term_2(data[30:]) (old_index, tail) = binary_to_term_2(tail) (old_uniq, tail) = binary_to_term_2(tail) (pid, tail) = binary_to_term_2(tail) free_vars = [] while num_free > 0: (v, tail) = binary_to_term_2(tail) free_vars.append(v) num_free -= 1 return Fun(mod=mod, arity=arity, pid=pid, index=index, uniq=uniq, old_index=old_index, old_uniq=old_uniq, free=free_vars), tail raise ETFDecodeException("Unknown tag %d" % data[0])