parent
07edd0dbff
commit
cf2dee95d2
@ -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…
Reference in new issue