sippy 2.2.1__tar.gz → 2.2.3__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.
Files changed (153) hide show
  1. sippy-2.2.3/MANIFEST.in +4 -0
  2. {sippy-2.2.1/sippy.egg-info → sippy-2.2.3}/PKG-INFO +1 -1
  3. {sippy-2.2.1 → sippy-2.2.3}/setup.py +2 -4
  4. sippy-2.2.1/sippy/B2BRoute.py → sippy-2.2.3/sippy/B2B/Route.py +1 -1
  5. sippy-2.2.3/sippy/B2B/SimpleAPI.py +142 -0
  6. sippy-2.2.3/sippy/B2B/States.py +36 -0
  7. {sippy-2.2.1 → sippy-2.2.3}/sippy/CLIManager.py +39 -13
  8. {sippy-2.2.1 → sippy-2.2.3}/sippy/Core/EventDispatcher.py +32 -5
  9. {sippy-2.2.1 → sippy-2.2.3}/sippy/MsgBody.py +1 -0
  10. {sippy-2.2.1 → sippy-2.2.3}/sippy/MultipartMixBody.py +20 -5
  11. {sippy-2.2.1 → sippy-2.2.3}/sippy/MyConfigParser.py +1 -1
  12. sippy-2.2.1/sippy/Rtp_proxy/Client/stream.py → sippy-2.2.3/sippy/Rtp_proxy/Client/Worker/external.py +14 -104
  13. sippy-2.2.3/sippy/Rtp_proxy/Client/Worker/internal.py +95 -0
  14. sippy-2.2.3/sippy/Rtp_proxy/Client/internal.py +67 -0
  15. {sippy-2.2.1 → sippy-2.2.3}/sippy/Rtp_proxy/Client/local.py +4 -16
  16. sippy-2.2.3/sippy/Rtp_proxy/Client/stream.py +106 -0
  17. {sippy-2.2.1 → sippy-2.2.3}/sippy/Rtp_proxy/Client/udp.py +11 -19
  18. {sippy-2.2.1 → sippy-2.2.3}/sippy/Rtp_proxy/Cmd/sequencer.py +5 -1
  19. {sippy-2.2.1 → sippy-2.2.3}/sippy/Rtp_proxy/Session/side.py +5 -5
  20. {sippy-2.2.1 → sippy-2.2.3}/sippy/Rtp_proxy/Session/update.py +1 -0
  21. {sippy-2.2.1 → sippy-2.2.3}/sippy/Rtp_proxy/client.py +108 -72
  22. {sippy-2.2.1 → sippy-2.2.3}/sippy/Rtp_proxy/session.py +29 -25
  23. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipContentType.py +2 -0
  24. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipTransactionManager.py +20 -4
  25. sippy-2.2.3/sippy/Time/__init__.py +0 -0
  26. {sippy-2.2.1 → sippy-2.2.3}/sippy/UA.py +16 -6
  27. {sippy-2.2.1 → sippy-2.2.3}/sippy/UaStateDead.py +3 -0
  28. {sippy-2.2.1 → sippy-2.2.3}/sippy/b2bua.py +46 -129
  29. {sippy-2.2.1 → sippy-2.2.3/sippy.egg-info}/PKG-INFO +1 -1
  30. {sippy-2.2.1 → sippy-2.2.3}/sippy.egg-info/SOURCES.txt +9 -2
  31. sippy-2.2.3/tests/__init__.py +0 -0
  32. {sippy-2.2.1 → sippy-2.2.3}/tests/test_B2BRoute.py +1 -1
  33. {sippy-2.2.1 → sippy-2.2.3}/tests/test_B2BTransforms.py +1 -1
  34. sippy-2.2.1/MANIFEST.in +0 -1
  35. {sippy-2.2.1 → sippy-2.2.3}/LICENSE +0 -0
  36. {sippy-2.2.1 → sippy-2.2.3}/README.md +0 -0
  37. {sippy-2.2.1 → sippy-2.2.3}/pyproject.toml +0 -0
  38. {sippy-2.2.1 → sippy-2.2.3}/requirements.txt +0 -0
  39. {sippy-2.2.1 → sippy-2.2.3}/setup.cfg +0 -0
  40. /sippy-2.2.1/sippy/B2BTransforms.py → /sippy-2.2.3/sippy/B2B/Transforms.py +0 -0
  41. {sippy-2.2.1/sippy/Core → sippy-2.2.3/sippy/B2B}/__init__.py +0 -0
  42. {sippy-2.2.1 → sippy-2.2.3}/sippy/CCEvents.py +0 -0
  43. {sippy-2.2.1 → sippy-2.2.3}/sippy/Core/Exceptions.py +0 -0
  44. {sippy-2.2.1/sippy/Exceptions → sippy-2.2.3/sippy/Core}/__init__.py +0 -0
  45. {sippy-2.2.1 → sippy-2.2.3}/sippy/ESipHeaderCSV.py +0 -0
  46. {sippy-2.2.1 → sippy-2.2.3}/sippy/ESipHeaderIgnore.py +0 -0
  47. {sippy-2.2.1 → sippy-2.2.3}/sippy/Exceptions/RtpProxyError.py +0 -0
  48. {sippy-2.2.1 → sippy-2.2.3}/sippy/Exceptions/SdpParseError.py +0 -0
  49. {sippy-2.2.1 → sippy-2.2.3}/sippy/Exceptions/SipHandlingError.py +0 -0
  50. {sippy-2.2.1 → sippy-2.2.3}/sippy/Exceptions/SipParseError.py +0 -0
  51. {sippy-2.2.1/sippy/Math → sippy-2.2.3/sippy/Exceptions}/__init__.py +0 -0
  52. {sippy-2.2.1 → sippy-2.2.3}/sippy/External_command.py +0 -0
  53. {sippy-2.2.1 → sippy-2.2.3}/sippy/FakeAccounting.py +0 -0
  54. {sippy-2.2.1/sippy/Security → sippy-2.2.3/sippy/Math}/__init__.py +0 -0
  55. {sippy-2.2.1 → sippy-2.2.3}/sippy/Math/recfilter.py +0 -0
  56. {sippy-2.2.1 → sippy-2.2.3}/sippy/Network_server.py +0 -0
  57. {sippy-2.2.1 → sippy-2.2.3}/sippy/RadiusAccounting.py +0 -0
  58. {sippy-2.2.1 → sippy-2.2.3}/sippy/RadiusAuthorisation.py +0 -0
  59. {sippy-2.2.1 → sippy-2.2.3}/sippy/Radius_client.py +0 -0
  60. {sippy-2.2.1/sippy/Time → sippy-2.2.3/sippy/Rtp_proxy/Client/Worker}/__init__.py +0 -0
  61. {sippy-2.2.1 → sippy-2.2.3}/sippy/Rtp_proxy/Client/__init__.py +0 -0
  62. {sippy-2.2.1 → sippy-2.2.3}/sippy/Rtp_proxy/Client/net.py +0 -0
  63. {sippy-2.2.1 → sippy-2.2.3}/sippy/Rtp_proxy/Cmd/__init__.py +0 -0
  64. {sippy-2.2.1 → sippy-2.2.3}/sippy/Rtp_proxy/Session/__init__.py +0 -0
  65. {sippy-2.2.1 → sippy-2.2.3}/sippy/Rtp_proxy/Session/subcommand.py +0 -0
  66. {sippy-2.2.1 → sippy-2.2.3}/sippy/Rtp_proxy/Session/subcommand_ice.py +0 -0
  67. {sippy-2.2.1 → sippy-2.2.3}/sippy/Rtp_proxy/Session/webrtc.py +0 -0
  68. {sippy-2.2.1 → sippy-2.2.3}/sippy/Rtp_proxy/__init__.py +0 -0
  69. {sippy-2.2.1 → sippy-2.2.3}/sippy/Rtp_proxy/cmd.py +0 -0
  70. {sippy-2.2.1 → sippy-2.2.3}/sippy/SdpBody.py +0 -0
  71. {sippy-2.2.1 → sippy-2.2.3}/sippy/SdpConnecton.py +0 -0
  72. {sippy-2.2.1 → sippy-2.2.3}/sippy/SdpGeneric.py +0 -0
  73. {sippy-2.2.1 → sippy-2.2.3}/sippy/SdpMedia.py +0 -0
  74. {sippy-2.2.1 → sippy-2.2.3}/sippy/SdpMediaDescription.py +0 -0
  75. {sippy-2.2.1 → sippy-2.2.3}/sippy/SdpOrigin.py +0 -0
  76. {sippy-2.2.1 → sippy-2.2.3}/sippy/Security/SipNonce.py +0 -0
  77. {sippy-2.2.1/tests → sippy-2.2.3/sippy/Security}/__init__.py +0 -0
  78. {sippy-2.2.1 → sippy-2.2.3}/sippy/Signal.py +0 -0
  79. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipAddress.py +0 -0
  80. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipAddressHF.py +0 -0
  81. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipAllow.py +0 -0
  82. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipAlso.py +0 -0
  83. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipAuthorization.py +0 -0
  84. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipCCDiversion.py +0 -0
  85. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipCSeq.py +0 -0
  86. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipCallId.py +0 -0
  87. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipCiscoGUID.py +0 -0
  88. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipConf.py +0 -0
  89. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipContact.py +0 -0
  90. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipContentLength.py +0 -0
  91. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipDiversion.py +0 -0
  92. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipExpires.py +0 -0
  93. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipFrom.py +0 -0
  94. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipGenericHF.py +0 -0
  95. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipHeader.py +0 -0
  96. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipLogger.py +0 -0
  97. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipMaxForwards.py +0 -0
  98. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipMsg.py +0 -0
  99. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipNumericHF.py +0 -0
  100. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipPAssertedIdentity.py +0 -0
  101. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipPath.py +0 -0
  102. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipProxyAuthenticate.py +0 -0
  103. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipProxyAuthorization.py +0 -0
  104. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipRAck.py +0 -0
  105. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipRSeq.py +0 -0
  106. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipReason.py +0 -0
  107. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipRecordRoute.py +0 -0
  108. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipReferTo.py +0 -0
  109. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipReferredBy.py +0 -0
  110. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipRegistrationAgent.py +0 -0
  111. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipReplaces.py +0 -0
  112. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipRequest.py +0 -0
  113. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipResponse.py +0 -0
  114. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipRoute.py +0 -0
  115. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipServer.py +0 -0
  116. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipSupported.py +0 -0
  117. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipTo.py +0 -0
  118. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipURL.py +0 -0
  119. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipUserAgent.py +0 -0
  120. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipVia.py +0 -0
  121. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipWWWAuthenticate.py +0 -0
  122. {sippy-2.2.1 → sippy-2.2.3}/sippy/SipWarning.py +0 -0
  123. {sippy-2.2.1 → sippy-2.2.3}/sippy/StatefulProxy.py +0 -0
  124. {sippy-2.2.1 → sippy-2.2.3}/sippy/Time/MonoTime.py +0 -0
  125. {sippy-2.2.1 → sippy-2.2.3}/sippy/Time/Timeout.py +0 -0
  126. {sippy-2.2.1 → sippy-2.2.3}/sippy/Time/clock_dtime.py +0 -0
  127. {sippy-2.2.1 → sippy-2.2.3}/sippy/UaStateConnected.py +0 -0
  128. {sippy-2.2.1 → sippy-2.2.3}/sippy/UaStateDisconnected.py +0 -0
  129. {sippy-2.2.1 → sippy-2.2.3}/sippy/UaStateFailed.py +0 -0
  130. {sippy-2.2.1 → sippy-2.2.3}/sippy/UaStateGeneric.py +0 -0
  131. {sippy-2.2.1 → sippy-2.2.3}/sippy/UacStateCancelling.py +0 -0
  132. {sippy-2.2.1 → sippy-2.2.3}/sippy/UacStateIdle.py +0 -0
  133. {sippy-2.2.1 → sippy-2.2.3}/sippy/UacStateRinging.py +0 -0
  134. {sippy-2.2.1 → sippy-2.2.3}/sippy/UacStateTrying.py +0 -0
  135. {sippy-2.2.1 → sippy-2.2.3}/sippy/UacStateUpdating.py +0 -0
  136. {sippy-2.2.1 → sippy-2.2.3}/sippy/UasStateIdle.py +0 -0
  137. {sippy-2.2.1 → sippy-2.2.3}/sippy/UasStateRinging.py +0 -0
  138. {sippy-2.2.1 → sippy-2.2.3}/sippy/UasStateTrying.py +0 -0
  139. {sippy-2.2.1 → sippy-2.2.3}/sippy/UasStateUpdating.py +0 -0
  140. {sippy-2.2.1 → sippy-2.2.3}/sippy/Udp_server.py +0 -0
  141. {sippy-2.2.1 → sippy-2.2.3}/sippy/Wss_server.py +0 -0
  142. {sippy-2.2.1 → sippy-2.2.3}/sippy/XMPP_server.py +0 -0
  143. {sippy-2.2.1 → sippy-2.2.3}/sippy/__init__.py +0 -0
  144. {sippy-2.2.1 → sippy-2.2.3}/sippy/b2bua_simple.py +0 -0
  145. {sippy-2.2.1 → sippy-2.2.3}/sippy/dictionary +0 -0
  146. {sippy-2.2.1 → sippy-2.2.3}/sippy/misc.py +0 -0
  147. {sippy-2.2.1 → sippy-2.2.3}/sippy.egg-info/dependency_links.txt +0 -0
  148. {sippy-2.2.1 → sippy-2.2.3}/sippy.egg-info/entry_points.txt +0 -0
  149. {sippy-2.2.1 → sippy-2.2.3}/sippy.egg-info/requires.txt +0 -0
  150. {sippy-2.2.1 → sippy-2.2.3}/sippy.egg-info/top_level.txt +0 -0
  151. {sippy-2.2.1 → sippy-2.2.3}/tests/test_Multipart.py +0 -0
  152. {sippy-2.2.1 → sippy-2.2.3}/tests/test_SdbBody.py +0 -0
  153. {sippy-2.2.1 → sippy-2.2.3}/tests/test_Udp_server.py +0 -0
