示例#1
0
    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
示例#2
0
    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
示例#3
0
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])