Skip to content

Commit f16afc1

Browse files
authored
Merge branch 'main' into improvement/bitswap
2 parents f165abd + 29e361b commit f16afc1

16 files changed

Lines changed: 902 additions & 196 deletions

File tree

extra/multihash-spec

Lines changed: 0 additions & 1 deletion
This file was deleted.

extra/py-multihash

Lines changed: 0 additions & 1 deletion
This file was deleted.

extra/pymultihash

Lines changed: 0 additions & 1 deletion
This file was deleted.

interop/perf/Dockerfile

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,14 @@ FROM python:3.13-slim
22

33
WORKDIR /app
44

5-
# Install system dependencies (contributing.rst + transport Dockerfile)
6-
RUN apt-get update && apt-get install -y \
7-
redis-tools \
5+
# Install only the toolchain needed to build/install py-libp2p.
6+
RUN apt-get update && apt-get install -y --no-install-recommends \
87
build-essential \
98
cmake \
109
pkg-config \
1110
libgmp-dev \
12-
git \
13-
curl \
14-
&& rm -rf /var/lib/apt/lists/*
11+
&& apt-get clean \
12+
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* /var/cache/apt/*.bin
1513

1614
# Install uv (docs/contributing.rst Option 2: same as CI)
1715
RUN pip install --no-cache-dir uv

libp2p/security/noise/io.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
MAX_NOISE_MESSAGE_LEN = 2 ** (8 * SIZE_NOISE_MESSAGE_LEN) - 1
2323
SIZE_NOISE_MESSAGE_BODY_LEN = 2
2424
MAX_NOISE_MESSAGE_BODY_LEN = MAX_NOISE_MESSAGE_LEN - SIZE_NOISE_MESSAGE_BODY_LEN
25+
# Max plaintext per Noise message: 65535 - 16 bytes Poly1305 MAC overhead.
26+
# Matches go-libp2p's MaxPlaintextLength in p2p/security/noise/rw.go.
27+
MAX_PLAINTEXT_LENGTH = MAX_NOISE_MESSAGE_LEN - 16
2528
BYTE_ORDER = "big"
2629

2730
# | Noise packet |
@@ -53,14 +56,26 @@ def __init__(self, conn: IRawConnection, noise_state: NoiseState) -> None:
5356
self.noise_state = noise_state
5457

5558
async def write_msg(self, msg: bytes, prefix_encoded: bool = False) -> None:
56-
logger.debug(f"Noise write_msg: encrypting {len(msg)} bytes")
57-
data_encrypted = self.encrypt(msg)
58-
if prefix_encoded:
59-
# Manually add the prefix if needed
60-
data_encrypted = self.prefix + data_encrypted
61-
logger.debug(f"Noise write_msg: writing {len(data_encrypted)} encrypted bytes")
62-
await self.read_writer.write_msg(data_encrypted)
63-
logger.debug("Noise write_msg: write completed successfully")
59+
# Chunk large messages to stay within the Noise 65535-byte transport
60+
# message limit, matching go-libp2p's noise/rw.go Write() approach.
61+
if len(msg) <= MAX_PLAINTEXT_LENGTH:
62+
# Fast path: single message (covers handshake and small writes)
63+
data_encrypted = self.encrypt(msg)
64+
if prefix_encoded:
65+
data_encrypted = self.prefix + data_encrypted
66+
await self.read_writer.write_msg(data_encrypted)
67+
else:
68+
# Slow path: chunk into multiple Noise messages
69+
total = len(msg)
70+
written = 0
71+
while written < total:
72+
end = min(written + MAX_PLAINTEXT_LENGTH, total)
73+
chunk = msg[written:end]
74+
data_encrypted = self.encrypt(chunk)
75+
if prefix_encoded and written == 0:
76+
data_encrypted = self.prefix + data_encrypted
77+
await self.read_writer.write_msg(data_encrypted)
78+
written = end
6479

6580
async def read_msg(self, prefix_encoded: bool = False) -> bytes:
6681
logger.debug("Noise read_msg: reading encrypted message")

libp2p/security/secure_session.py

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -94,24 +94,47 @@ async def read(self, n: int | None = None) -> bytes:
9494
return b""
9595

9696
data_from_buffer = self._drain(n)
97-
if len(data_from_buffer) > 0:
97+
if n is None and len(data_from_buffer) > 0:
9898
return data_from_buffer
9999

100-
msg = await self.conn.read_msg()
100+
if n is None:
101+
msg = await self.conn.read_msg()
101102

102-
# If underlying connection returned empty bytes, treat as closed
103-
# and raise to signal that reads after close are invalid.
104-
if msg == b"":
105-
raise Exception("Connection closed")
103+
# If underlying connection returned empty bytes, treat as closed
104+
# and raise to signal that reads after close are invalid.
105+
if msg == b"":
106+
raise Exception("Connection closed")
106107

107-
if n is None:
108108
return msg
109109

110-
if n < len(msg):
111-
self._fill(msg)
112-
return self._drain(n)
113-
else:
114-
return msg
110+
if len(data_from_buffer) == n:
111+
return data_from_buffer
112+
113+
result = bytearray(data_from_buffer)
114+
while len(result) < n:
115+
needed = n - len(result)
116+
drained = self._drain(needed)
117+
if drained:
118+
result.extend(drained)
119+
continue
120+
121+
msg = await self.conn.read_msg()
122+
123+
# If the connection closes after a partial read, return the bytes
124+
# we already assembled. This preserves the stream-read behavior
125+
# expected by higher layers.
126+
if msg == b"":
127+
if result:
128+
return bytes(result)
129+
raise Exception("Connection closed")
130+
131+
if len(msg) <= needed:
132+
result.extend(msg)
133+
else:
134+
result.extend(msg[:needed])
135+
self._fill(msg[needed:])
136+
137+
return bytes(result)
115138

116139
async def write(self, data: bytes) -> None:
117140
await self.conn.write_msg(data)

0 commit comments

Comments
 (0)