Summary
PR #1309 adds libp2p/transport/webrtc/_varint.py with encode_uvarint() and decode_uvarint() for WebRTC data-channel and signaling framing. The same unsigned LEB128 logic already exists in the canonical project module libp2p/utils/varint.py, which is used by mplex, msgio, peer envelopes, identify, interop tests, and others.
This is duplicated code on the same wire format; WebRTC should reuse the shared utility rather than maintain a fourth in-tree varint implementation (alongside libp2p/utils/varint.py, the external varint PyPI package used in kad-dht/bitswap/filecoin, and _varint_size() in pubsub/rpc_queue.py).
Current duplication
WebRTC (_varint.py) |
Existing (libp2p/utils/varint.py) |
encode_uvarint(value) |
encode_uvarint(value) — same algorithm |
decode_uvarint(data, offset=0) -> (value, bytes_consumed) |
decode_varint_with_size(data) -> (value, bytes_consumed) (no offset; use data[offset:]) |
| — |
decode_uvarint_from_stream(reader) — async stream read |
Consumers today:
libp2p/transport/webrtc/stream.py — encode_uvarint / decode_uvarint for SCTP message framing
libp2p/transport/webrtc/signaling.py — encode_uvarint for writes; custom _read_uvarint() for async reads (duplicates decode_uvarint_from_stream)
The encode loops are functionally identical (while value > 0x7F vs while value >= 0x80 is equivalent). Decode-with-length maps 1:1 to decode_varint_with_size.
Suggested approach
-
Delete libp2p/transport/webrtc/_varint.py.
-
Update imports in stream.py and signaling.py:
from libp2p.utils.varint import (
encode_uvarint,
decode_varint_with_size,
decode_uvarint_from_stream,
)
-
Replace decode_uvarint(raw) with decode_varint_with_size(raw) in stream.py (same (value, consumed) tuple).
-
Replace signaling._read_uvarint() with decode_uvarint_from_stream(stream), mapping ParseError to WebRTCSignalingError at the call site if needed.
-
Update tests in tests/core/transport/webrtc/test_signaling.py that import from _varint to use libp2p.utils.varint (or test via public signaling/stream APIs only).
No wire-format or protocol change — this is a pure refactor.
Minor API differences to handle
- Offset parameter: WebRTC
decode_uvarint(data, offset) can be replaced by slicing: decode_varint_with_size(data[offset:]), then adjust consumed if callers need absolute offset (currently stream.py uses offset 0 only).
- Error types: WebRTC raises
ValueError on truncated varints; utils uses ParseError in some stream paths and ValueError in others. Preserve existing WebRTC error handling at boundaries if tests depend on it.
- Empty/truncated input: Confirm edge-case behavior matches (utils
decode_varint_with_size on empty bytes returns (0, 0); WebRTC raises ValueError — verify call sites never hit empty prefix, or add explicit checks).
Scope / timing
Acceptance criteria
Refs #546, #1309.
Summary
PR #1309 adds
libp2p/transport/webrtc/_varint.pywithencode_uvarint()anddecode_uvarint()for WebRTC data-channel and signaling framing. The same unsigned LEB128 logic already exists in the canonical project modulelibp2p/utils/varint.py, which is used by mplex, msgio, peer envelopes, identify, interop tests, and others.This is duplicated code on the same wire format; WebRTC should reuse the shared utility rather than maintain a fourth in-tree varint implementation (alongside
libp2p/utils/varint.py, the externalvarintPyPI package used in kad-dht/bitswap/filecoin, and_varint_size()inpubsub/rpc_queue.py).Current duplication
_varint.py)libp2p/utils/varint.py)encode_uvarint(value)encode_uvarint(value)— same algorithmdecode_uvarint(data, offset=0) -> (value, bytes_consumed)decode_varint_with_size(data) -> (value, bytes_consumed)(no offset; usedata[offset:])decode_uvarint_from_stream(reader)— async stream readConsumers today:
libp2p/transport/webrtc/stream.py—encode_uvarint/decode_uvarintfor SCTP message framinglibp2p/transport/webrtc/signaling.py—encode_uvarintfor writes; custom_read_uvarint()for async reads (duplicatesdecode_uvarint_from_stream)The encode loops are functionally identical (
while value > 0x7Fvswhile value >= 0x80is equivalent). Decode-with-length maps 1:1 todecode_varint_with_size.Suggested approach
Delete
libp2p/transport/webrtc/_varint.py.Update imports in
stream.pyandsignaling.py:Replace
decode_uvarint(raw)withdecode_varint_with_size(raw)instream.py(same(value, consumed)tuple).Replace
signaling._read_uvarint()withdecode_uvarint_from_stream(stream), mappingParseErrortoWebRTCSignalingErrorat the call site if needed.Update tests in
tests/core/transport/webrtc/test_signaling.pythat import from_varintto uselibp2p.utils.varint(or test via public signaling/stream APIs only).No wire-format or protocol change — this is a pure refactor.
Minor API differences to handle
decode_uvarint(data, offset)can be replaced by slicing:decode_varint_with_size(data[offset:]), then adjustconsumedif callers need absolute offset (currentlystream.pyuses offset 0 only).ValueErroron truncated varints; utils usesParseErrorin some stream paths andValueErrorin others. Preserve existing WebRTC error handling at boundaries if tests depend on it.decode_varint_with_sizeon empty bytes returns(0, 0); WebRTC raisesValueError— verify call sites never hit empty prefix, or add explicit checks).Scope / timing
Acceptance criteria
_varint.pyremovedlibp2p.utils.varinttests/core/transport/webrtc/tests passRefs #546, #1309.