@@ -0,0 +1,4 @@
1
+ include README.md
2
+ include requirements.txt
3
+ include sippy/dictionary
4
+ recursive-include sippy/UI/static *
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sippy
3
- Version: 2.2.1
3
+ Version: 2.2.3
4
4
  Summary: RFC3261 SIP Stack and Back-to-Back User Agent (B2BUA)
5
5
  Home-page: http://www.b2bua.org/
6
6
  Author: Sippy Software, Inc.
@@ -8,13 +8,11 @@ with open("README.md", "r") as fh:
8
8
 
9
9
  setup(
10
10
  name = "sippy",
11
- version = "2.2.1",
11
+ version = "2.2.3",
12
12
  packages = find_packages(),
13
13
 
14
14
  install_requires = requirements,
15
- package_data = {
16
- '': ['dictionary', '*.md']
17
- },
15
+ include_package_data=True,
18
16
  test_suite = 'tests',
19
17
 
20
18
  entry_points = {
@@ -26,7 +26,7 @@
26
26
 
27
27
  from sippy.SipHeader import SipHeader
28
28
  from sippy.SipConf import SipConf
29
- from sippy.B2BTransforms import getTransProc
29
+ from sippy.B2B.Transforms import getTransProc
30
30
 
31
31
  try:
32
32
  from urllib import unquote
@@ -0,0 +1,142 @@
1
+ # Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved.
2
+ # Copyright (c) 2006-2025 Sippy Software, Inc. All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without modification,
5
+ # are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ #
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation and/or
12
+ # other materials provided with the distribution.
13
+ #
14
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
18
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
21
+ # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24
+
25
+ from ..CLIManager import CLIConnectionManager, CLIManager
26
+ from ..Time.MonoTime import MonoTime
27
+ from .States import CCStateConnected, CCStateARComplete
28
+
29
+ class B2BSimpleAPI(CLIConnectionManager):
30
+ global_config = None
31
+
32
+ def __init__(self, global_config):
33
+ cmdfile = global_config['b2bua_socket']
34
+ self.global_config = global_config
35
+ if cmdfile.startswith('tcp:'):
36
+ parts = cmdfile[4:].split(':', 1)
37
+ if len(parts) == 1:
38
+ address = (parts[0], 12345)
39
+ else:
40
+ address = (parts[0], int(parts[1]))
41
+ super().__init__(self.recvCommand, address, tcp = True)
42
+ else:
43
+ if cmdfile.startswith('unix:'):
44
+ cmdfile = cmdfile[5:]
45
+ super().__init__(self.recvCommand, cmdfile)
46
+
47
+ def recvCommand(self, clim, cmd):
48
+ ccm = self.global_config['_cmap']
49
+ args = cmd.split()
50
+ cmd = args.pop(0).lower()
51
+ if cmd == 'q':
52
+ clim.close()
53
+ return False
54
+ if cmd == 'l':
55
+ res = 'In-memory calls:\n'
56
+ total = 0
57
+ for (cid, sname), uaast, uaost in ccm.listActiveCalls():
58
+ res += f'{cid}: {sname} ('
59
+ if uaast is not None:
60
+ _s, _t, _h, _p, _cld, _cli = uaast
61
+ res += f'{_s} {_t}:{_h}:{_p} {_cld} {_cli} -> '
62
+ else:
63
+ res += 'N/A -> '
64
+ if uaost is not None:
65
+ _s, _t, _h, _p, _cld, _cli = uaost
66
+ res += f'{_s} {_t}:{_h}:{_p} {_cld} {_cli})\n'
67
+ else:
68
+ res += 'N/A)\n'
69
+ total += 1
70
+ res += 'Total: %d\n' % total
71
+ clim.send(res)
72
+ return False
73
+ if cmd == 'lt':
74
+ res = 'In-memory server transactions:\n'
75
+ for tid, t in self.global_config['_sip_tm'].tserver.iteritems():
76
+ res += '%s %s %s\n' % (tid, t.method, t.state)
77
+ res += 'In-memory client transactions:\n'
78
+ for tid, t in self.global_config['_sip_tm'].tclient.iteritems():
79
+ res += '%s %s %s\n' % (tid, t.method, t.state)
80
+ clim.send(res)
81
+ return False
82
+ if cmd in ('lt', 'llt'):
83
+ if cmd == 'llt':
84
+ mindur = 60.0
85
+ else:
86
+ mindur = 0.0
87
+ ctime = MonoTime()
88
+ res = 'In-memory server transactions:\n'
89
+ for tid, t in self.global_config['_sip_tm'].tserver.iteritems():
90
+ duration = ctime - t.rtime
91
+ if duration < mindur:
92
+ continue
93
+ res += '%s %s %s %s\n' % (tid, t.method, t.state, duration)
94
+ res += 'In-memory client transactions:\n'
95
+ for tid, t in self.global_config['_sip_tm'].tclient.iteritems():
96
+ duration = ctime - t.rtime
97
+ if duration < mindur:
98
+ continue
99
+ res += '%s %s %s %s\n' % (tid, t.method, t.state, duration)
100
+ clim.send(res)
101
+ return False
102
+ if cmd == 'd':
103
+ if len(args) != 1:
104
+ clim.send('ERROR: syntax error: d <call-id>\n')
105
+ return False
106
+ if args[0] == '*':
107
+ self.discAll()
108
+ clim.send('OK\n')
109
+ return False
110
+ dlist = [x for x in ccm.ccmap if str(x.cId) == args[0]]
111
+ if len(dlist) == 0:
112
+ clim.send('ERROR: no call with id of %s has been found\n' % args[0])
113
+ return False
114
+ for cc in dlist:
115
+ cc.disconnect()
116
+ clim.send('OK\n')
117
+ return False
118
+ if cmd == 'r':
119
+ if len(args) != 1:
120
+ clim.send('ERROR: syntax error: r [<id>]\n')
121
+ return False
122
+ idx = int(args[0])
123
+ dlist = [x for x in ccm.ccmap if x.id == idx]
124
+ if len(dlist) == 0:
125
+ clim.send('ERROR: no call with id of %d has been found\n' % idx)
126
+ return False
127
+ for cc in dlist:
128
+ if not cc.proxied:
129
+ continue
130
+ if cc.state == CCStateConnected:
131
+ cc.disconnect(MonoTime().getOffsetCopy(-60), origin = 'media_timeout')
132
+ continue
133
+ if cc.state == CCStateARComplete:
134
+ cc.uaO.disconnect(MonoTime().getOffsetCopy(-60), origin = 'media_timeout')
135
+ continue
136
+ clim.send('OK\n')
137
+ return False
138
+ clim.send('ERROR: unknown command\n')
139
+ return False
140
+
141
+ def set_rtp_io_socket(self, rtpp_nsock, rtpp_nsock_spec):
142
+ CLIManager(rtpp_nsock, self.recvCommand)
@@ -0,0 +1,36 @@
1
+ # Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved.
2
+ # Copyright (c) 2006-2025 Sippy Software, Inc. All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without modification,
5
+ # are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ #
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation and/or
12
+ # other materials provided with the distribution.
13
+ #
14
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
18
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
21
+ # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24
+
25
+ class CCStateIdle(object):
26
+ sname = 'Idle'
27
+ class CCStateWaitRoute(object):
28
+ sname = 'WaitRoute'
29
+ class CCStateARComplete(object):
30
+ sname = 'ARComplete'
31
+ class CCStateConnected(object):
32
+ sname = 'Connected'
33
+ class CCStateDead(object):
34
+ sname = 'Dead'
35
+ class CCStateDisconnecting(object):
36
+ sname = 'Disconnecting'
@@ -27,7 +27,7 @@
27
27
 
28
28
  from __future__ import print_function
29
29
 
30
- from os import remove, chown, chmod
30
+ from os import remove, chown, chmod, pipe, read, write, close
31
31
  import socket
32
32
  from errno import EPIPE, ENOTCONN, EBADF, ECONNABORTED
33
33
  from select import poll, POLLIN, POLLNVAL
@@ -48,19 +48,30 @@ class _Acceptor(Thread):
48
48
  pollobj = None
49
49
  fileno = None
50
50
 
51
- def __init__(self, clicm):
51
+ def __init__(self, clicm, wakeup_r):
52
52
  Thread.__init__(self)
53
53
  self.clicm = clicm
54
54
  self.pollobj = poll()
55
55
  self.fileno = self.clicm.serversock.fileno()
56
+ self.wakeup_r = wakeup_r
56
57
  self.pollobj.register(self.fileno, POLLIN)
58
+ self.pollobj.register(self.wakeup_r, POLLIN)
57
59
  self.start()
58
60
 
59
61
  def run(self):
60
62
  #print(self.run, 'enter')
61
63
  while True:
62
64
  #print(self.run, 'cycle')
63
- pollret = dict(self.pollobj.poll()).get(self.fileno, 0)
65
+ revents = dict(self.pollobj.poll())
66
+ if self.wakeup_r in revents and (revents[self.wakeup_r] & POLLIN):
67
+ try:
68
+ # drain the pipe (best-effort)
69
+ read(self.wakeup_r, 1024)
70
+ except OSError:
71
+ dump_exception('CLIConnectionManager: unhandled exception draining wakeup socket')
72
+ pass
73
+ break
74
+ pollret = revents.get(self.fileno, 0)
64
75
  if pollret & POLLNVAL != 0:
65
76
  break
66
77
  if pollret & POLLIN == 0:
@@ -74,16 +85,18 @@ class _Acceptor(Thread):
74
85
  break
75
86
  else:
76
87
  raise
77
- except Exception as e:
78
- dump_exception('CLIConnectionManager: unhandled exception when accepting incoming connection')
88
+ except Exception:
89
+ dump_exception('CLIConnectionManager: unhandled exception accepting incoming connection')
79
90
  break
80
91
  #print(self.run, 'handle_accept')
81
92
  ED2.callFromThread(self.clicm.handle_accept, clientsock, addr)
82
93
  self.clicm = None
94
+ close(self.wakeup_r)
83
95
  #print(self.run, 'exit')
84
96
 
85
97
  class CLIConnectionManager(object):
86
98
  command_cb = None
99
+ address = None
87
100
  tcp = False
88
101
  accept_list: list = None
89
102
  serversock = None
@@ -116,7 +129,9 @@ class CLIConnectionManager(object):
116
129
  if sock_mode != None:
117
130
  chmod(address, sock_mode)
118
131
  self.serversock.listen(backlog)
119
- self.atr = _Acceptor(self)
132
+ wakeup_r, self._wakeup_w = pipe()
133
+ self.atr = _Acceptor(self, wakeup_r)
134
+ self.address = address
120
135
 
121
136
  def handle_accept(self, conn, address):
122
137
  #print(self.handle_accept)
@@ -125,16 +140,24 @@ class CLIConnectionManager(object):
125
140
  conn.close()
126
141
  return
127
142
  try:
128
- cm = CLIManager(conn, self.command_cb, address)
129
- except Exception as e:
130
- dump_exception('CLIConnectionManager: unhandled exception when setting up incoming connection handler')
143
+ cm = CLIManager(conn, self.command_cb)
144
+ except Exception:
145
+ dump_exception('CLIConnectionManager: unhandled exception setting up incoming connection handler')
131
146
  conn.close()
132
147
  return
133
148
 
134
149
  def shutdown(self):
150
+ try:
151
+ write(self._wakeup_w, b'\x01')
152
+ except OSError:
153
+ dump_exception('CLIConnectionManager: unhandled exception waking up acceptor')
154
+ pass
155
+ close(self._wakeup_w)
135
156
  self.serversock.close()
136
157
  self.command_cb = None
137
158
  self.atr.join()
159
+ if not self.tcp:
160
+ remove(self.address)
138
161
 
139
162
  class _CLIManager_w(Thread):
140
163
  daemon = True
@@ -174,6 +197,11 @@ class _CLIManager_w(Thread):
174
197
  except BrokenPipeError:
175
198
  self.clim = None
176
199
  return
200
+ except socket.error as why:
201
+ if why.errno != EBADF:
202
+ raise
203
+ self.clim = None
204
+ return
177
205
  if res == len(wbuffer):
178
206
  break
179
207
  if res > 0:
@@ -239,14 +267,12 @@ class CLIManager(object):
239
267
  clientsock = None
240
268
  command_cb = None
241
269
  close_pendind = False
242
- raddr = None
243
270
  wthr = None
244
271
  rthr = None
245
272
 
246
- def __init__(self, clientsock, command_cb, raddr):
273
+ def __init__(self, clientsock, command_cb):
247
274
  self.clientsock = clientsock
248
275
  self.command_cb = command_cb
249
- self.raddr = raddr
250
276
  self.wthr = _CLIManager_w(clientsock, self)
251
277
  self.rthr = _CLIManager_r(clientsock, self)
252
278
 
@@ -254,7 +280,7 @@ class CLIManager(object):
254
280
  try:
255
281
  self.command_cb(self, cmd)
256
282
  except:
257
- dump_exception('CLIManager: unhandled exception when processing incoming data')
283
+ dump_exception('CLIManager: unhandled exception processing incoming data')
258
284
  self.close()
259
285
 
260
286
  def send(self, data):
@@ -29,12 +29,13 @@ from __future__ import print_function
29
29
  from functools import partial
30
30
  from datetime import datetime
31
31
  from heapq import heappush, heappop, heapify
32
- from threading import Lock
32
+ from threading import Lock, local as t_local
33
33
  from random import random
34
34
  import sys, traceback, signal
35
35
  from _thread import get_ident
36
36
  from sippy.Time.MonoTime import MonoTime
37
37
  from sippy.Core.Exceptions import dump_exception, StdException
38
+ from queue import Queue
38
39
 
39
40
  from elperiodic.ElPeriodic import ElPeriodic
40
41
 
@@ -123,6 +124,8 @@ class EventDispatcher2(Singleton):
123
124
  ed_inum = 0
124
125
  elp = None
125
126
  bands = None
127
+ _exception = None
128
+ tloc_data = None
126
129
 
127
130
  def __init__(self, freq = 100.0):
128
131
  EventDispatcher2.state_lock.acquire()
@@ -140,6 +143,7 @@ class EventDispatcher2(Singleton):
140
143
  self.elp = ElPeriodic(freq)
141
144
  self.elp.CFT_enable(signal.SIGURG)
142
145
  self.bands = [(freq, 0),]
146
+ self.tloc_data = t_local()
143
147
 
144
148
  def signal(self, signum, frame):
145
149
  self.signals_pending.append(signum)
@@ -233,16 +237,35 @@ class EventDispatcher2(Singleton):
233
237
  def dispatchThreadCallback(self, thread_cb, cb_params):
234
238
  try:
235
239
  thread_cb(*cb_params)
236
- except Exception as ex:
237
- if isinstance(ex, SystemExit):
238
- raise
240
+ except BaseException as ex:
241
+ if isinstance(ex, (SystemExit, KeyboardInterrupt)):
242
+ self._exception = ex
243
+ return
239
244
  dump_exception('EventDispatcher2: unhandled exception when processing from-thread-call')
240
- #print('dispatchThreadCallback dispatched', thread_cb, cb_params)
245
+
246
+ def dispatchThreadCallbackSync(self, res_cb_q, thread_cb, cb_params):
247
+ try:
248
+ res = thread_cb(*cb_params)
249
+ except BaseException as ex:
250
+ rval = (None, ex)
251
+ else:
252
+ rval = (res, None)
253
+ res_cb_q.put(rval)
241
254
 
242
255
  def callFromThread(self, thread_cb, *cb_params):
243
256
  self.elp.call_from_thread(self.dispatchThreadCallback, thread_cb, cb_params)
244
257
  #print('EventDispatcher2.callFromThread completed', str(self), thread_cb, cb_params)
245
258
 
259
+ def callFromThreadSync(self, thread_cb, *cb_params):
260
+ if not hasattr(self.tloc_data, 'res_cb_q'):
261
+ self.tloc_data.res_cb_q = Queue()
262
+ res_cb_q = self.tloc_data.res_cb_q
263
+ self.elp.call_from_thread(self.dispatchThreadCallbackSync, res_cb_q, thread_cb, cb_params)
264
+ res, ex = res_cb_q.get()
265
+ if ex is not None:
266
+ raise ex
267
+ return res
268
+
246
269
  def loop(self, timeout = None, freq = None):
247
270
  if freq != None and self.bands[0][0] != freq:
248
271
  for fb in self.bands:
@@ -274,6 +297,10 @@ class EventDispatcher2(Singleton):
274
297
  break
275
298
  self.elp.procrastinate()
276
299
  self.last_ts = MonoTime()
300
+ if self._exception is not None:
301
+ ex = self._exception
302
+ self._exception = None
303
+ raise ex
277
304
  return self.el_rval
278
305
 
279
306
  def breakLoop(self, rval=0):
@@ -47,6 +47,7 @@ class MsgBody(object):
47
47
  if content != None:
48
48
  self.mtype = mtype
49
49
  self.content = content
50
+ self.parsed = type(content) not in str_types
50
51
  return
51
52
  if cself is not None:
52
53
  if type(cself.content) in str_types:
@@ -23,14 +23,22 @@
23
23
  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
24
  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
25
 
26
+ from secrets import token_hex
27
+
26
28
  from sippy.SipHeader import SipHeader
29
+ from sippy.SipContentType import SipContentType
27
30
 
28
31
  class MultipartMixBody():
29
32
  parts = None
30
33
  boundary = None
34
+ mtype = SipContentType('multipart/mixed')
35
+ mtype.parse()
31
36
 
32
37
  def __init__(self, body = None, ctype = None):
38
+ self.parts = []
39
+ self.part_headers = []
33
40
  if body is None:
41
+ self.setBoundary(token_hex(16))
34
42
  return
35
43
  sep = f'--{ctype.params["boundary"]}'
36
44
  bparts = body.split(sep)
@@ -38,8 +46,6 @@ class MultipartMixBody():
38
46
  assert len(bparts[0]) == 0
39
47
  assert bparts[-1].strip() == '--'
40
48
  parts = [p.lstrip() for p in bparts[1:-1]]
41
- self.parts = []
42
- self.part_headers = []
43
49
  for sect in parts:
44
50
  headers = []
45
51
  ct = None
@@ -55,18 +61,24 @@ class MultipartMixBody():
55
61
  sect = MsgBody(sect, ct)
56
62
  self.parts.append(sect)
57
63
  self.part_headers.append(headers)
58
- self.boundary = ctype.params["boundary"]
64
+ self.setBoundary(ctype.params["boundary"])
65
+
66
+ def setBoundary(self, bnd):
67
+ self.boundary = bnd
68
+ mtype = self.mtype.getCopy()
69
+ mtype.params["boundary"] = bnd
70
+ self.mtype = mtype
59
71
 
60
72
  def __str__(self):
61
73
  bnd = f'--{self.boundary}\r\n'
62
- parts = [f'{bnd}Content-Type: {p.mtype}\r\n{p}' for p in self.parts]
74
+ parts = [f'{bnd}Content-Type: {p.mtype}\r\n\r\n{p}' for p in self.parts]
63
75
  s = ''.join(parts)
64
76
  s += f'{bnd[:-2]}--\r\n'
65
77
  return s
66
78
 
67
79
  def localStr(self, local_addr = None):
68
80
  bnd = f'--{self.boundary}\r\n'
69
- parts = [f'{bnd}Content-Type: {p.mtype}\r\n{p.localStr(local_addr)}'
81
+ parts = [f'{bnd}Content-Type: {p.mtype}\r\n\r\n{p.localStr(local_addr)}'
70
82
  for p in self.parts]
71
83
  s = ''.join(parts)
72
84
  s += f'{bnd[:-2]}--\r\n'
@@ -78,5 +90,8 @@ class MultipartMixBody():
78
90
  cself.boundary = self.boundary
79
91
  return cself
80
92
 
93
+ def getContentType(self):
94
+ return self.mtype.getCopy()
95
+
81
96
  if 'MsgBody' not in globals():
82
97
  from sippy.MsgBody import MsgBody
@@ -33,7 +33,7 @@ except ImportError:
33
33
  from configparser import RawConfigParser
34
34
  _boolean_states = RawConfigParser.BOOLEAN_STATES
35
35
  from sippy.SipConf import SipConf
36
- from sippy.B2BTransforms import getTransProc
36
+ from sippy.B2B.Transforms import getTransProc
37
37
 
38
38
  SUPPORTED_OPTIONS = { \
39
39
  'acct_enable': ('B', 'enable or disable Radius accounting'), \