-
Notifications
You must be signed in to change notification settings - Fork 32
Expand file tree
/
Copy pathtransforms.py
More file actions
180 lines (157 loc) · 6.69 KB
/
Copy pathtransforms.py
File metadata and controls
180 lines (157 loc) · 6.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import io
import logging
from collections.abc import Generator
from io import BytesIO
import varint
from . import exceptions
from .codecs import CodecBase, codec_by_name
from .protocols import Protocol, protocol_with_code, protocol_with_name
logger = logging.getLogger(__name__)
def string_to_bytes(string: str) -> bytes:
bs: list[bytes] = []
for proto, codec, value in string_iter(string):
logger.debug(
f"[DEBUG string_to_bytes] LOOP: proto={proto.name}, codec={codec}, value={value}"
)
logger.debug(
f"[DEBUG string_to_bytes] Processing: proto={proto.name}, "
f"codec.SIZE={getattr(codec, 'SIZE', None) if codec else None}, value={value}"
)
logger.debug(f"[DEBUG string_to_bytes] Protocol code: {proto.code}")
encoded_code = varint.encode(proto.code)
logger.debug(f"[DEBUG string_to_bytes] Encoded protocol code: {encoded_code}")
bs.append(encoded_code)
# Special case: protocols with codec=None or SIZE=0 are flag protocols
# (no value, no length prefix, no buffer)
if codec is None or getattr(codec, "SIZE", None) == 0:
logger.debug(
f"[DEBUG string_to_bytes] Protocol {proto.name} has no data, "
"skipping value encoding"
)
continue
if value is None:
raise ValueError("Value cannot be None")
try:
logger.debug(f"[DEBUG string_to_bytes] Raw CID value before encoding: {value}")
buf = codec.to_bytes(proto, value)
logger.debug(f"[DEBUG string_to_bytes] Generated buf: proto={proto.name}, buf={buf!r}")
except Exception as exc:
logger.debug(f"[DEBUG string_to_bytes] Error: {exc}")
raise exceptions.StringParseError(str(exc), string) from exc
logger.debug(
f"[DEBUG string_to_bytes] Appending: proto={proto.name}, "
f"codec.SIZE={getattr(codec, 'SIZE', None)}"
)
# Only add length prefix for variable-sized codecs (SIZE <= 0)
if codec.SIZE <= 0:
bs.append(varint.encode(len(buf)))
logger.debug(
f"[DEBUG string_to_bytes] Appending varint length: {varint.encode(len(buf))}"
)
# Only append the buffer if it's not empty
if buf:
bs.append(buf)
logger.debug(f"[DEBUG string_to_bytes] Final bs: {bs}")
return b"".join(bs)
def bytes_to_string(buf: bytes) -> str:
"""Convert a binary multiaddr to its string representation
Raises
------
~multiaddr.exceptions.BinaryParseError
The given bytes are not a valid multiaddr.
"""
if not buf:
return ""
bs = BytesIO(buf)
strings = []
code = None
proto = None
while bs.tell() < len(buf):
try:
code = varint.decode_stream(bs)
logger.debug(f"[DEBUG bytes_to_string] Decoded protocol code: {code}")
proto = protocol_with_code(code)
logger.debug(f"[DEBUG bytes_to_string] Protocol name: {proto.name}")
if proto.codec is not None:
codec = codec_by_name(proto.codec)
if codec.SIZE > 0:
value = codec.to_string(proto, bs.read(codec.SIZE // 8))
else:
# For variable-sized codecs,
# read the length prefix but don't pass it to the codec
size = varint.decode_stream(bs)
value = codec.to_string(proto, bs.read(size))
logger.debug(f"[DEBUG] bytes_to_string: proto={proto.name}, value='{value}'")
if codec.IS_PATH and value.startswith("/"):
# For path protocols, the codec already handles URL encoding
strings.append("/" + proto.name + value) # type: ignore[arg-type]
else:
strings.append("/" + proto.name + "/" + value) # type: ignore[arg-type]
else:
strings.append("/" + proto.name) # type: ignore[arg-type]
except Exception as exc:
# Use the code as the protocol identifier if proto is not available
# Ensure we always have either a string or an integer
protocol_id = proto.name if proto is not None else (code if code is not None else 0)
raise exceptions.BinaryParseError(str(exc), buf, protocol_id, exc) from exc
return "".join(strings)
def size_for_addr(codec: CodecBase, buf_io: io.BytesIO) -> int:
if codec.SIZE >= 0:
return codec.SIZE // 8
else:
return varint.decode_stream(buf_io)
def string_iter(
string: str,
) -> Generator[tuple[Protocol, CodecBase | None, str | None], None, None]:
"""Iterate over the parts of a string multiaddr.
Args:
string: The string multiaddr to iterate over
Yields:
A tuple of (protocol, codec, value) for each part of the multiaddr
"""
if not string:
return
parts = string.strip("/").split("/")
i = 0
while i < len(parts):
proto_name = parts[i]
try:
proto = protocol_with_name(proto_name)
except exceptions.ProtocolNotFoundError as exc:
raise exceptions.StringParseError(str(exc), string) from exc
codec = codec_by_name(proto.codec)
value = None
if proto.codec is not None:
if i + 1 >= len(parts):
raise exceptions.StringParseError(
f"missing value for protocol: {proto_name}", string
)
if getattr(codec, "IS_PATH", False):
value = "/".join(parts[i + 1 :])
i = len(parts) - 1
else:
value = parts[i + 1]
i += 1 # Skip the next part since we used it as value
logger.debug(f"[DEBUG string_iter] Using next part as value: {value}")
yield proto, codec, value
else:
logger.debug(f"[DEBUG string_iter] No value found for protocol {proto.name}")
yield proto, codec, None
i += 1
def bytes_iter(buf: bytes) -> Generator[tuple[int, Protocol, CodecBase, bytes], None, None]:
buf_io = io.BytesIO(buf)
while buf_io.tell() < len(buf):
offset = buf_io.tell()
code = varint.decode_stream(buf_io)
proto = None
try:
proto = protocol_with_code(code)
codec = codec_by_name(proto.codec)
except (ImportError, exceptions.ProtocolNotFoundError) as exc:
raise exceptions.BinaryParseError(
"Unknown Protocol",
buf,
proto.name if proto else code,
) from exc
size = size_for_addr(codec, buf_io)
yield offset, proto, codec, buf_io.read(size)