├── libp2p ├── protocols │ ├── __init__.py │ ├── identify.py │ ├── multistream.py │ └── identify.proto ├── utils │ ├── __init__.py │ ├── multiaddr │ │ ├── __init__.py │ │ ├── exceptions.py │ │ ├── address.py │ │ ├── protocols.py │ │ ├── parse.py │ │ └── conversion.py │ ├── secio │ │ └── spipe.proto │ ├── varint.py │ └── base58.py ├── multiplexers │ └── __init__.py ├── transports │ ├── __init__.py │ ├── tcp_asyncio.py │ └── transport_base.py ├── __init__.py ├── exceptions.py ├── event_loop.py ├── peer.py ├── connection.py └── swarm.py ├── .gitignore ├── test ├── base58_encode_decode.json ├── test_multiaddr.py └── test_asyncio_reactor.py └── README.md /libp2p/protocols/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libp2p/protocols/identify.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libp2p/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libp2p/multiplexers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libp2p/transports/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libp2p/__init__.py: -------------------------------------------------------------------------------- 1 | import libp2p.reactor 2 | -------------------------------------------------------------------------------- /libp2p/exceptions.py: -------------------------------------------------------------------------------- 1 | class Libp2pException(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /libp2p/utils/multiaddr/__init__.py: -------------------------------------------------------------------------------- 1 | from libp2p.utils.multiaddr.address import * 2 | -------------------------------------------------------------------------------- /libp2p/transports/tcp_asyncio.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from libp2p.transports.transport_base import TransportBase 4 | -------------------------------------------------------------------------------- /libp2p/event_loop.py: -------------------------------------------------------------------------------- 1 | """ 2 | Core event loop for libp2p, a.k.a. a thin wrapper around asyncio. 3 | """ 4 | import asyncio 5 | 6 | 7 | class EventLoop: 8 | pass 9 | -------------------------------------------------------------------------------- /libp2p/protocols/multistream.py: -------------------------------------------------------------------------------- 1 | # Identification string of this protocol. Note that on the wire this must be 2 | # encoded in UTF-8. 3 | PATH = "/multistream/1.0.0" 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | *.egg-info 4 | *.pyc 5 | *.swp 6 | *~ 7 | .tox/ 8 | .coverage 9 | htmlcov* 10 | **/__pycache__/ 11 | .cache 12 | docs/build/ 13 | 14 | # generated protobuf classes 15 | *_pb2.py 16 | *_pb3.py 17 | -------------------------------------------------------------------------------- /libp2p/peer.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | 4 | PeerID = namedtuple('PeerID', [ 5 | 'id', 6 | 'public_key', 7 | 'private_key' 8 | ]) 9 | 10 | 11 | PeerInfo = namedtuple('PeerInfo', [ 12 | 'id', 13 | 'multiaddrs' 14 | ]) 15 | -------------------------------------------------------------------------------- /libp2p/utils/multiaddr/exceptions.py: -------------------------------------------------------------------------------- 1 | from libp2p.exceptions import Libp2pException 2 | 3 | 4 | class MultiAddressException(Libp2pException): 5 | pass 6 | 7 | 8 | class AddressException(MultiAddressException): 9 | pass 10 | 11 | 12 | class ProtocolException(MultiAddressException): 13 | pass 14 | -------------------------------------------------------------------------------- /libp2p/utils/secio/spipe.proto: -------------------------------------------------------------------------------- 1 | package spipe.pb; 2 | 3 | message Propose { 4 | optional bytes rand = 1; 5 | optional bytes pubkey = 2; 6 | optional string exchanges = 3; 7 | optional string ciphers = 4; 8 | optional string hashes = 5; 9 | } 10 | 11 | message Exchange { 12 | optional bytes epubkey = 1; 13 | optional bytes signature = 2; 14 | } 15 | -------------------------------------------------------------------------------- /libp2p/connection.py: -------------------------------------------------------------------------------- 1 | class ConnectionBase: 2 | """ 3 | Abstract base class for Connection objects. 4 | """ 5 | @property 6 | def is_listener(self): 7 | raise NotImplemented 8 | 9 | # @coroutine 10 | def dial(self): 11 | raise NotImplemented 12 | 13 | # @coroutine 14 | def accept(self): 15 | raise NotImplemented 16 | -------------------------------------------------------------------------------- /libp2p/transports/transport_base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Abstract base classes for transports. 3 | """ 4 | 5 | 6 | class TransportBase: 7 | """ 8 | Abstract base class for Transport objects. 9 | """ 10 | def create_connection(self, address, **kwargs): 11 | raise NotImplemented 12 | 13 | def create_listener(self, **kwargs): 14 | raise NotImplemented 15 | 16 | -------------------------------------------------------------------------------- /test/base58_encode_decode.json: -------------------------------------------------------------------------------- 1 | [ 2 | ["", ""], 3 | ["61", "2g"], 4 | ["626262", "a3gV"], 5 | ["636363", "aPEr"], 6 | ["73696d706c792061206c6f6e6720737472696e67", "2cFupjhnEsSn59qHXstmK2ffpLv2"], 7 | ["00eb15231dfceb60925886b67d065299925915aeb172c06647", "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"], 8 | ["516b6fcd0f", "ABnLTmg"], 9 | ["bf4f89001e670274dd", "3SEo3LWLoPntC"], 10 | ["572e4794", "3EFU7m"], 11 | ["ecac89cad93923c02321", "EJDM8drfXA6uyA"], 12 | ["10c8511e", "Rt5zm"], 13 | ["00000000000000000000", "1111111111"] 14 | ] 15 | -------------------------------------------------------------------------------- /libp2p/utils/multiaddr/address.py: -------------------------------------------------------------------------------- 1 | from libp2p.utils.multiaddr import parse 2 | 3 | 4 | class MultiAddress(object): 5 | 6 | def __init__(self, address): 7 | str_repr, byte_repr = parse.parse_addr(address) 8 | 9 | self.str_repr = str_repr 10 | self.byte_repr = byte_repr 11 | 12 | 13 | def __repr__(self): 14 | return "".format(self.str_repr) 15 | 16 | def as_bytes(self): 17 | """ 18 | Returns the binary format as an immutable bytes object. 19 | """ 20 | return bytes(self.byte_repr) 21 | -------------------------------------------------------------------------------- /libp2p/utils/varint.py: -------------------------------------------------------------------------------- 1 | """ 2 | Varint encoding library. 3 | 4 | ref: https://developers.google.com/protocol-buffers/docs/encoding?hl=en 5 | """ 6 | 7 | 8 | def uvarint_encode(value): 9 | buf = bytearray() 10 | bits = value & 0x7f 11 | value >>= 7 12 | size = 1 13 | while value: 14 | buf.append(0x80|bits) 15 | bits = value & 0x7f 16 | value >>= 7 17 | size += 1 18 | buf.append(bits) 19 | return bytes(buf), size 20 | 21 | 22 | def uvarint_decode(buf): 23 | if not isinstance(buf, (bytes, bytearray)): 24 | buf = bytes(buf) 25 | size = result = shift = 0 26 | while True: 27 | b = buf[size] 28 | result |= ((b & 0x7f) << shift) 29 | size += 1 30 | if not (b & 0x80): 31 | return result, size 32 | shift += 7 33 | -------------------------------------------------------------------------------- /test/test_multiaddr.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('..') 3 | 4 | from libp2p.peer.multiaddr import MultiAddress 5 | from libp2p.utils.base58 import b58encode, b58decode 6 | 7 | 8 | with open('base58_encode_decode.json') as f: 9 | import json 10 | tests = json.loads(f.read()) 11 | 12 | for d, e in tests: 13 | if len(d) == 0: 14 | continue 15 | i = int(d, 16) 16 | print("{} -> {} (should be: {})".format(d, b58encode(i), e)) 17 | 18 | 19 | ma = MultiAddress("/ip4/127.0.0.1/tcp/4001/ipfs/QmerTTan8gkTEugcb4DmFpAw8Z7bkDQfhoGh6AHzQaqD1Y") 20 | 21 | print(ma) 22 | print(len(ma.str_repr)) 23 | print(ma.as_bytes().__repr__()) 24 | print(len(ma.as_bytes())) 25 | 26 | ma2 = MultiAddress(ma.as_bytes()) 27 | 28 | print(ma2) 29 | print(len(ma2.str_repr)) 30 | print(ma2.as_bytes().__repr__()) 31 | print(len(ma2.as_bytes())) 32 | -------------------------------------------------------------------------------- /libp2p/utils/base58.py: -------------------------------------------------------------------------------- 1 | """ 2 | Base 58 encoding library. 3 | """ 4 | ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' 5 | 6 | 7 | def b58encode(num, alpha=ALPHABET): 8 | """ 9 | Returns num in a base58-encoded string. 10 | """ 11 | encode = '' 12 | if (num < 0): 13 | return '' 14 | while (num >= 58): 15 | mod = num % 58 16 | encode = alpha[mod] + encode 17 | num = num // 58 18 | if (num): 19 | encode = alpha[num] + encode 20 | return encode 21 | 22 | 23 | def b58decode(s, alpha=ALPHABET): 24 | """ 25 | Decodes the base58-encoded string s into an integer. 26 | """ 27 | decoded = 0 28 | multi = 1 29 | s = s[::-1] 30 | for char in s: 31 | decoded += multi * alpha.index(char) 32 | multi = multi * 58 33 | return decoded 34 | -------------------------------------------------------------------------------- /libp2p/protocols/identify.proto: -------------------------------------------------------------------------------- 1 | message Identify { 2 | 3 | // protocolVersion determines compatibility between peers 4 | optional string protocolVersion = 5; // e.g. ipfs/1.0.0 5 | 6 | // agentVersion is like a UserAgent string in browsers, or client version in bittorrent 7 | // includes the client name and client. 8 | optional string agentVersion = 6; // e.g. go-ipfs/0.1.0 9 | 10 | // publicKey is this node's public key (which also gives its node.ID) 11 | // - may not need to be sent, as secure channel implies it has been sent. 12 | // - then again, if we change / disable secure channel, may still want it. 13 | optional bytes publicKey = 1; 14 | 15 | // listenAddrs are the multiaddrs the sender node listens for open connections on 16 | repeated bytes listenAddrs = 2; 17 | 18 | // oservedAddr is the multiaddr of the remote endpoint that the sender node perceives 19 | // this is useful information to convey to the other side, as it helps the remote endpoint 20 | // determine whether its connection to the local peer goes through NAT. 21 | optional bytes observedAddr = 4; 22 | 23 | // (DEPRECATED) protocols are the services this node is running 24 | // repeated string protocols = 3; 25 | } 26 | -------------------------------------------------------------------------------- /libp2p/utils/multiaddr/protocols.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | from libp2p.utils.multiaddr.exceptions import ProtocolException 4 | 5 | 6 | # Definitions of protocol names, codes, and address sizes--respectively. 7 | 8 | IP4 = 'ip4' 9 | TCP = 'tcp' 10 | UDP = 'udp' 11 | DCCP = 'dccp' 12 | IP6 = 'ip6' 13 | SCTP = 'sctp' 14 | IPFS = 'ipfs' 15 | 16 | 17 | __protocol_names = { 18 | IP4 : ( IP4, 4, 32), 19 | TCP : ( TCP, 6, 16), 20 | UDP : ( UDP, 17, 16), 21 | DCCP : ( DCCP, 33, 16), 22 | IP6 : ( IP6, 41, 128), 23 | SCTP : ( SCTP, 132, 16), 24 | IPFS : ( IPFS, 21, -1) # IPFS hashes are varint encoded 25 | } 26 | 27 | __protocol_codes = dict([(p[1], p) for p in __protocol_names.values()]) 28 | 29 | 30 | Protocol = namedtuple('Protocol', ('name', 'code', 'size')) 31 | 32 | 33 | def get_by_code(code): 34 | try: 35 | args = __protocol_codes[int(code)] 36 | except: 37 | try: 38 | msg = "Invalid protocol code: {}".format(code) 39 | except: 40 | msg = "Invalid protocol code" 41 | raise ProtocolException(msg) 42 | else: 43 | return Protocol(*args) 44 | 45 | 46 | def get_by_name(name): 47 | try: 48 | args = __protocol_names[str(name).lower()] 49 | except: 50 | try: 51 | msg = "Invalid protocol name: {}".format(name) 52 | except: 53 | msg = "Invalid protocol name" 54 | raise ProtocolException(msg) 55 | else: 56 | return Protocol(*args) 57 | -------------------------------------------------------------------------------- /libp2p/swarm.py: -------------------------------------------------------------------------------- 1 | import async 2 | from collections import namedtuple 3 | 4 | 5 | SwarmConfig = namedtuple('SwarmConfig', [ 6 | 'peerid', 7 | 'transports', 8 | 'multiplexers', 9 | 'handlers' 10 | ]) 11 | 12 | 13 | class Swarm: 14 | 15 | def __init__(self, cfg): 16 | 17 | self._info = cfg.peerid # peer id for this host 18 | self._transports = cfg.transports # supported transports 19 | self._muxers = cfg.multiplexers # supported multiplexers 20 | self._handlers = cfg.handlers # handlers for supported protocols 21 | 22 | self._conns = {} # active connections 23 | self._muxed_conns = {} # active multiplexed connections 24 | self._listeners = {} # active listeners 25 | 26 | @async.coroutine 27 | def dial(peer_info, protocol, **kwargs): 28 | """ 29 | 1. Check if there is an existing connection to the given peer that is 30 | either straight or multiplexed. If so, either upgrade the straight 31 | connection to a multiplexed one and return that, or return the 32 | existing multiplexed connection. 33 | 2. If there is no existing connection, create a new one. 34 | 2a. Check that we support the transports in at least one of the 35 | multiaddresses provided by the peer info. 36 | 2b. Try creating connections with the given multiaddresses until 37 | one works (TODO: perferences?). 38 | 2c. Return connection object. 39 | """ 40 | pass 41 | -------------------------------------------------------------------------------- /test/test_asyncio_reactor.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('..') 3 | 4 | import asyncio 5 | from libp2p import reactor 6 | 7 | from pprint import pprint 8 | 9 | 10 | class TestSubsystem(reactor.BaseSubsystem): 11 | 12 | def init(self): 13 | self.reactor.schedule_task(2, self.some_async_task, loop=True) 14 | self.reactor.schedule_task(5, self.some_task, loop=True) 15 | yield from self.add_message("Hello World") 16 | 17 | @asyncio.coroutine 18 | def some_async_task(self): 19 | yield from self.add_message("async stuff") 20 | 21 | def some_task(self): 22 | print("SS1: blocking stuff") 23 | 24 | def handle_message(self, msg): 25 | yield print("SS1 processing message: ", msg) 26 | 27 | 28 | class TestSubsystem2(reactor.BaseSubsystem): 29 | 30 | def __init__(self, other): 31 | self.other = other 32 | super().__init__(self) 33 | 34 | def init(self): 35 | self.reactor.schedule_task(3, self.some_async_task, loop=True) 36 | self.reactor.schedule_task(4, self.some_task, loop=True) 37 | yield from self.add_message("Hello World") 38 | 39 | @asyncio.coroutine 40 | def some_async_task(self): 41 | yield from self.other.add_message("msg from test2") 42 | 43 | def some_task(self): 44 | print("SS2: blocking stuff") 45 | 46 | def handle_message(self, msg): 47 | yield print("SS2 processing message: ", msg) 48 | 49 | 50 | if __name__ == "__main__": 51 | r = reactor.get_reactor() 52 | 53 | # here I would like to be able to do: 54 | # `r.set_protocol_handler(protocol, t1)` 55 | t1 = TestSubsystem() 56 | r.add_subsystem("test1", t1) 57 | r.add_subsystem("test2", TestSubsystem2(t1)) 58 | r.run() 59 | -------------------------------------------------------------------------------- /libp2p/utils/multiaddr/parse.py: -------------------------------------------------------------------------------- 1 | """ 2 | MultiAddress string and byte parsing. 3 | """ 4 | from libp2p.utils.multiaddr import protocols, conversion 5 | from libp2p.utils.multiaddr.exceptions import AddressException 6 | from libp2p.utils.multiaddr.exceptions import ProtocolException 7 | 8 | 9 | def raise_invalid(string): 10 | try: 11 | msg = "Invalid address: {}".format(string) 12 | except: 13 | msg = "Invalid address" 14 | raise AddressException(msg) 15 | 16 | 17 | def string_to_tuples(string): 18 | """ 19 | Converts a multiaddr string into a list of tuples corresponding to each 20 | address part. 21 | """ 22 | parts = string.split('/')[1:] 23 | 24 | # parts list should be even length 25 | if len(parts) % 2: 26 | raise_invalid(string) 27 | 28 | tuples = [] 29 | 30 | for i in range(0, len(parts), 2): 31 | proto = protocols.get_by_name(parts[i]) 32 | tuples.append((proto, parts[i+1])) 33 | 34 | return tuples 35 | 36 | 37 | def tuples_to_bytes(tuples): 38 | """ 39 | Converts a list of tuples corresponding to the parts of a MultiAddress into 40 | a bytes object. 41 | """ 42 | b = bytearray() 43 | 44 | for proto, addr in tuples: 45 | b.extend(conversion.proto_to_bytes(proto.code)) 46 | b.extend(conversion.to_bytes(proto, addr)) 47 | 48 | return bytes(b) 49 | 50 | 51 | def bytes_to_tuples(addr): 52 | """ 53 | Converts the binary format of a multiaddr into a list of tuples 54 | corresponding to each part of the multiaddr. 55 | """ 56 | tuples = [] 57 | 58 | i = 0 59 | while i < len(addr): 60 | proto_code, proto_size = conversion.proto_from_bytes(addr[i:]) 61 | proto = protocols.get_by_code(proto_code) 62 | string, string_size = conversion.to_string(proto, addr[i+proto_size:]) 63 | tuples.append((proto, string)) 64 | i += (string_size+proto_size) 65 | 66 | return tuples 67 | 68 | 69 | def tuples_to_string(tuples): 70 | """ 71 | Converts a list of tuples into multiaddr string representation. 72 | """ 73 | return '/' + '/'.join(['/'.join((t[0].name, str(t[1]))) for t in tuples]) 74 | 75 | 76 | def string_to_bytes(string): 77 | """ 78 | Converts a multiaddr string into its binary format. 79 | """ 80 | return tuples_to_bytes(string_to_tuples(string)) 81 | 82 | 83 | def bytes_to_string(addr): 84 | """ 85 | Converts a multiaddr in binary format to its string representation. 86 | """ 87 | return tuples_to_string(bytes_to_tuples(addr)) 88 | 89 | 90 | def parse_addr(addr): 91 | """ 92 | Returns the parsed string and binary formats of a given multiaddr. 93 | """ 94 | if isinstance(addr, str): 95 | return addr, string_to_bytes(addr) 96 | elif isinstance(addr, (bytes, bytearray)): 97 | return bytes_to_string(addr), addr 98 | else: 99 | msg = "Address must be type, type, or type" 100 | raise AddressException(msg) 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | libp2p for Python 2 | ================= 3 | 4 | An outline of the libp2p spec as I currently understand it: 5 | 6 | - Node Layer 7 | - This is effectively the highest layer of abstraction (This is basically a nice interface for the swarm layer). 8 | - The interface consists simply of: 9 | ```python 10 | def id(self): 11 | # Returns PeerID object associated with this node. 12 | 13 | def open_stream(self, protocol, remote_peer_info): 14 | # Returns stream object representing connection to the peer. The remote_peer_info 15 | # object contains contact information in the form of multiaddresses. 16 | # (see: https://github.com/jbenet/multiaddr) 17 | 18 | def set_stream_handler(self, protocol, handler): 19 | # Sets handler factory for incoming streams on the given protocol. 20 | ``` 21 | - *NOTE: In our case this will all be built on some kind of event loop (likely asyncio) and so it will all function with callbacks (cue resounding cry of frustration).* 22 | 23 | - Swarm Layer 24 | - The swarm layer sits on top of all incoming and outgoing *connections* associated with this node. 25 | - The interface consists of: 26 | ```python 27 | def add_transport(self, transport, **options): 28 | # Adds a transport to be supported by this swarm. Any transport implementations 29 | # must inherit from some TBD base class. 30 | 31 | def upgrade_connection(self, new_conn, **options): 32 | # Returns upgraded connections. 33 | # (I am unsure about what this is for at the moment of writing this.) 34 | 35 | def upgrade_to_multiplexer(self, muxer, **options): 36 | # Returns stream multiplexer from upgraded connection. 37 | # (I am also unsure about what this is for at the moment of writing this.) 38 | 39 | def dial(self, protocol, remote_peer_id, **options): 40 | # Opens a connection with the remote peer using the transport determined to be 41 | # the most appropriate. 42 | 43 | def set_protocol_handler(self, protocol, handler): 44 | # Sets handler factory for given protocol. 45 | ``` 46 | 47 | - Connections 48 | - Connections are abstracted to appear as separate streams to the user, but under the hood they may be multiplexed with other streams on the same underlying transport. This is accomplished using [multistream protocol formats](https://github.com/jbenet/multistream). 49 | - The point of `multistream` is to have a streaming format that allows easy layering and embedding of multiple streams on the same underlying transport. 50 | - The `multistream` format requires protocols to be defined as paths (e.g. `/ipfs-dht/0.2.3`), and the stream itself is initiated with a header which contains this information. The header contains the protocol path (encoded in UTF-8) suffixed by a newline character and prefixed by the header length (encoded as a `varint`; the newline is included in the length): 51 | ``` 52 | \n 53 | 54 | ... 55 | ``` 56 | - The agenda behind `multistream` is also to develop human readable protocols that can be used with the internet as a whole. For example: 57 | ``` 58 | /ipfs/Qmaa4Rw81a3a1VEx4LxB7HADUAXvZFhCoRdBzsMZyZmqHD/ipfs.protocol 59 | /http/w3id.org/ipfs/ipfs-1.1.0.json 60 | ``` 61 | - `multistream-select` is a `multistream` protocol that allows for querying and selecting between subprotocols on a stream. The `multistream-select` protocol listens for or communicates a protocol to speak and then nests (or "upgrades"?) the protocol: 62 | ``` 63 | /ipfs/QmdRKVhvzyATs3L6dosSb6w8hKuqfZK2SyPVqcYJ5VLYa2/multistream-select/0.3.0 64 | /ipfs/QmVXZiejj3sXEmxuQxF2RjmFbEiE9w7T82xDn3uYNuhbFb/ipfs-dht/0.2.3 65 | 66 | 67 | ``` 68 | -------------------------------------------------------------------------------- /libp2p/utils/multiaddr/conversion.py: -------------------------------------------------------------------------------- 1 | """ 2 | Conversions between python types and bytes objects. 3 | """ 4 | from socket import AF_INET6, inet_aton, inet_ntoa, inet_ntop, inet_pton 5 | import struct 6 | 7 | from libp2p.utils.multiaddr import protocols 8 | from libp2p.utils.multiaddr.exceptions import AddressException 9 | from libp2p.utils.base58 import b58encode, b58decode 10 | from libp2p.utils.varint import uvarint_encode, uvarint_decode 11 | 12 | 13 | ######## 14 | # IPv4 # 15 | ######## 16 | 17 | def ip4_string_to_bytes(string): 18 | """ 19 | Converts an ip4 address from string representation to a bytes object. 20 | """ 21 | return inet_aton(string) 22 | 23 | 24 | def ip4_bytes_to_long(ip4): 25 | """ 26 | Converts an ip4 address from byte representation to a long. 27 | """ 28 | return struct.unpack('!L', ip4)[0] 29 | 30 | 31 | def ip4_long_to_bytes(ip4): 32 | """ 33 | Converts an ip4 address from long representation to a bytes object. 34 | """ 35 | return struct.pack('!L', ip4) 36 | 37 | 38 | def ip4_bytes_to_string(ip4): 39 | """ 40 | Converts an ip4 address from long representation to a string. 41 | """ 42 | return inet_ntoa(ip4) 43 | 44 | 45 | ######## 46 | # IPv6 # 47 | ######## 48 | 49 | def ip6_string_to_bytes(string): 50 | """ 51 | Converts an ip6 address from string representation to a bytes object. 52 | """ 53 | return inet_pton(AF_INET6, string) 54 | 55 | 56 | def ip6_bytes_to_long(ip6): 57 | """ 58 | Converts an ip6 address from byte representation to a long. 59 | """ 60 | a, b = struct.unpack('!QQ', ip6) 61 | return (a << 64) | b 62 | 63 | 64 | def ip6_long_to_bytes(ip6): 65 | """ 66 | Converts an ip6 address from 16 byte long representation to a bytes object. 67 | """ 68 | a, b = ip6 >> 64, ip6 % (2<<64) 69 | return struct.pack('!QQ', a, b) 70 | 71 | 72 | def ip6_bytes_to_string(ip6): 73 | """ 74 | Converts an ip6 address from long representation to a string. 75 | """ 76 | return inet_ntop(AF_INET6, ip6) 77 | 78 | 79 | 80 | ######## 81 | # Misc # 82 | ######## 83 | 84 | def port_to_bytes(port): 85 | """ 86 | Converts a port number to an unsigned short. 87 | """ 88 | return struct.pack('!H', int(port)) 89 | 90 | 91 | def port_from_bytes(port): 92 | """ 93 | Converts a port number from a bytes object to an int. 94 | """ 95 | return struct.unpack('!H', port)[0] 96 | 97 | 98 | def proto_to_bytes(code): 99 | """ 100 | Converts a protocol code into an unsigned varint. 101 | """ 102 | return uvarint_encode(code)[0] 103 | 104 | 105 | def proto_from_bytes(code): 106 | """ 107 | Converts a protocol code from a bytes oject to an int. 108 | """ 109 | return uvarint_decode(code) 110 | 111 | 112 | 113 | def multihash_to_bytes(string): 114 | """ 115 | Converts a multihash string as an unsigned varint. 116 | """ 117 | return uvarint_encode(b58decode(string))[0] 118 | 119 | 120 | def multihash_to_string(mhash): 121 | """ 122 | Converts a uvarint encoded multihash into a string. 123 | """ 124 | return b58encode(uvarint_decode(mhash)[0]) 125 | 126 | 127 | 128 | def to_bytes(proto, string): 129 | """ 130 | Properly converts address string or port to bytes based on given protocol. 131 | """ 132 | if proto.name == protocols.IP4: 133 | addr = ip4_string_to_bytes(string) 134 | elif proto.name == protocols.IP6: 135 | addr = ip6_string_to_bytes(string) 136 | elif proto.name == protocols.TCP: 137 | addr = port_to_bytes(string) 138 | elif proto.name == protocols.UDP: 139 | addr = port_to_bytes(string) 140 | elif proto.name == protocols.IPFS: 141 | addr = multihash_to_bytes(string) 142 | else: 143 | msg = "Protocol not implemented: {}".format(proto.name) 144 | raise AddressException(msg) 145 | return addr 146 | 147 | 148 | def to_string(proto, addr): 149 | """ 150 | Properly converts bytes to string or int representation based on the given 151 | protocol. Returns string representation of address and the number of bytes 152 | from the buffer consumed. 153 | """ 154 | if proto.name == protocols.IP4: 155 | size = proto.size//8 156 | string = ip4_bytes_to_string(addr[:size]) 157 | elif proto.name == protocols.IP6: 158 | size = proto.size//8 159 | string = ip6_bytes_to_string(addr[:size]) 160 | elif proto.name == protocols.TCP: 161 | size = proto.size//8 162 | string = port_from_bytes(addr[:size]) 163 | elif proto.name == protocols.UDP: 164 | size = proto.size//8 165 | string = port_from_bytes(addr[:size]) 166 | elif proto.name == protocols.IPFS: 167 | varint, size = uvarint_decode(addr) 168 | string = b58encode(varint) 169 | else: 170 | msg = "Protocol not implemented: {}".format(proto.name) 171 | raise AddressException(msg) 172 | return string, size 173 | --------------------------------------------------------------------------------