Skip to content

Commit 1759c1d

Browse files
authored
Merge pull request #38 from alexander255/codec_submodules
Move out address codec implementations into submodules
2 parents 4a99835 + 1345fcd commit 1759c1d

File tree

16 files changed

+304
-219
lines changed

16 files changed

+304
-219
lines changed

multiaddr/codec.py

Lines changed: 16 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,21 @@
1-
import base58
2-
import base64
3-
import os
1+
from __future__ import absolute_import
2+
import importlib
43

5-
import idna
6-
from netaddr import IPAddress
7-
import six
8-
import struct
94
import varint
105

11-
from .protocols import P_DNS
12-
from .protocols import P_DNS4
13-
from .protocols import P_DNS6
14-
from .protocols import P_DCCP
15-
from .protocols import P_IP4
16-
from .protocols import P_IP6
17-
from .protocols import P_P2P
18-
from .protocols import P_ONION
196
from .protocols import protocol_with_code
207
from .protocols import protocol_with_name
21-
from .protocols import P_SCTP
22-
from .protocols import P_TCP
23-
from .protocols import P_UDP
24-
from .protocols import P_UNIX
258
from .protocols import read_varint_code
269

2710

28-
if hasattr(os, "fsencode") and hasattr(os, "fsdecode"):
29-
fsencode = os.fsencode
30-
fsdecode = os.fsdecode
31-
else: # PY2
32-
import sys
33-
34-
def fsencode(path):
35-
if not isinstance(path, six.binary_type):
36-
path = path.encode(sys.getfilesystemencoding())
37-
return path
38-
39-
def fsdecode(path):
40-
if not isinstance(path, six.text_type):
41-
path = path.decode(sys.getfilesystemencoding())
42-
return path
11+
CODEC_CACHE = {}
12+
def find_codec_by_name(name):
13+
if not name:
14+
raise ValueError("unknown protocol codec")
15+
codec = CODEC_CACHE.get(name)
16+
if not codec:
17+
codec = CODEC_CACHE[name] = importlib.import_module(".codecs.{0}".format(name), __package__)
18+
return codec
4319

4420

4521
def string_to_bytes(string):
@@ -65,7 +41,10 @@ def string_to_bytes(string):
6541
"protocol requires address, none given: %s" % proto.name)
6642
if proto.path:
6743
sp = ["/" + "/".join(sp)]
68-
bs.append(address_string_to_bytes(proto, sp.pop(0)))
44+
if not proto.codec:
45+
raise ValueError("failed to parse %s addr: unknown" % proto.name)
46+
codec = find_codec_by_name(proto.codec)
47+
bs.append(codec.to_bytes(proto, sp.pop(0)))
6948
return b''.join(bs)
7049

7150

@@ -79,7 +58,8 @@ def bytes_to_string(buf):
7958
maddr_component += proto.name
8059
size = size_for_addr(proto, buf)
8160
if size > 0:
82-
addr = address_bytes_to_string(proto, buf[:size])
61+
codec = find_codec_by_name(proto.codec)
62+
addr = codec.to_string(proto, buf[:size])
8363
if not (proto.path and addr[0] == '/'):
8464
maddr_component += '/'
8565
maddr_component += addr
@@ -88,122 +68,6 @@ def bytes_to_string(buf):
8868
return '/'.join(st)
8969

9070

