From ec8fcdce7bf8ecce7f802d4c14138af154948bff Mon Sep 17 00:00:00 2001 From: TJ Porter Date: Fri, 5 Jan 2024 15:31:12 -0600 Subject: [PATCH 1/8] [CHANGE] Updated the Globals documentation to include the new global variables --- docs/Globals.rst | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/Globals.rst b/docs/Globals.rst index 20a0577..4a7752d 100644 --- a/docs/Globals.rst +++ b/docs/Globals.rst @@ -8,3 +8,36 @@ pyVoIP.\ **DEBUG** = False pyVoIP.\ **TRANSMIT_DELAY_REDUCTION** = 0.0 The higher this variable is, the more often RTP packets are sent. This *should* only ever need to be 0.0. However, when testing on Windows, there has sometimes been jittering, setting this to 0.75 fixed this in testing, but you may need to tinker with this number on a per-system basis. + +pyVoIP.\ **ALLOW_BASIC_AUTH** = False + Controls whether Basic authentication (`RFC 7617 `_) is allowed for SIP authentication. Basic authentication is deprecated as it will send your password in plain-text, likely in the clear (unencrypted) as well. As such this is disabled be default. + +pyVoIP.\ **ALLOW_MD5_AUTH** = True + MD5 Digest authentication is deprecated per `RFC 8760 `_ as it a weak hash. However, it is still used often so it is enabled by default. + +pyVoIP.\ **REGISTER_FAILURE_THRESHOLD** = 3 + If registration fails this many times, VoIPPhone's status will be set to FAILED and the phone will stop. + +pyVoIP.\ **ALLOW_TLS_FALLBACK** = False + If this is set to True TLS will fall back to TCP if the TLS handshake fails. This is off by default, as it would be irresponsible to have a security feature disabled by default. + + This feature is currently not implemented. + +pyVoIP.\ **TLS_CHECK_HOSTNAME** = True + Is used to create SSLContexts. See `Python's documentation `_ on this feature. + + You should use the set_tls_security to change this variable. + +pyVoIP.\ **TLS_VERIFY_MODE** = True + Is used to create SSLContexts. See `Python's documentation `_ on this feature. + + You should use the set_tls_security to change this variable. + +pyVoIP.\ **SIP_STATE_EB_LOCATION** = ":memory:" + This variable allows you to save the SIP message state database to a file instead of storing it in memory which is the default. This is useful for debugging, however pyVoIP does not delete the database afterwards which will cause an Exception upon restarting pyVoIP. For this reason, we recommend you do not change this variable in production. + +Global Functions +######## + +pyVoIP.\ **set_tls_security**\ (verify_mode: `ssl.VerifyMode `) -> None + This method ensures that TLS_CHECK_HOSTNAME and TLS_VERIFY_MODE are set correctly depending on the TLS certificate verification settings you want to use. From 71047d88b54cd8fc4f349eb154e336763ad00af8 Mon Sep 17 00:00:00 2001 From: TJ Porter Date: Sat, 6 Jan 2024 01:30:11 -0600 Subject: [PATCH 2/8] [CHANGE] Completely updated VoIP.rst [FIX] Fixed spelling errors and wording in Global.rst --- docs/Globals.rst | 19 +++-- docs/VoIP.rst | 179 +++++++++++++++++++++++++---------------------- 2 files changed, 106 insertions(+), 92 deletions(-) diff --git a/docs/Globals.rst b/docs/Globals.rst index 4a7752d..40b507d 100644 --- a/docs/Globals.rst +++ b/docs/Globals.rst @@ -1,10 +1,13 @@ +Globals +####### + Global Variables -######## +**************** There are a few global variables that may assist you if you're having problems with the library. pyVoIP.\ **DEBUG** = False - If set to true, pyVoIP will print debug messages that may be useful if you need to open a GitHub issue. Otherwise, does nothing. + If set to true, pyVoIP will print debug messages that may be useful if you need to troubleshoot or open a GitHub issue. pyVoIP.\ **TRANSMIT_DELAY_REDUCTION** = 0.0 The higher this variable is, the more often RTP packets are sent. This *should* only ever need to be 0.0. However, when testing on Windows, there has sometimes been jittering, setting this to 0.75 fixed this in testing, but you may need to tinker with this number on a per-system basis. @@ -26,18 +29,20 @@ pyVoIP.\ **ALLOW_TLS_FALLBACK** = False pyVoIP.\ **TLS_CHECK_HOSTNAME** = True Is used to create SSLContexts. See `Python's documentation `_ on this feature. - You should use the set_tls_security to change this variable. + You should use the :ref:`set_tls_security ` function to change this variable. pyVoIP.\ **TLS_VERIFY_MODE** = True Is used to create SSLContexts. See `Python's documentation `_ on this feature. - You should use the set_tls_security to change this variable. + You should use the :ref:`set_tls_security ` function to change this variable. -pyVoIP.\ **SIP_STATE_EB_LOCATION** = ":memory:" +pyVoIP.\ **SIP_STATE_DB_LOCATION** = ":memory:" This variable allows you to save the SIP message state database to a file instead of storing it in memory which is the default. This is useful for debugging, however pyVoIP does not delete the database afterwards which will cause an Exception upon restarting pyVoIP. For this reason, we recommend you do not change this variable in production. Global Functions -######## +**************** + +.. _set_tls_security: -pyVoIP.\ **set_tls_security**\ (verify_mode: `ssl.VerifyMode `) -> None +pyVoIP.\ **set_tls_security**\ (verify_mode: `ssl.VerifyMode `_) -> None This method ensures that TLS_CHECK_HOSTNAME and TLS_VERIFY_MODE are set correctly depending on the TLS certificate verification settings you want to use. diff --git a/docs/VoIP.rst b/docs/VoIP.rst index 7dec461..6d464a6 100644 --- a/docs/VoIP.rst +++ b/docs/VoIP.rst @@ -1,60 +1,68 @@ VoIP - The Bridge Between SIP and RTP ##################################### -The VoIP module coordinates between the SIP and RTP modules in order to create an effective Voice over Internet Protocol system. The VoIP system is made for your convenience, and if you have a particularly intricate situation, you can use the SIP and RTP modules independently and create your own version of the VoIP module. If you choose to use the VoIP module, this section will explain how. +The VoIP module coordinates between the SIP and RTP modules in order to create an effective Voice over Internet Protocol system. The VoIP system is made for your convenience, and if you have a particularly intricate situation, you can override the SIP module on initialization to fit your use case. Errors ******** -There are two errors under ``pyVoIP.VoIP``. +There are three errors under ``pyVoIP.VoIP.error``. .. _invalidstateerror: -*exception* VoIP.\ **InvalidStateError** - This is thrown by :ref:`VoIPCall` when you try to perform an action that cannot be performed during the current :ref:`CallState`. For example denying a call that has already been answered, hanging up a call that hasn't been answered yet, or has already been ended. +*exception* pyVoIP.VoIP.error.\ **InvalidStateError** + This is thrown by :ref:`VoIPCall` when you try to perform an action that cannot be performed during the current :ref:`CallState`. For example denying a call that has already been answered, hanging up a call that hasn't been answered yet, or has already ended. -*exception* VoIP.\ **InvalidRangeError** - This is thrown by :ref:`VoIPPhone` when you define the rtpPort ranges as rtpPortLow > rtpPortHigh. However, this is not checked by :ref:`VoIPCall`, so if you are using your own class instead of VoIPPhone, make sure these ranges are correct. +*exception* pyVoIP.VoIP.error.\ **InvalidRangeError** + This is thrown by :ref:`VoIPPhone` when you define the RTP port ranges as rtp_port_low > rtp_port_high. However, this is not checked by :ref:`VoIPCall`, so if you are using your own class instead of VoIPPhone, make sure these ranges are correct. -*exception* VoIP.\ **NoPortsAvailableError** - This is thrown when a call is attempting to be initiated but no ports are available. +*exception* pyVoIP.VoIP.error.\ **NoPortsAvailableError** + This is thrown when a call is attempting to be initiated but no RTP ports are available. Enums *********** .. _callstate: -VoIP.\ **CallState** - CallState is an Enum with four attributes. - +*enum* pyVoIP.VoIP.\ **CallState** + CallState is an Enum with six attributes. + CallState.\ **DIALING** This CallState is used to describe when a :term:`user` has originated a call to a :term:`client`, but it has yet to be answered. - + + In this state, you can use ``VoIPCall.cancel()``. + CallState.\ **RINGING** This CallState is used to describe when a :term:`client` is calling, but the call has yet to be answered. - + In this state, you can use ``VoIPCall.answer()`` or ``VoIPCall.deny()``. - + + CallState.\ **PROGRESS** + This CallState is used when a 183 Session Progress response is received on a call that is dialing. + + In this state, you can use ``VoIPCall.answer()``, ``VoIPCall.deny()``, or ``VoIPCall.cancel()``. + CallState.\ **ANSWRED** This CallState is used to describe when a call has been answered and is active. - + In this state, you can use ``VoIPCall.hangup()``. - + + CallState.\ **CANCELING** + This CallState is used when a dialing call is canceled with ``VoIPCall.cancel()``. + CallState.\ **ENDED** This CallState is used to describe when a call has been terminated. - - In this state, you can not use any functions. -.. _phonestatus +.. _PhoneStatus: -VoIP.\ **PhoneStatus** +*enum* pyVoIP.VoIP.\ **PhoneStatus** PhoneStatus is an Enum with five attributes. PhoneStatus.\ **INACTIVE** This PhoneStatus is used when ``VoIPPhone.start()`` has not been called, or after the phone has fully stopped after calling ``VoIPPhone.stop()``. PhoneStatus.\ **REGISTERING** - This PhoneStatus is used when ``VoIPPhone.start()`` has been called, but has not finished starting. + This PhoneStatus is used when ``VoIPPhone.start()`` has been called, but has not finished starting, or when the phone is re-registering. PhoneStatus.\ **REGISTERED** This PhoneStatus is used when ``VoIPPhone`` has finished starting successfully, and is ready for use. @@ -73,114 +81,115 @@ Classes VoIPCall ========= -The VoIPCall class is used to represent a single VoIP Session, which may be to multiple :term:`clients`. +The VoIPCall class is used to represent a single VoIP session, which may be to multiple :term:`clients`. -*class* VoIP.\ **VoIPCall**\ (phone: :ref:`VoIPPhone`, request: :ref:`SIPMessage`, session_id: int, myIP: str, rtpPortLow: int, rtpPortHigh: int) +*class* pyVoIP.VoIP.\ **VoIPCall**\ (phone: :ref:`VoIPPhone`, callstate: :ref:`CallState `, request: :ref:`SIPMessage`, session_id: int, bind_ip: str, conn: :ref:`VoIPConnection`, ms: Optional[Dict[int, RTP.PayloadType]] = None, sendmode="sendonly") The *phone* argument is the initating instance of :ref:`VoIPPhone`. - + The *callstate* arguement is the initiating :ref:`CallState`. - + The *request* argument is the :ref:`SIPMessage` representation of the SIP INVITE request from the VoIP server. - + The *session_id* argument is a unique code used to identify the session with `SDP `_ when answering the call. - - The *myIP* argument is the IP address it will pass to :ref:`RTPClient`'s to bind to. - - The *ms* arguement is a dictionary with int as the key and a :ref:`PayloadType` as the value. This is only used when originating the call. - - - **dtmf_callback**\ (code: str) -> None - This method is called by :ref:`RTPClient`'s when a telephone-event DTMF message is received. The *code* argument is a string. It should be an Event in complinace with `RFC 4733 Section 3.2 `_. - + + The *bind_ip* argument is the IP address it will pass to :ref:`RTPClient`'s to bind to. + + The *ms* arguement is a dictionary with int as the key and a :ref:`PayloadType` as the value. This is only used when originating the call. + + **get_dtmf**\ (length=1) -> str - This method can be called get the next pressed DTMF key. DTMF's are stored in an ``io.StringIO`` and act as a stack. Meaning if the :term:`client` presses the numbers 1-9-5 you'll have the following output: - + This method can be called get the next pressed DTMF key. DTMF's are stored in an `io.StringIO `_ which is a buffer. Calling this method when there a key has not been pressed returns an empty string. To return the entire contents of the buffer set length to a negative number or None. If the :term:`client` presses the numbers 1-9-5 you'll have the following output: + .. code-block:: python - - VoIPCall.get_dtmf() + + self.get_dtmf() >>> '1' - VoIPCall.get_dtmf(length=2) + self.get_dtmf(length=2) >>> '95' - VoIPCall.get_dtmf() + self.get_dtmf() >>> '' - - As you can see, calling this method when there a key has not been pressed returns an empty string. - + + **answer**\ () -> None Answers the call if the phone's state is CallState.RINGING. - - **answered**\ (request: :ref:`SIPMessage`) -> None - This function is called by :ref:`SIPClient` when a call originated by the :term:`user` has been answered by the :term:`client`. - + + **transfer**\ (user: Optional[str] = None, uri: Optional[str] = None, blind=True) -> bool + Sends a REFER request to transfer the call. If blind is true (default), the call will immediately end after received a 200 or 202 response. Otherwise, it will wait for the Transferee to report a successful transfer. Or, if the transfer is unsuccessful, the call will continue. This function returns true if the transfer is blind or successful, and returns false if it is unsuccessful. + + If using a URI to transfer, you must use a complete URI to include <> brackets as necessary. + + **ringing**\ (request: :ref:`SIPMessage`) -> None + This function is what is called when receiving a new call. Custom VoIPCall classes should override this function to answer the call. + **deny**\ () -> None Denies the call if the phone's state is CallState.RINGING. - + **hangup**\ () -> None Ends the call if the phone's state is CallState.ANSWRED. - - **bye**\ () -> None - Ends the call but does not send a SIP BYE message to the SIP server. This function is used to end the call on the server side when the client ended the call. **THE** :term:`USER` **SHOUND NOT CALL THIS FUNCTION OR THE** :term:`CLIENT` **WILL BE LEFT ON THE LINE WITH NO RESPONSE. CALL HANGUP() INSTEAD.** - + + **cancel**\ () -> None + Cancels a dialing call. + **write_audio**\ (data: bytes) -> None - Writes linear/raw audio data to the transmit buffer before being encoded and sent. The *data* argument MUST be bytes. **This audio must be linear/not encoded,** :ref:`RTPClient` **will encode it before transmitting.** - + Writes linear/raw audio data to the transmit buffer before being encoded and sent. The *data* argument MUST be bytes. **This audio must be linear/not encoded.** :ref:`RTPClient` **will encode it before transmitting.** + **read_audio**\ (length=160, blocking=True) -> bytes - Reads linear/raw audio data from the received buffer. Returns *length* amount of bytes. Default length is 160 as that is the amount of bytes sent per PCMU/PCMA packet. When *blocking* is set to true, this function will not return until data is available. When *blocking* is set to false and data is not available, this function will return ``b"\x80" * length``. + Reads linear/raw audio data from the received buffer. Returns *length* amount of bytes. Default length is 160 as that is the amount of bytes sent per PCMU/PCMA packet. When *blocking* is set to true, this function will not return until data is available. When *blocking* is set to false and data is not available, this function will return ``b"\x80" * length``. .. _VoIPPhoneParameter: VoIPPhoneParameter -========= - -*class* VoIP.\ **VoIPPhone**\ (server: str, port: int, username: str, password: str, callCallback: Optional[Callable] = None, bind_ip="0.0.0.0", bind_port=5060, transport_mode=SIP.TransportMode.UDP, rtp_port_low=10000, rtp_port_high=20000, callClass: Type[VoIPCall] = None, sipClass: Type[SIP.SIPClient] = None) +================== +*class* pyVoIP.VoIP.\ **VoIPPhoneParameter**\ (server: str, port: int, credentials_manager: Optional[:ref:`CredentialsManager`], bind_ip="0.0.0.0", bind_port=5060, bind_network="0.0.0.0/0", hostname: Optional[str] = None, remote_hostname: Optional[str] = None, transport_mode=\ :ref:`TransportMode`.UDP, cert_file: Optional[str] = None, key_file: Optional[str] = None, key_password: Optional[str] = None, rtp_port_low=10000, rtp_port_high=20000, call_class: Type[VoIPCall] = None, sip_class: Type[SIP.SIPClient] = None) The *server* argument is your PBX/VoIP server's IP, represented as a string. The *port* argument is your PBX/VoIP server's port, represented as an integer. - The *username* argument is your SIP account username on the PBX/VoIP server, represented as a string. + The *credentials_manager* argument is a :ref:`CredentialsManager` instance that stores all usernames and passwords your phone may need. + + The *bind_ip* argument is used to bind SIP and RTP ports to receive incoming calls. Default is to bind to 0.0.0.0, however, this is not recommended. + + The *bind_port* argument is the port SIP will bind to to receive SIP requests. The default for this protocol is port 5060, but any port can be used. - The *password* argument is your SIP account password on the PBX/VoIP server, represented as a string. + The *bind_network* argument is used to configure pyVoIP's NAT. pyVoIP uses this to know whether to use the *hostname* or *remote_hostname* when generating SIP requests to in-network and out-of-network devices respectively. Value must be a string with IPv4 CIDR notation. - The *bind_ip* argument is used to bind SIP and RTP ports to receive incoming calls. If left as None, the VoIPPhone will bind to 0.0.0.0. + The *hostname* argument is used to generate SIP requests and responses with devices within pyVoIP's *bind_network*. If left as None, the *bind_ip* will be used instead. - The *bind_port* argument is the port SIP will bind to to receive SIP requests. The default for this protocol is port 5060, but any port can be used. + The *remote_hostname* argument is used to generate SIP requests and responses with devices outside of pyVoIP's *bind_network*. If left as None, pyVoIP will throw a :ref:`NATError` if a request is sent outside of pyVoIP's *bind_network*. - The *transport_mode* argument is SIP.TransportMode.UDP or SIP.TransportMode.TCP. + The *transport_mode* argument determines whether pyVoIP will use UDP, TCP, or TLS. Value should be a :ref:`TransportMode`. - The *rtp_port_low* and *rtp_port_high* arguments are used to generate random ports to use for audio transfer. Per RFC 4566 Sections `5.7 `_ and `5.14 `_, it can take multiple ports to fully communicate with other :term:`clients`, as such a large range is recommended. If an invalid range is given, a :ref:`InvalidStateError` will be thrown. + The *cert_file*, *key_file*, and *key_password* arguments are used to load certificates in pyVoIP's server context if using TLS for the transport mode. See `Python's documentation on load_cert_chain `_ for more details. - The *callClass* argument allows to override the used :ref:`VoIPCall` class (must be a child class of :ref:`VoIPCall`). + The *rtp_port_low* and *rtp_port_high* arguments are used to generate random ports to use for audio transfer. Per RFC 4566 Sections `5.7 `_ and `5.14 `_, it can take multiple ports to fully communicate with other :term:`clients`, as such a large range is recommended. If an invalid range is given, a :ref:`InvalidStateError` will be thrown. - The *sipClass* argument allows to override the used :ref:`SIPClient` class (must be a child class of :ref:`SIPClient`). + The *call_class* argument allows to override the used :ref:`VoIPCall` class (must be a child class of :ref:`VoIPCall`). + + The *sip_class* argument allows to override the used :ref:`SIPClient` class (must be a child class of :ref:`SIPClient`). .. _VoIPPhone: VoIPPhone ========= -The VoIPPhone class is used to manage the :ref:`SIPClient` class and create :ref:`VoIPCall`'s when there is an incoming call. It then uses the VoIPCall class to handle the call states. - -*class* VoIP.\ **VoIPPhone**\ (voip_phone_parameter: VoIPPhoneParameter) +The VoIPPhone class is used to manage the :ref:`SIPClient` class and create :ref:`VoIPCall`'s when there is an incoming call or a :term:`user` makes a call. It then uses the VoIPCall class to handle the call's states. - **callback**\ (request: :ref:`SIPMessage`) -> None - This method is called by the :ref:`SIPClient` when an INVITE or BYE request is received. This function then creates a :ref:`VoIPCall` or terminates it respectively. When a VoIPCall is created, it will then pass it to the *callCallback* function as an argument. If *callCallback* is set to None, this function replies as BUSY. **This function should not be called by the** :term:`user`. - - **get_status**\ () -> PhoneStatus - This method returns the :ref:`PhoneStatus`. - - **request_port**\ (blocking=True) -> int - This method is called when a new port is needed to use in a :ref:`VoIPCall`. If blocking is set to True, this will wait until a port is available. Otherwise, it will raise NoPortsAvailableError. - - **release_ports**\ (call: Optional[VoIPCall] = None) -> None - This method is called when a call ends. If call is provided, it will only release the ports used by that :ref:`VoIPCall`. Otherwise, it will iterate through all active calls, and release all ports that are no longer in use. +*class* pyVoIP.VoIP.\ **VoIPPhone**\ (voip_phone_parameter: :ref:`VoIPPhoneParameter`) + **get_status**\ () -> :ref:`PhoneStatus ` + This method returns the phone's current status. **start**\ () -> None - This method starts the :ref:`SIPClient` class. On failure, this will automatically call stop(). + This method starts the :ref:`SIPClient` class. On failure, this will automatically call stop(). **stop**\ () -> None - This method ends all currently ongoing calls, then stops the :ref:`SIPClient` class + This method ends all ongoing calls, then stops the :ref:`SIPClient` class - **call**\ (number: str) -> :ref:`VoIPCall` - Originates a call using PCMU and telephone-event. The *number* argument must be a string, and it returns a :ref:`VoIPCall` class in CallState.DIALING. You should use a while loop to wait until the CallState is ANSWRED. + **call**\ (number: str, payload_types: Optional[List[:ref:`PayloadType`]] = None) -> :ref:`VoIPCall` + Originates a call using the specified *payload_types*, or PCMU and telephone-event by default. The *number* argument must be a string. + + Returns a :ref:`VoIPCall` class in CallState.DIALING. + + **message**\ (number: str, body: str, ctype = "text/plain") -> bool + Sends a MESSAGE request to the *number* with the text of *body*, and the Content-Type header is set to the value of *ctype*. From 79d6762760e5bfbd76fce2a8582a2b9db1581e8f Mon Sep 17 00:00:00 2001 From: TJ Porter Date: Sun, 7 Jan 2024 12:57:23 -0600 Subject: [PATCH 3/8] [ADD] Added missing parameter in VoIPPhoneParameter docs [CHANGE] Changed wording in VoIPPhoneParameter docs [CHANGE] Completely updated SIP.rst --- docs/SIP.rst | 224 ++++++++++++-------------------------------------- docs/VoIP.rst | 8 +- 2 files changed, 56 insertions(+), 176 deletions(-) diff --git a/docs/SIP.rst b/docs/SIP.rst index b0fb78f..a97922c 100644 --- a/docs/SIP.rst +++ b/docs/SIP.rst @@ -1,21 +1,21 @@ SIP - Session Initiation Protocol ################################## -The SIP module receives, parses, and responds to incoming SIP requests/messages. If appropriate, it then forwards them to the *callback* method of :ref:`VoIPPhone`. +The SIP module receives, parses, and responds to incoming SIP requests and responses. If appropriate, it then forwards them to the :ref:`VoIPPhone`. Errors ******* -There are two errors under ``pyVoIP.SIP``. +There are two errors under ``pyVoIP.SIP.error``. .. _InvalidAccountInfoError: -*exception* SIP.\ **InvalidAccountInfoError** - This is thrown when :ref:`SIPClient` gets a bad response when trying to register with the PBX/VoIP server. This error also kills the SIP REGISTER thread, so you will need to call SIPClient.stop() then SIPClient.start(). +*exception* pyVoIP.SIP.\ **InvalidAccountInfoError** + This is thrown when :ref:`SIPClient` gets a bad response when trying to register with the PBX/VoIP server. This error also kills the SIP REGISTER thread, so you will need to call SIPClient.stop() then SIPClient.start(). -.. _sip-parse-error: +.. _SIPParseError: -*exception* SIP.\ **SIPParseError** +*exception* pyVoIP.SIP.\ **SIPParseError** This is thrown when :ref:`SIPMessage` is unable to parse a SIP message/request. .. _Enums: @@ -23,40 +23,37 @@ There are two errors under ``pyVoIP.SIP``. Enums ****** -SIP.\ **SIPMessageType** - SIPMessageType is an IntEnum with two attributes. It's stored in ``SIPMessage.type`` to effectively parse the message. - - SIPMessageType.\ **MESSAGE** +pyVoIP.SIP.message.\ **SIPMessageType** + SIPMessageType is an IntEnum with two members. It's stored in ``SIPMessage.type`` to effectively parse the message. + SIPMessageType.\ **REQUEST** This SIPMessageType is used to signify the message was a SIP request. SIPMessageType.\ **RESPONSE** This SIPMessageType is used to signify the message was a SIP response. -SIP.\ **SIPStatus** - SIPStatus is used for :ref:`SIPMessage`'s with SIPMessageType.RESPONSE. They will not all be listed here, but a complete list can be found on `Wikipedia `_. SIPStatus has the following attributes: - +pyVoIP.SIP.message.\ **SIPStatus** + SIPStatus is used for :ref:`SIPMessage`'s with SIPMessageType.RESPONSE. They will not all be listed here, but a complete list can be found on `Wikipedia `_. SIPStatus has the following attributes: status.\ **value** - This is the integer value of the status. For example, ``SIPStatus.OK.value`` is equal to ``int(200)``. + This is the integer value of the status. For example, ``SIPStatus.OK.value`` is equal to ``int(200)``. status.\ **phrase** - This is the string value of the status, usually written next to the number in a SIP response. For example, ``SIPStatus.TRYING.phrase`` is equal to ``'Trying'``. + This is the string value of the status, usually written next to the number in a SIP response. For example, ``SIPStatus.TRYING.phrase`` is equal to ``"Trying"``. status.\ **description** - This is the string value of the description of the status, it can be useful for debugging. For example, ``SIPStatus.OK.description`` is equal to ``'Request successful'`` Not all responses have a description. - - Here are a few common SIPStatus' and their attributes in the order of value, phrase, description: + This is the string value of the description of the status, it can be useful for debugging. For example, ``SIPStatus.OK.description`` is equal to ``"Request successful"`` Not all responses have a description. +Here are a few common SIPStatus members and their attributes in the order of value, phrase, description: SIPStatus.\ **TRYING** - 100, 'Trying', 'Extended search being performed, may take a significant time' + 100, "Trying", "Extended search being performed, may take a significant time" SIPStatus.\ **RINGING** - 180, 'Ringing', 'Destination user agent received INVITE, and is alerting user of call' + 180, "Ringing", "Destination user agent received INVITE, and is alerting user of call" SIPStatus.\ **OK** - 200, 'OK', 'Request successful' + 200, "OK", "Request successful" SIPStatus.\ **BUSY_HERE** - 486, 'Busy Here', 'Callee is busy' + 486, "Busy Here", "Callee is busy" Classes ******** @@ -66,153 +63,61 @@ Classes SIPClient ========== -The SIPClient class is used to communicate with the PBX/VoIP server. It is responsible for registering with the server, and receiving phone calls. +The SIPClient class is used to communicate with the PBX/VoIP server. It is responsible for registering with the server, and receiving phone calls. -*class* SIP.\ **SIPClient**\ (server: str, port: int, username: str, password: str, myIP="0.0.0.0", myPort=5060, callCallback: Optional[Callable[[SIPMessage], None]] = None) +*class* pyVoIP.SIP.client.\ **SIPClient**\ (server: str, port: int, user: str, credentials_manager: :ref:`CredentialsManager`, bind_ip="0.0.0.0", bind_network="0.0.0.0/0", hostname: Optional[str] = None, remote_hostname: Optional[str] = None, bind_port=5060, call_callback: Optional[Callable[[:ref:`VoIPConnection`, :ref:`SIPMessage`], Optional[str]]] = None, fatal_callback: Optional[Callable[..., None]] = None, transport_mode: :ref:`TransportMode` = TransportMode.UDP, cert_file: Optional[str] = None, key_file: Optional[str] = None, key_password: :ref:`KEY_PASSWORD` = None) The *server* argument is your PBX/VoIP server's IP. - - The *port* argument is your PBX/VoIP server's port. - - The *username* argument is your SIP account username on the PBX/VoIP server. - - The *password* argument is your SIP account password on the PBX/VoIP server. - - The *myIP* argument is used to bind a socket and receive incoming SIP requests and responses. - - The *myPort* argument is the port SIPClient will bind to, to receive incoming SIP requests and responses. The default for this protocol is port 5060, but any port can be used. - - The *callCallback* argument is the callback function for :ref:`VoIPPhone`. VoIPPhone will process the SIP request, and perform the appropriate actions. - - **recv**\ () -> None - This method is called by SIPClient.start() and is responsible for receiving and parsing through SIP requests. **This should not be called by the** :term:`user`. - - **parseMessage**\ (message: :ref:`SIPMessage`) -> None - *Deprecated.* Please use ``parse_message`` instead. - - **parse_message**\ (message: :ref:`SIPMessage`) -> None - This method is called by SIPClient.recv() and is responsible for parsing through SIP responses. **This should not be called by the** :term:`user`. - - **start**\ () -> None - This method is called by :ref:`VoIPPhone`.start(). It starts the REGISTER and recv() threads. It is also what initiates the bound port. **This should not be called by the** :term:`user`. - - **stop**\ () -> None - This method is called by :ref:`VoIPPhone`.stop(). It stops the REGISTER and recv() threads. It will also close the bound port. **This should not be called by the** :term:`user`. - - **genCallID**\ () -> str - *Deprecated.* **This should not be called by the** :term:`user`. - - **gen_call_id**\ () -> str - This method is called by other 'gen' methods when a new Call-ID header is needed. See `RFC 3261 Section 20.8 `_. **This should not be called by the** :term:`user`. - - **lastCallID**\ () -> str - *Deprecated.* **This should not be called by the** :term:`user`. - - **last_call_id**\ () -> str - This method is called by other 'gen' methods when the last Call-ID header is needed. See `RFC 3261 Section 20.8 `_. **This should not be called by the** :term:`user`. - - **genTag**\ () -> str - *Deprecated.* **This should not be called by the** :term:`user`. - - **gen_tag**\ () -> str - This method is called by other 'gen' methods when a new tag is needed. See `RFC 3261 Section 8.2.6.2 `_. **This should not be called by the** :term:`user`. - - **genSIPVersionNotSupported**\ () -> str - *Deprecated.* **This should not be called by the** :term:`user`. - - **gen_sip_version_not_supported**\ () -> str - This method is called by the recv() thread when it has received a SIP message that is not SIP version 2.0. - - **genAuthorization**\ (request: :ref:`SIPMessage`) -> bytes - *Deprecated.* **This should not be called by the** :term:`user`. - **gen_authorization**\ (request: :ref:`SIPMessage`) -> bytes - This calculates the authroization hash in response to the WWW-Authenticate header. See `RFC 3261 Section 20.7 `_. The *request* argument should be a 401 Unauthorized response. **This should not be called by the** :term:`user`. - - **genRegister**\ (request: :ref:`SIPMessage`, deregister: bool = False) -> str - *Deprecated.* **This should not be called by the** :term:`user`. + The *port* argument is your PBX/VoIP server's port. - **gen_register**\ (request: :ref:`SIPMessage`, deregister: bool = False) -> str - This method generates a SIP REGISTER request. The *request* argument should be a 401 Unauthorized response. If *deregister* is set to true, a SIP DE-REGISTER request is generated instead. **This should not be called by the** :term:`user`. - - **genBusy**\ (request: :ref:`SIPMessage`) -> str - *Deprecated.* **This should not be called by the** :term:`user`. + The *user* argument is the user element of the URI. This MAY not be the username which is used for authentication. - **gen_busy**\ (request: :ref:`SIPMessage`) -> str - This method generates a SIP 486 'Busy Here' response. The *request* argument should be a SIP INVITE request. - - **genOk**\ (request: :ref:`SIPMessage`) -> str - *Deprecated.* **This should not be called by the** :term:`user`. + The *credentials_manager* argument is a :ref:`CredentialsManager` instance that stores all usernames and passwords your phone may need. - **gen_ok**\ (request: :ref:`SIPMessage`) -> str - This method generates a SIP 200 'Ok' response. The *request* argument should be a SIP BYE request. - - **genInvite**\ (number: str, sess_id: str, ms: dict[int, dict[str, RTP.\ :ref:`PayloadType`]], sendtype: RTP.\ :ref:`TransmitType`, branch: str, call_id: str) -> str - *Deprecated.* **This should not be called by the** :term:`user`. + The *bind_ip* argument is used to bind SIP and RTP ports to receive incoming calls. Default is to bind to 0.0.0.0, however, this is not recommended. - **gen_invite**\ (number: str, sess_id: str, ms: dict[int, dict[str, RTP.\ :ref:`PayloadType`]], sendtype: RTP.\ :ref:`TransmitType`, branch: str, call_id: str) -> str - This method generates a SIP INVITE request. This is called by SIPClient.invite(). + The *bind_network* argument is used to configure pyVoIP's NAT. pyVoIP uses this to know whether to use the *hostname* or *remote_hostname* when generating SIP requests to in-network and out-of-network devices respectively. Value must be a string with IPv4 CIDR notation. - The *number* argument must be the number being called as a string. + The *hostname* argument is used to generate SIP requests and responses with devices within pyVoIP's *bind_network*. If left as None, the *bind_ip* will be used instead. - The *sess_id* argument must be a unique number. + The *remote_hostname* argument is used to generate SIP requests and responses with devices outside of pyVoIP's *bind_network*. If left as None, pyVoIP will throw a :ref:`NATError` if a request is sent outside of pyVoIP's *bind_network*. - The *ms* argument is a dictionary of the media types to be used. Currently only PCMU and telephone-event is supported. + The *bind_port* argument is the port SIP will bind to to receive SIP requests. The default for this protocol is port 5060, but any port can be used. - The *sendtype* argument must be an instance of :ref:`TransmitType`. + The *call_callback* argument is a function that tells the :ref:`VoIPPhone` instance it is receiving a call. - The *branch* argument must be a unique string starting with "z9hG4bK". See `RFC 3261 Section 8.1.1.7 `_. + The *fatal_callback* argument is a function that tells the :ref:`VoIPPhone` instance there was a fatal error, e.g., failed to register. - The *call_id* argument must be a unique string. See `RFC 3261 Section 8.1.1.4 `_. - - **genRinging**\ (request: :ref:`SIPMessage`) -> str - *Deprecated.* **This should not be called by the** :term:`user`. - - **gen_ringing**\ (request: :ref:`SIPMessage`) -> str - This method generates a SIP 180 'Ringing' response. The *request* argument should be a SIP INVITE request. - - **genAnswer**\ (request: :ref:`SIPMessage`, sess_id: str, ms: list[dict[str, Any]], sendtype: RTP.\ :ref:`TransmitType`) - *Deprecated.* **This should not be called by the** :term:`user`. + The *transport_mode* argument determines whether pyVoIP will use UDP, TCP, or TLS. Value should be a :ref:`TransportMode`. - **gen_answer**\ (request: :ref:`SIPMessage`, sess_id: str, ms: list[dict[str, Any]], sendtype: RTP.\ :ref:`TransmitType`) - This method generates a SIP 200 'OK' response. Which, when in reply to an INVITE request, tells the server the :term:`user` has answered. **This should not be called by the** :term:`user`. - - The *request* argument should be a SIP INVITE request. - - The *sess_id* argument should be a string casted integer. This will be used for the SDP o tag. See `RFC 4566 Section 5.2 `_. The *sess_id* argument will also server as the ** argument in the SDP o tag. - - The *ms* argument should be a list of parsed SDP m tags, found in the :ref:`SIPMessage`.body attribute. This is used to generate the response SDP m tags. See `RFC 4566 Section 5.14 `_. - - The *sendtype* argument should be a RTP.\ :ref:`TransmitType` enum. This will be used to generate the SDP a tag. See `RFC 4567 Section 6 `_. - - **genBye**\ (request: :ref:`SIPMessage`) -> str - *Deprecated.* **This should not be called by the** :term:`user`. + The *cert_file*, *key_file*, and *key_password* arguments are used to load certificates in pyVoIP's server context if using TLS for the transport mode. See `Python's documentation on load_cert_chain `_ for more details. - **gen_bye**\ (request: :ref:`SIPMessage`) -> str - This method generates a SIP BYE request. This is used to end a call. The *request* argument should be a SIP INVITE request. **This should not be called by the** :term:`user`. + **start**\ () -> None + This method starts the SIPClient and registers with the PBX/VoIP server. It is called automatically when :ref:`VoIPPhone` starts. - **genAck**\ (request: :ref:`SIPMessage`) -> str - *Deprecated.* **This should not be called by the** :term:`user`. + **stop**\ () -> None + This method stops the SIPClient and deregisters with the PBX/VoIP server. It is called automatically when :ref:`VoIPPhone` stops. - **gen_ack**\ (request: :ref:`SIPMessage`) -> str - This method generates a SIP ACK response. The *request* argument should be a SIP 401 response. + **send**\ (request: str) -> :ref:`VoIPConnection` + This method starts a new SIP dialog and sends the request using the request to determine its destination. Returns the VoIPConnection to continue the dialog. **invite**\ (number: str, ms: dict[int, dict[str, RTP.\ :ref:`PayloadType`]], sendtype: RTP.\ :ref:`TransmitType`) - This method generates a SIP INVITE request. This method is called by :ref:`VoIPPhone`.call(). + This method generates a SIP INVITE request. This method is called by :ref:`VoIPPhone`.call(). The *number* argument must be the number being called as a string. - The *ms* argument is a dictionary of the media types to be used. Currently only PCMU and telephone-event is supported. + The *ms* argument is a dictionary of the media types to be used. Currently only PCMU and telephone-event is supported. The *sendtype* argument must be an instance of :ref:`TransmitType`. **bye**\ (request: :ref:`SIPMessage`) -> None - This method is called by :ref:`VoIPCall`.hangup(). It calls genBye(), and then transmits the generated request. **This should not be called by the** :term:`user`. + This method is called by :ref:`VoIPCall`.hangup(). It generates a BYE request, and then transmits the generated request. **This should not be called by the** :term:`user`. **deregister**\ () -> bool - This method is called by SIPClient.stop() after the REGISTER thread is stopped. It will generate and transmit a REGISTER request with an Expiration of zero. Telling the PBX/VoIP server it is turning off. **This should not be called by the** :term:`user`. + This method is called by SIPClient.stop() after the REGISTER thread is stopped. It will generate and transmit a REGISTER request with an Expiration of zero. Telling the PBX/VoIP server it is turning off. **This should not be called by the** :term:`user`. **register**\ () -> bool - This method is called by the REGISTER thread. It will generate and transmit a REGISTER request telling the PBX/VoIP server that it will be online for at least 300 seconds. The REGISTER thread will call this function every 295 seconds. **This should not be called by the** :term:`user`. + This method is called by the REGISTER thread. It will generate and transmit a REGISTER request telling the PBX/VoIP server that it will be online for at least 300 seconds. The REGISTER thread will call this function every 295 seconds. **This should not be called by the** :term:`user`. .. _SIPMessage: @@ -221,61 +126,34 @@ SIPMessage The SIPMessage class is used to parse SIP requests and responses and makes them easily processed by other classes. -*class* SIP.\ **SIPMessage**\ (data: bytes) - The *data* argument is the SIP message in bytes. It is then passed to SIPMessage.parse(). +*class* pyVoIP.SIP.message.\ **SIPMessage**\ (data: bytes) + The *data* argument is the SIP message in bytes. It is then passed to SIPMessage.parse(). SIPMessage has the following attributes: SIPMessage.\ **heading** - This attribute is the first line of the SIP message as a string. It contains the SIP Version, and the method/response code. + This attribute is the first line of the SIP message as a string. It contains the SIP Version, and the method/response code. SIPMessage.\ **type** This attribute will be a :ref:`SIPMessageType`. SIPMessage.\ **status** - This attribute will be a :ref:`SIPStatus`. It will be set to ``int(0)`` if the message is a request. + This attribute will be a :ref:`SIPStatus`. It will be set to ``int(0)`` if the message is a request. SIPMessage.\ **method** - This attribute will be a string representation of the method. It will be set to None if the message is a response. + This attribute will be a string representation of the method. It will be set to None if the message is a response. SIPMessage.\ **headers** This attribute is a dictionary of all the headers in the request, and their parsed values. SIPMessage.\ **body** - This attribute is a dictionary of all the SDP tags in the request, and their parsed values. + This attribute is a dictionary of the content of the body. SIPMessage.\ **authentication** - This attribute is a dictionary of a parsed Authentication header. There are two authentication headers: Authorization, and WWW-Authenticate. See RFC 3261 Sections `20.7 `_ and `20.44 `_ respectively. + This attribute is a dictionary of a parsed Authentication header. There are two authentication headers: Authorization, and WWW-Authenticate. See RFC 3261 Sections `20.7 `_ and `20.44 `_ respectively. SIPMessage.\ **raw** This attribute is an unparsed version of the *data* argument, in bytes. **summary**\ () -> str This method returns a string representation of the SIP request. - - **parse**\ (data: bytes) -> None - This method is called by the initialization of the class. It decides the SIPMessageType, and sends it to the corresponding parse function. *Data* is the original *data* argument in the initialization of the class. **This should not be called by the** :term:`user`. - - **parseSIPResponse**\ (data: bytes) -> None - *Deprecated.* **This should not be called by the** :term:`user`. - - **parse_sip_response**\ (data: bytes) -> None - This method is called by parse(). It sets the *header*, *version*, and *status* attributes and may raise a :ref:`SIPParseError` if the SIP response is an unsupported SIP version. It then calls parseHeader() for each header in the request. *Data* is the original *data* argument in the initialization of the class. **This should not be called by the** :term:`user`. - - **parseSIPMessage**\ (data: bytes) -> None - *Deprecated.* **This should not be called by the** :term:`user`. - - **parse_sip_message**\ (data: bytes) -> None - This method is called by parse(). It sets the *header*, *version*, and *method* attributes and may raise a :ref:`SIPParseError` if the SIP request is an unsupported SIP version. It then calls parseHeader() and parseBody() for each header or tag in the request respectively. *Data* is the original *data* argument in the initialization of the class. **This should not be called by the** :term:`user`. - - **parseHeader**\ (header: str, data: str) -> None - *Deprecated.* **This should not be called by the** :term:`user`. - - **parse_header**\ (header: str, data: str) -> None - This method is called by parseSIPResponse() and parseSIPMessage(). The *header* argument is the name of the header, i.e. 'Call-ID' or 'CSeq', represented as a string. The *data* argument is the value of the header, i.e. 'Ogq-T7iBmNozoUu3GL9Lvg..' or '1 INVITE', represented as a string. **This should not be called by the** :term:`user`. - - **parseBody**\ (header: str, data: str) -> None - *Deprecated.* **This should not be called by the** :term:`user`. - - **parse_body**\ (header: str, data: str) -> None - This method is called by parseSIPResponse() and parseSIPMessage(). The *header* argument is the name of the SDP tag, i.e. 'm' or 'a', represented as a string. The *data* argument is the value of the header, i.e. 'audio 56704 RTP/AVP 0' or 'sendrecv', represented as a string. **This should not be called by the** :term:`user`. diff --git a/docs/VoIP.rst b/docs/VoIP.rst index 6d464a6..740852c 100644 --- a/docs/VoIP.rst +++ b/docs/VoIP.rst @@ -141,10 +141,12 @@ The VoIPCall class is used to represent a single VoIP session, which may be to m VoIPPhoneParameter ================== -*class* pyVoIP.VoIP.\ **VoIPPhoneParameter**\ (server: str, port: int, credentials_manager: Optional[:ref:`CredentialsManager`], bind_ip="0.0.0.0", bind_port=5060, bind_network="0.0.0.0/0", hostname: Optional[str] = None, remote_hostname: Optional[str] = None, transport_mode=\ :ref:`TransportMode`.UDP, cert_file: Optional[str] = None, key_file: Optional[str] = None, key_password: Optional[str] = None, rtp_port_low=10000, rtp_port_high=20000, call_class: Type[VoIPCall] = None, sip_class: Type[SIP.SIPClient] = None) - The *server* argument is your PBX/VoIP server's IP, represented as a string. +*class* pyVoIP.VoIP.\ **VoIPPhoneParameter**\ (server: str, port: int, user: str, credentials_manager: Optional[:ref:`CredentialsManager`], bind_ip="0.0.0.0", bind_port=5060, bind_network="0.0.0.0/0", hostname: Optional[str] = None, remote_hostname: Optional[str] = None, transport_mode=\ :ref:`TransportMode`.UDP, cert_file: Optional[str] = None, key_file: Optional[str] = None, key_password: Optional[str] = None, rtp_port_low=10000, rtp_port_high=20000, call_class: Type[VoIPCall] = None, sip_class: Type[SIP.SIPClient] = None) + The *server* argument is your PBX/VoIP server's IP. - The *port* argument is your PBX/VoIP server's port, represented as an integer. + The *port* argument is your PBX/VoIP server's port. + + The *user* argument is the user element of the URI. This MAY not be the username which is used for authentication. The *credentials_manager* argument is a :ref:`CredentialsManager` instance that stores all usernames and passwords your phone may need. From 25906cbc213ebe8c7e4095c1b71c17e500f640f0 Mon Sep 17 00:00:00 2001 From: TJ Porter Date: Sun, 7 Jan 2024 12:59:43 -0600 Subject: [PATCH 4/8] [FIX] Added new line to fix docs error --- docs/SIP.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/SIP.rst b/docs/SIP.rst index a97922c..491cf9a 100644 --- a/docs/SIP.rst +++ b/docs/SIP.rst @@ -25,6 +25,7 @@ Enums pyVoIP.SIP.message.\ **SIPMessageType** SIPMessageType is an IntEnum with two members. It's stored in ``SIPMessage.type`` to effectively parse the message. + SIPMessageType.\ **REQUEST** This SIPMessageType is used to signify the message was a SIP request. From 333ad09831b02819b5c3974668577382c51dc93f Mon Sep 17 00:00:00 2001 From: TJ Porter Date: Sun, 7 Jan 2024 23:38:21 -0600 Subject: [PATCH 5/8] [ADD] Added Credentials.rst doc --- docs/Credentials.rst | 28 ++++++++++++++++++ docs/RTP.rst | 67 +++++++++++++++++++++++--------------------- docs/index.rst | 1 + 3 files changed, 64 insertions(+), 32 deletions(-) create mode 100644 docs/Credentials.rst diff --git a/docs/Credentials.rst b/docs/Credentials.rst new file mode 100644 index 0000000..dd82afb --- /dev/null +++ b/docs/Credentials.rst @@ -0,0 +1,28 @@ +Credentials +########### + +Since SIP requests can traverse multiple servers and can receive multiple challenges, the Credentials Manager was made to store multiple passwords and pyVoIP will use the appropriate password upon request. + +Per `RFC 3261 Section 22.1 `_, SIP uses authentication similar to HTTP authentication (`RFC 2617 `_), with the main difference being ``The realm string alone defines the protection domain.``. However, some services always use the same domain. For example, if you need to authenticate with two seperate Asterisk servers, the realm will almost certainly be ``asterisk`` for both, despite being otherwise unrelated servers. For that reason, the Credentials Manager also supports server filtering. + +.. _CredentialsManager: + +CredentialsManager +================== + +*class* pyVoIP.credentials.\ **CredentialsManager**\ () + **add**\ (username: str, password: str, server: Optional[str] = None, realm: Optional[str] = None, user: Optional[str] = None) -> None + This method registers a username and password combination with the Credentials Manager. + + The *username* argument is the username that will be used in the Authentication header and digest calculation. + + The *password* argument is the password that will be used in the Authentication header digest calculation. + + The *server* argument is used to determine the correct credentials when challenged for authentication. If *server* is left as ``None``, the credentials may be selected with any server. + + The *realm* argument is used to determine the correct credentials when challenged for authentication. If *realm* is left as ``None``, the credentials may be selected with any realm. + + The *user* argument is used to determine the correct credentials when challenged for authentication. If *user* is left as ``None``, the credentials may be selected with any user. The *user* argument is the user in the SIP URI, **not** the username used in authentication. + + **get**\ (server: str, realm: str, user: str) -> Dict[str, str] + Looks for credentials that match the server, realm, and user in that order. If no matchng credentials are found, this will return anonymous credentials as a server MAY accept them per `RFC 3261 Section 22.1 `_. diff --git a/docs/RTP.rst b/docs/RTP.rst index 235a46b..c178ba6 100644 --- a/docs/RTP.rst +++ b/docs/RTP.rst @@ -3,10 +3,13 @@ RTP - Real-time Transport Protocol The RTP module recives and transmits sound and phone-event data for a particular phone call. -The RTP module has two methods that are used by various classes for packet parsing. +Functions +********* + +The RTP module has two functions that are used by various classes for packet parsing. RTP.\ **byte_to_bits**\ (byte: bytes) -> str - This method converts a single byte into an eight character string of ones and zeros. The *byte* argument must be a single byte. + This method converts a single byte into an eight character string of ones and zeros. The *byte* argument must be a single byte. RTP.\ **add_bytes**\ (bytes: bytes) -> int This method takes multiple bytes and adds them together into an integer. @@ -15,19 +18,19 @@ Errors ******* *exception* RTP.\ **DynamicPayloadType** - This may be thrown when you try to int cast a dynamic PayloadType. Most PayloadTypes have a number assigned in `RFC 3551 Section 6 `_. However, some are considered to be 'dynamic' meaning the PBX/VoIP server will pick an available number, and define it. + This may be thrown when you try to int cast a dynamic PayloadType. Most PayloadTypes have a number assigned in `RFC 3551 Section 6 `_. However, some are considered to be 'dynamic' meaning the PBX/VoIP server will pick an available number, and define it. *exception* RTP.\ **RTPParseError** - This is thrown by :ref:`RTPMessage` when unable to parse a RTP message. It may also be thrown by `RTPClient` when it's unable to encode or decode the RTP packet payload. + This is thrown by :ref:`RTPMessage` when unable to parse a RTP message. It may also be thrown by `RTPClient` when it's unable to encode or decode the RTP packet payload. Enums ******* RTP.\ **RTPProtocol** - RTPProtocol is an Enum with three attributes. It defines the method that packets are to be sent with. Currently, only AVP is supported. + RTPProtocol is an Enum with three attributes. It defines the method that packets are to be sent with. Currently, only AVP is supported. RTPProtocol.\ **UDP** - This means the audio should be sent with pure UDP. Returns ``'udp'`` when string casted. + This means the audio should be sent with pure UDP. Returns ``'udp'`` when string casted. RTPProtocol.\ **AVP** This means the audio should be sent with RTP Audio/Video Protocol described in :RFC:`3551`. Returns ``'RTP/AVP'`` when string casted. @@ -38,27 +41,27 @@ RTP.\ **RTPProtocol** .. _transmittype: RTP.\ **TransmitType** - TransmitType is an Enum with four attributes. It describes how the :ref:`RTPClient` should act. + TransmitType is an Enum with four attributes. It describes how the :ref:`RTPClient` should act. TransmitType.\ **RECVONLY** - This means the RTPClient should only recive audio, not transmit it. Returns ``'recvonly'`` when string casted. + This means the RTPClient should only recive audio, not transmit it. Returns ``'recvonly'`` when string casted. TransmitType.\ **SENDRECV** - This means the RTPClient should send and receive audio. Returns ``'sendrecv'`` when string casted. + This means the RTPClient should send and receive audio. Returns ``'sendrecv'`` when string casted. TransmitType.\ **SENDONLY** - This means the RTPClient should only send audio, not receive it. Returns ``'sendonly'`` when string casted. + This means the RTPClient should only send audio, not receive it. Returns ``'sendonly'`` when string casted. TransmitType.\ **INACTIVE** - This means the RTP client should not send or receive audio, and instead wait to be activated. Returns ``'inactive'`` when string casted. + This means the RTP client should not send or receive audio, and instead wait to be activated. Returns ``'inactive'`` when string casted. .. _payload-type: RTP.\ **PayloadType** - PayloadType is an Enum with multiple attributes. It described the list of attributes in `RFC 3551 Section 6 `_. Currently, only one dynamic event is assigned: telephone-event. Telephone-event is used for sending and recieving DTMF codes. There are a few conflicing names in the RFC as they're the same codec with varrying options so we will go over the conflicts here. PayloadType has the following attributes: + PayloadType is an Enum with multiple attributes. It described the list of attributes in `RFC 3551 Section 6 `_. Currently, only one dynamic event is assigned: telephone-event. Telephone-event is used for sending and recieving DTMF codes. There are a few conflicing names in the RFC as they're the same codec with varrying options so we will go over the conflicts here. PayloadType has the following attributes: type.\ **value** - This is either the number assigned as PT in the RFC 3551 Section 6 chart, or it is the encoding name if it is dynamic. Int casting the PayloadType will return this number, or raise a DynamicPayloadType error if the protocol is dynamic. + This is either the number assigned as PT in the RFC 3551 Section 6 chart, or it is the encoding name if it is dynamic. Int casting the PayloadType will return this number, or raise a DynamicPayloadType error if the protocol is dynamic. type.\ **rate** This will return the clock rate of the codec. @@ -67,7 +70,7 @@ RTP.\ **PayloadType** This will return the number of channels the used in the codec, or for Non-codecs like telephone-event, it will return zero. type.\ **description** - This will return the encoding name of the payload. String casting the PayloadType will return this value. + This will return the encoding name of the payload. String casting the PayloadType will return this value. PayloadType.\ **DVI4_8000** This variation of the DVI4 Codec has the attributes: value 5, rate 8000, channel 1, description "DVI4" @@ -88,7 +91,7 @@ RTP.\ **PayloadType** This variation of the L16 Codec has the attributes: value 11, rate 44100, channel 2, description "L16" PayloadType.\ **EVENT** - This is the dynamic non-codec 'telephone-event'. Telephone-event is used for sending and receiving DTMF codes. + This is the dynamic non-codec 'telephone-event'. Telephone-event is used for sending and receiving DTMF codes. Classes ********* @@ -103,13 +106,13 @@ The RTPPacketManager class utilizes an ``io.ByteIO`` that stores either received RTP.\ **RTPPacketManager**\ () **read**\ (length=160) -> bytes - Reads *length* bytes from the ByteIO. This will always return the length requested, and will append ``b'\x80'``'s onto the end of the available bytes to achieve this length. + Reads *length* bytes from the ByteIO. This will always return the length requested, and will append ``b'\x80'``'s onto the end of the available bytes to achieve this length. **rebuild**\ (reset: bool, offset=0, data=b'') -> None - This rebuilds the ByteIO if packets are sent out of order. Setting the argument *reset* to ``True`` will wipe all data in the ByteIO and insert in the data in the argument *data* at the position in the argument *offset*. + This rebuilds the ByteIO if packets are sent out of order. Setting the argument *reset* to ``True`` will wipe all data in the ByteIO and insert in the data in the argument *data* at the position in the argument *offset*. **write**\ (offset: int, data: bytes) -> None - Writes the data in the argument *data* to the ByteIO at the position in the argument *offset*. RTP data comes with a timestamp that is passed as the offset in this case. This makes it so a hole left by delayed packets can be filled later. If a packet with a timestamp sooner than any other timestamp received, it will rebuild the ByteIO with the new data. If this new position is over 100,000 bytes before the earliest byte, the ByteIO is completely wiped and starts over. This is to prevent Overflow errors. + Writes the data in the argument *data* to the ByteIO at the position in the argument *offset*. RTP data comes with a timestamp that is passed as the offset in this case. This makes it so a hole left by delayed packets can be filled later. If a packet with a timestamp sooner than any other timestamp received, it will rebuild the ByteIO with the new data. If this new position is over 100,000 bytes before the earliest byte, the ByteIO is completely wiped and starts over. This is to prevent Overflow errors. .. _RTPMessage: @@ -122,7 +125,7 @@ RTP.\ **RTPMessage**\ (data: bytes, assoc: dict[int, :ref:`PayloadType` as the value. This way RTPMessage can determine what number a dynamic payload is. This association dictionary is generated by :ref:`VoIPCall`. + The *assoc* argument is a dictionary, using the payload number as a key and a :ref:`PayloadType` as the value. This way RTPMessage can determine what number a dynamic payload is. This association dictionary is generated by :ref:`VoIPCall`. RTPMessage has attributes that come from `RFC 3550 Section 5.1 `_. RTPMessage has the following attributes: @@ -163,7 +166,7 @@ RTP.\ **RTPMessage**\ (data: bytes, assoc: dict[int, :ref:`PayloadType None - This method is called by the initialization of the class. It determines the RTP version, whether the packet has padding, has a header extension, and other information about the backet. + This method is called by the initialization of the class. It determines the RTP version, whether the packet has padding, has a header extension, and other information about the backet. .. _RTPClient: @@ -174,7 +177,7 @@ The RTPClient is used to send and receive RTP packets and encode/decode the audi *class* RTP.\ **RTPClient**\ (assoc: dict[int, :ref:`PayloadType`], inIP: str, inPort: int, outIP: str, outPort: int, sendrecv: :ref:`TransmitType`, dtmf: Optional[Callable[[str], None] = None): - The *assoc* argument is a dictionary, using the payload number as a key and a :ref:`PayloadType` as the value. This way, RTPMessage can determine what a number a dynamic payload is. This association dictionary is generated by :ref:`VoIPCall`. + The *assoc* argument is a dictionary, using the payload number as a key and a :ref:`PayloadType` as the value. This way, RTPMessage can determine what a number a dynamic payload is. This association dictionary is generated by :ref:`VoIPCall`. The *inIP* argument is used to receive incoming RTP message. @@ -184,7 +187,7 @@ The RTPClient is used to send and receive RTP packets and encode/decode the audi The *outPort* argument is used to transmit RTP packets. - The *sendrecv* argument describes how the RTPClient should act. Please reference :ref:`TransmitType` for more details. + The *sendrecv* argument describes how the RTPClient should act. Please reference :ref:`TransmitType` for more details. The *dtmf* argument is set to the callback :ref:`VoIPCall`.dtmfCallback(). @@ -192,13 +195,13 @@ The RTPClient is used to send and receive RTP packets and encode/decode the audi This method is called by :ref:`VoIPCall`.answer(). It starts the recv() and trans() threads. It is also what initiates the bound port. **This should not be called by the** :term:`user`. **stop**\ () -> None - This method is called by :ref:`VoIPCall`.hangup() and :ref:`VoIPCall`.bye(). It stops the recv() and trans() threads. It will also close the bound port. **This should not be called by the** :term:`user`. + This method is called by :ref:`VoIPCall`.hangup() and :ref:`VoIPCall`.bye(). It stops the recv() and trans() threads. It will also close the bound port. **This should not be called by the** :term:`user`. **read**\ (length=160, blocking=True) -> bytes - This method is called by :ref:`VoIPCall`.readAudio(). It reads linear/raw audio data from the received buffer. Returns *length* amount of bytes. Default length is 160 as that is the amount of bytes sent per PCMU/PCMA packet. When *blocking* is set to true, this function will not return until data is available. When *blocking* is set to false and data is not available, this function will return bytes(length). + This method is called by :ref:`VoIPCall`.readAudio(). It reads linear/raw audio data from the received buffer. Returns *length* amount of bytes. Default length is 160 as that is the amount of bytes sent per PCMU/PCMA packet. When *blocking* is set to true, this function will not return until data is available. When *blocking* is set to false and data is not available, this function will return bytes(length). **write**\ (data: bytes) -> None - This method is called by :ref:`VoIPCall`.writeAudio(). It queues the data written to be sent to the :term:`client`. + This method is called by :ref:`VoIPCall`.writeAudio(). It queues the data written to be sent to the :term:`client`. **recv**\ () -> None This method is called by RTPClient.start() and is responsible for receiving and parsing through RTP packets. **This should not be called by the** :term:`user`. @@ -210,35 +213,35 @@ The RTPClient is used to send and receive RTP packets and encode/decode the audi *Deprecated.* Please use ``parse_packet`` instead. **parse_packet**\ (packet: bytes) -> None - This method is called by the recv() thread. It converts the argument *packet* into a :ref:`RTPMessage`, then sends it to the proper parse function depending on the :ref:`PayloadType`. + This method is called by the recv() thread. It converts the argument *packet* into a :ref:`RTPMessage`, then sends it to the proper parse function depending on the :ref:`PayloadType`. **encodePacket**\ (payload: bytes) -> bytes *Deprecated.* Please use ``encode_packet`` instead. **encode_packet**\ (payload: bytes) -> bytes - This method is called by the trans() thread. It encoded the argument *payload* into the prefered codec. Currently, PCMU is the hardcoded prefered codec. The trans() thread will use the payload to create the RTP packet before transmitting. + This method is called by the trans() thread. It encoded the argument *payload* into the prefered codec. Currently, PCMU is the hardcoded prefered codec. The trans() thread will use the payload to create the RTP packet before transmitting. **parsePCMU**\ (packet: :ref:`RTPMessage`) -> None *Deprecated.* Please use ``parse_pcmu`` instead. **parse_pcmu**\ (packet: :ref:`RTPMessage`) -> None - This method is called by parse_packet(). It will decode the *packet*'s payload from PCMU to linear/raw audio and write it to the incoming :ref:`RTPPacketManager`. + This method is called by parse_packet(). It will decode the *packet*'s payload from PCMU to linear/raw audio and write it to the incoming :ref:`RTPPacketManager`. **encodePCMU**\ (payload: bytes) -> bytes - This method is called by encode_packet(). It will encode the *payload* into the PCMU audio codec. + This method is called by encode_packet(). It will encode the *payload* into the PCMU audio codec. **parsePCMA**\ (packet: :ref:`RTPMessage`) -> None - This method is called by parse_packet(). It will decode the *packet*'s payload from PCMA to linear/raw audio and write it to the incoming :ref:`RTPPacketManager`. + This method is called by parse_packet(). It will decode the *packet*'s payload from PCMA to linear/raw audio and write it to the incoming :ref:`RTPPacketManager`. **encodePCMA**\ (payload: bytes) -> bytes *Deprecated.* Please use ``encode_pcma`` instead. **encode_pcma**\ (payload: bytes) -> bytes - This method is called by encode_packet(). It will encode the *payload* into the PCMA audio codec. + This method is called by encode_packet(). It will encode the *payload* into the PCMA audio codec. **parseTelephoneEvent**\ (packet: :ref:`RTPMessage`) -> None *Deprecated* Please use ``parse_telephone_event`` instead. **parse_telephone_event**\ (packet: :ref:`RTPMessage`) -> None - This method is called by parse_packet(). It will decode the *packet*'s payload from the telephone-event non-codec to the string representation of the event. It will then call :ref:`VoIPCall`.dtmf_callback(). + This method is called by parse_packet(). It will decode the *packet*'s payload from the telephone-event non-codec to the string representation of the event. It will then call :ref:`VoIPCall`.dtmf_callback(). diff --git a/docs/index.rst b/docs/index.rst index 3da0a8b..0757ae2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -29,5 +29,6 @@ In this documentation we will use the following terms: Examples Globals VoIP + Credentials SIP RTP From 8c4894bf6e07fe90d6a6d0c04e3cc608df30c9d7 Mon Sep 17 00:00:00 2001 From: TJ Porter Date: Wed, 10 Jan 2024 01:11:35 -0600 Subject: [PATCH 6/8] [ADD] Added Networking.rst doc [ADD] Added Types.rst doc [CHANGE] Changed all references to RFCs as a whole to use the :RFC: tag and not a link. Links are still used if it references a specific section [CHANGE] Standardized references to Python's documentation [CHANGE] Changed RTP.rst items to show full import path (pyVoIP.RTP.*) [FIX] Fixed some incorrect documentation in RTP.rst [FIX] Fixed labels for SIPMessageType and SIPStatus in SIP.rst [REMOVE] Removed deprecated methods from RTP.rst --- docs/Credentials.rst | 2 +- docs/Globals.rst | 10 ++-- docs/Networking.rst | 124 +++++++++++++++++++++++++++++++++++++++++++ docs/RTP.rst | 41 +++++--------- docs/SIP.rst | 22 ++++---- docs/Types.rst | 24 +++++++++ docs/VoIP.rst | 16 +++--- docs/index.rst | 2 + 8 files changed, 189 insertions(+), 52 deletions(-) create mode 100644 docs/Networking.rst create mode 100644 docs/Types.rst diff --git a/docs/Credentials.rst b/docs/Credentials.rst index dd82afb..f63a657 100644 --- a/docs/Credentials.rst +++ b/docs/Credentials.rst @@ -3,7 +3,7 @@ Credentials Since SIP requests can traverse multiple servers and can receive multiple challenges, the Credentials Manager was made to store multiple passwords and pyVoIP will use the appropriate password upon request. -Per `RFC 3261 Section 22.1 `_, SIP uses authentication similar to HTTP authentication (`RFC 2617 `_), with the main difference being ``The realm string alone defines the protection domain.``. However, some services always use the same domain. For example, if you need to authenticate with two seperate Asterisk servers, the realm will almost certainly be ``asterisk`` for both, despite being otherwise unrelated servers. For that reason, the Credentials Manager also supports server filtering. +Per `RFC 3261 Section 22.1 `_, SIP uses authentication similar to HTTP authentication (:RFC:`2617`), with the main difference being ``The realm string alone defines the protection domain.``. However, some services always use the same domain. For example, if you need to authenticate with two seperate Asterisk servers, the realm will almost certainly be ``asterisk`` for both, despite being otherwise unrelated servers. For that reason, the Credentials Manager also supports server filtering. .. _CredentialsManager: diff --git a/docs/Globals.rst b/docs/Globals.rst index 40b507d..2a8fab9 100644 --- a/docs/Globals.rst +++ b/docs/Globals.rst @@ -13,10 +13,10 @@ pyVoIP.\ **TRANSMIT_DELAY_REDUCTION** = 0.0 The higher this variable is, the more often RTP packets are sent. This *should* only ever need to be 0.0. However, when testing on Windows, there has sometimes been jittering, setting this to 0.75 fixed this in testing, but you may need to tinker with this number on a per-system basis. pyVoIP.\ **ALLOW_BASIC_AUTH** = False - Controls whether Basic authentication (`RFC 7617 `_) is allowed for SIP authentication. Basic authentication is deprecated as it will send your password in plain-text, likely in the clear (unencrypted) as well. As such this is disabled be default. + Controls whether Basic authentication (:RFC:`7617`) is allowed for SIP authentication. Basic authentication is deprecated as it will send your password in plain-text, likely in the clear (unencrypted) as well. As such this is disabled be default. pyVoIP.\ **ALLOW_MD5_AUTH** = True - MD5 Digest authentication is deprecated per `RFC 8760 `_ as it a weak hash. However, it is still used often so it is enabled by default. + MD5 Digest authentication is deprecated per `RFC 8760 Section 3 `_ as it a weak hash. However, it is still used often so it is enabled by default. pyVoIP.\ **REGISTER_FAILURE_THRESHOLD** = 3 If registration fails this many times, VoIPPhone's status will be set to FAILED and the phone will stop. @@ -27,12 +27,12 @@ pyVoIP.\ **ALLOW_TLS_FALLBACK** = False This feature is currently not implemented. pyVoIP.\ **TLS_CHECK_HOSTNAME** = True - Is used to create SSLContexts. See `Python's documentation `_ on this feature. + Is used to create SSLContexts. See Python's documentation on `check_hostname `_ for more details. You should use the :ref:`set_tls_security ` function to change this variable. pyVoIP.\ **TLS_VERIFY_MODE** = True - Is used to create SSLContexts. See `Python's documentation `_ on this feature. + Is used to create SSLContexts. See Python's documentation on `verify_mode `_ for more details. You should use the :ref:`set_tls_security ` function to change this variable. @@ -44,5 +44,5 @@ Global Functions .. _set_tls_security: -pyVoIP.\ **set_tls_security**\ (verify_mode: `ssl.VerifyMode `_) -> None +pyVoIP.\ **set_tls_security**\ (verify_mode: `VerifyMode `_) -> None This method ensures that TLS_CHECK_HOSTNAME and TLS_VERIFY_MODE are set correctly depending on the TLS certificate verification settings you want to use. diff --git a/docs/Networking.rst b/docs/Networking.rst new file mode 100644 index 0000000..7d08174 --- /dev/null +++ b/docs/Networking.rst @@ -0,0 +1,124 @@ +Networking +########## + +VoIP uses a lot of complex networking in order to accomplish its tasks. The networking module handles all of these complex operations. + +Errors +******* + +.. _NATError: + +*exception* pyVoIP.networking.nat.\ **NATError** + This is thrown when :ref:`NAT` is either unable to resolve a hostname or when a remote hostname has not been specified and an attempt was made to connect to a remote host. + +Enums +****** + +.. _AddressType: + +pyVoIP.networking.nat.\ **AddressType** + Used for determining remote or local tags in SIP messages. + + AddressType.\ **REMOTE** + + AddressType.\ **LOCAL** + +.. _TransportMode: + +pyVoIP.sock.transport.\ **TransportMode** + TransportMode is used by pyVoIP to determine what communication protocol to use. TransportMode has the following properties: + + TransportMode.\ **value** + This is the string value of the TransportMode. For example, ``UDP`` or ``TLS``. + + TransportMode.\ **socket_type** + This is the `SocketKind `_ associated with the TransportMode. + + TransportMode.\ **tls_mode** + This is either `PROTOCOL_TLS `_ when using TLS or None otherwise. + + Here is a list of current supported transport modes: + + TransportMode.\ **UDP** + "UDP", `SOCK_DGRAM `_, None + + TransportMode.\ **TCP** + "TCP", `SOCK_STREAM `_, None + + TransportMode.\ **TLS** + "TLS", `SOCK_STREAM `_, `PROTOCOL_TLS `_ + +Classes +******** + +.. _NAT: + +NAT +=== + +The NAT class is used automatically understand and translate IPs and hostnames for LAN to WAN connections and vice versa. + +*class* pyVoIP.networking.nat.\ **NAT**\ (bind_ip: str, network: str, hostname: Optional[str] = None, remote_hostname: Optional[str] = None) + The *bind_ip* argument is the IP address that pyVoIP will bind its sockets to. + + The *network* argument is used to know whether to use the *hostname* or *remote_hostname* when generating SIP requests to in-network and out-of-network devices respectively. Value must be a string with IPv4 CIDR notation. + + The *hostname* argument is used to generate SIP requests and responses with devices within pyVoIP's *bind_network*. If left as None, the *bind_ip* will be used instead. + + The *remote_hostname* argument is used to generate SIP requests and responses with devices outside of pyVoIP's *bind_network*. If left as None, pyVoIP will throw a :ref:`NATError` if a request is sent outside of pyVoIP's *bind_network*. + + **get_host**\ (host: str) -> str + This method return the hostname another :term:`client` needs to connect to us. + + **check_host**\ (host: str) -> :ref:`AddressType` + This method determine if a host is a remote computer or not. + +.. _VoIPSocket: + +VoIPSocket +========== + +The VoIPSocket class is the phone's main SIP socket. It receives and processes all new dialogs, and all messages if using :ref:`TransportMode`.UDP. + +*class* pyVoIP.sock.sock.\ **VoIPSocket**\ (mode: :ref:`TransportMode`, bind_ip: str, bind_port: int, sip: :ref:`SIPClient`, cert_file: Optional[str] = None, key_file: Optional[str] = None, key_password: :ref:`KEY_PASSWORD` = None) + The *TransportMode* argument is used to determine what communication protocol to use. + + The *bind_ip* argument is the IP address that pyVoIP will bind its sockets to. + + The *bind_port* argument is the port SIP will bind to to receive SIP requests. + + The *sip* argument is a :ref:`SIPClient` instance reference. + + The *cert_file*, *key_file*, and *key_password* arguments are used to load certificates in pyVoIP's server context if using TLS for the transport mode. See Python's documentation on `load_cert_chain `_ for more details. + + **get_database_dump**\ (pretty=False) -> str + If using UDP, all messages and dialog states are stored in an in-memory sqlite3 database. This function will return a string with all entries from the dialogs (listening) table and unread messages (msgs) table. If *pretty* is set to true, it will use Python's pprint module to make the test reader friendly for a print statement. If *pretty* is set to false, it will return JSON instead. + + **send**\ (data: bytes) -> :ref:`VoIPConnection` + Creates a new connection / dialog, sends the data, then returns the socket. + +.. _VoIPConnection: + +VoIPConnection +============== + +The VoIPConnecion class is a wrapper for Python's sockets. Since UDP, TCP, and TLS sockets all have different quarks in Python, this class consolidates everything into one interface. For UDP, VoIPConnection will pull messages from :ref:`VoIPSocket`'s database. + +*class* pyVoIP.sock.sock.\ **VoIPConnection**\ (voip_sock: :ref:`VoIPSocket`, conn: Optional[:ref:`SOCKETS`, message: :ref:`SIPMessage`) + The *voiop_socket* argument is a :ref:`VoIPSocket` instance reference. + + The *conn* argument is the underlying Python socket. + + The *message* argument is the :ref:`SIPMessage` used to initate the dialog. + + **send**\ (data: Union[bytes, str]) -> None + Sends *data* to the :term:`client`. If *data* is a string, it will be UTF8 encoded first. + + **peak**\ () -> bytes + Calls ``recv`` with *peak* set to true. + + **recv**\ (nbytes=8192, timeout=0, peak=False) -> bytes + Receives the next *nbytes* from the socket. The *timeout* argument is in seconds, and if set to ``0`` it will not timeout. If the *peak* argument is set to True, it will receive the next *nbytes* from the socket and return them, however, the same data will be returned upon the next call of ``recv``. + + **close**\ () -> None + Closes the socket. diff --git a/docs/RTP.rst b/docs/RTP.rst index c178ba6..a22e82f 100644 --- a/docs/RTP.rst +++ b/docs/RTP.rst @@ -8,25 +8,25 @@ Functions The RTP module has two functions that are used by various classes for packet parsing. -RTP.\ **byte_to_bits**\ (byte: bytes) -> str +pyVoIP.RTP.\ **byte_to_bits**\ (byte: bytes) -> str This method converts a single byte into an eight character string of ones and zeros. The *byte* argument must be a single byte. -RTP.\ **add_bytes**\ (bytes: bytes) -> int +pyVoIP.RTP.\ **add_bytes**\ (bytes: bytes) -> int This method takes multiple bytes and adds them together into an integer. Errors ******* -*exception* RTP.\ **DynamicPayloadType** +*exception* pyVoIP.RTP.\ **DynamicPayloadType** This may be thrown when you try to int cast a dynamic PayloadType. Most PayloadTypes have a number assigned in `RFC 3551 Section 6 `_. However, some are considered to be 'dynamic' meaning the PBX/VoIP server will pick an available number, and define it. -*exception* RTP.\ **RTPParseError** +*exception* pyVoIP.RTP.\ **RTPParseError** This is thrown by :ref:`RTPMessage` when unable to parse a RTP message. It may also be thrown by `RTPClient` when it's unable to encode or decode the RTP packet payload. Enums ******* -RTP.\ **RTPProtocol** +pyVoIP.RTP.\ **RTPProtocol** RTPProtocol is an Enum with three attributes. It defines the method that packets are to be sent with. Currently, only AVP is supported. RTPProtocol.\ **UDP** @@ -40,7 +40,7 @@ RTP.\ **RTPProtocol** .. _transmittype: -RTP.\ **TransmitType** +pyVoIP.RTP.\ **TransmitType** TransmitType is an Enum with four attributes. It describes how the :ref:`RTPClient` should act. TransmitType.\ **RECVONLY** @@ -57,11 +57,11 @@ RTP.\ **TransmitType** .. _payload-type: -RTP.\ **PayloadType** +pyVoIP.RTP.\ **PayloadType** PayloadType is an Enum with multiple attributes. It described the list of attributes in `RFC 3551 Section 6 `_. Currently, only one dynamic event is assigned: telephone-event. Telephone-event is used for sending and recieving DTMF codes. There are a few conflicing names in the RFC as they're the same codec with varrying options so we will go over the conflicts here. PayloadType has the following attributes: type.\ **value** - This is either the number assigned as PT in the RFC 3551 Section 6 chart, or it is the encoding name if it is dynamic. Int casting the PayloadType will return this number, or raise a DynamicPayloadType error if the protocol is dynamic. + This is either the number assigned as PT in the `RFC 3551 Section 6 chart `_, or it is the encoding name if it is dynamic. Int casting the PayloadType will return this number, or raise a DynamicPayloadType error if the protocol is dynamic. type.\ **rate** This will return the clock rate of the codec. @@ -103,7 +103,7 @@ RTPPacketManager The RTPPacketManager class utilizes an ``io.ByteIO`` that stores either received payloads, or raw audio data waiting to be transmitted. -RTP.\ **RTPPacketManager**\ () +pyVoIP.RTP.\ **RTPPacketManager**\ () **read**\ (length=160) -> bytes Reads *length* bytes from the ByteIO. This will always return the length requested, and will append ``b'\x80'``'s onto the end of the available bytes to achieve this length. @@ -121,7 +121,7 @@ RTPMessage The RTPMessage class is used to parse RTP packets and makes them easily processed by the :ref:`RTPClient`. -RTP.\ **RTPMessage**\ (data: bytes, assoc: dict[int, :ref:`PayloadType`]) +pyVoIP.RTP.\ **RTPMessage**\ (data: bytes, assoc: dict[int, :ref:`PayloadType`]) The *data* argument is the received RTP packet in bytes. @@ -175,7 +175,7 @@ RTPClient The RTPClient is used to send and receive RTP packets and encode/decode the audio codecs. -*class* RTP.\ **RTPClient**\ (assoc: dict[int, :ref:`PayloadType`], inIP: str, inPort: int, outIP: str, outPort: int, sendrecv: :ref:`TransmitType`, dtmf: Optional[Callable[[str], None] = None): +*class* pyVoIP.RTP.\ **RTPClient**\ (assoc: dict[int, :ref:`PayloadType`], inIP: str, inPort: int, outIP: str, outPort: int, sendrecv: :ref:`TransmitType`, dtmf: Optional[Callable[[str], None] = None): The *assoc* argument is a dictionary, using the payload number as a key and a :ref:`PayloadType` as the value. This way, RTPMessage can determine what a number a dynamic payload is. This association dictionary is generated by :ref:`VoIPCall`. @@ -209,39 +209,24 @@ The RTPClient is used to send and receive RTP packets and encode/decode the audi **trans**\ () -> None This method is called by RTPClient.start() and is responsible for transmitting RTP packets. **This should not be called by the** :term:`user`. - **parsePacket**\ (packet: bytes) -> None - *Deprecated.* Please use ``parse_packet`` instead. - **parse_packet**\ (packet: bytes) -> None This method is called by the recv() thread. It converts the argument *packet* into a :ref:`RTPMessage`, then sends it to the proper parse function depending on the :ref:`PayloadType`. - **encodePacket**\ (payload: bytes) -> bytes - *Deprecated.* Please use ``encode_packet`` instead. - **encode_packet**\ (payload: bytes) -> bytes This method is called by the trans() thread. It encoded the argument *payload* into the prefered codec. Currently, PCMU is the hardcoded prefered codec. The trans() thread will use the payload to create the RTP packet before transmitting. - **parsePCMU**\ (packet: :ref:`RTPMessage`) -> None - *Deprecated.* Please use ``parse_pcmu`` instead. - **parse_pcmu**\ (packet: :ref:`RTPMessage`) -> None This method is called by parse_packet(). It will decode the *packet*'s payload from PCMU to linear/raw audio and write it to the incoming :ref:`RTPPacketManager`. - **encodePCMU**\ (payload: bytes) -> bytes + **encode_pcmu**\ (packet: bytes) -> bytes This method is called by encode_packet(). It will encode the *payload* into the PCMU audio codec. - **parsePCMA**\ (packet: :ref:`RTPMessage`) -> None + **parse_pcma**\ (packet: :ref:`RTPMessage`) -> None This method is called by parse_packet(). It will decode the *packet*'s payload from PCMA to linear/raw audio and write it to the incoming :ref:`RTPPacketManager`. - **encodePCMA**\ (payload: bytes) -> bytes - *Deprecated.* Please use ``encode_pcma`` instead. - **encode_pcma**\ (payload: bytes) -> bytes This method is called by encode_packet(). It will encode the *payload* into the PCMA audio codec. - **parseTelephoneEvent**\ (packet: :ref:`RTPMessage`) -> None - *Deprecated* Please use ``parse_telephone_event`` instead. - **parse_telephone_event**\ (packet: :ref:`RTPMessage`) -> None This method is called by parse_packet(). It will decode the *packet*'s payload from the telephone-event non-codec to the string representation of the event. It will then call :ref:`VoIPCall`.dtmf_callback(). diff --git a/docs/SIP.rst b/docs/SIP.rst index 491cf9a..22fab6e 100644 --- a/docs/SIP.rst +++ b/docs/SIP.rst @@ -18,11 +18,11 @@ There are two errors under ``pyVoIP.SIP.error``. *exception* pyVoIP.SIP.\ **SIPParseError** This is thrown when :ref:`SIPMessage` is unable to parse a SIP message/request. -.. _Enums: - Enums ****** +.. _SIPMessageType: + pyVoIP.SIP.message.\ **SIPMessageType** SIPMessageType is an IntEnum with two members. It's stored in ``SIPMessage.type`` to effectively parse the message. @@ -32,6 +32,8 @@ pyVoIP.SIP.message.\ **SIPMessageType** SIPMessageType.\ **RESPONSE** This SIPMessageType is used to signify the message was a SIP response. +.. _SIPStatus: + pyVoIP.SIP.message.\ **SIPStatus** SIPStatus is used for :ref:`SIPMessage`'s with SIPMessageType.RESPONSE. They will not all be listed here, but a complete list can be found on `Wikipedia `_. SIPStatus has the following attributes: status.\ **value** @@ -66,7 +68,7 @@ SIPClient The SIPClient class is used to communicate with the PBX/VoIP server. It is responsible for registering with the server, and receiving phone calls. -*class* pyVoIP.SIP.client.\ **SIPClient**\ (server: str, port: int, user: str, credentials_manager: :ref:`CredentialsManager`, bind_ip="0.0.0.0", bind_network="0.0.0.0/0", hostname: Optional[str] = None, remote_hostname: Optional[str] = None, bind_port=5060, call_callback: Optional[Callable[[:ref:`VoIPConnection`, :ref:`SIPMessage`], Optional[str]]] = None, fatal_callback: Optional[Callable[..., None]] = None, transport_mode: :ref:`TransportMode` = TransportMode.UDP, cert_file: Optional[str] = None, key_file: Optional[str] = None, key_password: :ref:`KEY_PASSWORD` = None) +*class* pyVoIP.SIP.client.\ **SIPClient**\ (server: str, port: int, user: str, credentials_manager: :ref:`CredentialsManager`, bind_ip="0.0.0.0", bind_network="0.0.0.0/0", hostname: Optional[str] = None, remote_hostname: Optional[str] = None, bind_port=5060, call_callback: Optional[Callable[[:ref:`VoIPConnection`, :ref:`SIPMessage`], Optional[str]]] = None, fatal_callback: Optional[Callable[..., None]] = None, transport_mode: :ref:`TransportMode` = TransportMode.UDP, cert_file: Optional[str] = None, key_file: Optional[str] = None, key_password: :ref:`KEY_PASSWORD` = None) The *server* argument is your PBX/VoIP server's IP. The *port* argument is your PBX/VoIP server's port. @@ -81,7 +83,7 @@ The SIPClient class is used to communicate with the PBX/VoIP server. It is respo The *hostname* argument is used to generate SIP requests and responses with devices within pyVoIP's *bind_network*. If left as None, the *bind_ip* will be used instead. - The *remote_hostname* argument is used to generate SIP requests and responses with devices outside of pyVoIP's *bind_network*. If left as None, pyVoIP will throw a :ref:`NATError` if a request is sent outside of pyVoIP's *bind_network*. + The *remote_hostname* argument is used to generate SIP requests and responses with devices outside of pyVoIP's *bind_network*. If left as None, pyVoIP will throw a :ref:`NATError` if a request is sent outside of pyVoIP's *bind_network*. The *bind_port* argument is the port SIP will bind to to receive SIP requests. The default for this protocol is port 5060, but any port can be used. @@ -89,9 +91,9 @@ The SIPClient class is used to communicate with the PBX/VoIP server. It is respo The *fatal_callback* argument is a function that tells the :ref:`VoIPPhone` instance there was a fatal error, e.g., failed to register. - The *transport_mode* argument determines whether pyVoIP will use UDP, TCP, or TLS. Value should be a :ref:`TransportMode`. + The *transport_mode* argument determines whether pyVoIP will use UDP, TCP, or TLS. Value should be a :ref:`TransportMode`. - The *cert_file*, *key_file*, and *key_password* arguments are used to load certificates in pyVoIP's server context if using TLS for the transport mode. See `Python's documentation on load_cert_chain `_ for more details. + The *cert_file*, *key_file*, and *key_password* arguments are used to load certificates in pyVoIP's server context if using TLS for the transport mode. See Python's documentation on `load_cert_chain `_ for more details. **start**\ () -> None This method starts the SIPClient and registers with the PBX/VoIP server. It is called automatically when :ref:`VoIPPhone` starts. @@ -102,14 +104,14 @@ The SIPClient class is used to communicate with the PBX/VoIP server. It is respo **send**\ (request: str) -> :ref:`VoIPConnection` This method starts a new SIP dialog and sends the request using the request to determine its destination. Returns the VoIPConnection to continue the dialog. - **invite**\ (number: str, ms: dict[int, dict[str, RTP.\ :ref:`PayloadType`]], sendtype: RTP.\ :ref:`TransmitType`) + **invite**\ (number: str, ms: dict[int, dict[str, :ref:`PayloadType`]], sendtype: :ref:`TransmitType`) This method generates a SIP INVITE request. This method is called by :ref:`VoIPPhone`.call(). The *number* argument must be the number being called as a string. The *ms* argument is a dictionary of the media types to be used. Currently only PCMU and telephone-event is supported. - The *sendtype* argument must be an instance of :ref:`TransmitType`. + The *sendtype* argument must be an instance of :ref:`TransmitType`. **bye**\ (request: :ref:`SIPMessage`) -> None This method is called by :ref:`VoIPCall`.hangup(). It generates a BYE request, and then transmits the generated request. **This should not be called by the** :term:`user`. @@ -136,10 +138,10 @@ The SIPMessage class is used to parse SIP requests and responses and makes them This attribute is the first line of the SIP message as a string. It contains the SIP Version, and the method/response code. SIPMessage.\ **type** - This attribute will be a :ref:`SIPMessageType`. + This attribute will be a :ref:`SIPMessageType`. SIPMessage.\ **status** - This attribute will be a :ref:`SIPStatus`. It will be set to ``int(0)`` if the message is a request. + This attribute will be a :ref:`SIPStatus`. It will be set to ``int(0)`` if the message is a request. SIPMessage.\ **method** This attribute will be a string representation of the method. It will be set to None if the message is a response. diff --git a/docs/Types.rst b/docs/Types.rst new file mode 100644 index 0000000..7c26eba --- /dev/null +++ b/docs/Types.rst @@ -0,0 +1,24 @@ +Types +##### + +pyVoIP has several type aliases that it stores in ``pyVoIP.types``. + +.. _URI_HEADER: + +pyVoIP.types.\ **URI_HEADER** = Dict[str, Union[str, int]] + This is for URI Headers (such as To, From, Contact, etc) dictionaries in a :ref:`SIPMessage`. + +.. _SOCKETS: + +pyVoIP.types.\ **SOCKETS** = Union[socket.socket, ssl.SSLSocket] + This is in a few places in :ref:`VoIPSocket` and :ref:`VoIPConnection`. + +.. _KEY_PASSWORD: + +pyVoIP.types.\ **KEY_PASSWORD** = Union[bytes, bytearray, str, Callable[[], bytes], Callable[[], bytearray], Callable[[], str]] + This is used for TLS settings. See Python's documentation on `load_cert_chain's password argument `_ for more details. + +.. _CREDENTIALS_DICT: + +pyVoIP.types.\ **CREDENTIALS_DICT** = Dict[Optional[str], Dict[Optional[str], Dict[Optional[str], Dict[str, str]]]] + This is the format of the :ref:`CredentialsManager`'s internal dictionary. diff --git a/docs/VoIP.rst b/docs/VoIP.rst index 740852c..2aefd2d 100644 --- a/docs/VoIP.rst +++ b/docs/VoIP.rst @@ -83,7 +83,7 @@ VoIPCall The VoIPCall class is used to represent a single VoIP session, which may be to multiple :term:`clients`. -*class* pyVoIP.VoIP.\ **VoIPCall**\ (phone: :ref:`VoIPPhone`, callstate: :ref:`CallState `, request: :ref:`SIPMessage`, session_id: int, bind_ip: str, conn: :ref:`VoIPConnection`, ms: Optional[Dict[int, RTP.PayloadType]] = None, sendmode="sendonly") +*class* pyVoIP.VoIP.\ **VoIPCall**\ (phone: :ref:`VoIPPhone`, callstate: :ref:`CallState `, request: :ref:`SIPMessage`, session_id: int, bind_ip: str, conn: :ref:`VoIPConnection`, ms: Optional[Dict[int, :ref:`PayloadType`]] = None, sendmode="sendonly") The *phone* argument is the initating instance of :ref:`VoIPPhone`. The *callstate* arguement is the initiating :ref:`CallState`. @@ -92,13 +92,13 @@ The VoIPCall class is used to represent a single VoIP session, which may be to m The *session_id* argument is a unique code used to identify the session with `SDP `_ when answering the call. - The *bind_ip* argument is the IP address it will pass to :ref:`RTPClient`'s to bind to. + The *bind_ip* argument is the IP address that pyVoIP will bind its sockets to. The *ms* arguement is a dictionary with int as the key and a :ref:`PayloadType` as the value. This is only used when originating the call. **get_dtmf**\ (length=1) -> str - This method can be called get the next pressed DTMF key. DTMF's are stored in an `io.StringIO `_ which is a buffer. Calling this method when there a key has not been pressed returns an empty string. To return the entire contents of the buffer set length to a negative number or None. If the :term:`client` presses the numbers 1-9-5 you'll have the following output: + This method can be called get the next pressed DTMF key. DTMF's are stored in an `StringIO `_ which is a buffer. Calling this method when there a key has not been pressed returns an empty string. To return the entire contents of the buffer set length to a negative number or None. If the :term:`client` presses the numbers 1-9-5 you'll have the following output: .. code-block:: python @@ -141,7 +141,7 @@ The VoIPCall class is used to represent a single VoIP session, which may be to m VoIPPhoneParameter ================== -*class* pyVoIP.VoIP.\ **VoIPPhoneParameter**\ (server: str, port: int, user: str, credentials_manager: Optional[:ref:`CredentialsManager`], bind_ip="0.0.0.0", bind_port=5060, bind_network="0.0.0.0/0", hostname: Optional[str] = None, remote_hostname: Optional[str] = None, transport_mode=\ :ref:`TransportMode`.UDP, cert_file: Optional[str] = None, key_file: Optional[str] = None, key_password: Optional[str] = None, rtp_port_low=10000, rtp_port_high=20000, call_class: Type[VoIPCall] = None, sip_class: Type[SIP.SIPClient] = None) +*class* pyVoIP.VoIP.\ **VoIPPhoneParameter**\ (server: str, port: int, user: str, credentials_manager: Optional[:ref:`CredentialsManager`], bind_ip="0.0.0.0", bind_port=5060, bind_network="0.0.0.0/0", hostname: Optional[str] = None, remote_hostname: Optional[str] = None, transport_mode=\ :ref:`TransportMode`.UDP, cert_file: Optional[str] = None, key_file: Optional[str] = None, key_password: :ref:`KEY_PASSWORD` = None, rtp_port_low=10000, rtp_port_high=20000, call_class: Type[VoIPCall] = None, sip_class: Type[SIP.SIPClient] = None) The *server* argument is your PBX/VoIP server's IP. The *port* argument is your PBX/VoIP server's port. @@ -158,11 +158,11 @@ VoIPPhoneParameter The *hostname* argument is used to generate SIP requests and responses with devices within pyVoIP's *bind_network*. If left as None, the *bind_ip* will be used instead. - The *remote_hostname* argument is used to generate SIP requests and responses with devices outside of pyVoIP's *bind_network*. If left as None, pyVoIP will throw a :ref:`NATError` if a request is sent outside of pyVoIP's *bind_network*. + The *remote_hostname* argument is used to generate SIP requests and responses with devices outside of pyVoIP's *bind_network*. If left as None, pyVoIP will throw a :ref:`NATError` if a request is sent outside of pyVoIP's *bind_network*. - The *transport_mode* argument determines whether pyVoIP will use UDP, TCP, or TLS. Value should be a :ref:`TransportMode`. + The *transport_mode* argument determines whether pyVoIP will use UDP, TCP, or TLS. Value should be a :ref:`TransportMode`. - The *cert_file*, *key_file*, and *key_password* arguments are used to load certificates in pyVoIP's server context if using TLS for the transport mode. See `Python's documentation on load_cert_chain `_ for more details. + The *cert_file*, *key_file*, and *key_password* arguments are used to load certificates in pyVoIP's server context if using TLS for the transport mode. See Python's documentation on `load_cert_chain `_ for more details. The *rtp_port_low* and *rtp_port_high* arguments are used to generate random ports to use for audio transfer. Per RFC 4566 Sections `5.7 `_ and `5.14 `_, it can take multiple ports to fully communicate with other :term:`clients`, as such a large range is recommended. If an invalid range is given, a :ref:`InvalidStateError` will be thrown. @@ -187,7 +187,7 @@ The VoIPPhone class is used to manage the :ref:`SIPClient` class and create :ref **stop**\ () -> None This method ends all ongoing calls, then stops the :ref:`SIPClient` class - **call**\ (number: str, payload_types: Optional[List[:ref:`PayloadType`]] = None) -> :ref:`VoIPCall` + **call**\ (number: str, payload_types: Optional[List[:ref:`PayloadType`]] = None) -> :ref:`VoIPCall` Originates a call using the specified *payload_types*, or PCMU and telephone-event by default. The *number* argument must be a string. Returns a :ref:`VoIPCall` class in CallState.DIALING. diff --git a/docs/index.rst b/docs/index.rst index 0757ae2..f076d6f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -32,3 +32,5 @@ In this documentation we will use the following terms: Credentials SIP RTP + Networking + Types From 59a74854675a802376402b9d6d96dce1eee3a444 Mon Sep 17 00:00:00 2001 From: TJ Porter Date: Wed, 10 Jan 2024 13:18:45 -0600 Subject: [PATCH 7/8] [CHANGE] Completely updated Examples.rst [FIX] Fixed incorrect import paths in VoIP.rst [FIX] Fixed warnings in docs/conf.py --- docs/Examples.rst | 78 +++++++++++++++++++++++++++++++---------------- docs/VoIP.rst | 10 +++--- docs/conf.py | 2 +- 3 files changed, 57 insertions(+), 33 deletions(-) diff --git a/docs/Examples.rst b/docs/Examples.rst index f39b7d6..b63acf5 100644 --- a/docs/Examples.rst +++ b/docs/Examples.rst @@ -6,15 +6,18 @@ Here we will go over a few basic phone setups. Setup ***** -PyVoIP uses :ref:`VoIPPhone` child class to initiate phone calls. In the example below, our ringing function is named ``Call.ringing``. +PyVoIP uses a :ref:`VoIPPhone` class to receive and initiate phone calls. The settings for our phone are passed via the :ref:`VoIPPhoneParameter` dataclass. When a call is received, a new instance of a :ref:`VoIPCall` is initialized. You can overwrite this class in initialization of VoIPPhone. -We are also importing :ref:`VoIPPhone` and :ref:`InvalidStateError`. VoIPPhone is the main class for our `softphone `_. An InvalidStateError is thrown when you try to perform an impossible command. For example, denying the call when the phone is already answered, answering when it's already answered, etc. +In this example, we are importing :ref:`CredentialsManager`, :ref:`VoIPPhone`, :ref:`VoIPPhoneParameter`, :ref:`VoIPCall`, and :ref:`InvalidStateError`. :ref:`CredentialsManager` stores and retreives passwords for authentication with registrars. :ref:`VoIPPhone` is the main class for our `softphone `_. :ref:`VoIPPhoneParameter` is the settings for our :ref:`VoIPPhone`. :ref:`VoIPCall` will be used to create our custom answering class. An :ref:`InvalidStateError` is thrown when you try to perform an impossible command. For example, denying the call when the phone is already answered, answering when it's already answered, etc. The following will create a phone that answers and automatically hangs up: .. code-block:: python - from pyVoIP.VoIP import VoIPPhone, VoIPCall, InvalidStateError + from pyVoIP.credentials import CredentialsManager + from pyVoIP.VoIP.call import VoIPCall + from pyVoIP.VoIP.error import InvalidStateError + from pyVoIP.VoIP.phone import VoIPPhone, VoIPPhoneParamter class Call(VoIPCall): @@ -26,7 +29,10 @@ The following will create a phone that answers and automatically hangs up: pass if __name__ == "__main__": - phone = VoIPPhone(, , , , bind_ip=, callClass=Call) + cm = CredentialsManager() + cm.add(, ) + params = VoIPPhoneParamter(, , , cm, bind_ip=, call_class=Call) + phone = VoIPPhone(params) phone.start() input('Press enter to disable the phone') phone.stop() @@ -34,11 +40,14 @@ The following will create a phone that answers and automatically hangs up: Announcement Board ****************** -Let's say you want to make a phone that when you call it, it plays an announcement message, then hangs up. We can accomplish this with the builtin libraries `wave `_, `audioop `_, `time `_, and by importing :ref:`CallState`. +Let's say you want to make a phone that when you call it, it plays an announcement message, then hangs up. We can accomplish this with the builtin libraries `wave `_, `audioop `_, `time `_, and by importing :ref:`CallState`. .. code-block:: python - from pyVoIP.VoIP import VoIPPhone, VoIPCall, InvalidStateError, CallState + from pyVoIP.credentials import CredentialsManager + from pyVoIP.VoIP.call import VoIPCall + from pyVoIP.VoIP.error import InvalidStateError + from pyVoIP.VoIP.phone import VoIPPhone, VoIPPhoneParamter import time import wave @@ -65,12 +74,15 @@ Let's say you want to make a phone that when you call it, it plays an announceme call.hangup() if __name__ == "__main__": - phone = VoIPPhone(, , , , bind_ip=, callClass=Call) + cm = CredentialsManager() + cm.add(, ) + params = VoIPPhoneParamter(, , , cm, bind_ip=, call_class=Call) + phone = VoIPPhone(params) phone.start() input('Press enter to disable the phone') phone.stop() -Something important to note is our wait function. We are currently using: +Something important to note is our wait function. We are currently using: .. code-block:: python @@ -79,18 +91,21 @@ Something important to note is our wait function. We are currently using: while time.time() <= stop and call.state == CallState.ANSWERED: time.sleep(0.1) -This could be replaced with ``time.sleep(frames / 8000)``. However, doing so will not cause the thread to automatically close if the user hangs up, or if ``VoIPPhone().stop()`` is called; using the while loop method will fix this issue. The ``time.sleep(0.1)`` inside the while loop is also important. Supplementing ``time.sleep(0.1)`` for ``pass`` will cause your CPU to ramp up while running the loop, making the RTP (audio being sent out and received) lag. This can make the voice audibly slow or choppy. +This could be replaced with ``time.sleep(frames / 8000)``. However, doing so will not cause the thread to automatically close if the user hangs up, or if ``VoIPPhone().stop()`` is called. Using the while loop method will fix this issue. The ``time.sleep(0.1)`` inside the while loop is also important. Supplementing ``time.sleep(0.1)`` for ``pass`` will cause your CPU to ramp up while running the loop, making the RTP (audio being sent out and received) lag. This can make the voice audibly slow or choppy. -*Note: Audio must be 8 bit, 8000Hz, and Mono/1 channel. You can accomplish this in a free program called* `Audacity `_. *To make an audio recording Mono, go to Tracks > Mix > Mix Stereo Down to Mono. To make an audio recording 8000 Hz, go to Tracks > Resample... and select 8000, then ensure that your 'Project Rate' in the bottom left is also set to 8000. To make an audio recording 8 bit, go to File > Export > Export as WAV, then change 'Save as type:' to 'Other uncompressed files', then set 'Header:' to 'WAV (Microsoft)', then set the 'Encoding:' to 'Unsigned 8-bit PCM'* + **Important Note:** *Audio must be 8 bit, 8000Hz, and Mono/1 channel. You can accomplish this in a free program called* `Audacity `_. *To make an audio recording Mono, go to Tracks > Mix > Mix Stereo Down to Mono. To make an audio recording 8000 Hz, go to Tracks > Resample... and select 8000, then ensure that your 'Project Rate' in the bottom left is also set to 8000. To make an audio recording 8 bit, go to File > Export > Export as WAV, then change 'Save as type:' to 'Other uncompressed files', then set 'Header:' to 'WAV (Microsoft)', then set the 'Encoding:' to 'Unsigned 8-bit PCM'* IVR/Phone Menus **************** -We can use the following code to create `IVR Menus `_. Currently, we cannot make 'breaking' IVR menus. Breaking IVR menus in this context means, a user selecting an option mid-prompt will cancel the prompt, and start the next action. Support for breaking IVR's will be made in the future. For now, here is the code for a non-breaking IVR: +We can use the following code to create `IVR Menus `_. Currently, we cannot make 'breaking' IVR menus. Breaking IVR menus in this context means, a user selecting an option mid-prompt will cancel the prompt, and start the next action. Support for breaking IVR's will be made in the future. For now, here is the code for a non-breaking IVR: .. code-block:: python - from pyVoIP.VoIP import VoIPPhone, VoIPCall, InvalidStateError, CallState + from pyVoIP.credentials import CredentialsManager + from pyVoIP.VoIP.call import VoIPCall + from pyVoIP.VoIP.error import InvalidStateError + from pyVoIP.VoIP.phone import VoIPPhone, VoIPPhoneParamter import time import wave @@ -109,11 +124,11 @@ We can use the following code to create `IVR Menus , , , , bind_ip=, callClass=Call) + cm = CredentialsManager() + cm.add(, ) + params = VoIPPhoneParamter(, , , cm, bind_ip=, call_class=Call) + phone = VoIPPhone(params) phone.start() input('Press enter to disable the phone') phone.stop() -Please note that ``get_dtmf()`` is actually ``get_dtmf(length=1)``, and as it is technically an ``io.StringBuffer()``, it will return ``""`` instead of ``None``. This may be important if you wanted an 'if anything else, do that' clause. Lastly, VoIPCall stores all DTMF keys pressed since the call was established; meaning, users can press any key they want before the prompt even finishes, or may press a wrong key before the prompt even starts. +Please note that ``get_dtmf()`` is actually ``get_dtmf(length=1)``, and as it is technically an ``io.StringBuffer()``, it will return ``""`` instead of ``None``. This may be important if you wanted an 'if anything else, do that' clause. Lastly, VoIPCall stores all DTMF keys pressed since the call was established; meaning, users can press any key they want before the prompt even finishes, or may press a wrong key before the prompt even starts. -Call state handling for outgoing calls -************************************** +Call State Handling +******************* -We can use the following code to handle various states for the outgoing calls: +We can use the following code to handle various states for calls: .. code-block:: python - from pyVoIP.VoIP import VoIPPhone, VoIPCall, InvalidStateError, CallState + from pyVoIP.credentials import CredentialsManager + from pyVoIP.VoIP.call import VoIPCall + from pyVoIP.VoIP.error import InvalidStateError + from pyVoIP.VoIP.phone import VoIPPhone, VoIPPhoneParamter import time import wave class Call(VoIPCall): def progress(self, request): - print('Progress') - super().progress(request) + print('Progress') + super().progress(request) def busy(self, request): print('Call ended - callee is busy') - super().progress(request) + super().busy(request) def answered(self, request): - print('Answered') + print('Answered') super().answered() def bye(self): @@ -158,7 +179,10 @@ We can use the following code to handle various states for the outgoing calls: super().bye() if __name__ == '__main__': - phone = VoIPPhone(, , , , bind_ip=, callClass=Call) + cm = CredentialsManager() + cm.add(, ) + params = VoIPPhoneParamter(, , , cm, bind_ip=, call_class=Call) + phone = VoIPPhone(params) phone.start() phone.call() input('Press enter to disable the phone\n') diff --git a/docs/VoIP.rst b/docs/VoIP.rst index 2aefd2d..9374adf 100644 --- a/docs/VoIP.rst +++ b/docs/VoIP.rst @@ -24,7 +24,7 @@ Enums .. _callstate: -*enum* pyVoIP.VoIP.\ **CallState** +*enum* pyVoIP.VoIP.call.\ **CallState** CallState is an Enum with six attributes. CallState.\ **DIALING** @@ -55,7 +55,7 @@ Enums .. _PhoneStatus: -*enum* pyVoIP.VoIP.\ **PhoneStatus** +*enum* pyVoIP.VoIP.status.\ **PhoneStatus** PhoneStatus is an Enum with five attributes. PhoneStatus.\ **INACTIVE** @@ -83,7 +83,7 @@ VoIPCall The VoIPCall class is used to represent a single VoIP session, which may be to multiple :term:`clients`. -*class* pyVoIP.VoIP.\ **VoIPCall**\ (phone: :ref:`VoIPPhone`, callstate: :ref:`CallState `, request: :ref:`SIPMessage`, session_id: int, bind_ip: str, conn: :ref:`VoIPConnection`, ms: Optional[Dict[int, :ref:`PayloadType`]] = None, sendmode="sendonly") +*class* pyVoIP.VoIP.call.\ **VoIPCall**\ (phone: :ref:`VoIPPhone`, callstate: :ref:`CallState `, request: :ref:`SIPMessage`, session_id: int, bind_ip: str, conn: :ref:`VoIPConnection`, ms: Optional[Dict[int, :ref:`PayloadType`]] = None, sendmode="sendonly") The *phone* argument is the initating instance of :ref:`VoIPPhone`. The *callstate* arguement is the initiating :ref:`CallState`. @@ -141,7 +141,7 @@ The VoIPCall class is used to represent a single VoIP session, which may be to m VoIPPhoneParameter ================== -*class* pyVoIP.VoIP.\ **VoIPPhoneParameter**\ (server: str, port: int, user: str, credentials_manager: Optional[:ref:`CredentialsManager`], bind_ip="0.0.0.0", bind_port=5060, bind_network="0.0.0.0/0", hostname: Optional[str] = None, remote_hostname: Optional[str] = None, transport_mode=\ :ref:`TransportMode`.UDP, cert_file: Optional[str] = None, key_file: Optional[str] = None, key_password: :ref:`KEY_PASSWORD` = None, rtp_port_low=10000, rtp_port_high=20000, call_class: Type[VoIPCall] = None, sip_class: Type[SIP.SIPClient] = None) +*class* pyVoIP.VoIP.phone.\ **VoIPPhoneParameter**\ (server: str, port: int, user: str, credentials_manager: Optional[:ref:`CredentialsManager`], bind_ip="0.0.0.0", bind_port=5060, bind_network="0.0.0.0/0", hostname: Optional[str] = None, remote_hostname: Optional[str] = None, transport_mode=\ :ref:`TransportMode`.UDP, cert_file: Optional[str] = None, key_file: Optional[str] = None, key_password: :ref:`KEY_PASSWORD` = None, rtp_port_low=10000, rtp_port_high=20000, call_class: Type[VoIPCall] = None, sip_class: Type[SIP.SIPClient] = None) The *server* argument is your PBX/VoIP server's IP. The *port* argument is your PBX/VoIP server's port. @@ -177,7 +177,7 @@ VoIPPhone The VoIPPhone class is used to manage the :ref:`SIPClient` class and create :ref:`VoIPCall`'s when there is an incoming call or a :term:`user` makes a call. It then uses the VoIPCall class to handle the call's states. -*class* pyVoIP.VoIP.\ **VoIPPhone**\ (voip_phone_parameter: :ref:`VoIPPhoneParameter`) +*class* pyVoIP.VoIP.phone.\ **VoIPPhone**\ (voip_phone_parameter: :ref:`VoIPPhoneParameter`) **get_status**\ () -> :ref:`PhoneStatus ` This method returns the phone's current status. diff --git a/docs/conf.py b/docs/conf.py index 07b45d7..b4cc551 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -56,4 +56,4 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] +html_static_path = [""] From 6cf17342c0d2e741f904182f89f952ef98eafa40 Mon Sep 17 00:00:00 2001 From: TJ Porter Date: Thu, 11 Jan 2024 09:37:29 -0600 Subject: [PATCH 8/8] [CHANGE] Updated example in README.md --- README.md | 38 +++++++++++++++++++++++--------------- docs/Examples.rst | 2 +- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c4a06d2..8d81ab3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # pyVoIP -PyVoIP is a pure python VoIP/SIP/RTP library. Currently, it supports PCMA, PCMU, and telephone-event. +PyVoIP is a pure python VoIP/SIP/RTP library. Currently, it supports PCMA, PCMU, and telephone-event. -This library does not depend on a sound library, i.e. you can use any sound library that can handle linear sound data i.e. pyaudio or even wave. Keep in mind PCMU/PCMA only supports 8000Hz, 1 channel, 8 bit audio. +This library does not depend on a sound library, i.e. you can use any sound library that can handle linear sound data such as pyaudio or even wave. Keep in mind PCMU/PCMA only supports 8000Hz, 1 channel, 8 bit audio. ## Getting Started Simply run `pip install pyVoIP`, or if installing from source: @@ -18,20 +18,28 @@ Don't forget to check out [the documentation](https://pyvoip.readthedocs.io/)! This basic code will simple make a phone that will automatically answer then hang up. ```python -from pyVoIP.VoIP import VoIPPhone, InvalidStateError - -def answer(call): # This will be your callback function for when you receive a phone call. - try: - call.answer() - call.hangup() - except InvalidStateError: - pass - +from pyVoIP.credentials import CredentialsManager +from pyVoIP.VoIP.call import VoIPCall +from pyVoIP.VoIP.error import InvalidStateError +from pyVoIP.VoIP.phone import VoIPPhone, VoIPPhoneParamter + +class Call(VoIPCall): + + def ringing(self, invite_request): + try: + self.answer() + self.hangup() + except InvalidStateError: + pass + if __name__ == "__main__": - phone=VoIPPhone(, , , , callCallback=answer, myIP=, sipPort=, rtpPortLow=, rtpPortHigh=) - phone.start() - input('Press enter to disable the phone') - phone.stop() + cm = CredentialsManager() + cm.add(, ) + params = VoIPPhoneParamter(, , , cm, bind_ip=, call_class=Call) + phone = VoIPPhone(params) + phone.start() + input('Press enter to disable the phone') + phone.stop() ``` ### Sponsors diff --git a/docs/Examples.rst b/docs/Examples.rst index b63acf5..e4d42cc 100644 --- a/docs/Examples.rst +++ b/docs/Examples.rst @@ -31,7 +31,7 @@ The following will create a phone that answers and automatically hangs up: if __name__ == "__main__": cm = CredentialsManager() cm.add(, ) - params = VoIPPhoneParamter(, , , cm, bind_ip=, call_class=Call) + params = VoIPPhoneParamter(, , , cm, bind_ip=, call_class=Call) phone = VoIPPhone(params) phone.start() input('Press enter to disable the phone')