Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ async def main():
lambda: MySession(
aor="sips:alice@example.com",
username="alice",
password="secret",
password="secret", # noqa: S106
),
host="sip.example.com",
port=5061,
Expand Down
19 changes: 16 additions & 3 deletions docs/feature_roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,25 @@ with SDES key exchange carried inline in the SDP `a=crypto:` attribute

### NAT Traversal (STUN)

STUN Binding Request / Response with `XOR-MAPPED-ADDRESS` for RTP public
address discovery ([RFC 5389]). Uses Cloudflare's STUN server by default;
STUN Binding Request / Response with `XOR-MAPPED-ADDRESS` and `MAPPED-ADDRESS`
for RTP public address discovery ([RFC 5389]). Full IPv4 and IPv6 address
parsing for both attribute types. Uses Cloudflare's STUN server by default;
configurable or disabled per session.

### Session Description (SDP)

Offer / answer model for audio calls. Codec negotiation for Opus ([RFC 7587]),
G.722, PCMU, and PCMA ([RFC 3551]). Full SDP lexer with Pygments syntax
highlighting.
highlighting. IPv6 connection addresses advertised with `IP6` address type
per [RFC 4566 §5.7].

### IPv6

Full dual-stack support across SIP signalling, RTP media, and STUN discovery.
IPv6 addresses in SIP URIs and Via/Contact headers are wrapped in square
brackets per [RFC 2732]. The RTP UDP socket is bound to `::` when the SIP
signalling connection is over IPv6. STUN XOR-MAPPED-ADDRESS and MAPPED-ADDRESS
attributes with IPv6 address family are correctly decoded per [RFC 5389 §15.2].

### Audio Codecs

Expand Down Expand Up @@ -59,12 +69,15 @@ ______________________________________________________________________
[ollama]: https://ollama.com/
[pocket tts]: https://github.com/pocket-ai/pocket-tts
[pyav]: https://pyav.org/
[rfc 2732]: https://datatracker.ietf.org/doc/html/rfc2732
[rfc 3261]: https://datatracker.ietf.org/doc/html/rfc3261
[rfc 3550]: https://datatracker.ietf.org/doc/html/rfc3550
[rfc 3551]: https://datatracker.ietf.org/doc/html/rfc3551
[rfc 3711]: https://datatracker.ietf.org/doc/html/rfc3711
[rfc 4566 §5.7]: https://datatracker.ietf.org/doc/html/rfc4566#section-5.7
[rfc 4568]: https://datatracker.ietf.org/doc/html/rfc4568
[rfc 5389]: https://datatracker.ietf.org/doc/html/rfc5389
[rfc 5389 §15.2]: https://datatracker.ietf.org/doc/html/rfc5389#section-15.2
[rfc 7587]: https://datatracker.ietf.org/doc/html/rfc7587
[rfc 7983]: https://datatracker.ietf.org/doc/html/rfc7983
[rfc 8760]: https://datatracker.ietf.org/doc/html/rfc8760
12 changes: 6 additions & 6 deletions tests/sip/test_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def test_parse__response(self):
result = Message.parse(data)
assert isinstance(result, Response)
assert result.status_code == 200
assert result.reason == "OK"
assert result.phrase == "OK"
assert result.version == "SIP/2.0"
assert result.headers == {
"Via": "SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds"
Expand Down Expand Up @@ -81,7 +81,7 @@ def test_parse__roundtrip_response(self):
"""Round-trip a SIP response through parse and bytes."""
response = Response(
status_code=404,
reason="Not Found",
phrase="Not Found",
headers={"From": "sip:bob@biloxi.com"},
)
assert Message.parse(bytes(response)) == response
Expand Down Expand Up @@ -112,7 +112,7 @@ def test_parse__from_header__roundtrip_preserves_raw_value(self):
"""str(CallerID) equals the original header string, so serialization is unchanged."""
data = (
b"INVITE sip:bob@biloxi.com SIP/2.0\r\n"
b'From: "015114455910" <sip:015114455910@telefonica.de>;tag=abc\r\n'
b'From: "08001234567" <sip:08001234567@telefonica.de>;tag=abc\r\n'
b"\r\n"
)
result = Message.parse(data)
Expand Down Expand Up @@ -165,7 +165,7 @@ def test_response__bytes(self):
"""Serialize a SIP response to bytes."""
response = Response(
status_code=200,
reason="OK",
phrase="OK",
headers={"Via": "SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds"},
)
assert bytes(response) == (
Expand All @@ -177,15 +177,15 @@ def test_response__bytes(self):
def test_response__bytes__with_sdp_body(self):
"""Serialize a SIP response with an SDP body to bytes."""
sdp = SessionDescription()
response = Response(status_code=200, reason="OK", body=sdp)
response = Response(status_code=200, phrase="OK", body=sdp)
serialized = bytes(response)
assert b"Content-Length:" in serialized
assert b"v=0" in serialized

def test_response__bytes__with_sdp_body__auto_content_length(self):
"""Auto-calculate Content-Length when SDP body is present and header is not set."""
sdp = SessionDescription()
response = Response(status_code=200, reason="OK", body=sdp)
response = Response(status_code=200, phrase="OK", body=sdp)
serialized = bytes(response)
assert b"Content-Length:" in serialized
parsed = Message.parse(serialized)
Expand Down
Loading
Loading