91-
def address_string_to_bytes(proto, addr_string):
92-
if proto.code == P_IP4: # ipv4
93-
try:
94-
return IPAddress(addr_string, version=4).packed
95-
except Exception:
96-
raise ValueError("failed to parse ip4 addr: %s" % addr_string)
97-
elif proto.code == P_IP6: # ipv6
98-
try:
99-
return IPAddress(addr_string, version=6).packed
100-
except Exception:
101-
raise ValueError("failed to parse ip6 addr: %s" % addr_string)
102-
# tcp udp dccp sctp
103-
elif proto.code in [P_TCP, P_UDP, P_DCCP, P_SCTP]:
104-
try:
105-
return struct.pack('>H', int(addr_string, 10))
106-
except ValueError as ex:
107-
raise ValueError("failed to parse %s addr: %s"
108-
% (proto.name, str(ex)))
109-
except struct.error:
110-
raise ValueError("failed to parse %s addr: %s" %
111-
(proto.name, "greater than 65536"))
112-
elif proto.code == P_ONION:
113-
addr = addr_string.split(":")
114-
if len(addr) != 2:
115-
raise ValueError(
116-
"failed to parse %s addr: %s does not contain a port number."
117-
% (proto.name, addr_string))
118-
119-
# onion address without the ".onion" substring
120-
if len(addr[0]) != 16:
121-
raise ValueError(
122-
"failed to parse %s addr: %s not a Tor onion address."
123-
% (proto.name, addr_string))
124-
try:
125-
onion_host_bytes = base64.b32decode(addr[0].upper())
126-
except Exception as ex:
127-
raise ValueError(
128-
"failed to decode base32 %s addr: %s %s"
129-
% (proto.name, addr_string, str(ex)))
130-
131-
# onion port number
132-
try:
133-
port = int(addr[1])
134-
except Exception as ex:
135-
raise ValueError("failed to parse %s addr: %s"
136-
% (proto.name, str(ex)))
137-
if port >= 65536:
138-
raise ValueError("failed to parse %s addr: %s"
139-
% (proto.name, "port greater than 65536"))
140-
if port < 1:
141-
raise ValueError("failed to parse %s addr: %s"
142-
% (proto.name, "port less than 1"))
143-
144-
return b''.join([onion_host_bytes, struct.pack('>H', port)])
145-
elif proto.code == P_P2P: # ipfs
146-
# the address is a varint prefixed multihash string representation
147-
try:
148-
if six.PY2 and isinstance(addr_string, unicode):
149-
addr_string = addr_string.encode("ascii")
150-
mm = base58.b58decode(addr_string)
151-
except Exception as ex:
152-
raise ValueError("failed to parse p2p addr: %s %s"
153-
% (addr_string, str(ex)))
154-
size = varint.encode(len(mm))
155-
if len(mm) < 5:
156-
# TODO - port go-multihash so we can do this correctly
157-
raise ValueError("invalid P2P multihash: %s" % mm)
158-
return b''.join([size, mm])
159-
elif proto.code == P_UNIX:
160-
addr_string_bytes = fsencode(addr_string)
161-
size = varint.encode(len(addr_string_bytes))
162-
return b''.join([size, addr_string_bytes])
163-
elif proto.code in (P_DNS, P_DNS4, P_DNS6):
164-
addr_string_bytes = idna.encode(addr_string, uts46=True)
165-
size = varint.encode(len(addr_string_bytes))
166-
return b''.join([size, addr_string_bytes])
167-
else:
168-
raise ValueError("failed to parse %s addr: unknown" % proto.name)
169-
170-
171-
packed_net_bytes_to_int = None
172-
173-
174-
def address_bytes_to_string(proto, buf):
175-
global packed_net_bytes_to_int
176-
if packed_net_bytes_to_int is None:
177-
from .util import packed_net_bytes_to_int
178-
179-
if proto.code == P_IP4:
180-
return six.text_type(IPAddress(packed_net_bytes_to_int(buf), 4))
181-
elif proto.code == P_IP6:
182-
return six.text_type(IPAddress(packed_net_bytes_to_int(buf), 6))
183-
elif proto.code in [P_TCP, P_UDP, P_DCCP, P_SCTP]:
184-
if len(buf) != 2:
185-
raise ValueError("Not a uint16")
186-
return six.text_type(struct.unpack('>H', buf)[0])
187-
elif proto.code == P_ONION:
188-
addr_bytes, port_bytes = (buf[:-2], buf[-2:])
189-
addr = base64.b32encode(addr_bytes).decode('ascii').lower()
190-
port = six.text_type(struct.unpack('>H', port_bytes)[0])
191-
return u':'.join([addr, port])
192-
elif proto.code == P_P2P:
193-
size, num_bytes_read = read_varint_code(buf)
194-
buf = buf[num_bytes_read:]
195-
if len(buf) != size:
196-
raise ValueError("inconsistent lengths")
197-
return base58.b58encode(buf).decode('ascii')
198-
elif proto.code == P_UNIX:
199-
size, num_bytes_read = read_varint_code(buf)
200-
return fsdecode(buf[num_bytes_read:])
201-
elif proto.code in (P_DNS, P_DNS4, P_DNS6):
202-
size, num_bytes_read = read_varint_code(buf)
203-
return idna.decode(buf[num_bytes_read:])
204-
raise ValueError("unknown protocol")
205-
206-
20771
def size_for_addr(proto, buf):
20872
if proto.size >= 0:
20973
return proto.size // 8

multiaddr/codecs/__init__.py

Whitespace-only changes.

multiaddr/codecs/fspath.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from __future__ import absolute_import
2+
import os
3+
4+
import six
5+
import varint
6+
7+
from ..protocols import read_varint_code
8+
9+
10+
if hasattr(os, "fsencode") and hasattr(os, "fsdecode"):
11+
fsencode = os.fsencode
12+
fsdecode = os.fsdecode
13+
else: # PY2
14+
import sys
15+
16+
def fsencode(path):
17+
if not isinstance(path, six.binary_type):
18+
path = path.encode(sys.getfilesystemencoding())
19+
return path
20+
21+
def fsdecode(path):
22+
if not isinstance(path, six.text_type):
23+
path = path.decode(sys.getfilesystemencoding())
24+
return path
25+
26+
27+
def to_bytes(proto, string):
28+
bytes = fsencode(string)
29+
size = varint.encode(len(bytes))
30+
return b''.join([size, bytes])
31+
32+
33+
def to_string(proto, buf):
34+
size, num_bytes_read = read_varint_code(buf)
35+
return fsdecode(buf[num_bytes_read:])

