Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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