code cleanup; add SIP lexer and data handlers;

pull/86/head
Bryan Biedenkapp 11 months ago
parent 07edd0dbff
commit cf2dee95d2

@ -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"

@ -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"

@ -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<void(const Request&, Reply&)> RequestHandlerType;
/**
* @brief Initializes a new instance of the DebugRequestDispatcher class.
* @brief Initializes a new instance of the BasicRequestDispatcher class.
*/
BasicRequestDispatcher() { /* stub */ }
/**

@ -34,7 +34,6 @@ namespace network
{
namespace http
{
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------

@ -28,7 +28,6 @@ namespace network
{
namespace http
{
// ---------------------------------------------------------------------------
// Class Prototypes
// ---------------------------------------------------------------------------

@ -28,7 +28,6 @@ namespace network
{
namespace http
{
// ---------------------------------------------------------------------------
// Class Prototypes
// ---------------------------------------------------------------------------

@ -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__

@ -27,7 +27,6 @@ namespace network
{
namespace http
{
// ---------------------------------------------------------------------------
// Class Prototypes
// ---------------------------------------------------------------------------

@ -34,7 +34,6 @@ namespace network
{
namespace http
{
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------

@ -40,7 +40,6 @@ namespace network
{
namespace http
{
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------

@ -37,7 +37,6 @@ namespace network
{
namespace http
{
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------

@ -42,7 +42,6 @@ namespace network
// ---------------------------------------------------------------------------
// Class Declaration
//
// ---------------------------------------------------------------------------
/**

@ -26,7 +26,6 @@ namespace network
{
namespace http
{
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------

@ -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 <functional>
#include <map>
#include <string>
#include <regex>
#include <memory>
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<typename Request, typename Reply>
struct RequestMatcher {
typedef std::function<void(const Request&, Reply&, const RequestMatch&)> 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<Request, Reply>& 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<Request, Reply>& 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<Request, Reply>& 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<Request, Reply>& 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<Request, Reply>& 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<Request, Reply>& 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<Request, Reply>& 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<Request, Reply>& 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<Request, Reply>& 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<Request, Reply>& 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<Request, Reply>& 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<Request, Reply>& 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<std::string, RequestHandlerType> m_handlers;
};
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class implements SIP request dispatching.
* @tparam Request SIP request.
* @tparam Reply SIP reply.
*/
template<typename Request = SIPPayload, typename Reply = SIPPayload>
class SIPRequestDispatcher {
typedef RequestMatcher<Request, Reply> 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<MatcherType>(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<MatcherType> MatcherTypePtr;
std::string m_basePath;
std::map<std::string, MatcherTypePtr> 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<typename Request = SIPPayload, typename Reply = SIPPayload>
class SIPBasicRequestDispatcher {
public:
typedef std::function<void(const Request&, Reply&)> 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<typename Request = SIPPayload, typename Reply = SIPPayload>
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<SIPPayload, SIPPayload> DefaultSIPRequestDispatcher;
} // namespace sip
} // namespace network
#endif // __SIP__DISPATCHER_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 <string>
#include <vector>
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<Header> List of SIP headers.
*/
std::vector<Header> 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<Header>(); }
/**
* @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<Header> m_headers;
};
} // namespace sip
} // namespace network
#endif // __SIP__SIP_HEADERS_H__

@ -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 <cctype>
// ---------------------------------------------------------------------------
// 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<LexedHeader>();
}
// ---------------------------------------------------------------------------
// 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';
}

@ -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 <tuple>
#include <vector>
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<ResultType, InputIterator>
*/
template <typename InputIterator>
std::tuple<ResultType, InputIterator> 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<LexedHeader> 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__

@ -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 <string>
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<asio::const_buffer> SIPPayload::toBuffers()
{
std::vector<asio::const_buffer> 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()));
}
}

@ -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 <string>
#include <vector>
#include <asio.hpp>
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<asio::const_buffer> List of buffers representing the SIP payload.
*/
std::vector<asio::const_buffer> 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__
Loading…
Cancel
Save

Powered by TurnKey Linux.