multiaddr/codecs/idna.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from __future__ import absolute_import
2+
3+
import idna
4+
import varint
5+
6+
from ..protocols import read_varint_code
7+
8+
9+
def to_bytes(proto, string):
10+
bytes = idna.encode(string, uts46=True)
11+
size = varint.encode(len(bytes))
12+
return b''.join([size, bytes])
13+
14+
15+
def to_string(proto, buf):
16+
size, num_bytes_read = read_varint_code(buf)
17+
return idna.decode(buf[num_bytes_read:])

multiaddr/codecs/ip4.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from __future__ import absolute_import
2+
3+
import netaddr
4+
import six
5+
6+
from ..util import packed_net_bytes_to_int
7+
8+
9+
def to_bytes(proto, string):
10+
try:
11+
return netaddr.IPAddress(string, version=4).packed
12+
except Exception:
13+
raise ValueError("failed to parse ip4 addr: %s" % string)
14+
15+
16+
def to_string(proto, buf):
17+
ip_addr = netaddr.IPAddress(packed_net_bytes_to_int(buf), version=4)
18+
return six.text_type(ip_addr)

multiaddr/codecs/ip6.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from __future__ import absolute_import
2+
3+
import netaddr
4+
import six
5+
6+
from ..util import packed_net_bytes_to_int
7+
8+
9+
def to_bytes(proto, string):
10+
try:
11+
return netaddr.IPAddress(string, version=6).packed
12+
except Exception:
13+
raise ValueError("failed to parse ip4 addr: %s" % string)
14+
15+
16+
def to_string(proto, buf):
17+
ip_addr = netaddr.IPAddress(packed_net_bytes_to_int(buf), version=6)
18+
return six.text_type(ip_addr)

multiaddr/codecs/onion.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from __future__ import absolute_import
2+
import base64
3+
import struct
4+
5+
import six
6+
7+
8+
def to_bytes(proto, string):
9+
addr = string.split(":")
10+
if len(addr) != 2:
11+
raise ValueError(
12+
"failed to parse %s addr: %s does not contain a port number."
13+
% (proto.name, string))
14+
15+
# onion address without the ".onion" substring
16+
if len(addr[0]) != 16:
17+
raise ValueError(
18+
"failed to parse %s addr: %s not a Tor onion address."
19+
% (proto.name, string))
20+
try:
21+
onion_host_bytes = base64.b32decode(addr[0].upper())
22+
except Exception as ex:
23+
raise ValueError(
24+
"failed to decode base32 %s addr: %s %s"
25+
% (proto.name, string, str(ex)))
26+
27+
# onion port number
28+
try:
29+
port = int(addr[1])
30+
except Exception as ex:
31+
raise ValueError("failed to parse %s addr: %s"
32+
% (proto.name, str(ex)))
33+
if port >= 65536:
34+
raise ValueError("failed to parse %s addr: %s"
35+
% (proto.name, "port greater than 65536"))
36+
if port < 1:
37+
raise ValueError("failed to parse %s addr: %s"
38+
% (proto.name, "port less than 1"))
39+
40+
return b''.join((onion_host_bytes, struct.pack('>H', port)))
41+
42+
43+
def to_string(proto, buf):
44+
addr_bytes, port_bytes = (buf[:-2], buf[-2:])
45+
addr = base64.b32encode(addr_bytes).decode('ascii').lower()
46+
port = six.text_type(struct.unpack('>H', port_bytes)[0])
47+
return u':'.join([addr, port])

multiaddr/codecs/p2p.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from __future__ import absolute_import
2+
3+
import base58
4+
import six
5+
import varint
6+
7+
from ..protocols import read_varint_code
8+
9+
10+
def to_bytes(proto, string):
11+
# the address is a base58-encoded string
12+
try:
13+
if six.PY2 and isinstance(string, unicode):
14+
string = string.encode("ascii")
15+
mm = base58.b58decode(string)
16+
except Exception as ex:
17+
raise ValueError("failed to parse p2p addr: %s %s" % (string, str(ex)))
18+
size = varint.encode(len(mm))
19+
if len(mm) < 5:
20+
raise ValueError("invalid P2P multihash: %s" % mm)
21+
return b''.join([size, mm])
22+
23+
24+
def to_string(proto, buf):
25+
size, num_bytes_read = read_varint_code(buf)
26+
buf = buf[num_bytes_read:]
27+
if len(buf) != size:
28+
raise ValueError("inconsistent lengths")
29+
return base58.b58encode(buf).decode('ascii')

multiaddr/codecs/uint16be.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from __future__ import absolute_import
2+
import struct
3+
4+
import six
5+
6+
7+
def to_bytes(proto, string):
8+
try:
9+
return struct.pack('>H', int(string, 10))
10+
except ValueError as ex:
11+
raise ValueError("failed to parse %s addr: %s"
12+
% (proto.name, str(ex)))
13+
except struct.error:
14+
raise ValueError("failed to parse %s addr: %s" %
15+
(proto.name, "greater than 65536"))
16+
17+
18+
def to_string(proto, buf):
19+
if len(buf) != 2:
20+
raise ValueError("Not a uint16")
21+
return six.text_type(struct.unpack('>H', buf)[0])

0 commit comments

Comments
 (0)