siphon-sip 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of siphon-sip might be problematic. Click here for more details.
- siphon_sip-0.3.0/.gitignore +27 -0
- siphon_sip-0.3.0/PKG-INFO +360 -0
- siphon_sip-0.3.0/README.md +334 -0
- siphon_sip-0.3.0/pyproject.toml +53 -0
- siphon_sip-0.3.0/siphon_sdk/__init__.py +36 -0
- siphon_sip-0.3.0/siphon_sdk/call.py +1876 -0
- siphon_sip-0.3.0/siphon_sdk/http.py +302 -0
- siphon_sip-0.3.0/siphon_sdk/http_testing.py +199 -0
- siphon_sip-0.3.0/siphon_sdk/lcr.py +386 -0
- siphon_sip-0.3.0/siphon_sdk/mock_module.py +9626 -0
- siphon_sip-0.3.0/siphon_sdk/numbers.py +390 -0
- siphon_sip-0.3.0/siphon_sdk/py.typed +0 -0
- siphon_sip-0.3.0/siphon_sdk/reply.py +467 -0
- siphon_sip-0.3.0/siphon_sdk/request.py +1366 -0
- siphon_sip-0.3.0/siphon_sdk/sdp.py +777 -0
- siphon_sip-0.3.0/siphon_sdk/smpp.py +643 -0
- siphon_sip-0.3.0/siphon_sdk/smpp_testing.py +255 -0
- siphon_sip-0.3.0/siphon_sdk/srs.py +201 -0
- siphon_sip-0.3.0/siphon_sdk/testing.py +880 -0
- siphon_sip-0.3.0/siphon_sdk/types.py +370 -0
- siphon_sip-0.3.0/tests/test_auth_integrity_protected.py +311 -0
- siphon_sip-0.3.0/tests/test_auth_nonce.py +92 -0
- siphon_sip-0.3.0/tests/test_auth_supplied_credential.py +100 -0
- siphon_sip-0.3.0/tests/test_auth_user_normalisation.py +68 -0
- siphon_sip-0.3.0/tests/test_b2bua_bridge.py +180 -0
- siphon_sip-0.3.0/tests/test_b2bua_default.py +71 -0
- siphon_sip-0.3.0/tests/test_b2bua_invite_auth.py +167 -0
- siphon_sip-0.3.0/tests/test_b2bua_originate.py +266 -0
- siphon_sip-0.3.0/tests/test_b2bua_refer.py +350 -0
- siphon_sip-0.3.0/tests/test_b2bua_replace_peer.py +157 -0
- siphon_sip-0.3.0/tests/test_b2bua_terminate.py +76 -0
- siphon_sip-0.3.0/tests/test_cache_list_len.py +75 -0
- siphon_sip-0.3.0/tests/test_call_answer_progress.py +83 -0
- siphon_sip-0.3.0/tests/test_call_body_and_local_tag.py +115 -0
- siphon_sip-0.3.0/tests/test_call_dial.py +161 -0
- siphon_sip-0.3.0/tests/test_call_flow.py +77 -0
- siphon_sip-0.3.0/tests/test_call_handover.py +63 -0
- siphon_sip-0.3.0/tests/test_call_session_timer.py +32 -0
- siphon_sip-0.3.0/tests/test_caller_id_clir.py +155 -0
- siphon_sip-0.3.0/tests/test_cdr_write.py +116 -0
- siphon_sip-0.3.0/tests/test_diameter_server_mock.py +211 -0
- siphon_sip-0.3.0/tests/test_from_gateway.py +149 -0
- siphon_sip-0.3.0/tests/test_handler_selection.py +134 -0
- siphon_sip-0.3.0/tests/test_http_mock.py +265 -0
- siphon_sip-0.3.0/tests/test_ims_scripts.py +588 -0
- siphon_sip-0.3.0/tests/test_ims_ue_b2bua.py +88 -0
- siphon_sip-0.3.0/tests/test_inline_script.py +192 -0
- siphon_sip-0.3.0/tests/test_ipsec_pending_sa.py +127 -0
- siphon_sip-0.3.0/tests/test_lcr.py +228 -0
- siphon_sip-0.3.0/tests/test_lcr_destination.py +93 -0
- siphon_sip-0.3.0/tests/test_li_mock.py +215 -0
- siphon_sip-0.3.0/tests/test_multi_value_headers.py +82 -0
- siphon_sip-0.3.0/tests/test_numbers.py +159 -0
- siphon_sip-0.3.0/tests/test_on_cancel.py +93 -0
- siphon_sip-0.3.0/tests/test_play_media_wait.py +60 -0
- siphon_sip-0.3.0/tests/test_presence_mock.py +138 -0
- siphon_sip-0.3.0/tests/test_proxy_default.py +109 -0
- siphon_sip-0.3.0/tests/test_qos_mock.py +172 -0
- siphon_sip-0.3.0/tests/test_registrar_alias_chain.py +85 -0
- siphon_sip-0.3.0/tests/test_registrar_lookup_contact.py +81 -0
- siphon_sip-0.3.0/tests/test_registrar_received.py +193 -0
- siphon_sip-0.3.0/tests/test_registrar_refusals.py +244 -0
- siphon_sip-0.3.0/tests/test_registration_aka.py +93 -0
- siphon_sip-0.3.0/tests/test_remove_headers_matching.py +30 -0
- siphon_sip-0.3.0/tests/test_reply_reject.py +72 -0
- siphon_sip-0.3.0/tests/test_request_consumed_routes.py +103 -0
- siphon_sip-0.3.0/tests/test_request_reply_headers.py +118 -0
- siphon_sip-0.3.0/tests/test_rf_charging.py +227 -0
- siphon_sip-0.3.0/tests/test_ro_charging.py +138 -0
- siphon_sip-0.3.0/tests/test_rtpengine_beep_and_tone.py +368 -0
- siphon_sip-0.3.0/tests/test_rtpengine_media.py +861 -0
- siphon_sip-0.3.0/tests/test_rx_aar_specific_actions.py +62 -0
- siphon_sip-0.3.0/tests/test_save_proxy.py +109 -0
- siphon_sip-0.3.0/tests/test_sbi_bsf.py +117 -0
- siphon_sip-0.3.0/tests/test_sbi_callbacks.py +27 -0
- siphon_sip-0.3.0/tests/test_sbi_event_subscription.py +75 -0
- siphon_sip-0.3.0/tests/test_sdp.py +497 -0
- siphon_sip-0.3.0/tests/test_send_socket.py +52 -0
- siphon_sip-0.3.0/tests/test_smpp_mock.py +390 -0
- siphon_sip-0.3.0/tests/test_stir_mock.py +152 -0
- siphon_sip-0.3.0/tests/test_timer_api.py +114 -0
- siphon_sip-0.3.0/tests/test_uri_setters.py +97 -0
- siphon_sip-0.3.0/tests/test_whatsapp_calling.py +136 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/target
|
|
2
|
+
fuzz/target/
|
|
3
|
+
fuzz/artifacts/
|
|
4
|
+
fuzz/corpus/
|
|
5
|
+
sbom.spdx.json
|
|
6
|
+
sbom.cdx.json
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.pyc
|
|
9
|
+
*.log
|
|
10
|
+
.venv/
|
|
11
|
+
sdk/.venv/
|
|
12
|
+
sdk/dist/
|
|
13
|
+
sdk/*.egg-info/
|
|
14
|
+
.claude/
|
|
15
|
+
CLAUDE.md
|
|
16
|
+
|
|
17
|
+
# Private deployment configs
|
|
18
|
+
|
|
19
|
+
# Private test fixtures
|
|
20
|
+
sipp/ipsec/
|
|
21
|
+
|
|
22
|
+
# mkdocs build output
|
|
23
|
+
/site/
|
|
24
|
+
|
|
25
|
+
# Packet captures from the lawful-intercept capture profile
|
|
26
|
+
# (scripts/validate_li_capture.sh) — build artefacts, never committed.
|
|
27
|
+
sipp/li/captures/
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: siphon-sip
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Mock library and type stubs for SIPhon SIP proxy/B2BUA scripts — enables unit testing and LLM-assisted script authoring
|
|
5
|
+
Project-URL: Homepage, https://github.com/siphon-project/siphon-sip
|
|
6
|
+
Project-URL: Documentation, https://siphon-sip.org/reference/
|
|
7
|
+
Project-URL: Repository, https://github.com/siphon-project/siphon-sip
|
|
8
|
+
Author: SIPhon Authors
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: b2bua,ims,mock,proxy,sip,testing,voip
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Telecommunications Industry
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Communications :: Telephony
|
|
22
|
+
Classifier: Topic :: Software Development :: Testing :: Mocking
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
<p align="center">
|
|
28
|
+
<img src="https://raw.githubusercontent.com/siphon-project/siphon-sip/main/assets/logo.svg" alt="SIPhon" width="100">
|
|
29
|
+
</p>
|
|
30
|
+
|
|
31
|
+
# siphon-sip
|
|
32
|
+
|
|
33
|
+
Mock library and type stubs for [SIPhon](https://github.com/siphon-project/siphon-sip) scripts — enables unit testing without the Rust binary and provides rich context for LLM-assisted script authoring.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install siphon-sip
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
> The PyPI distribution is **`siphon-sip`** (matching the `siphon-sip` crate). The import package is still **`siphon_sdk`** — `from siphon_sdk import …`.
|
|
42
|
+
|
|
43
|
+
## What is SIPhon?
|
|
44
|
+
|
|
45
|
+
SIPhon is a high-performance SIP proxy, B2BUA, and IMS platform written in Rust with Python scripting. Scripts use decorators to handle SIP events:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from siphon import proxy, registrar, auth, log
|
|
49
|
+
|
|
50
|
+
@proxy.on_request
|
|
51
|
+
def route(request):
|
|
52
|
+
if request.method == "REGISTER":
|
|
53
|
+
if not auth.require_digest(request, realm="example.com"):
|
|
54
|
+
return
|
|
55
|
+
registrar.save(request)
|
|
56
|
+
request.reply(200, "OK")
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
contacts = registrar.lookup(request.ruri)
|
|
60
|
+
if not contacts:
|
|
61
|
+
request.reply(404, "Not Found")
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
request.record_route()
|
|
65
|
+
request.fork([c.uri for c in contacts])
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
This SDK lets you **test** these scripts with pytest — no Rust binary needed.
|
|
69
|
+
|
|
70
|
+
## Quick start
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from siphon_sdk.testing import SipTestHarness
|
|
74
|
+
from siphon_sdk.types import Contact
|
|
75
|
+
|
|
76
|
+
harness = SipTestHarness(local_domains=["example.com"])
|
|
77
|
+
harness.load_script("scripts/proxy_default.py")
|
|
78
|
+
|
|
79
|
+
# Pre-populate the registrar
|
|
80
|
+
harness.registrar.add_contact(
|
|
81
|
+
"sip:alice@example.com",
|
|
82
|
+
Contact(uri="sip:alice@192.168.1.5:5060"),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Test REGISTER challenge
|
|
86
|
+
result = harness.send_request("REGISTER", "sip:alice@example.com",
|
|
87
|
+
from_uri="sip:alice@example.com")
|
|
88
|
+
assert result.status_code == 401 # digest challenge
|
|
89
|
+
|
|
90
|
+
# Test INVITE routing
|
|
91
|
+
result = harness.send_request("INVITE", "sip:alice@example.com")
|
|
92
|
+
assert result.action == "fork"
|
|
93
|
+
assert "sip:alice@192.168.1.5:5060" in result.targets
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Testing B2BUA scripts
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
harness = SipTestHarness()
|
|
100
|
+
harness.load_script("scripts/b2bua_default.py")
|
|
101
|
+
|
|
102
|
+
harness.registrar.add_contact(
|
|
103
|
+
"sip:bob@example.com",
|
|
104
|
+
Contact(uri="sip:bob@10.0.0.2:5060"),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
result = harness.send_invite(ruri="sip:bob@example.com")
|
|
108
|
+
assert result.action == "fork"
|
|
109
|
+
assert result.targets == ["sip:bob@10.0.0.2:5060"]
|
|
110
|
+
|
|
111
|
+
# Test BYE handling
|
|
112
|
+
result = harness.send_bye(initiator_side="a")
|
|
113
|
+
assert result.was_terminated
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Testing extension scripts (SMPP, HTTP)
|
|
117
|
+
|
|
118
|
+
The opt-in [siphon extensions](https://siphon-sip.org/extensions/) inject extra
|
|
119
|
+
namespaces (`smpp`, `http`) at runtime. The SDK mocks them too, with dedicated
|
|
120
|
+
harnesses, so extension scripts are testable from the same `pip install
|
|
121
|
+
siphon-sip` — no running SMSC or HTTP listener required:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from siphon_sdk.smpp_testing import SmppTestHarness
|
|
125
|
+
|
|
126
|
+
harness = SmppTestHarness()
|
|
127
|
+
harness.load_script("scripts/gateway.py")
|
|
128
|
+
assert harness.bind("esme1", password="s3cret")
|
|
129
|
+
reply = harness.submit_sm(source_addr="15550100", destination_addr="15550101",
|
|
130
|
+
short_message=b"hi")
|
|
131
|
+
assert reply.ok
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from siphon_sdk.http_testing import HttpTestHarness
|
|
136
|
+
from siphon_sdk.http import MockResponse
|
|
137
|
+
|
|
138
|
+
harness = HttpTestHarness()
|
|
139
|
+
harness.add_response(MockResponse(status=200, body=b'{"ok":true}'))
|
|
140
|
+
harness.load_script("scripts/api.py")
|
|
141
|
+
|
|
142
|
+
resp = harness.request("GET", "/users/42")
|
|
143
|
+
assert resp.status == 200
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Inline scripts
|
|
147
|
+
|
|
148
|
+
Test scripts without separate files:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
harness = SipTestHarness()
|
|
152
|
+
harness.load_source("""
|
|
153
|
+
from siphon import proxy
|
|
154
|
+
|
|
155
|
+
@proxy.on_request
|
|
156
|
+
def route(request):
|
|
157
|
+
if request.source_ip_in(["10.0.0.0/8"]):
|
|
158
|
+
request.relay()
|
|
159
|
+
else:
|
|
160
|
+
request.reply(403, "Forbidden")
|
|
161
|
+
""")
|
|
162
|
+
|
|
163
|
+
result = harness.send_request("INVITE", "sip:bob@host", source_ip="10.1.2.3")
|
|
164
|
+
assert result.was_relayed
|
|
165
|
+
|
|
166
|
+
result = harness.send_request("INVITE", "sip:bob@host", source_ip="8.8.8.8")
|
|
167
|
+
assert result.status_code == 403
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Async handlers + RTPEngine
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
harness = SipTestHarness()
|
|
174
|
+
harness.load_source("""
|
|
175
|
+
from siphon import proxy, rtpengine
|
|
176
|
+
|
|
177
|
+
@proxy.on_request
|
|
178
|
+
async def route(request):
|
|
179
|
+
if request.method == "INVITE" and request.body:
|
|
180
|
+
await rtpengine.offer(request, profile="srtp_to_rtp")
|
|
181
|
+
request.relay()
|
|
182
|
+
""")
|
|
183
|
+
|
|
184
|
+
result = harness.send_request("INVITE", "sip:bob@host",
|
|
185
|
+
body=b"v=0\\r\\n...",
|
|
186
|
+
content_type="application/sdp")
|
|
187
|
+
assert result.was_relayed
|
|
188
|
+
assert harness.rtpengine.operations == [("offer", "srtp_to_rtp")]
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Controlling mock behavior
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
# Auth: allow or deny all
|
|
195
|
+
harness.auth._allow = True # all auth checks pass
|
|
196
|
+
|
|
197
|
+
# Rate limiting
|
|
198
|
+
harness.proxy._utils._rate_limit_allow = False # simulate overload
|
|
199
|
+
|
|
200
|
+
# Cache: pre-populate
|
|
201
|
+
harness.cache.set_data("cnam", {"key": "value"})
|
|
202
|
+
|
|
203
|
+
# Registrar: add contacts directly
|
|
204
|
+
harness.registrar.add_contact("sip:alice@host", Contact(uri="sip:alice@1.2.3.4"))
|
|
205
|
+
|
|
206
|
+
# Log: inspect captured messages
|
|
207
|
+
assert any("error" in msg for level, msg in harness.log.messages)
|
|
208
|
+
|
|
209
|
+
# Reset between tests
|
|
210
|
+
harness.reset()
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Result assertions
|
|
214
|
+
|
|
215
|
+
`RequestResult` provides convenient properties:
|
|
216
|
+
|
|
217
|
+
| Property | Description |
|
|
218
|
+
|----------|-------------|
|
|
219
|
+
| `.action` | Primary action: `"reply"`, `"relay"`, `"fork"`, `"silent_drop"` |
|
|
220
|
+
| `.status_code` | SIP status code (200, 401, 404, etc.) |
|
|
221
|
+
| `.reason` | Reason phrase |
|
|
222
|
+
| `.targets` | Fork targets list |
|
|
223
|
+
| `.strategy` | Fork strategy (`"parallel"` / `"sequential"`) |
|
|
224
|
+
| `.was_relayed` | `True` if `relay()` was called |
|
|
225
|
+
| `.was_forked` | `True` if `fork()` was called |
|
|
226
|
+
| `.was_dropped` | `True` if handler returned without action (silent drop) |
|
|
227
|
+
| `.record_routed` | `True` if `record_route()` was called |
|
|
228
|
+
| `.request` | The mock `Request` object for header inspection |
|
|
229
|
+
|
|
230
|
+
## API reference
|
|
231
|
+
|
|
232
|
+
### Namespaces
|
|
233
|
+
|
|
234
|
+
| Import | Description |
|
|
235
|
+
|--------|-------------|
|
|
236
|
+
| `proxy` | Stateful/stateless proxy decorators and utilities |
|
|
237
|
+
| `registrar` | Address-of-record contact store |
|
|
238
|
+
| `auth` | SIP digest authentication |
|
|
239
|
+
| `b2bua` | Back-to-back user agent call control |
|
|
240
|
+
| `log` | Structured logging |
|
|
241
|
+
| `cache` | Named cache (local LRU + Redis) |
|
|
242
|
+
| `rtpengine` | RTPEngine media proxy operations |
|
|
243
|
+
| `gateway` | Destination groups, load balancing, health probing |
|
|
244
|
+
| `cdr` | Call detail records |
|
|
245
|
+
| `diameter` | Diameter protocol (Cx, Ro, Rx, Rf, Sh) |
|
|
246
|
+
| `presence` | SUBSCRIBE/NOTIFY, PIDF presence |
|
|
247
|
+
| `li` | Lawful intercept (ETSI X1/X2/X3, SIPREC) |
|
|
248
|
+
| `registration` | Outbound REGISTER client (trunk registration) |
|
|
249
|
+
|
|
250
|
+
### Request properties
|
|
251
|
+
|
|
252
|
+
| Property | Type | Description |
|
|
253
|
+
|----------|------|-------------|
|
|
254
|
+
| `method` | `str` | SIP method (`"INVITE"`, `"REGISTER"`, etc.) |
|
|
255
|
+
| `ruri` | `SipUri` | Request-URI |
|
|
256
|
+
| `from_uri` | `SipUri \| None` | From header URI |
|
|
257
|
+
| `to_uri` | `SipUri \| None` | To header URI |
|
|
258
|
+
| `from_tag` | `str \| None` | From-tag |
|
|
259
|
+
| `to_tag` | `str \| None` | To-tag (`None` for initial requests) |
|
|
260
|
+
| `call_id` | `str \| None` | Call-ID |
|
|
261
|
+
| `cseq` | `(int, str) \| None` | CSeq tuple |
|
|
262
|
+
| `in_dialog` | `bool` | Both tags present |
|
|
263
|
+
| `max_forwards` | `int` | Max-Forwards value |
|
|
264
|
+
| `body` | `bytes \| None` | Message body |
|
|
265
|
+
| `content_type` | `str \| None` | Content-Type |
|
|
266
|
+
| `transport` | `str` | `"udp"`, `"tcp"`, `"tls"`, `"ws"`, `"wss"` |
|
|
267
|
+
| `source_ip` | `str` | Sender IP |
|
|
268
|
+
| `auth_user` | `str \| None` | Authenticated username |
|
|
269
|
+
| `event` | `str \| None` | Event header |
|
|
270
|
+
|
|
271
|
+
### Request methods
|
|
272
|
+
|
|
273
|
+
| Method | Description |
|
|
274
|
+
|--------|-------------|
|
|
275
|
+
| `reply(code, reason)` | Send SIP response |
|
|
276
|
+
| `relay(next_hop=None)` | Forward to destination |
|
|
277
|
+
| `fork(targets, strategy="parallel")` | Fork to multiple targets |
|
|
278
|
+
| `record_route()` | Insert Record-Route |
|
|
279
|
+
| `loose_route() -> bool` | RFC 3261 loose routing |
|
|
280
|
+
| `get_header(name) -> str \| None` | Get header value |
|
|
281
|
+
| `set_header(name, value)` | Set header |
|
|
282
|
+
| `remove_header(name)` | Remove header |
|
|
283
|
+
| `has_header(name) -> bool` | Check header exists |
|
|
284
|
+
| `has_body(content_type) -> bool` | Check body type |
|
|
285
|
+
| `set_ruri_user(value)` | Set R-URI user part |
|
|
286
|
+
| `set_ruri_host(value)` | Set R-URI host |
|
|
287
|
+
| `source_ip_in(cidrs) -> bool` | CIDR membership check |
|
|
288
|
+
| `generate_icid() -> str` | Generate charging ID |
|
|
289
|
+
| `add_path(uri)` | Prepend Path header |
|
|
290
|
+
| `prepend_route(uri)` | Prepend Route header |
|
|
291
|
+
| `fix_nated_register()` | NAT fixup for REGISTER |
|
|
292
|
+
| `fix_nated_contact()` | NAT fixup for Contact |
|
|
293
|
+
|
|
294
|
+
### Registrar
|
|
295
|
+
|
|
296
|
+
| Method | Description |
|
|
297
|
+
|--------|-------------|
|
|
298
|
+
| `save(request, force=False)` | Save REGISTER bindings |
|
|
299
|
+
| `lookup(uri) -> list[Contact]` | Look up contacts (sorted by q-value) |
|
|
300
|
+
| `is_registered(uri) -> bool` | Check if URI has contacts |
|
|
301
|
+
| `service_route(uri) -> list[str]` | Get stored service routes (RFC 3608) |
|
|
302
|
+
| `set_service_routes(aor, routes)` | Store service routes for an AoR |
|
|
303
|
+
| `save_pending(request)` | IMS: save binding in pending state |
|
|
304
|
+
| `confirm_pending(uri)` | IMS: promote pending to active after SAR |
|
|
305
|
+
| `asserted_identity(uri) -> str \| None` | IMS: stored P-Asserted-Identity |
|
|
306
|
+
| `reginfo_xml(aor, state, version) -> str` | Generate reginfo XML (RFC 3680) |
|
|
307
|
+
| `on_change` | Decorator: fires on registration state changes |
|
|
308
|
+
|
|
309
|
+
`lookup()` returns `Contact` objects:
|
|
310
|
+
|
|
311
|
+
| Field | Description |
|
|
312
|
+
|-------|-------------|
|
|
313
|
+
| `uri` | Contact URI as advertised by the UE |
|
|
314
|
+
| `received` | Transport source of the REGISTER as a SIP URI (`sip:<ip>:<port>;transport=<proto>`), or `None`. Prefer it over `uri` when routing — the Contact URI may carry a private/NAT address (`contact.received or contact.uri`) |
|
|
315
|
+
| `q` | Quality value (0.0–1.0), higher wins |
|
|
316
|
+
| `expires` | Seconds remaining on the binding |
|
|
317
|
+
| `age_secs` | Seconds since the binding was created or refreshed |
|
|
318
|
+
| `path` | RFC 3327 Path headers stored with the binding |
|
|
319
|
+
| `instance_id` / `instance_epoch` | Which siphon instance/process accepted the REGISTER |
|
|
320
|
+
| `is_local` | `True` when *this* process accepted the binding |
|
|
321
|
+
| `flow_token` / `flow` | Captured inbound flow for RFC 5626 connection reuse |
|
|
322
|
+
| `params` | Contact-header params preserved from the REGISTER (RFC 3840 feature tags) |
|
|
323
|
+
| `kind` | `"ue"` (routable) or `"as"` (AS capability record) |
|
|
324
|
+
|
|
325
|
+
### Auth
|
|
326
|
+
|
|
327
|
+
| Method | Description |
|
|
328
|
+
|--------|-------------|
|
|
329
|
+
| `require_www_digest(request, realm) -> bool` | 401 challenge |
|
|
330
|
+
| `require_proxy_digest(request, realm) -> bool` | 407 challenge |
|
|
331
|
+
| `require_digest(request, realm) -> bool` | Alias for www_digest |
|
|
332
|
+
| `verify_digest(request, realm) -> bool` | Verify without challenge |
|
|
333
|
+
| `require_ims_digest(request, realm) -> bool` | IMS AKA via Diameter Cx MAR |
|
|
334
|
+
| `require_aka_digest(request, realm) -> bool` | Local Milenage AKA (no HSS) |
|
|
335
|
+
|
|
336
|
+
### B2BUA call
|
|
337
|
+
|
|
338
|
+
Each B-leg gets a fresh Call-ID and From-tag by default, fully decoupling the two SIP dialogs. Use `keep_call_id()` to opt out of Call-ID regeneration.
|
|
339
|
+
|
|
340
|
+
| Property/Method | Description |
|
|
341
|
+
|----------------|-------------|
|
|
342
|
+
| `call.id` | UUID |
|
|
343
|
+
| `call.state` | `"calling"`, `"ringing"`, `"answered"`, `"terminated"` |
|
|
344
|
+
| `call.from_uri` | A-leg From URI |
|
|
345
|
+
| `call.ruri` | A-leg Request-URI |
|
|
346
|
+
| `call.reject(code, reason)` | Reject call |
|
|
347
|
+
| `call.dial(uri, timeout=30)` | Dial single target |
|
|
348
|
+
| `call.fork(targets, strategy, timeout)` | Fork to multiple |
|
|
349
|
+
| `call.terminate()` | End call (BYE both legs) |
|
|
350
|
+
| `call.keep_call_id()` | Copy A-leg Call-ID to B-leg (From-tag always unique) |
|
|
351
|
+
| `call.set_credentials(user, pass)` | B-leg digest auth credentials (auto 401/407 retry) |
|
|
352
|
+
| `call.media.anchor(engine)` | Anchor media through RTPEngine |
|
|
353
|
+
| `call.media.release()` | Release media anchor |
|
|
354
|
+
| `call.session_timer(expires, min_se, refresher)` | Per-call RFC 4028 session timer |
|
|
355
|
+
| `call.record(srs_uri)` | Start SIPREC recording |
|
|
356
|
+
| `call.stop_recording()` | Stop SIPREC recording |
|
|
357
|
+
|
|
358
|
+
## License
|
|
359
|
+
|
|
360
|
+
MIT
|