From cf2dee95d247d88a2ff5926dfeb9a1b82dc08c03 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 14 Mar 2025 11:46:24 -0400 Subject: [PATCH] code cleanup; add SIP lexer and data handlers; --- src/common/CMakeLists.txt | 2 + src/common/Log.h | 1 + src/common/network/rest/RequestDispatcher.h | 4 +- src/common/network/rest/http/HTTPClient.h | 1 - src/common/network/rest/http/HTTPHeaders.h | 1 - src/common/network/rest/http/HTTPLexer.h | 1 - src/common/network/rest/http/HTTPPayload.h | 7 +- .../network/rest/http/HTTPRequestHandler.h | 1 - src/common/network/rest/http/HTTPServer.h | 1 - .../network/rest/http/SecureHTTPClient.h | 1 - .../network/rest/http/SecureHTTPServer.h | 1 - .../network/rest/http/ServerConnection.h | 1 - .../rest/http/ServerConnectionManager.h | 1 - src/common/network/sip/RequestDispatcher.h | 386 +++++++++++++++++ src/common/network/sip/SIPHeaders.h | 149 +++++++ src/common/network/sip/SIPLexer.cpp | 402 ++++++++++++++++++ src/common/network/sip/SIPLexer.h | 192 +++++++++ src/common/network/sip/SIPPayload.cpp | 220 ++++++++++ src/common/network/sip/SIPPayload.h | 152 +++++++ 19 files changed, 1509 insertions(+), 15 deletions(-) create mode 100644 src/common/network/sip/RequestDispatcher.h create mode 100644 src/common/network/sip/SIPHeaders.h create mode 100644 src/common/network/sip/SIPLexer.cpp create mode 100644 src/common/network/sip/SIPLexer.h create mode 100644 src/common/network/sip/SIPPayload.cpp create mode 100644 src/common/network/sip/SIPPayload.h diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 6f58ac17..de41287e 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -45,6 +45,7 @@ file(GLOB common_SRC "src/common/network/*.cpp" "src/common/network/rest/*.cpp" "src/common/network/rest/http/*.cpp" + "src/common/network/sip/*.cpp" "src/common/network/tcp/*.cpp" "src/common/network/udp/*.cpp" "src/common/network/viface/*.cpp" @@ -93,6 +94,7 @@ file(GLOB common_INCLUDE "src/common/network/json/*.h" "src/common/network/rest/*.h" "src/common/network/rest/http/*.h" + "src/common/network/sip/*.h" "src/common/network/tcp/*.h" "src/common/network/udp/*.h" "src/common/network/viface/*.h" diff --git a/src/common/Log.h b/src/common/Log.h index 2087b808..349c1816 100644 --- a/src/common/Log.h +++ b/src/common/Log.h @@ -38,6 +38,7 @@ #define LOG_HOST "HOST" #define LOG_REST "RESTAPI" +#define LOG_SIP "SIP" #define LOG_MODEM "MODEM" #define LOG_RF "RF" #define LOG_NET "NET" diff --git a/src/common/network/rest/RequestDispatcher.h b/src/common/network/rest/RequestDispatcher.h index a751100a..6a3b20fb 100644 --- a/src/common/network/rest/RequestDispatcher.h +++ b/src/common/network/rest/RequestDispatcher.h @@ -16,7 +16,7 @@ * @ingroup rest * * @file RequestDispatcher.h - * @ingroup tcp_socket + * @ingroup rest */ #if !defined(__REST__DISPATCHER_H__) #define __REST__DISPATCHER_H__ @@ -272,7 +272,7 @@ namespace network typedef std::function RequestHandlerType; /** - * @brief Initializes a new instance of the DebugRequestDispatcher class. + * @brief Initializes a new instance of the BasicRequestDispatcher class. */ BasicRequestDispatcher() { /* stub */ } /** diff --git a/src/common/network/rest/http/HTTPClient.h b/src/common/network/rest/http/HTTPClient.h index 2662e9af..94c2a4f5 100644 --- a/src/common/network/rest/http/HTTPClient.h +++ b/src/common/network/rest/http/HTTPClient.h @@ -34,7 +34,6 @@ namespace network { namespace http { - // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- diff --git a/src/common/network/rest/http/HTTPHeaders.h b/src/common/network/rest/http/HTTPHeaders.h index 94c5fef3..409e00be 100644 --- a/src/common/network/rest/http/HTTPHeaders.h +++ b/src/common/network/rest/http/HTTPHeaders.h @@ -28,7 +28,6 @@ namespace network { namespace http { - // --------------------------------------------------------------------------- // Class Prototypes // --------------------------------------------------------------------------- diff --git a/src/common/network/rest/http/HTTPLexer.h b/src/common/network/rest/http/HTTPLexer.h index 3fabf1cb..736af7fc 100644 --- a/src/common/network/rest/http/HTTPLexer.h +++ b/src/common/network/rest/http/HTTPLexer.h @@ -28,7 +28,6 @@ namespace network { namespace http { - // --------------------------------------------------------------------------- // Class Prototypes // --------------------------------------------------------------------------- diff --git a/src/common/network/rest/http/HTTPPayload.h b/src/common/network/rest/http/HTTPPayload.h index baac35dd..91e686ce 100644 --- a/src/common/network/rest/http/HTTPPayload.h +++ b/src/common/network/rest/http/HTTPPayload.h @@ -14,8 +14,8 @@ * @file HTTPPayload.cpp * @ingroup http */ -#if !defined(__REST_HTTP__HTTP_REPLY_H__) -#define __REST_HTTP__HTTP_REPLY_H__ +#if !defined(__REST_HTTP__HTTP_PAYLOAD_H__) +#define __REST_HTTP__HTTP_PAYLOAD_H__ #include "common/Defines.h" #include "common/network/json/json.h" @@ -32,7 +32,6 @@ namespace network { namespace http { - // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- @@ -142,4 +141,4 @@ namespace network } // namespace rest } // namespace network -#endif // __REST_HTTP__HTTP_REPLY_H__ +#endif // __REST_HTTP__HTTP_PAYLOAD_H__ diff --git a/src/common/network/rest/http/HTTPRequestHandler.h b/src/common/network/rest/http/HTTPRequestHandler.h index 4b8c940f..41bfbfe7 100644 --- a/src/common/network/rest/http/HTTPRequestHandler.h +++ b/src/common/network/rest/http/HTTPRequestHandler.h @@ -27,7 +27,6 @@ namespace network { namespace http { - // --------------------------------------------------------------------------- // Class Prototypes // --------------------------------------------------------------------------- diff --git a/src/common/network/rest/http/HTTPServer.h b/src/common/network/rest/http/HTTPServer.h index fde6ce6e..25f25130 100644 --- a/src/common/network/rest/http/HTTPServer.h +++ b/src/common/network/rest/http/HTTPServer.h @@ -34,7 +34,6 @@ namespace network { namespace http { - // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- diff --git a/src/common/network/rest/http/SecureHTTPClient.h b/src/common/network/rest/http/SecureHTTPClient.h index debbef3f..3a68da5b 100644 --- a/src/common/network/rest/http/SecureHTTPClient.h +++ b/src/common/network/rest/http/SecureHTTPClient.h @@ -40,7 +40,6 @@ namespace network { namespace http { - // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- diff --git a/src/common/network/rest/http/SecureHTTPServer.h b/src/common/network/rest/http/SecureHTTPServer.h index be5a474f..84116cde 100644 --- a/src/common/network/rest/http/SecureHTTPServer.h +++ b/src/common/network/rest/http/SecureHTTPServer.h @@ -37,7 +37,6 @@ namespace network { namespace http { - // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- diff --git a/src/common/network/rest/http/ServerConnection.h b/src/common/network/rest/http/ServerConnection.h index eb983a0f..da5f32ad 100644 --- a/src/common/network/rest/http/ServerConnection.h +++ b/src/common/network/rest/http/ServerConnection.h @@ -42,7 +42,6 @@ namespace network // --------------------------------------------------------------------------- // Class Declaration - // // --------------------------------------------------------------------------- /** diff --git a/src/common/network/rest/http/ServerConnectionManager.h b/src/common/network/rest/http/ServerConnectionManager.h index 733da6e5..dc4e4840 100644 --- a/src/common/network/rest/http/ServerConnectionManager.h +++ b/src/common/network/rest/http/ServerConnectionManager.h @@ -26,7 +26,6 @@ namespace network { namespace http { - // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- diff --git a/src/common/network/sip/RequestDispatcher.h b/src/common/network/sip/RequestDispatcher.h new file mode 100644 index 00000000..f027754b --- /dev/null +++ b/src/common/network/sip/RequestDispatcher.h @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @defgroup sip Session Initiation Protocol Services + * @brief Implementation for Session Initiation Protocol services. + * @ingroup network_core + * + * @file RequestDispatcher.h + * @ingroup sip + */ +#if !defined(__SIP__DISPATCHER_H__) +#define __SIP__DISPATCHER_H__ + +#include "common/Defines.h" +#include "common/network/sip/SIPPayload.h" +#include "common/Log.h" + +#include +#include +#include +#include +#include + +namespace network +{ + namespace sip + { + // --------------------------------------------------------------------------- + // Structure Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Structure representing a SIP request match. + * @ingroup sip + */ + struct RequestMatch : std::smatch { + /** + * @brief Initializes a new instance of the RequestMatch structure. + * @param m String matcher. + * @param c Content. + */ + RequestMatch(const std::smatch& m, const std::string& c) : std::smatch(m), content(c) { /* stub */ } + + std::string content; + }; + + // --------------------------------------------------------------------------- + // Structure Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Structure representing a request matcher. + * @ingroup rest + */ + template + struct RequestMatcher { + typedef std::function RequestHandlerType; + + /** + * @brief Initializes a new instance of the RequestMatcher structure. + * @param expression Matching expression. + */ + explicit RequestMatcher(const std::string& expression) : m_expression(expression), m_isRegEx(false) { /* stub */ } + + /** + * @brief Handler for INVITE requests. + * @param handler INVITE request handler. + * @return RequestMatcher* Instance of a RequestMatcher. + */ + RequestMatcher& invite(RequestHandlerType handler) { + m_handlers[SIP_INVITE] = handler; + return *this; + } + /** + * @brief Handler for ACK requests. + * @param handler ACK request handler. + * @return RequestMatcher* Instance of a RequestMatcher. + */ + RequestMatcher& ack(RequestHandlerType handler) { + m_handlers[SIP_ACK] = handler; + return *this; + } + /** + * @brief Handler for BYE requests. + * @param handler BYE request handler. + * @return RequestMatcher* Instance of a RequestMatcher. + */ + RequestMatcher& bye(RequestHandlerType handler) { + m_handlers[SIP_BYE] = handler; + return *this; + } + /** + * @brief Handler for CANCEL requests. + * @param handler CANCEL request handler. + * @return RequestMatcher* Instance of a RequestMatcher. + */ + RequestMatcher& cancel(RequestHandlerType handler) { + m_handlers[SIP_CANCEL] = handler; + return *this; + } + /** + * @brief Handler for REGISTER requests. + * @param handler REGISTER request handler. + * @return RequestMatcher* Instance of a RequestMatcher. + */ + RequestMatcher& registerReq(RequestHandlerType handler) { + m_handlers[SIP_REGISTER] = handler; + return *this; + } + /** + * @brief Handler for OPTIONS requests. + * @param handler OPTIONS request handler. + * @return RequestMatcher* Instance of a RequestMatcher. + */ + RequestMatcher& options(RequestHandlerType handler) { + m_handlers[SIP_OPTIONS] = handler; + return *this; + } + /** + * @brief Handler for SUBSCRIBE requests. + * @param handler SUBSCRIBE request handler. + * @return RequestMatcher* Instance of a RequestMatcher. + */ + RequestMatcher& subscribe(RequestHandlerType handler) { + m_handlers[SIP_SUBSCRIBE] = handler; + return *this; + } + /** + * @brief Handler for NOTIFY requests. + * @param handler NOTIFY request handler. + * @return RequestMatcher* Instance of a RequestMatcher. + */ + RequestMatcher& notify(RequestHandlerType handler) { + m_handlers[SIP_NOTIFY] = handler; + return *this; + } + /** + * @brief Handler for PUBLISH requests. + * @param handler PUBLISH request handler. + * @return RequestMatcher* Instance of a RequestMatcher. + */ + RequestMatcher& publish(RequestHandlerType handler) { + m_handlers[SIP_PUBLISH] = handler; + return *this; + } + /** + * @brief Handler for INFO requests. + * @param handler INFO request handler. + * @return RequestMatcher* Instance of a RequestMatcher. + */ + RequestMatcher& info(RequestHandlerType handler) { + m_handlers[SIP_INFO] = handler; + return *this; + } + /** + * @brief Handler for MESSAGE requests. + * @param handler MESSAGE request handler. + * @return RequestMatcher* Instance of a RequestMatcher. + */ + RequestMatcher& message(RequestHandlerType handler) { + m_handlers[SIP_MESSAGE] = handler; + return *this; + } + /** + * @brief Handler for UPDATE requests. + * @param handler UPDATE request handler. + * @return RequestMatcher* Instance of a RequestMatcher. + */ + RequestMatcher& update(RequestHandlerType handler) { + m_handlers[SIP_UPDATE] = handler; + return *this; + } + + /** + * @brief Helper to determine if the request matcher is a regular expression. + * @returns bool True, if request matcher is a regular expression, otherwise false. + */ + bool regex() const { return m_isRegEx; } + /** + * @brief Helper to set the regular expression flag. + * @param regEx Flag indicating whether or not the request matcher is a regular expression. + */ + void setRegEx(bool regEx) { m_isRegEx = regEx; } + + /** + * @brief Helper to handle the actual request. + * @param request HTTP request. + * @param reply HTTP reply. + * @param what What matched. + */ + void handleRequest(const Request& request, Reply& reply, const std::smatch &what) { + // dispatching to matching based on handler + RequestMatch match(what, request.content); + auto& handler = m_handlers[request.method]; + if (handler) { + handler(request, reply, match); + } + } + + private: + std::string m_expression; + bool m_isRegEx; + std::map m_handlers; + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief This class implements SIP request dispatching. + * @tparam Request SIP request. + * @tparam Reply SIP reply. + */ + template + class SIPRequestDispatcher { + typedef RequestMatcher MatcherType; + public: + /** + * @brief Initializes a new instance of the SIPRequestDispatcher class. + */ + SIPRequestDispatcher() : m_basePath(), m_debug(false) { /* stub */ } + /** + * @brief Initializes a new instance of the SIPRequestDispatcher class. + * @param debug Flag indicating whether or not verbose logging should be enabled. + */ + SIPRequestDispatcher(bool debug) : m_basePath(), m_debug(debug) { /* stub */ } + /** + * @brief Initializes a new instance of the SIPRequestDispatcher class. + * @param basePath + * @param debug Flag indicating whether or not verbose logging should be enabled. + */ + SIPRequestDispatcher(const std::string& basePath, bool debug) : m_basePath(basePath), m_debug(debug) { /* stub */ } + + /** + * @brief Helper to match a request patch. + * @param expression Matching expression. + * @param regex Flag indicating whether or not this match is a regular expression. + * @returns MatcherType Instance of a request matcher. + */ + MatcherType& match(const std::string& expression, bool regex = false) + { + MatcherTypePtr& p = m_matchers[expression]; + if (!p) { + if (m_debug) { + ::LogDebug(LOG_REST, "creating SIPRequestDispatcher, expression = %s", expression.c_str()); + } + p = std::make_shared(expression); + } else { + if (m_debug) { + ::LogDebug(LOG_REST, "fetching SIPRequestDispatcher, expression = %s", expression.c_str()); + } + } + + p->setRegEx(regex); + return *p; + } + + /** + * @brief Helper to handle SIP request. + * @param request SIP request. + * @param reply SIP reply. + */ + void handleRequest(const Request& request, Reply& reply) + { + for (const auto& matcher : m_matchers) { + std::smatch what; + if (!matcher.second->regex()) { + if (request.uri.find(matcher.first) != std::string::npos) { + if (m_debug) { + ::LogDebug(LOG_REST, "non-regex endpoint, uri = %s, expression = %s", request.uri.c_str(), matcher.first.c_str()); + } + + //what = matcher.first; + + matcher.second->handleRequest(request, reply, what); + return; + } + } else { + if (std::regex_match(request.uri, what, std::regex(matcher.first))) { + if (m_debug) { + ::LogDebug(LOG_REST, "regex endpoint, uri = %s, expression = %s", request.uri.c_str(), matcher.first.c_str()); + } + + matcher.second->handleRequest(request, reply, what); + return; + } + } + } + + ::LogError(LOG_REST, "unknown endpoint, uri = %s", request.uri.c_str()); + reply = SIPPayload::statusPayload(SIPPayload::BAD_REQUEST, "application/sdp"); + } + + private: + typedef std::shared_ptr MatcherTypePtr; + + std::string m_basePath; + std::map m_matchers; + + bool m_debug; + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief This class implements a generic basic request dispatcher. + * @tparam Request SIP request. + * @tparam Reply SIP reply. + */ + template + class SIPBasicRequestDispatcher { + public: + typedef std::function RequestHandlerType; + + /** + * @brief Initializes a new instance of the SIPBasicRequestDispatcher class. + */ + SIPBasicRequestDispatcher() { /* stub */ } + /** + * @brief Initializes a new instance of the SIPBasicRequestDispatcher class. + * @param handler Instance of a RequestHandlerType for this dispatcher. + */ + SIPBasicRequestDispatcher(RequestHandlerType handler) : m_handler(handler) { /* stub */ } + + /** + * @brief Helper to handle SIP request. + * @param request SIP request. + * @param reply SIP reply. + */ + void handleRequest(const Request& request, Reply& reply) + { + if (m_handler) { + m_handler(request, reply); + } + } + + private: + RequestHandlerType m_handler; + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief This class implements a generic debug request dispatcher. + * @tparam Request SIP request. + * @tparam Reply SIP reply. + */ + template + class SIPDebugRequestDispatcher { + public: + /** + * @brief Initializes a new instance of the SIPDebugRequestDispatcher class. + */ + SIPDebugRequestDispatcher() { /* stub */ } + + /** + * @brief Helper to handle SIP request. + * @param request SIP request. + * @param reply SIP reply. + */ + void handleRequest(const Request& request, Reply& reply) + { + for (auto header : request.headers.headers()) + ::LogDebugEx(LOG_SIP, "SIPDebugRequestDispatcher::handleRequest()", "header = %s, value = %s", header.name.c_str(), header.value.c_str()); + + ::LogDebugEx(LOG_SIP, "SIPDebugRequestDispatcher::handleRequest()", "content = %s", request.content.c_str()); + } + }; + + typedef SIPRequestDispatcher DefaultSIPRequestDispatcher; + } // namespace sip +} // namespace network + +#endif // __SIP__DISPATCHER_H__ diff --git a/src/common/network/sip/SIPHeaders.h b/src/common/network/sip/SIPHeaders.h new file mode 100644 index 00000000..df1b43df --- /dev/null +++ b/src/common/network/sip/SIPHeaders.h @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: BSL-1.0 +/* + * Digital Voice Modem - Common Library + * BSL-1.0 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (c) 2003-2013 Christopher M. Kohlhoff + * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file SIPClient.h + * @ingroup sip + */ +#if !defined(__SIP__SIP_HEADERS_H__) +#define __SIP__SIP_HEADERS_H__ + +#include "common/Defines.h" +#include "common/Log.h" +#include "common/Utils.h" + +#include +#include + +namespace network +{ + namespace sip + { + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + + struct SIPPayload; + + // --------------------------------------------------------------------------- + // Structure Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Represents SIP headers. + * @ingroup sip + */ + struct SIPHeaders { + /** + * @brief Structure representing an individual SIP header. + * @ingroup sip + */ + struct Header + { + /** + * @brief Header name. + */ + std::string name; + /** + * @brief Header value. + */ + std::string value; + + /** + * @brief Initializes a new instance of the Header class. + */ + Header() : name{}, value{} { /* stub */} + /** + * @brief Initializes a new instance of the Header class + * @param n Header name. + * @param v Header value. + */ + Header(std::string n, std::string v) : name{n}, value{v} { /* stub */ } + }; + + /** + * @brief Gets the list of SIP headers. + * @returns std::vector
List of SIP headers. + */ + std::vector
headers() const { return m_headers; } + /** + * @brief Returns true if the headers are empty. + * @returns bool True, if no SIP headers are present, otherwise false. + */ + bool empty() const { return m_headers.empty(); } + /** + * @brief Returns the number of headers. + * @returns std::size_t Number of headers. + */ + std::size_t size() const { return m_headers.size(); } + /** + * @brief Clears the list of SIP headers. + */ + void clearHeaders() { m_headers = std::vector
(); } + /** + * @brief Helper to add a SIP header. + * @param name Header name. + * @param value Header value. + */ + void add(const std::string& name, const std::string& value) + { + //::LogDebugEx(LOG_SIP, "SIPHeaders::add()", "header = %s, value = %s", name.c_str(), value.c_str()); + for (auto& header : m_headers) { + if (::strtolower(header.name) == ::strtolower(name)) { + header.value = value; + return; + } + } + + m_headers.push_back(Header(name, value)); + //for (auto header : m_headers) + // ::LogDebugEx(LOG_SIP, "SIPHeaders::add()", "m_headers.header = %s, m_headers.value = %s", header.name.c_str(), header.value.c_str()); + } + /** + * @brief Helper to remove a SIP header. + * @param headerName Header name. + */ + void remove(const std::string headerName) + { + auto header = std::find_if(m_headers.begin(), m_headers.end(), [&](const Header& h) { + return ::strtolower(h.name) == ::strtolower(headerName); + }); + + if (header != m_headers.end()) { + m_headers.erase(header); + } + } + /** + * @brief Helper to find the named SIP header. + * @param headerName Header name. + * @returns std::string Value of named header (if any). + */ + std::string find(const std::string headerName) const + { + auto header = std::find_if(m_headers.begin(), m_headers.end(), [&](const Header& h) { + return ::strtolower(h.name) == ::strtolower(headerName); + }); + + if (header != m_headers.end()) { + return header->value; + } + else { + return ""; + } + } + + private: + friend struct SIPPayload; + std::vector
m_headers; + }; + } // namespace sip +} // namespace network + +#endif // __SIP__SIP_HEADERS_H__ diff --git a/src/common/network/sip/SIPLexer.cpp b/src/common/network/sip/SIPLexer.cpp new file mode 100644 index 00000000..ff408329 --- /dev/null +++ b/src/common/network/sip/SIPLexer.cpp @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: BSL-1.0 +/* + * Digital Voice Modem - Common Library + * BSL-1.0 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (c) 2003-2013 Christopher M. Kohlhoff + * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "network/sip/SIPLexer.h" +#include "network/sip/SIPPayload.h" +#include "Log.h" + +using namespace network::sip; + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the SIPLexer class. */ + +SIPLexer::SIPLexer(bool clientLexer) : + m_headers(), + m_clientLexer(clientLexer), + m_consumed(0U), + m_state(METHOD_START) +{ + if (m_clientLexer) { + m_state = SIP_VERSION_S; + } +} + +/* Reset to initial parser state. */ + +void SIPLexer::reset() +{ + m_state = METHOD_START; + if (m_clientLexer) { + m_state = SIP_VERSION_S; + } + + m_headers = std::vector(); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Handle the next character of input. */ + +SIPLexer::ResultType SIPLexer::consume(SIPPayload& req, char input) +{ + m_consumed++; + switch (m_state) + { + /* + ** HTTP Method + */ + + case METHOD_START: + if (!isChar(input) || isControl(input) || isSpecial(input)) + return BAD; + else { + m_state = METHOD; + req.method.push_back(input); + return INDETERMINATE; + } + case METHOD: + if (input == ' ') { + m_state = URI; + return INDETERMINATE; + } + else if (!isChar(input) || isControl(input) || isSpecial(input)) { + return BAD; + } + else { + req.method.push_back(input); + return INDETERMINATE; + } + + /* + ** URI + */ + + case URI: + if (input == ' ') { + m_state = SIP_VERSION_S; + return INDETERMINATE; + } + else if (isControl(input)) { + return BAD; + } + else { + req.uri.push_back(input); + return INDETERMINATE; + } + + /* + ** SIP/2.0 + ** SIP/2.0 200 OK + */ + case SIP_VERSION_S: + if (input == 'S') { + m_state = SIP_VERSION_I; + return INDETERMINATE; + } + else { + return BAD; + } + case SIP_VERSION_I: + if (input == 'I') { + m_state = SIP_VERSION_P; + return INDETERMINATE; + } + else { + return BAD; + } + case SIP_VERSION_P: + if (input == 'P') { + m_state = SIP_VERSION_SLASH; + return INDETERMINATE; + } + else { + return BAD; + } + case SIP_VERSION_SLASH: + if (input == '/') { + req.sipVersionMajor = 0; + req.sipVersionMinor = 0; + m_state = SIP_VERSION_MAJOR_START; + return INDETERMINATE; + } + else { + return BAD; + } + case SIP_VERSION_MAJOR_START: + if (isDigit(input)) { + req.sipVersionMajor = req.sipVersionMajor * 10 + input - '0'; + m_state = SIP_VERSION_MAJOR; + return INDETERMINATE; + } + else { + return BAD; + } + case SIP_VERSION_MAJOR: + if (input == '.') { + m_state = SIP_VERSION_MINOR_START; + return INDETERMINATE; + } + else if (isDigit(input)) { + req.sipVersionMajor = req.sipVersionMajor * 10 + input - '0'; + return INDETERMINATE; + } + else { + return BAD; + } + case SIP_VERSION_MINOR_START: + if (isDigit(input)) { + req.sipVersionMinor = req.sipVersionMinor * 10 + input - '0'; + m_state = SIP_VERSION_MINOR; + return INDETERMINATE; + } + else { + return BAD; + } + case SIP_VERSION_MINOR: + if (input == '\r') { + m_state = EXPECTING_NEWLINE_1; + if (m_clientLexer) { + return BAD; + } + else { + return INDETERMINATE; + } + } + else if (input == ' ') { + if (m_clientLexer) { + m_state = SIP_STATUS_1; + return INDETERMINATE; + } + else { + return BAD; + } + } + else if (isDigit(input)) { + req.sipVersionMinor = req.sipVersionMinor * 10 + input - '0'; + return INDETERMINATE; + } + else { + return BAD; + } + case SIP_STATUS_1: + if (isDigit(input)) { + m_state = SIP_STATUS_2; + m_status = m_status * 10 + input - '0'; + return INDETERMINATE; + } + else { + return BAD; + } + case SIP_STATUS_2: + if (isDigit(input)) { + m_state = SIP_STATUS_3; + m_status = m_status * 10 + input - '0'; + return INDETERMINATE; + } + else { + return BAD; + } + case SIP_STATUS_3: + if (isDigit(input)) { + m_state = SIP_STATUS_END; + m_status = m_status * 10 + input - '0'; + req.status = (SIPPayload::StatusType)m_status; + return INDETERMINATE; + } + else { + return BAD; + } + case SIP_STATUS_END: + if (input == ' ') { + m_state = SIP_STATUS_MESSAGE; + return INDETERMINATE; + } + else { + return BAD; + } + case SIP_STATUS_MESSAGE_START: + if (!isChar(input) || isControl(input) || isSpecial(input)) { + return BAD; + } + else { + m_state = SIP_STATUS_MESSAGE; + return INDETERMINATE; + } + case SIP_STATUS_MESSAGE: + if (input == '\r') { + m_state = EXPECTING_NEWLINE_1; + return INDETERMINATE; + } + else if (input == ' ') { + m_state = SIP_STATUS_MESSAGE; + return INDETERMINATE; + } + else if (!isChar(input) || isControl(input) || isSpecial(input)) { + return BAD; + } + else { + return INDETERMINATE; + } + + case EXPECTING_NEWLINE_1: + if (input == '\n') { + m_state = HEADER_LINE_START; + return INDETERMINATE; + } + else { + return BAD; + } + + /* + ** Headers + */ + + case HEADER_LINE_START: + if (input == '\r') { + m_state = EXPECTING_NEWLINE_3; + return INDETERMINATE; + } + else if (!req.headers.empty() && (input == ' ' || input == '\t')) { + m_state = HEADER_LWS; + return INDETERMINATE; + } + else if (!isChar(input) || isControl(input) || isSpecial(input)) { + return BAD; + } + else { + m_headers.push_back(LexedHeader()); + m_headers.back().name.push_back(std::tolower(input)); + m_state = HEADER_NAME; + return INDETERMINATE; + } + + case HEADER_LWS: + if (input == '\r') { + m_state = EXPECTING_NEWLINE_2; + return INDETERMINATE; + } + else if (input == ' ' || input == '\t') { + return INDETERMINATE; + } + else if (isControl(input)) { + return BAD; + } + else { + m_state = HEADER_VALUE; + m_headers.back().value.push_back(input); + return INDETERMINATE; + } + + case HEADER_NAME: + if (input == ':') { + m_state = SPACE_BEFORE_HEADER_VALUE; + return INDETERMINATE; + } + else if (!isChar(input) || isControl(input) || isSpecial(input)) { + return BAD; + } + else + { + m_headers.back().name.push_back(std::tolower(input)); + return INDETERMINATE; + } + + case SPACE_BEFORE_HEADER_VALUE: + if (input == ' ') { + m_state = HEADER_VALUE; + return INDETERMINATE; + } + else { + return BAD; + } + + case HEADER_VALUE: + if (input == '\r') { + m_state = EXPECTING_NEWLINE_2; + return INDETERMINATE; + } + else if (isControl(input)) { + return BAD; + } + else { + m_headers.back().value.push_back(input); + return INDETERMINATE; + } + + case EXPECTING_NEWLINE_2: + if (input == '\n') { + m_state = HEADER_LINE_START; + return INDETERMINATE; + } + else { + return BAD; + } + + case EXPECTING_NEWLINE_3: + if (input == '\n') { + for (auto header : m_headers) { + //::LogDebugEx(LOG_SIP, "SIPLexer::consume()", "header = %s, value = %s", header.name.c_str(), header.value.c_str()); + req.headers.add(header.name, header.value); + } + + return GOOD; + } else { + return BAD; + } + + default: + return BAD; + } +} + +/* Check if a byte is an SIP character. */ + +bool SIPLexer::isChar(int c) +{ + return c >= 0 && c <= 127; +} + +/* Check if a byte is an SIP control character. */ + +bool SIPLexer::isControl(int c) +{ + return (c >= 0 && c <= 31) || (c == 127); +} + +/* Check if a byte is an SIP special character. */ + +bool SIPLexer::isSpecial(int c) +{ + switch (c) + { + case '(': case ')': case '<': case '>': case '@': + case ',': case ';': case ':': case '\\': case '"': + case '/': case '[': case ']': case '?': case '=': + case '{': case '}': case ' ': case '\t': + return true; + default: + return false; + } +} + +/* Check if a byte is an digit. */ + +bool SIPLexer::isDigit(int c) +{ + return c >= '0' && c <= '9'; +} diff --git a/src/common/network/sip/SIPLexer.h b/src/common/network/sip/SIPLexer.h new file mode 100644 index 00000000..ec897ebb --- /dev/null +++ b/src/common/network/sip/SIPLexer.h @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: BSL-1.0 +/* + * Digital Voice Modem - Common Library + * BSL-1.0 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (c) 2003-2013 Christopher M. Kohlhoff + * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file SIPLexer.h + * @ingroup sip + * @file SIPLexer.cpp + * @ingroup sip + */ +#if !defined(__SIP__SIP_LEXER_H__) +#define __SIP__SIP_LEXER_H__ + +#include "common/Defines.h" + +#include +#include + +namespace network +{ + namespace sip + { + // --------------------------------------------------------------------------- + // Class Prototypes + // --------------------------------------------------------------------------- + + struct SIPPayload; + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief This class implements the lexer for incoming payloads. + */ + class SIPLexer { + public: + /** + * @brief Lexing result. + */ + enum ResultType { GOOD, BAD, INDETERMINATE, CONTINUE }; + + /** + * @brief Initializes a new instance of the SIPLexer class. + * @param clientLexer Flag indicating this lexer is used for a SIP client. + */ + SIPLexer(bool clientLexer); + + /** + * @brief Reset to initial parser state. + */ + void reset(); + + /** + * @brief Parse some data. The enum return value is good when a complete request has + * been parsed, bad if the data is invalid, indeterminate when more data is + * required. The InputIterator return value indicates how much of the input + * has been consumed. + * @tparam InputIterator + * @param payload SIP request payload. + * @param begin + * @param end + * @returns std::tuple + */ + template + std::tuple parse(SIPPayload& payload, InputIterator begin, InputIterator end) + { + while (begin != end) { + ResultType result = consume(payload, *begin++); + if (result == GOOD || result == BAD) + return std::make_tuple(result, begin); + } + return std::make_tuple(INDETERMINATE, begin); + } + + /** + * @brief Returns flag indicating whether or not characters have been consumed from the payload. + * @returns True, if characters were consumed, otherwise false. + */ + uint32_t consumed() const { return m_consumed; } + + private: + /** + * @brief Handle the next character of input. + * @param payload SIP request payload. + * @param input Character. + */ + ResultType consume(SIPPayload& payload, char input); + + /** + * @brief Check if a byte is an SIP character. + * @param c Character. + * @returns bool True, if character is an SIP character, otherwise false. + */ + static bool isChar(int c); + /** + * @brief Check if a byte is an SIP control character. + * @param c Character. + * @returns bool True, if character is an SIP control character, otherwise false. + */ + static bool isControl(int c); + /** + * @brief Check if a byte is an SIP special character. + * @param c Character. + * @returns bool True, if character is an SIP special character, otherwise false. + */ + static bool isSpecial(int c); + /** + * @brief Check if a byte is an digit. + * @param c Character. + * @returns bool True, if character is a digit, otherwise false. + */ + static bool isDigit(int c); + + /** + * @brief Structure representing lexed SIP headers. + */ + struct LexedHeader + { + /** + * @brief Header name. + */ + std::string name; + /** + * @brief Header value. + */ + std::string value; + + /** + * @brief Initializes a new instance of the LexedHeader class + */ + LexedHeader() { /* stub */ } + /** + * @brief Initializes a new instance of the LexedHeader class + * @param n Header name. + * @param v Header value. + */ + LexedHeader(const std::string& n, const std::string& v) : name(n), value(v) {} + }; + + std::vector m_headers; + uint16_t m_status; + bool m_clientLexer = false; + uint32_t m_consumed; + + /** + * @brief Lexer machine state. + */ + enum state + { + METHOD_START, //! SIP Method Start + METHOD, //! SIP Method + URI, //! SIP URI + + SIP_VERSION_S, //! SIP Version: S + SIP_VERSION_I, //! SIP Version: I + SIP_VERSION_P, //! SIP Version: P + SIP_VERSION_SLASH, //! SIP Version: / + SIP_VERSION_MAJOR_START, //! SIP Version Major Start + SIP_VERSION_MAJOR, //! SIP Version Major + SIP_VERSION_MINOR_START, //! SIP Version Minor Start + SIP_VERSION_MINOR, //! SIP Version Minor + + SIP_STATUS_1, //! Status Number 1 + SIP_STATUS_2, //! Status Number 2 + SIP_STATUS_3, //! Status Number 3 + SIP_STATUS_END, //! Status End + SIP_STATUS_MESSAGE_START, //! Status Message Start + SIP_STATUS_MESSAGE, //! Status Message End + + EXPECTING_NEWLINE_1, //! + + HEADER_LINE_START, //! Header Line Start + HEADER_LWS, //! + HEADER_NAME, //! Header Name + SPACE_BEFORE_HEADER_VALUE, //! + HEADER_VALUE, //! Header Value + + EXPECTING_NEWLINE_2, //! + EXPECTING_NEWLINE_3 //! + } m_state; + }; + } // namespace sip +} // namespace network + +#endif // __SIP__SIP_LEXER_H__ diff --git a/src/common/network/sip/SIPPayload.cpp b/src/common/network/sip/SIPPayload.cpp new file mode 100644 index 00000000..ffc7b012 --- /dev/null +++ b/src/common/network/sip/SIPPayload.cpp @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: BSL-1.0 +/* + * Digital Voice Modem - Common Library + * BSL-1.0 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (c) 2003-2013 Christopher M. Kohlhoff + * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "network/sip/SIPPayload.h" +#include "Log.h" +#include "Utils.h" + +using namespace network::sip; + +#include + +namespace status_strings { + const std::string trying = "SIP/2.0 100 Trying\r\n"; + const std::string ringing = "SIP/2.0 180 Ringing\r\n"; + const std::string ok = "SIP/2.0 200 OK\r\n"; + const std::string accepted = "SIP/2.0 202 Accepted\r\n"; + const std::string no_notify = "SIP/2.0 204 No Notification\r\n"; + const std::string multiple_choices = "SIP/2.0 300 Multiple Choices\r\n"; + const std::string moved_permanently = "SIP/2.0 301 Moved Permanently\r\n"; + const std::string moved_temporarily = "SIP/2.0 302 Moved Temporarily\r\n"; + const std::string bad_request = "SIP/2.0 400 Bad Request\r\n"; + const std::string unauthorized = "SIP/2.0 401 Unauthorized\r\n"; + const std::string forbidden = "SIP/2.0 403 Forbidden\r\n"; + const std::string not_found = "SIP/2.0 404 Not Found\r\n"; + const std::string internal_server_error = "SIP/2.0 500 Internal Server Error\r\n"; + const std::string not_implemented = "SIP/2.0 501 Not Implemented\r\n"; + const std::string bad_gateway = "SIP/2.0 502 Bad Gateway\r\n"; + const std::string service_unavailable = "SIP/2.0 503 Service Unavailable\r\n"; + const std::string busy_everywhere = "SIP/2.0 600 Busy Everywhere\r\n"; + const std::string decline = "SIP/2.0 603 Decline\r\n"; + + asio::const_buffer toBuffer(SIPPayload::StatusType status) + { + switch (status) + { + case SIPPayload::TRYING: + return asio::buffer(trying); + case SIPPayload::RINGING: + return asio::buffer(ringing); + case SIPPayload::OK: + return asio::buffer(ok); + case SIPPayload::ACCEPTED: + return asio::buffer(accepted); + case SIPPayload::NO_NOTIFY: + return asio::buffer(no_notify); + case SIPPayload::MULTIPLE_CHOICES: + return asio::buffer(multiple_choices); + case SIPPayload::MOVED_PERMANENTLY: + return asio::buffer(moved_permanently); + case SIPPayload::MOVED_TEMPORARILY: + return asio::buffer(moved_temporarily); + case SIPPayload::BAD_REQUEST: + return asio::buffer(bad_request); + case SIPPayload::UNAUTHORIZED: + return asio::buffer(unauthorized); + case SIPPayload::FORBIDDEN: + return asio::buffer(forbidden); + case SIPPayload::NOT_FOUND: + return asio::buffer(not_found); + case SIPPayload::INTERNAL_SERVER_ERROR: + return asio::buffer(internal_server_error); + case SIPPayload::NOT_IMPLEMENTED: + return asio::buffer(not_implemented); + case SIPPayload::BAD_GATEWAY: + return asio::buffer(bad_gateway); + case SIPPayload::SERVICE_UNAVAILABLE: + return asio::buffer(service_unavailable); + case SIPPayload::BUSY_EVERYWHERE: + return asio::buffer(busy_everywhere); + case SIPPayload::DECLINE: + return asio::buffer(decline); + default: + return asio::buffer(internal_server_error); + } + } +} // namespace status_strings + +namespace misc_strings { + const char name_value_separator[] = { ':', ' ' }; + const char request_method_separator[] = { ' ' }; + const char crlf[] = { '\r', '\n' }; + + const char sip_default_version[] = { 'S', 'I', 'P', '/', '2', '.', '0' }; +} // namespace misc_strings + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Convert the reply into a vector of buffers. The buffers do not own the underlying memory blocks, therefore the reply object must remain valid and not be changed until the write operation has completed. */ + +std::vector SIPPayload::toBuffers() +{ + std::vector buffers; + if (isClientPayload) { + // copy method and erase zero terminator + method.erase(std::find(method.begin(), method.end(), '\0'), method.end()); + + // copy URI and erase zero terminator + uri.erase(std::find(uri.begin(), uri.end(), '\0'), uri.end()); +#if DEBUG_SIP_PAYLOAD + ::LogDebugEx(LOG_SIP, "SIPPayload::toBuffers()", "method = %s, uri = %s", method.c_str(), uri.c_str()); +#endif + buffers.push_back(asio::buffer(method)); + buffers.push_back(asio::buffer(misc_strings::request_method_separator)); + buffers.push_back(asio::buffer(uri)); + buffers.push_back(asio::buffer(misc_strings::request_method_separator)); + buffers.push_back(asio::buffer(misc_strings::sip_default_version)); + buffers.push_back(asio::buffer(misc_strings::crlf)); + } + else { + buffers.push_back(status_strings::toBuffer(status)); + } + + for (std::size_t i = 0; i < headers.size(); ++i) { + SIPHeaders::Header& h = headers.m_headers[i]; +#if DEBUG_SIP_PAYLOAD + ::LogDebugEx(LOG_SIP, "SIPPayload::toBuffers()", "header = %s, value = %s", h.name.c_str(), h.value.c_str()); +#endif + + buffers.push_back(asio::buffer(h.name)); + buffers.push_back(asio::buffer(misc_strings::name_value_separator)); + buffers.push_back(asio::buffer(h.value)); + buffers.push_back(asio::buffer(misc_strings::crlf)); + } + + buffers.push_back(asio::buffer(misc_strings::crlf)); + if (content.size() > 0) + buffers.push_back(asio::buffer(content)); + +#if DEBUG_SIP_PAYLOAD + ::LogDebugEx(LOG_SIP, "SIPPayload::toBuffers()", "content = %s", content.c_str()); + for (auto buffer : buffers) + Utils::dump("[SIPPayload::toBuffers()] buffer[]", (uint8_t*)buffer.data(), buffer.size()); +#endif + + return buffers; +} + +/* Prepares payload for transmission by finalizing status and content type. */ + +void SIPPayload::payload(json::object& obj, SIPPayload::StatusType s) +{ + json::value v = json::value(obj); + std::string json = std::string(v.serialize()); + payload(json, s, "application/json"); +} + +/* Prepares payload for transmission by finalizing status and content type. */ + +void SIPPayload::payload(std::string& c, SIPPayload::StatusType s, const std::string& contentType) +{ + content = c; + status = s; + ensureDefaultHeaders(contentType); +} + +// --------------------------------------------------------------------------- +// Static Members +// --------------------------------------------------------------------------- + +/* Get a status payload. */ + +SIPPayload SIPPayload::requestPayload(std::string method, std::string uri) +{ + SIPPayload rep; + rep.isClientPayload = true; + rep.method = ::strtoupper(method); + rep.uri = std::string(uri); + return rep; +} + +/* Get a status payload. */ + +SIPPayload SIPPayload::statusPayload(SIPPayload::StatusType status, const std::string& contentType) +{ + SIPPayload rep; + rep.isClientPayload = false; + rep.status = status; + rep.ensureDefaultHeaders(contentType); + + return rep; +} + + +/* Helper to attach a host TCP stream reader. */ + +void SIPPayload::attachHostHeader(const asio::ip::tcp::endpoint remoteEndpoint) +{ + headers.add("Host", std::string(remoteEndpoint.address().to_string() + ":" + std::to_string(remoteEndpoint.port()))); +} + +// --------------------------------------------------------------------------- +// Private Members +// --------------------------------------------------------------------------- + +/* Internal helper to ensure the headers are of a default for the given content type. */ + +void SIPPayload::ensureDefaultHeaders(const std::string& contentType) +{ + if (!isClientPayload) { + headers.add("Content-Type", std::string(contentType)); + headers.add("Content-Length", std::to_string(content.size())); + headers.add("Server", std::string(("DVM/" __VER__))); + } + else { + headers.add("User-Agent", std::string(("DVM/" __VER__))); + headers.add("Accept", "*/*"); + headers.add("Content-Type", std::string(contentType)); + headers.add("Content-Length", std::to_string(content.size())); + } +} diff --git a/src/common/network/sip/SIPPayload.h b/src/common/network/sip/SIPPayload.h new file mode 100644 index 00000000..8ea68b99 --- /dev/null +++ b/src/common/network/sip/SIPPayload.h @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: BSL-1.0 +/* + * Digital Voice Modem - Common Library + * BSL-1.0 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (c) 2003-2013 Christopher M. Kohlhoff + * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file SIPPayload.h + * @ingroup sip + * @file SIPPayload.cpp + * @ingroup sip + */ +#if !defined(__SIP__SIP_PAYLOAD_H__) +#define __SIP__SIP_PAYLOAD_H__ + +#include "common/Defines.h" +#include "common/network/json/json.h" +#include "common/network/sip/SIPHeaders.h" + +#include +#include + +#include + +namespace network +{ + namespace sip + { + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + #define SIP_INVITE "INVITE" + #define SIP_ACK "ACK" + #define SIP_BYE "BYE" + #define SIP_CANCEL "CANCEL" + #define SIP_REGISTER "REGISTER" + #define SIP_OPTIONS "OPTIONS" + #define SIP_SUBSCRIBE "SUBSCRIBE" + #define SIP_NOTIFY "NOTIFY" + #define SIP_PUBLISH "PUBLISH" + #define SIP_INFO "INFO" + #define SIP_MESSAGE "MESSAGE" + #define SIP_UPDATE "UPDATE" + + // --------------------------------------------------------------------------- + // Structure Declaration + // --------------------------------------------------------------------------- + + /** + * @brief This struct implements a model of a payload to be sent to a + * SIP client/server. + * @ingroup sip + */ + struct SIPPayload { + /** + * @brief SIP Status/Response Codes + */ + enum StatusType { + TRYING = 100, //! SIP Trying 100 + RINGING = 180, //! SIP Ringing 180 + + OK = 200, //! SIP OK 200 + ACCEPTED = 202, //! SIP Accepted 202 + NO_NOTIFY = 204, //! SIP No Notification 204 + + MULTIPLE_CHOICES = 300, //! SIP Multiple Choices 300 + MOVED_PERMANENTLY = 301, //! SIP Moved Permenantly 301 + MOVED_TEMPORARILY = 302, //! SIP Moved Temporarily 302 + + BAD_REQUEST = 400, //! SIP Bad Request 400 + UNAUTHORIZED = 401, //! SIP Unauthorized 401 + FORBIDDEN = 403, //! SIP Forbidden 403 + NOT_FOUND = 404, //! SIP Not Found 404 + + INTERNAL_SERVER_ERROR = 500, //! SIP Internal Server Error 500 + NOT_IMPLEMENTED = 501, //! SIP Not Implemented 501 + BAD_GATEWAY = 502, //! SIP Bad Gateway 502 + SERVICE_UNAVAILABLE = 503, //! SIP Service Unavailable 503 + + BUSY_EVERYWHERE = 600, //! SIP Busy Everywhere 600 + DECLINE = 603, //! SIP Decline 603 + } status; + + SIPHeaders headers; + std::string content; + size_t contentLength; + + std::string method; + std::string uri; + + int sipVersionMajor; + int sipVersionMinor; + + bool isClientPayload = false; + + /** + * @brief Convert the payload into a vector of buffers. The buffers do not own the + * underlying memory blocks, therefore the payload object must remain valid and + * not be changed until the write operation has completed. + * @returns std::vector List of buffers representing the SIP payload. + */ + std::vector toBuffers(); + + /** + * @brief Prepares payload for transmission by finalizing status and content type. + * @param obj + * @param status SIP status. + */ + void payload(json::object& obj, StatusType status = OK); + /** + * @brief Prepares payload for transmission by finalizing status and content type. + * @param content + * @param status SIP status. + * @param contentType SIP content type. + */ + void payload(std::string& content, StatusType status = OK, const std::string& contentType = "application/sdp"); + + /** + * @brief Get a request payload. + * @param method SIP method. + * @param uri SIP uri. + */ + static SIPPayload requestPayload(std::string method, std::string uri); + /** + * @brief Get a status payload. + * @param status SIP status. + * @param contentType SIP content type. + */ + static SIPPayload statusPayload(StatusType status, const std::string& contentType = "application/sdp"); + + /** + * @brief Helper to attach a host TCP stream reader. + * @param remoteEndpoint Endpoint. + */ + void attachHostHeader(const asio::ip::tcp::endpoint remoteEndpoint); + + private: + /** + * @brief Internal helper to ensure the headers are of a default for the given content type. + * @param contentType SIP content type. + */ + void ensureDefaultHeaders(const std::string& contentType = "application/sdp"); + }; + } // namespace sip +} // namespace network + +#endif // __SIP__SIP_PAYLOAD_H__