├── src ├── SIP │ ├── SipMessageFactory.hpp │ ├── SipClient.cpp │ ├── SipClient.hpp │ ├── SipServer.hpp │ ├── SipMessageHeaders.h │ ├── SipMessageFactory.cpp │ ├── SipServer.cpp │ ├── Session.hpp │ ├── SipSdpMessage.hpp │ ├── SipMessageTypes.h │ ├── Session.cpp │ ├── SipMessage.hpp │ ├── RequestsHandler.hpp │ ├── SipSdpMessage.cpp │ ├── SipMessage.cpp │ └── RequestsHandler.cpp └── Helpers │ ├── IDGen.hpp │ ├── UdpServer.hpp │ ├── UdpServer.cpp │ └── cxxopts.hpp ├── main.cpp ├── CMakeLists.txt ├── LICENSE ├── README.md └── .gitignore /src/SIP/SipMessageFactory.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SIP_MESSAGE_FACTORY_HPP 2 | #define SIP_MESSAGE_FACTORY_HPP 3 | 4 | #include 5 | #include 6 | #include "SipSdpMessage.hpp" 7 | 8 | class SipMessageFactory 9 | { 10 | public: 11 | std::optional> createMessage(std::string message, sockaddr_in src); 12 | 13 | private: 14 | static constexpr auto SDP_CONTENT_TYPE = "application/sdp"; 15 | 16 | bool containsSdp(const std::string& message) const; 17 | }; 18 | 19 | #endif -------------------------------------------------------------------------------- /src/SIP/SipClient.cpp: -------------------------------------------------------------------------------- 1 | #include "SipClient.hpp" 2 | 3 | SipClient::SipClient(std::string number, sockaddr_in address) : _number(std::move(number)), _address(std::move(address)) 4 | { 5 | } 6 | 7 | bool SipClient::operator==(SipClient other) 8 | { 9 | if (_number == other.getNumber()) 10 | { 11 | return true; 12 | } 13 | 14 | return false; 15 | } 16 | 17 | std::string SipClient::getNumber() const 18 | { 19 | return _number; 20 | } 21 | 22 | sockaddr_in SipClient::getAddress() const 23 | { 24 | return _address; 25 | } -------------------------------------------------------------------------------- /src/SIP/SipClient.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SIP_CLIENT_HPP 2 | #define SIP_CLIENT_HPP 3 | 4 | #ifdef __linux__ 5 | #include 6 | #elif defined _WIN32 || defined _WIN64 7 | #include 8 | #endif 9 | 10 | #include 11 | 12 | class SipClient 13 | { 14 | public: 15 | SipClient(std::string number, sockaddr_in address); 16 | 17 | bool operator==(SipClient other); 18 | 19 | std::string getNumber() const; 20 | sockaddr_in getAddress() const; 21 | 22 | private: 23 | std::string _number; 24 | sockaddr_in _address; 25 | }; 26 | 27 | #endif -------------------------------------------------------------------------------- /src/SIP/SipServer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SIP_SERVER_HPP 2 | #define SIP_SERVER_HPP 3 | 4 | #include "UdpServer.hpp" 5 | #include "RequestsHandler.hpp" 6 | #include "Session.hpp" 7 | #include "SipMessageFactory.hpp" 8 | 9 | class SipServer 10 | { 11 | public: 12 | SipServer(std::string ip, int port = 5060); 13 | 14 | private: 15 | void onNewMessage(std::string data, sockaddr_in src); 16 | void onHandled(const sockaddr_in& dest, std::shared_ptr message); 17 | 18 | UdpServer _socket; 19 | RequestsHandler _handler; 20 | SipMessageFactory _messagesFactory; 21 | }; 22 | #endif -------------------------------------------------------------------------------- /src/Helpers/IDGen.hpp: -------------------------------------------------------------------------------- 1 | #ifndef IDGEN_HPP 2 | #define IDGEN_HPP 3 | 4 | #include 5 | #include 6 | 7 | class IDGen 8 | { 9 | public: 10 | IDGen() = delete; 11 | 12 | static std::string GenerateID(int len) { 13 | std::ostringstream id; 14 | for (int i = 0; i < len; ++i) { 15 | id << alphanum[rand() % (sizeof(alphanum) - 1)]; 16 | } 17 | 18 | return id.str(); 19 | } 20 | 21 | private: 22 | static constexpr char alphanum[] = 23 | "0123456789" 24 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 25 | "abcdefghijklmnopqrstuvwxyz"; 26 | }; 27 | 28 | #endif -------------------------------------------------------------------------------- /src/SIP/SipMessageHeaders.h: -------------------------------------------------------------------------------- 1 | #ifndef SIP_MESSAGE_HEADERS_H 2 | #define SIP_MESSAGE_HEADERS_H 3 | 4 | class SipMessageHeaders 5 | { 6 | public: 7 | SipMessageHeaders() = delete; 8 | 9 | static constexpr auto VIA = "Via"; 10 | static constexpr auto FROM = "From"; 11 | static constexpr auto TO = "To"; 12 | static constexpr auto CALL_ID = "Call-ID"; 13 | static constexpr auto CSEQ = "CSeq"; 14 | static constexpr auto CONTACT = "Contact"; 15 | static constexpr auto CONTENT_LENGTH = "Content-Length"; 16 | 17 | static constexpr auto HEADERS_DELIMETER = "\r\n"; 18 | }; 19 | 20 | #endif -------------------------------------------------------------------------------- /src/SIP/SipMessageFactory.cpp: -------------------------------------------------------------------------------- 1 | #include "SipMessageFactory.hpp" 2 | 3 | std::optional> SipMessageFactory::createMessage(std::string message, sockaddr_in src) 4 | { 5 | try 6 | { 7 | if (containsSdp(message)) 8 | { 9 | return std::make_shared(std::move(message), std::move(src)); 10 | } 11 | 12 | return std::make_shared(std::move(message), std::move(src)); 13 | } 14 | catch (const std::exception&) 15 | { 16 | return {}; 17 | } 18 | } 19 | 20 | bool SipMessageFactory::containsSdp(const std::string& message) const 21 | { 22 | return message.find(SDP_CONTENT_TYPE) != std::string::npos; 23 | } 24 | -------------------------------------------------------------------------------- /src/SIP/SipServer.cpp: -------------------------------------------------------------------------------- 1 | #include "SipServer.hpp" 2 | #include "SipMessageTypes.h" 3 | 4 | SipServer::SipServer(std::string ip, int port) : 5 | _socket(ip, port, std::bind(&SipServer::onNewMessage, this, std::placeholders::_1, std::placeholders::_2)), 6 | _handler(ip, port, std::bind(&SipServer::onHandled, this, std::placeholders::_1, std::placeholders::_2)) 7 | { 8 | _socket.startReceive(); 9 | } 10 | 11 | void SipServer::onNewMessage(std::string data, sockaddr_in src) 12 | { 13 | auto message = _messagesFactory.createMessage(std::move(data), std::move(src)); 14 | if (message.has_value()) 15 | { 16 | _handler.handle(std::move(message.value())); 17 | } 18 | } 19 | 20 | void SipServer::onHandled(const sockaddr_in& dest, std::shared_ptr message) 21 | { 22 | _socket.send(dest, message->toString()); 23 | } -------------------------------------------------------------------------------- /src/SIP/Session.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SESSION_HPP 2 | #define SESSION_HPP 3 | 4 | #include 5 | 6 | #include "SipClient.hpp" 7 | 8 | class Session 9 | { 10 | public: 11 | 12 | enum class State 13 | { 14 | Invited, 15 | Busy, 16 | Unavailable, 17 | Cancel, 18 | Bye, 19 | Connected, 20 | }; 21 | 22 | 23 | Session(std::string callID, std::shared_ptr src, uint32_t srcRtpPort); 24 | 25 | void setState(State state); 26 | void setDest(std::shared_ptr dest, uint32_t destRtpPort); 27 | 28 | std::string getCallID() const; 29 | std::shared_ptr getSrc() const; 30 | std::shared_ptr getDest() const; 31 | State getState() const; 32 | 33 | private: 34 | std::string _callID; 35 | std::shared_ptr _src; 36 | std::shared_ptr _dest; 37 | State _state; 38 | 39 | uint32_t _srcRtpPort; 40 | uint32_t _destRtpPort; 41 | }; 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /src/SIP/SipSdpMessage.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SIP_SDP_MESSAGE_HPP 2 | #define SIP_SDP_MESSAGE_HPP 3 | 4 | #include "SipMessage.hpp" 5 | 6 | class SipSdpMessage : public SipMessage 7 | { 8 | public: 9 | SipSdpMessage(std::string message, sockaddr_in src); 10 | 11 | void setMedia(std::string value); 12 | void setRtpPort(int port); 13 | 14 | std::string getVersion() const; 15 | std::string getOriginator() const; 16 | std::string getSessionName() const; 17 | std::string getConnectionInformation() const; 18 | std::string getTime() const; 19 | std::string getMedia() const; 20 | int getRtpPort() const; 21 | 22 | private: 23 | void parse() override; 24 | int extractRtpPort(std::string data) const; 25 | 26 | std::string _version; 27 | std::string _originator; 28 | std::string _sessionName; 29 | std::string _connectionInformation; 30 | std::string _time; 31 | std::string _media; 32 | int _rtpPort; 33 | }; 34 | 35 | #endif -------------------------------------------------------------------------------- /src/SIP/SipMessageTypes.h: -------------------------------------------------------------------------------- 1 | #ifndef SIP_MESSAGE_TYPES_H 2 | #define SIP_MESSAGE_TYPES_H 3 | 4 | class SipMessageTypes 5 | { 6 | public: 7 | SipMessageTypes() = delete; 8 | 9 | static constexpr auto REGISTER = "REGISTER"; 10 | static constexpr auto INVITE = "INVITE"; 11 | static constexpr auto CANCEL = "CANCEL"; 12 | static constexpr auto REQUEST_TERMINATED = "SIP/2.0 487 Request Terminated"; 13 | static constexpr auto TRYING = "SIP/2.0 100 Trying"; 14 | static constexpr auto RINGING = "SIP/2.0 180 Ringing"; 15 | static constexpr auto BUSY = "SIP/2.0 486 Busy Here"; 16 | static constexpr auto UNAVAIALBLE = "SIP/2.0 480 Temporarily Unavailable"; 17 | static constexpr auto OK = "SIP/2.0 200 OK"; 18 | static constexpr auto ACK = "ACK"; 19 | static constexpr auto BYE = "BYE"; 20 | static constexpr auto NOT_FOUND = "SIP/2.0 404 Not Found"; 21 | }; 22 | 23 | #endif -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "SipServer.hpp" 3 | #include "cxxopts.hpp" 4 | 5 | int main(int argc, char** argv) 6 | { 7 | cxxopts::Options options("SipServer", "Open source server for handling voip calls based on sip."); 8 | 9 | options.add_options() 10 | ("h,help", "Print usage") 11 | ("i,ip", "Sip server ip", cxxopts::value()) 12 | ("p,port", "Sip server ip.", cxxopts::value()->default_value(std::to_string(5060))); 13 | 14 | auto result = options.parse(argc, argv); 15 | 16 | if (result.count("help")) 17 | { 18 | std::cout << options.help() << std::endl; 19 | exit(0); 20 | } 21 | 22 | try 23 | { 24 | std::string ip = result["ip"].as(); 25 | int port = result["port"].as(); 26 | SipServer server(std::move(ip), port); 27 | std::cout << "Server has been started. Listening..." << std::endl; 28 | getchar(); 29 | } 30 | catch (const cxxopts::OptionException&) 31 | { 32 | std::cout << "Please enter ip and port." << std::endl; 33 | } 34 | return 0; 35 | } -------------------------------------------------------------------------------- /src/Helpers/UdpServer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef UDP_SERVER_HPP 2 | #define UDP_SERVER_HPP 3 | 4 | #ifdef __linux__ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #elif defined _WIN32 || defined _WIN64 11 | #include 12 | #endif 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | class UdpServer 20 | { 21 | public: 22 | using OnNewMessageEvent = std::function; 23 | static constexpr int BUFFER_SIZE = 2048; 24 | 25 | UdpServer(std::string ip, int port, OnNewMessageEvent event); 26 | ~UdpServer(); 27 | 28 | void startReceive(); 29 | int send(struct sockaddr_in address, std::string buffer); 30 | 31 | private: 32 | void closeServer(); 33 | 34 | std::string _ip; 35 | int _port; 36 | int _sockfd; 37 | sockaddr_in _servaddr; 38 | OnNewMessageEvent _onNewMessageEvent; 39 | std::atomic _keepRunning; 40 | std::thread _receiverThread; 41 | }; 42 | 43 | #endif -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | project ("SipServer") 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | set(SRC_DIR "${PROJECT_SOURCE_DIR}/src") 9 | 10 | set(HELPERS_SRC_FILES "${SRC_DIR}/Helpers") 11 | file(GLOB_RECURSE HELPERS_SRC 12 | "${HELPERS_SRC_FILES}/*.cpp" 13 | "${HELPERS_SRC_FILES}/*.h" 14 | "${HELPERS_SRC_FILES}/*.hpp" 15 | ) 16 | 17 | set(SIP_SRC_FILES "${SRC_DIR}/SIP") 18 | file(GLOB_RECURSE SIP_SRC 19 | "${SIP_SRC_FILES}/*.cpp" 20 | "${SIP_SRC_FILES}/*.h" 21 | "${SIP_SRC_FILES}/*.hpp" 22 | ) 23 | 24 | include_directories(${HELPERS_SRC_FILES} ${SIP_SRC_FILES}) 25 | add_executable (SipServer "main.cpp" ${SIP_SRC} ${HELPERS_SRC} ) 26 | if (WIN32) 27 | target_link_libraries(SipServer wsock32 ws2_32) 28 | elseif (UNIX) 29 | target_link_libraries(SipServer -pthread) 30 | else() 31 | message(FATAL_ERROR "Unsupported platform.") 32 | endif() 33 | 34 | source_group("Helpers" FILES ${HELPERS_SRC_FILES}) 35 | source_group("SIP" FILES ${SIP_SRC_FILES}) -------------------------------------------------------------------------------- /src/SIP/Session.cpp: -------------------------------------------------------------------------------- 1 | #include "Session.hpp" 2 | 3 | Session::Session(std::string callID, std::shared_ptr src, uint32_t srcRtpPort) : 4 | _callID(std::move(callID)), _src(src), _state(State::Invited), _srcRtpPort(srcRtpPort), _destRtpPort(0) 5 | { 6 | } 7 | 8 | void Session::setState(State state) 9 | { 10 | if (state == _state) 11 | return; 12 | _state = state; 13 | if (state == State::Connected) 14 | { 15 | std::cout << "Session Created between " << _src->getNumber() << " and " << _dest->getNumber() << std::endl; 16 | } 17 | } 18 | 19 | void Session::setDest(std::shared_ptr dest, uint32_t destRtpPort) 20 | { 21 | _dest = dest; 22 | _destRtpPort = destRtpPort; 23 | } 24 | 25 | std::string Session::getCallID() const 26 | { 27 | return _callID; 28 | } 29 | 30 | std::shared_ptr Session::getSrc() const 31 | { 32 | return _src; 33 | } 34 | 35 | std::shared_ptr Session::getDest() const 36 | { 37 | return _dest; 38 | } 39 | 40 | Session::State Session::getState() const 41 | { 42 | return _state; 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 BarGabriel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/SIP/SipMessage.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SIP_MESSAGE_HPP 2 | #define SIP_MESSAGE_HPP 3 | 4 | #ifdef __linux__ 5 | #include 6 | #elif defined _WIN32 || defined _WIN64 7 | #include 8 | #endif 9 | 10 | #include 11 | 12 | class SipMessage 13 | { 14 | public: 15 | 16 | SipMessage(std::string message, sockaddr_in src); 17 | 18 | void setType(std::string value); 19 | void setHeader(std::string value); 20 | void setVia(std::string value); 21 | void setFrom(std::string value); 22 | void setTo(std::string value); 23 | void setCallID(std::string value); 24 | void setCSeq(std::string value); 25 | void setContact(std::string value); 26 | void setContentLength(std::string value); 27 | 28 | 29 | std::string getType() const; 30 | std::string getHeader() const; 31 | std::string getVia() const; 32 | std::string getFrom() const; 33 | std::string getFromNumber() const; 34 | std::string getTo() const; 35 | std::string getToNumber() const; 36 | std::string getCallID() const; 37 | std::string getCSeq() const; 38 | std::string getContact() const; 39 | std::string getContactNumber() const; 40 | std::string getContentLength() const; 41 | sockaddr_in getSource() const; 42 | 43 | std::string toString() const; 44 | 45 | protected: 46 | virtual void parse(); 47 | bool isValidMessage() const; 48 | std::string extractNumber(std::string header) const; 49 | 50 | std::string _type; 51 | std::string _header; 52 | std::string _via; 53 | std::string _from; 54 | std::string _fromNumber; 55 | std::string _to; 56 | std::string _toNumber; 57 | std::string _callID; 58 | std::string _cSeq; 59 | std::string _contact; 60 | std::string _contactNumber; 61 | std::string _contentLength; 62 | std::string _messageStr; 63 | 64 | sockaddr_in _src; 65 | }; 66 | 67 | #endif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sip Server 2 | 3 | A simple sip server for handling VoIP calls based on sip protocol. 4 | 5 | # Features 6 | ### Registration Of Users 7 | The server supports registration process. 8 | When a user send REGISTER request, the server replies with 200 OK response. 9 | Note that the server does not validate user's credentials. 10 | 11 | ### Call Flow 12 | The server supports a normal call flow. 13 | When a user sends an INVITE request, the request is handled by the server and it is forward to the destination. 14 | If the destination is busy, the server send SIP/2.0 486 Busy Here message to the source. 15 | If the destination pick up the call, the server transfer 200 OK message to the source. 16 | If the source want to cancel the call, the server send cancel message to the destination, and replies to the source with 200 OK message and SIP/2.0 487 Request Terminated message. 17 | If one of the sides has been hung up, the server will got bye message which will be sending to the other side to indicate the end of the call. 18 | 19 | 20 | # Build 21 | 22 | #### Windows 23 | Using Visual Studio command prompt 24 | 25 | ```bash 26 | mkdir build && cd build 27 | cmake .. 28 | msbuild SipServer.sln 29 | ``` 30 | 31 | #### Linux 32 | 33 | ```bash 34 | mkdir build && cd build 35 | cmake .. 36 | make 37 | ``` 38 | # Program options 39 | `--ip=` The sip server ip. 40 | `--port=` The sip server port. The default value is 5060. 41 | 42 | # Usage Guide 43 | 1. Download a softphone software like [Zoiper](https://www.zoiper.com/en/voip-softphone/download/current), [Express Talk](https://www.nch.com.au/talk/index.html) or any other software. 44 | 2. Create new sip accounts and set their domain to the sip server ip. 45 | 3. Run the sip server. 46 | 4. Register the accounts and make calls :) 47 | -------------------------------------------------------------------------------- /src/SIP/RequestsHandler.hpp: -------------------------------------------------------------------------------- 1 | #ifndef REQUESTS_HANDLER_HPP 2 | #define REQUESTS_HANDLER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include "SipMessage.hpp" 8 | #include "SipClient.hpp" 9 | #include "Session.hpp" 10 | 11 | class RequestsHandler 12 | { 13 | public: 14 | 15 | using OnHandledEvent = std::function)>; 16 | 17 | RequestsHandler(std::string serverIp, int serverPort, 18 | OnHandledEvent onHandledEvent); 19 | 20 | void handle(std::shared_ptr request); 21 | 22 | std::optional> getSession(const std::string& callID); 23 | 24 | private: 25 | void initHandlers(); 26 | 27 | void OnRegister(std::shared_ptr data); 28 | void OnCancel(std::shared_ptr data); 29 | void onReqTerminated(std::shared_ptr data); 30 | void OnInvite(std::shared_ptr data); 31 | void OnTrying(std::shared_ptr data); 32 | void OnRinging(std::shared_ptr data); 33 | void OnBusy(std::shared_ptr data); 34 | void OnUnavailable(std::shared_ptr data); 35 | void OnBye(std::shared_ptr data); 36 | void OnOk(std::shared_ptr data); 37 | void OnAck(std::shared_ptr data); 38 | 39 | bool setCallState(const std::string& callID, Session::State state); 40 | void endCall(const std::string& callID, const std::string& srcNumber, const std::string& destNumber, const std::string& reason = ""); 41 | 42 | bool registerClient(std::shared_ptr client); 43 | void unregisterClient(std::shared_ptr client); 44 | 45 | std::optional> findClient(const std::string& number); 46 | 47 | void endHandle(const std::string& destNumber, std::shared_ptr message); 48 | 49 | std::unordered_map request)>> _handlers; 50 | std::unordered_map> _sessions; 51 | std::unordered_map> _clients; 52 | 53 | std::function)> _onNewClient; 54 | std::function)> _onUnregister; 55 | OnHandledEvent _onHandled; 56 | 57 | std::string _serverIp; 58 | int _serverPort; 59 | }; 60 | 61 | #endif -------------------------------------------------------------------------------- /src/Helpers/UdpServer.cpp: -------------------------------------------------------------------------------- 1 | #include "UdpServer.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | UdpServer::UdpServer(std::string ip, int port, OnNewMessageEvent event) : _ip(std::move(ip)), _port(port), _onNewMessageEvent(event), _keepRunning(false) 8 | { 9 | 10 | #if defined _WIN32 || defined _WIN64 11 | WSADATA wsa; 12 | if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) 13 | { 14 | std::cerr << "Failed. Error Code: " << WSAGetLastError() << std::endl; 15 | exit(EXIT_FAILURE); 16 | } 17 | #endif 18 | 19 | if ((_sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { 20 | std::cerr << "socket creation failed" << std::endl; 21 | exit(EXIT_FAILURE); 22 | } 23 | 24 | std::memset(&_servaddr, 0, sizeof(_servaddr)); 25 | _servaddr.sin_family = AF_INET; 26 | _servaddr.sin_addr.s_addr = inet_addr(_ip.c_str()); 27 | _servaddr.sin_port = htons(port); 28 | 29 | if (bind(_sockfd, reinterpret_cast(&_servaddr), sizeof(_servaddr)) < 0) 30 | { 31 | std::cerr << "bind failed" << std::endl; 32 | exit(EXIT_FAILURE); 33 | } 34 | } 35 | 36 | UdpServer::~UdpServer() 37 | { 38 | closeServer(); 39 | } 40 | 41 | void UdpServer::startReceive() 42 | { 43 | _keepRunning = true; 44 | _receiverThread = std::thread([=]() 45 | { 46 | char buffer[BUFFER_SIZE]; 47 | sockaddr_in senderEndPoint; 48 | std::memset(&senderEndPoint, 0, sizeof(senderEndPoint)); 49 | int len = sizeof(senderEndPoint); 50 | 51 | while (_keepRunning) 52 | { 53 | std::memset(&senderEndPoint, 0, sizeof(senderEndPoint)); 54 | #ifdef __linux__ 55 | recvfrom(_sockfd, buffer, BUFFER_SIZE, 0, reinterpret_cast(&senderEndPoint), (socklen_t*)&len); 56 | #elif defined _WIN32 || defined _WIN64 57 | recvfrom(_sockfd, buffer, BUFFER_SIZE, 0, reinterpret_cast(&senderEndPoint), &len); 58 | #endif 59 | if (!_keepRunning) return; 60 | _onNewMessageEvent(std::move(buffer), senderEndPoint); 61 | } 62 | }); 63 | } 64 | 65 | int UdpServer::send(sockaddr_in address, std::string buffer) 66 | { 67 | return sendto(_sockfd, buffer.c_str(), std::strlen(buffer.c_str()), 68 | 0, reinterpret_cast(&address), sizeof(address)); 69 | } 70 | 71 | void UdpServer::closeServer() 72 | { 73 | _keepRunning = false; 74 | shutdown(_sockfd, 2); 75 | #ifdef __linux__ 76 | close(_sockfd); 77 | #elif defined _WIN32 || defined _WIN64 78 | closesocket(_sockfd); 79 | #endif 80 | _receiverThread.join(); 81 | } 82 | -------------------------------------------------------------------------------- /src/SIP/SipSdpMessage.cpp: -------------------------------------------------------------------------------- 1 | #include "SipSdpMessage.hpp" 2 | #include "SipMessageHeaders.h" 3 | #include 4 | #include 5 | 6 | SipSdpMessage::SipSdpMessage(std::string message, sockaddr_in src) : SipMessage(std::move(message), std::move(src)) 7 | { 8 | parse(); 9 | } 10 | 11 | void SipSdpMessage::setMedia(std::string value) 12 | { 13 | auto mPos = _messageStr.find(_media); 14 | _messageStr.replace(mPos, _media.length(), value); 15 | _media = value; 16 | } 17 | 18 | void SipSdpMessage::setRtpPort(int port) 19 | { 20 | std::string currentRtpPort = std::to_string(_rtpPort); 21 | std::string copyM = _media; 22 | copyM.replace(_media.find(currentRtpPort), currentRtpPort.length(), std::to_string(port)); 23 | _rtpPort = port; 24 | setMedia(std::move(copyM)); 25 | } 26 | 27 | std::string SipSdpMessage::getVersion() const 28 | { 29 | return _version; 30 | } 31 | 32 | std::string SipSdpMessage::getOriginator() const 33 | { 34 | return _originator; 35 | } 36 | 37 | std::string SipSdpMessage::getSessionName() const 38 | { 39 | return _sessionName; 40 | } 41 | 42 | std::string SipSdpMessage::getConnectionInformation() const 43 | { 44 | return _connectionInformation; 45 | } 46 | 47 | std::string SipSdpMessage::getTime() const 48 | { 49 | return _time; 50 | } 51 | 52 | std::string SipSdpMessage::getMedia() const 53 | { 54 | return _media; 55 | } 56 | 57 | int SipSdpMessage::getRtpPort() const 58 | { 59 | return _rtpPort; 60 | } 61 | 62 | void SipSdpMessage::parse() 63 | { 64 | std::string msg = _messageStr; 65 | 66 | auto posOfM = msg.find("v="); 67 | msg.erase(0, posOfM); 68 | size_t pos = 0; 69 | while ((pos = msg.find(SipMessageHeaders::HEADERS_DELIMETER)) != std::string::npos) 70 | { 71 | std::string line = msg.substr(0, pos); 72 | if (line.find("v=") != std::string::npos) 73 | { 74 | _version = std::move(line); 75 | } 76 | else if (line.find("o=") != std::string::npos) 77 | { 78 | _originator = std::move(line); 79 | } 80 | else if (line.find("s=") != std::string::npos) 81 | { 82 | _sessionName = std::move(line); 83 | } 84 | else if (line.find("c=") != std::string::npos) 85 | { 86 | _connectionInformation = std::move(line); 87 | } 88 | else if (line.find("t=") != std::string::npos) 89 | { 90 | _time = std::move(line); 91 | } 92 | else if (line.find("m=") != std::string::npos) 93 | { 94 | _media = line; 95 | _rtpPort = extractRtpPort(std::move(line)); 96 | } 97 | msg.erase(0, pos + std::strlen(SipMessageHeaders::HEADERS_DELIMETER)); 98 | } 99 | } 100 | 101 | int SipSdpMessage::extractRtpPort(std::string data) const 102 | { 103 | data.erase(0, data.find(" ") + 1); 104 | std::string portStr = data.substr(0, data.find(" ")); 105 | return std::stoi(portStr); 106 | } -------------------------------------------------------------------------------- /src/SIP/SipMessage.cpp: -------------------------------------------------------------------------------- 1 | #include "SipMessage.hpp" 2 | #include "SipMessageTypes.h" 3 | #include "SipMessageHeaders.h" 4 | #include 5 | #include 6 | #include 7 | 8 | SipMessage::SipMessage(std::string message, sockaddr_in src) : _messageStr(std::move(message)), _src(std::move(src)) 9 | { 10 | parse(); 11 | } 12 | 13 | void SipMessage::parse() 14 | { 15 | std::string msg = _messageStr; 16 | 17 | size_t pos = msg.find(SipMessageHeaders::HEADERS_DELIMETER); 18 | _header = msg.substr(0, pos); 19 | msg.erase(0, pos + std::strlen(SipMessageHeaders::HEADERS_DELIMETER)); 20 | 21 | _type = _header.substr(0, _header.find(" ")); 22 | if (_type == "SIP/2.0") 23 | { 24 | _type = _header; 25 | } 26 | 27 | while ((pos = msg.find(SipMessageHeaders::HEADERS_DELIMETER)) != std::string::npos) { 28 | std::string line = msg.substr(0, pos); 29 | 30 | if (line.find(SipMessageHeaders::VIA) != std::string::npos) 31 | { 32 | _via = line; 33 | } 34 | else if (line.find(SipMessageHeaders::FROM) != std::string::npos) 35 | { 36 | _from = line; 37 | _fromNumber = extractNumber(line); 38 | } 39 | else if (line.find(SipMessageHeaders::TO) != std::string::npos) 40 | { 41 | _to = line; 42 | _toNumber = extractNumber(line); 43 | } 44 | else if (line.find(SipMessageHeaders::CALL_ID) != std::string::npos) 45 | { 46 | _callID = line; 47 | } 48 | else if (line.find(SipMessageHeaders::CSEQ) != std::string::npos) 49 | { 50 | _cSeq = line; 51 | } 52 | else if (line.find(SipMessageHeaders::CONTACT) != std::string::npos) 53 | { 54 | _contact = line; 55 | _contactNumber = extractNumber(std::move(line)); 56 | } 57 | else if (line.find(SipMessageHeaders::CONTENT_LENGTH) != std::string::npos) 58 | { 59 | _contentLength = line; 60 | } 61 | 62 | msg.erase(0, pos + std::strlen(SipMessageHeaders::HEADERS_DELIMETER)); 63 | } 64 | if (!isValidMessage()) 65 | { 66 | throw std::runtime_error("Invalid message."); 67 | } 68 | } 69 | 70 | bool SipMessage::isValidMessage() const 71 | { 72 | if (_via.empty() || _to.empty() || _from.empty() || _callID.empty() || _cSeq.empty()) 73 | { 74 | return false; 75 | } 76 | 77 | if ((_type == SipMessageTypes::INVITE || _type == SipMessageTypes::REGISTER) && _contact.empty()) 78 | { 79 | return false; 80 | } 81 | 82 | return true; 83 | } 84 | 85 | void SipMessage::setType(std::string value) 86 | { 87 | _type = std::move(value); 88 | } 89 | 90 | void SipMessage::setHeader(std::string value) 91 | { 92 | auto headerPos = _messageStr.find(_header); 93 | _messageStr.replace(headerPos, _header.length(), value); 94 | _header = std::move(value); 95 | } 96 | 97 | void SipMessage::setVia(std::string value) 98 | { 99 | auto viaPos = _messageStr.find(_via); 100 | _messageStr.replace(viaPos, _via.length(), value); 101 | _via = std::move(value); 102 | } 103 | 104 | void SipMessage::setFrom(std::string value) 105 | { 106 | auto fromPos = _messageStr.find(_from); 107 | _messageStr.replace(fromPos, _from.length(), value); 108 | _from = value; 109 | _fromNumber = extractNumber(std::move(value)); 110 | } 111 | 112 | void SipMessage::setTo(std::string value) 113 | { 114 | auto toPos = _messageStr.find(_to); 115 | _messageStr.replace(toPos, _to.length(), value); 116 | _to = value; 117 | _toNumber = extractNumber(std::move(value));; 118 | } 119 | 120 | void SipMessage::setCallID(std::string value) 121 | { 122 | auto callIdPos = _messageStr.find(_callID); 123 | _messageStr.replace(callIdPos, _callID.length(), value); 124 | _callID = std::move(value); 125 | } 126 | 127 | void SipMessage::setCSeq(std::string value) 128 | { 129 | auto cSeqPos = _messageStr.find(_cSeq); 130 | _messageStr.replace(cSeqPos, _cSeq.length(), value); 131 | _cSeq = std::move(value); 132 | } 133 | 134 | void SipMessage::setContact(std::string value) 135 | { 136 | auto contactPos = _messageStr.find(_contact); 137 | _messageStr.replace(contactPos, _contact.length(), value); 138 | _contact = std::move(value); 139 | } 140 | 141 | void SipMessage::setContentLength(std::string value) 142 | { 143 | auto contentLengthPos = _messageStr.find(_contentLength); 144 | _messageStr.replace(contentLengthPos, _contentLength.length(), value); 145 | _contentLength = std::move(value); 146 | } 147 | 148 | std::string SipMessage::toString() const 149 | { 150 | return _messageStr; 151 | } 152 | 153 | std::string SipMessage::getType() const 154 | { 155 | return _type; 156 | } 157 | 158 | std::string SipMessage::getHeader() const 159 | { 160 | return _header; 161 | } 162 | 163 | std::string SipMessage::getVia() const 164 | { 165 | return _via; 166 | } 167 | 168 | std::string SipMessage::getFrom() const 169 | { 170 | return _from; 171 | } 172 | 173 | std::string SipMessage::getFromNumber() const 174 | { 175 | return _fromNumber; 176 | } 177 | 178 | std::string SipMessage::getTo() const 179 | { 180 | return _to; 181 | } 182 | 183 | std::string SipMessage::getToNumber() const 184 | { 185 | return _toNumber; 186 | } 187 | 188 | std::string SipMessage::getCallID() const 189 | { 190 | return _callID; 191 | } 192 | 193 | std::string SipMessage::getCSeq() const 194 | { 195 | return _cSeq; 196 | } 197 | 198 | std::string SipMessage::getContact() const 199 | { 200 | return _contact; 201 | } 202 | 203 | std::string SipMessage::getContactNumber() const 204 | { 205 | return _contactNumber; 206 | } 207 | 208 | sockaddr_in SipMessage::getSource() const 209 | { 210 | return _src; 211 | } 212 | 213 | std::string SipMessage::getContentLength() const 214 | { 215 | return _contentLength; 216 | } 217 | 218 | std::string SipMessage::extractNumber(std::string header) const 219 | { 220 | auto indexOfNumber = header.find("sip:") + 4; 221 | return header.substr(indexOfNumber, header.find("@") - indexOfNumber); 222 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | build/ 36 | 37 | # Visual Studio 2015/2017 cache/options directory 38 | .vs/ 39 | # Uncomment if you have tasks that create the project's static files in wwwroot 40 | #wwwroot/ 41 | 42 | # Visual Studio 2017 auto generated files 43 | Generated\ Files/ 44 | 45 | # MSTest test Results 46 | [Tt]est[Rr]esult*/ 47 | [Bb]uild[Ll]og.* 48 | 49 | # NUnit 50 | *.VisualState.xml 51 | TestResult.xml 52 | nunit-*.xml 53 | 54 | # Build Results of an ATL Project 55 | [Dd]ebugPS/ 56 | [Rr]eleasePS/ 57 | dlldata.c 58 | 59 | # Benchmark Results 60 | BenchmarkDotNet.Artifacts/ 61 | 62 | # .NET Core 63 | project.lock.json 64 | project.fragment.lock.json 65 | artifacts/ 66 | 67 | # ASP.NET Scaffolding 68 | ScaffoldingReadMe.txt 69 | 70 | # StyleCop 71 | StyleCopReport.xml 72 | 73 | # Files built by Visual Studio 74 | *_i.c 75 | *_p.c 76 | *_h.h 77 | *.ilk 78 | *.meta 79 | *.obj 80 | *.iobj 81 | *.pch 82 | *.pdb 83 | *.ipdb 84 | *.pgc 85 | *.pgd 86 | *.rsp 87 | *.sbr 88 | *.tlb 89 | *.tli 90 | *.tlh 91 | *.tmp 92 | *.tmp_proj 93 | *_wpftmp.csproj 94 | *.log 95 | *.vspscc 96 | *.vssscc 97 | .builds 98 | *.pidb 99 | *.svclog 100 | *.scc 101 | 102 | # Chutzpah Test files 103 | _Chutzpah* 104 | 105 | # Visual C++ cache files 106 | ipch/ 107 | *.aps 108 | *.ncb 109 | *.opendb 110 | *.opensdf 111 | *.sdf 112 | *.cachefile 113 | *.VC.db 114 | *.VC.VC.opendb 115 | 116 | # Visual Studio profiler 117 | *.psess 118 | *.vsp 119 | *.vspx 120 | *.sap 121 | 122 | # Visual Studio Trace Files 123 | *.e2e 124 | 125 | # TFS 2012 Local Workspace 126 | $tf/ 127 | 128 | # Guidance Automation Toolkit 129 | *.gpState 130 | 131 | # ReSharper is a .NET coding add-in 132 | _ReSharper*/ 133 | *.[Rr]e[Ss]harper 134 | *.DotSettings.user 135 | 136 | # TeamCity is a build add-in 137 | _TeamCity* 138 | 139 | # DotCover is a Code Coverage Tool 140 | *.dotCover 141 | 142 | # AxoCover is a Code Coverage Tool 143 | .axoCover/* 144 | !.axoCover/settings.json 145 | 146 | # Coverlet is a free, cross platform Code Coverage Tool 147 | coverage*.json 148 | coverage*.xml 149 | coverage*.info 150 | 151 | # Visual Studio code coverage results 152 | *.coverage 153 | *.coveragexml 154 | 155 | # NCrunch 156 | _NCrunch_* 157 | .*crunch*.local.xml 158 | nCrunchTemp_* 159 | 160 | # MightyMoose 161 | *.mm.* 162 | AutoTest.Net/ 163 | 164 | # Web workbench (sass) 165 | .sass-cache/ 166 | 167 | # Installshield output folder 168 | [Ee]xpress/ 169 | 170 | # DocProject is a documentation generator add-in 171 | DocProject/buildhelp/ 172 | DocProject/Help/*.HxT 173 | DocProject/Help/*.HxC 174 | DocProject/Help/*.hhc 175 | DocProject/Help/*.hhk 176 | DocProject/Help/*.hhp 177 | DocProject/Help/Html2 178 | DocProject/Help/html 179 | 180 | # Click-Once directory 181 | publish/ 182 | 183 | # Publish Web Output 184 | *.[Pp]ublish.xml 185 | *.azurePubxml 186 | # Note: Comment the next line if you want to checkin your web deploy settings, 187 | # but database connection strings (with potential passwords) will be unencrypted 188 | *.pubxml 189 | *.publishproj 190 | 191 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 192 | # checkin your Azure Web App publish settings, but sensitive information contained 193 | # in these scripts will be unencrypted 194 | PublishScripts/ 195 | 196 | # NuGet Packages 197 | *.nupkg 198 | # NuGet Symbol Packages 199 | *.snupkg 200 | # The packages folder can be ignored because of Package Restore 201 | **/[Pp]ackages/* 202 | # except build/, which is used as an MSBuild target. 203 | !**/[Pp]ackages/build/ 204 | # Uncomment if necessary however generally it will be regenerated when needed 205 | #!**/[Pp]ackages/repositories.config 206 | # NuGet v3's project.json files produces more ignorable files 207 | *.nuget.props 208 | *.nuget.targets 209 | 210 | # Microsoft Azure Build Output 211 | csx/ 212 | *.build.csdef 213 | 214 | # Microsoft Azure Emulator 215 | ecf/ 216 | rcf/ 217 | 218 | # Windows Store app package directories and files 219 | AppPackages/ 220 | BundleArtifacts/ 221 | Package.StoreAssociation.xml 222 | _pkginfo.txt 223 | *.appx 224 | *.appxbundle 225 | *.appxupload 226 | 227 | # Visual Studio cache files 228 | # files ending in .cache can be ignored 229 | *.[Cc]ache 230 | # but keep track of directories ending in .cache 231 | !?*.[Cc]ache/ 232 | 233 | # Others 234 | ClientBin/ 235 | ~$* 236 | *~ 237 | *.dbmdl 238 | *.dbproj.schemaview 239 | *.jfm 240 | *.pfx 241 | *.publishsettings 242 | orleans.codegen.cs 243 | 244 | # Including strong name files can present a security risk 245 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 246 | #*.snk 247 | 248 | # Since there are multiple workflows, uncomment next line to ignore bower_components 249 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 250 | #bower_components/ 251 | 252 | # RIA/Silverlight projects 253 | Generated_Code/ 254 | 255 | # Backup & report files from converting an old project file 256 | # to a newer Visual Studio version. Backup files are not needed, 257 | # because we have git ;-) 258 | _UpgradeReport_Files/ 259 | Backup*/ 260 | UpgradeLog*.XML 261 | UpgradeLog*.htm 262 | ServiceFabricBackup/ 263 | *.rptproj.bak 264 | 265 | # SQL Server files 266 | *.mdf 267 | *.ldf 268 | *.ndf 269 | 270 | # Business Intelligence projects 271 | *.rdl.data 272 | *.bim.layout 273 | *.bim_*.settings 274 | *.rptproj.rsuser 275 | *- [Bb]ackup.rdl 276 | *- [Bb]ackup ([0-9]).rdl 277 | *- [Bb]ackup ([0-9][0-9]).rdl 278 | 279 | # Microsoft Fakes 280 | FakesAssemblies/ 281 | 282 | # GhostDoc plugin setting file 283 | *.GhostDoc.xml 284 | 285 | # Node.js Tools for Visual Studio 286 | .ntvs_analysis.dat 287 | node_modules/ 288 | 289 | # Visual Studio 6 build log 290 | *.plg 291 | 292 | # Visual Studio 6 workspace options file 293 | *.opt 294 | 295 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 296 | *.vbw 297 | 298 | # Visual Studio LightSwitch build output 299 | **/*.HTMLClient/GeneratedArtifacts 300 | **/*.DesktopClient/GeneratedArtifacts 301 | **/*.DesktopClient/ModelManifest.xml 302 | **/*.Server/GeneratedArtifacts 303 | **/*.Server/ModelManifest.xml 304 | _Pvt_Extensions 305 | 306 | # Paket dependency manager 307 | .paket/paket.exe 308 | paket-files/ 309 | 310 | # FAKE - F# Make 311 | .fake/ 312 | 313 | # CodeRush personal settings 314 | .cr/personal 315 | 316 | # Python Tools for Visual Studio (PTVS) 317 | __pycache__/ 318 | *.pyc 319 | 320 | # Cake - Uncomment if you are using it 321 | # tools/** 322 | # !tools/packages.config 323 | 324 | # Tabs Studio 325 | *.tss 326 | 327 | # Telerik's JustMock configuration file 328 | *.jmconfig 329 | 330 | # BizTalk build output 331 | *.btp.cs 332 | *.btm.cs 333 | *.odx.cs 334 | *.xsd.cs 335 | 336 | # OpenCover UI analysis results 337 | OpenCover/ 338 | 339 | # Azure Stream Analytics local run output 340 | ASALocalRun/ 341 | 342 | # MSBuild Binary and Structured Log 343 | *.binlog 344 | 345 | # NVidia Nsight GPU debugger configuration file 346 | *.nvuser 347 | 348 | # MFractors (Xamarin productivity tool) working folder 349 | .mfractor/ 350 | 351 | # Local History for Visual Studio 352 | .localhistory/ 353 | 354 | # BeatPulse healthcheck temp database 355 | healthchecksdb 356 | 357 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 358 | MigrationBackup/ 359 | 360 | # Ionide (cross platform F# VS Code tools) working folder 361 | .ionide/ 362 | 363 | # Fody - auto-generated XML schema 364 | FodyWeavers.xsd -------------------------------------------------------------------------------- /src/SIP/RequestsHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "RequestsHandler.hpp" 2 | #include 3 | #include "SipMessageTypes.h" 4 | #include "SipSdpMessage.hpp" 5 | #include "IDGen.hpp" 6 | 7 | RequestsHandler::RequestsHandler(std::string serverIp, int serverPort, 8 | OnHandledEvent onHandledEvent) : 9 | _serverIp(std::move(serverIp)), _serverPort(serverPort), 10 | _onHandled(onHandledEvent) 11 | { 12 | initHandlers(); 13 | } 14 | 15 | void RequestsHandler::initHandlers() 16 | { 17 | _handlers.emplace(SipMessageTypes::REGISTER, std::bind(&RequestsHandler::OnRegister, this, std::placeholders::_1)); 18 | _handlers.emplace(SipMessageTypes::CANCEL, std::bind(&RequestsHandler::OnCancel, this, std::placeholders::_1)); 19 | _handlers.emplace(SipMessageTypes::INVITE, std::bind(&RequestsHandler::OnInvite, this, std::placeholders::_1)); 20 | _handlers.emplace(SipMessageTypes::TRYING, std::bind(&RequestsHandler::OnTrying, this, std::placeholders::_1)); 21 | _handlers.emplace(SipMessageTypes::RINGING, std::bind(&RequestsHandler::OnRinging, this, std::placeholders::_1)); 22 | _handlers.emplace(SipMessageTypes::BUSY, std::bind(&RequestsHandler::OnBusy, this, std::placeholders::_1)); 23 | _handlers.emplace(SipMessageTypes::UNAVAIALBLE, std::bind(&RequestsHandler::OnUnavailable, this, std::placeholders::_1)); 24 | _handlers.emplace(SipMessageTypes::OK, std::bind(&RequestsHandler::OnOk, this, std::placeholders::_1)); 25 | _handlers.emplace(SipMessageTypes::ACK, std::bind(&RequestsHandler::OnAck, this, std::placeholders::_1)); 26 | _handlers.emplace(SipMessageTypes::BYE, std::bind(&RequestsHandler::OnBye, this, std::placeholders::_1)); 27 | _handlers.emplace(SipMessageTypes::REQUEST_TERMINATED, std::bind(&RequestsHandler::onReqTerminated, this, std::placeholders::_1)); 28 | } 29 | 30 | void RequestsHandler::handle(std::shared_ptr request) 31 | { 32 | if (_handlers.find(request->getType()) != _handlers.end()) 33 | { 34 | _handlers[request->getType()](std::move(request)); 35 | } 36 | } 37 | 38 | std::optional> RequestsHandler::getSession(const std::string& callID) 39 | { 40 | auto sessionIt = _sessions.find(callID); 41 | if (sessionIt != _sessions.end()) 42 | { 43 | return sessionIt->second; 44 | } 45 | return {}; 46 | } 47 | 48 | void RequestsHandler::OnRegister(std::shared_ptr data) 49 | { 50 | bool isUnregisterReq = data->getContact().find("expires=0") != -1; 51 | 52 | if (!isUnregisterReq) 53 | { 54 | auto newClient = std::make_shared(data->getFromNumber(), data->getSource()); 55 | registerClient(std::move(newClient)); 56 | } 57 | 58 | auto response = data; 59 | response->setHeader(SipMessageTypes::OK); 60 | response->setVia(data->getVia() + ";received=" + _serverIp); 61 | response->setTo(data->getTo() + ";tag=" + IDGen::GenerateID(9)); 62 | response->setContact("Contact: getFromNumber() + "@" + _serverIp + ":" + std::to_string(_serverPort) + ";transport=UDP>"); 63 | endHandle(response->getFromNumber(), response); 64 | 65 | if (isUnregisterReq) 66 | { 67 | auto newClient = std::make_shared(data->getFromNumber(), data->getSource()); 68 | unregisterClient(std::move(newClient)); 69 | } 70 | } 71 | 72 | void RequestsHandler::OnCancel(std::shared_ptr data) 73 | { 74 | setCallState(data->getCallID(), Session::State::Cancel); 75 | endHandle(data->getToNumber(), data); 76 | } 77 | 78 | void RequestsHandler::onReqTerminated(std::shared_ptr data) 79 | { 80 | endHandle(data->getFromNumber(), data); 81 | } 82 | 83 | void RequestsHandler::OnInvite(std::shared_ptr data) 84 | { 85 | // Check if the caller is registered 86 | auto caller = findClient(data->getFromNumber()); 87 | if (!caller.has_value()) 88 | { 89 | return; 90 | } 91 | 92 | // Check if the called is registered 93 | auto called = findClient(data->getToNumber()); 94 | if (!called.has_value()) 95 | { 96 | // Send "SIP/2.0 404 Not Found" 97 | data->setHeader(SipMessageTypes::NOT_FOUND); 98 | data->setContact("Contact: getNumber() + "@" + _serverIp + ":" + std::to_string(_serverPort) + ";transport=UDP>"); 99 | endHandle(data->getFromNumber(), data); 100 | return; 101 | } 102 | 103 | auto message = dynamic_cast(data.get()); 104 | if (!message) 105 | { 106 | std::cerr << "Couldn't get SDP from " << data->getFromNumber() << "'s INVITE request." << std::endl; 107 | return; 108 | } 109 | 110 | auto newSession = std::make_shared(data->getCallID(), caller.value(), message->getRtpPort()); 111 | _sessions.emplace(data->getCallID(), newSession); 112 | 113 | auto response = data; 114 | response->setContact("Contact: getNumber() + "@" + _serverIp + ":" + std::to_string(_serverPort) + ";transport=UDP>"); 115 | endHandle(data->getToNumber(), response); 116 | } 117 | 118 | void RequestsHandler::OnTrying(std::shared_ptr data) 119 | { 120 | endHandle(data->getFromNumber(), data); 121 | } 122 | 123 | void RequestsHandler::OnRinging(std::shared_ptr data) 124 | { 125 | endHandle(data->getFromNumber(), data); 126 | } 127 | 128 | void RequestsHandler::OnBusy(std::shared_ptr data) 129 | { 130 | setCallState(data->getCallID(), Session::State::Busy); 131 | endHandle(data->getFromNumber(), data); 132 | } 133 | 134 | void RequestsHandler::OnUnavailable(std::shared_ptr data) 135 | { 136 | setCallState(data->getCallID(), Session::State::Unavailable); 137 | endHandle(data->getFromNumber(), data); 138 | } 139 | 140 | void RequestsHandler::OnBye(std::shared_ptr data) 141 | { 142 | setCallState(data->getCallID(), Session::State::Bye); 143 | endHandle(data->getToNumber(), data); 144 | } 145 | 146 | void RequestsHandler::OnOk(std::shared_ptr data) 147 | { 148 | auto session = getSession(data->getCallID()); 149 | if (session.has_value()) 150 | { 151 | if (session.value()->getState() == Session::State::Cancel) 152 | { 153 | endHandle(data->getFromNumber(), data); 154 | return; 155 | } 156 | 157 | if (data->getCSeq().find(SipMessageTypes::INVITE) != std::string::npos) 158 | { 159 | auto client = findClient(data->getToNumber()); 160 | if (!client.has_value()) 161 | { 162 | return; 163 | } 164 | 165 | auto sdpMessage = dynamic_cast(data.get()); 166 | if (!sdpMessage) 167 | { 168 | std::cerr << "Coudn't get SDP from: " << client.value()->getNumber() << "'s OK message."; 169 | endCall(data->getCallID(), data->getFromNumber(), data->getToNumber(), "SDP parse error."); 170 | return; 171 | } 172 | session->get()->setDest(client.value(), sdpMessage->getRtpPort()); 173 | session->get()->setState(Session::State::Connected); 174 | auto response = data; 175 | response->setContact("Contact: getToNumber() + "@" + _serverIp + ":" + std::to_string(_serverPort) + ";transport=UDP>"); 176 | endHandle(data->getFromNumber(), std::move(response)); 177 | return; 178 | } 179 | 180 | if (session.value()->getState() == Session::State::Bye) 181 | { 182 | endHandle(data->getFromNumber(), data); 183 | endCall(data->getCallID(), data->getToNumber(), data->getFromNumber()); 184 | } 185 | } 186 | } 187 | 188 | void RequestsHandler::OnAck(std::shared_ptr data) 189 | { 190 | auto session = getSession(data->getCallID()); 191 | if (!session.has_value()) 192 | { 193 | return; 194 | } 195 | 196 | endHandle(data->getToNumber(), data); 197 | 198 | auto sessionState = session.value()->getState(); 199 | std::string endReason; 200 | if (sessionState == Session::State::Busy) 201 | { 202 | endReason = data->getToNumber() + " is busy."; 203 | endCall(data->getCallID(), data->getFromNumber(), data->getToNumber(), endReason); 204 | return; 205 | } 206 | 207 | if (sessionState == Session::State::Unavailable) 208 | { 209 | endReason = data->getToNumber() + " is unavailable."; 210 | endCall(data->getCallID(), data->getFromNumber(), data->getToNumber(), endReason); 211 | return; 212 | } 213 | 214 | if (sessionState == Session::State::Cancel) 215 | { 216 | endReason = data->getFromNumber() + " canceled the session."; 217 | endCall(data->getCallID(), data->getFromNumber(), data->getToNumber(), endReason); 218 | return; 219 | } 220 | } 221 | 222 | bool RequestsHandler::setCallState(const std::string& callID, Session::State state) 223 | { 224 | auto session = getSession(callID); 225 | if (session) 226 | { 227 | session->get()->setState(state); 228 | return true; 229 | } 230 | 231 | return false; 232 | } 233 | 234 | void RequestsHandler::endCall(const std::string& callID, const std::string& srcNumber, const std::string& destNumber, const std::string& reason) 235 | { 236 | if (_sessions.erase(callID) > 0) 237 | { 238 | std::ostringstream message; 239 | message << "Session has been disconnected between " << srcNumber << " and " << destNumber; 240 | if (!reason.empty()) 241 | { 242 | message << " because " << reason; 243 | } 244 | std::cout << message.str() << std::endl; 245 | } 246 | } 247 | 248 | bool RequestsHandler::registerClient(std::shared_ptr client) 249 | { 250 | if (_clients.find(client->getNumber()) == _clients.end()) { 251 | std::cout << "New Client: " << client->getNumber() << std::endl; 252 | _clients.emplace(client->getNumber(), client); 253 | return true; 254 | } 255 | return false; 256 | } 257 | 258 | void RequestsHandler::unregisterClient(std::shared_ptr client) 259 | { 260 | std::cout << "unregister client: " << client->getNumber() << std::endl; 261 | _clients.erase(client->getNumber()); 262 | } 263 | 264 | std::optional> RequestsHandler::findClient(const std::string& number) 265 | { 266 | auto it = _clients.find(number); 267 | if (it != _clients.end()) 268 | { 269 | return it->second; 270 | } 271 | 272 | return {}; 273 | } 274 | 275 | void RequestsHandler::endHandle(const std::string& destNumber, std::shared_ptr message) 276 | { 277 | auto destClient = findClient(destNumber); 278 | if (destClient.has_value()) 279 | { 280 | _onHandled(std::move(destClient.value()->getAddress()), std::move(message)); 281 | } 282 | else 283 | { 284 | message->setHeader(SipMessageTypes::NOT_FOUND); 285 | auto src = message->getSource(); 286 | _onHandled(src, std::move(message)); 287 | } 288 | } -------------------------------------------------------------------------------- /src/Helpers/cxxopts.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | */ 24 | 25 | #ifndef CXXOPTS_HPP_INCLUDED 26 | #define CXXOPTS_HPP_INCLUDED 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #if defined(__GNUC__) && !defined(__clang__) 45 | # if (__GNUC__ * 10 + __GNUC_MINOR__) < 49 46 | # define CXXOPTS_NO_REGEX true 47 | # endif 48 | #endif 49 | 50 | #ifndef CXXOPTS_NO_REGEX 51 | # include 52 | #endif // CXXOPTS_NO_REGEX 53 | 54 | #ifdef __cpp_lib_optional 55 | #include 56 | #define CXXOPTS_HAS_OPTIONAL 57 | #endif 58 | 59 | #if __cplusplus >= 201603L 60 | #define CXXOPTS_NODISCARD [[nodiscard]] 61 | #else 62 | #define CXXOPTS_NODISCARD 63 | #endif 64 | 65 | #ifndef CXXOPTS_VECTOR_DELIMITER 66 | #define CXXOPTS_VECTOR_DELIMITER ',' 67 | #endif 68 | 69 | #define CXXOPTS__VERSION_MAJOR 3 70 | #define CXXOPTS__VERSION_MINOR 0 71 | #define CXXOPTS__VERSION_PATCH 0 72 | 73 | #if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6 74 | #define CXXOPTS_NULL_DEREF_IGNORE 75 | #endif 76 | 77 | namespace cxxopts 78 | { 79 | static constexpr struct { 80 | uint8_t major, minor, patch; 81 | } version = { 82 | CXXOPTS__VERSION_MAJOR, 83 | CXXOPTS__VERSION_MINOR, 84 | CXXOPTS__VERSION_PATCH 85 | }; 86 | } // namespace cxxopts 87 | 88 | //when we ask cxxopts to use Unicode, help strings are processed using ICU, 89 | //which results in the correct lengths being computed for strings when they 90 | //are formatted for the help output 91 | //it is necessary to make sure that can be found by the 92 | //compiler, and that icu-uc is linked in to the binary. 93 | 94 | #ifdef CXXOPTS_USE_UNICODE 95 | #include 96 | 97 | namespace cxxopts 98 | { 99 | using String = icu::UnicodeString; 100 | 101 | inline 102 | String 103 | toLocalString(std::string s) 104 | { 105 | return icu::UnicodeString::fromUTF8(std::move(s)); 106 | } 107 | 108 | #if defined(__GNUC__) 109 | // GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: 110 | // warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor 111 | #pragma GCC diagnostic push 112 | #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" 113 | #pragma GCC diagnostic ignored "-Weffc++" 114 | // This will be ignored under other compilers like LLVM clang. 115 | #endif 116 | class UnicodeStringIterator : public 117 | std::iterator 118 | { 119 | public: 120 | 121 | UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) 122 | : s(string) 123 | , i(pos) 124 | { 125 | } 126 | 127 | value_type 128 | operator*() const 129 | { 130 | return s->char32At(i); 131 | } 132 | 133 | bool 134 | operator==(const UnicodeStringIterator& rhs) const 135 | { 136 | return s == rhs.s && i == rhs.i; 137 | } 138 | 139 | bool 140 | operator!=(const UnicodeStringIterator& rhs) const 141 | { 142 | return !(*this == rhs); 143 | } 144 | 145 | UnicodeStringIterator& 146 | operator++() 147 | { 148 | ++i; 149 | return *this; 150 | } 151 | 152 | UnicodeStringIterator 153 | operator+(int32_t v) 154 | { 155 | return UnicodeStringIterator(s, i + v); 156 | } 157 | 158 | private: 159 | const icu::UnicodeString* s; 160 | int32_t i; 161 | }; 162 | #if defined(__GNUC__) 163 | #pragma GCC diagnostic pop 164 | #endif 165 | 166 | inline 167 | String& 168 | stringAppend(String&s, String a) 169 | { 170 | return s.append(std::move(a)); 171 | } 172 | 173 | inline 174 | String& 175 | stringAppend(String& s, size_t n, UChar32 c) 176 | { 177 | for (size_t i = 0; i != n; ++i) 178 | { 179 | s.append(c); 180 | } 181 | 182 | return s; 183 | } 184 | 185 | template 186 | String& 187 | stringAppend(String& s, Iterator begin, Iterator end) 188 | { 189 | while (begin != end) 190 | { 191 | s.append(*begin); 192 | ++begin; 193 | } 194 | 195 | return s; 196 | } 197 | 198 | inline 199 | size_t 200 | stringLength(const String& s) 201 | { 202 | return s.length(); 203 | } 204 | 205 | inline 206 | std::string 207 | toUTF8String(const String& s) 208 | { 209 | std::string result; 210 | s.toUTF8String(result); 211 | 212 | return result; 213 | } 214 | 215 | inline 216 | bool 217 | empty(const String& s) 218 | { 219 | return s.isEmpty(); 220 | } 221 | } 222 | 223 | namespace std 224 | { 225 | inline 226 | cxxopts::UnicodeStringIterator 227 | begin(const icu::UnicodeString& s) 228 | { 229 | return cxxopts::UnicodeStringIterator(&s, 0); 230 | } 231 | 232 | inline 233 | cxxopts::UnicodeStringIterator 234 | end(const icu::UnicodeString& s) 235 | { 236 | return cxxopts::UnicodeStringIterator(&s, s.length()); 237 | } 238 | } 239 | 240 | //ifdef CXXOPTS_USE_UNICODE 241 | #else 242 | 243 | namespace cxxopts 244 | { 245 | using String = std::string; 246 | 247 | template 248 | T 249 | toLocalString(T&& t) 250 | { 251 | return std::forward(t); 252 | } 253 | 254 | inline 255 | size_t 256 | stringLength(const String& s) 257 | { 258 | return s.length(); 259 | } 260 | 261 | inline 262 | String& 263 | stringAppend(String&s, const String& a) 264 | { 265 | return s.append(a); 266 | } 267 | 268 | inline 269 | String& 270 | stringAppend(String& s, size_t n, char c) 271 | { 272 | return s.append(n, c); 273 | } 274 | 275 | template 276 | String& 277 | stringAppend(String& s, Iterator begin, Iterator end) 278 | { 279 | return s.append(begin, end); 280 | } 281 | 282 | template 283 | std::string 284 | toUTF8String(T&& t) 285 | { 286 | return std::forward(t); 287 | } 288 | 289 | inline 290 | bool 291 | empty(const std::string& s) 292 | { 293 | return s.empty(); 294 | } 295 | } // namespace cxxopts 296 | 297 | //ifdef CXXOPTS_USE_UNICODE 298 | #endif 299 | 300 | namespace cxxopts 301 | { 302 | namespace 303 | { 304 | #ifdef _WIN32 305 | const std::string LQUOTE("\'"); 306 | const std::string RQUOTE("\'"); 307 | #else 308 | const std::string LQUOTE("‘"); 309 | const std::string RQUOTE("’"); 310 | #endif 311 | } // namespace 312 | 313 | #if defined(__GNUC__) 314 | // GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: 315 | // warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor 316 | #pragma GCC diagnostic push 317 | #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" 318 | #pragma GCC diagnostic ignored "-Weffc++" 319 | // This will be ignored under other compilers like LLVM clang. 320 | #endif 321 | class Value : public std::enable_shared_from_this 322 | { 323 | public: 324 | 325 | virtual ~Value() = default; 326 | 327 | virtual 328 | std::shared_ptr 329 | clone() const = 0; 330 | 331 | virtual void 332 | parse(const std::string& text) const = 0; 333 | 334 | virtual void 335 | parse() const = 0; 336 | 337 | virtual bool 338 | has_default() const = 0; 339 | 340 | virtual bool 341 | is_container() const = 0; 342 | 343 | virtual bool 344 | has_implicit() const = 0; 345 | 346 | virtual std::string 347 | get_default_value() const = 0; 348 | 349 | virtual std::string 350 | get_implicit_value() const = 0; 351 | 352 | virtual std::shared_ptr 353 | default_value(const std::string& value) = 0; 354 | 355 | virtual std::shared_ptr 356 | implicit_value(const std::string& value) = 0; 357 | 358 | virtual std::shared_ptr 359 | no_implicit_value() = 0; 360 | 361 | virtual bool 362 | is_boolean() const = 0; 363 | }; 364 | #if defined(__GNUC__) 365 | #pragma GCC diagnostic pop 366 | #endif 367 | class OptionException : public std::exception 368 | { 369 | public: 370 | explicit OptionException(std::string message) 371 | : m_message(std::move(message)) 372 | { 373 | } 374 | 375 | CXXOPTS_NODISCARD 376 | const char* 377 | what() const noexcept override 378 | { 379 | return m_message.c_str(); 380 | } 381 | 382 | private: 383 | std::string m_message; 384 | }; 385 | 386 | class OptionSpecException : public OptionException 387 | { 388 | public: 389 | 390 | explicit OptionSpecException(const std::string& message) 391 | : OptionException(message) 392 | { 393 | } 394 | }; 395 | 396 | class OptionParseException : public OptionException 397 | { 398 | public: 399 | explicit OptionParseException(const std::string& message) 400 | : OptionException(message) 401 | { 402 | } 403 | }; 404 | 405 | class option_exists_error : public OptionSpecException 406 | { 407 | public: 408 | explicit option_exists_error(const std::string& option) 409 | : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") 410 | { 411 | } 412 | }; 413 | 414 | class invalid_option_format_error : public OptionSpecException 415 | { 416 | public: 417 | explicit invalid_option_format_error(const std::string& format) 418 | : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) 419 | { 420 | } 421 | }; 422 | 423 | class option_syntax_exception : public OptionParseException { 424 | public: 425 | explicit option_syntax_exception(const std::string& text) 426 | : OptionParseException("Argument " + LQUOTE + text + RQUOTE + 427 | " starts with a - but has incorrect syntax") 428 | { 429 | } 430 | }; 431 | 432 | class option_not_exists_exception : public OptionParseException 433 | { 434 | public: 435 | explicit option_not_exists_exception(const std::string& option) 436 | : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist") 437 | { 438 | } 439 | }; 440 | 441 | class missing_argument_exception : public OptionParseException 442 | { 443 | public: 444 | explicit missing_argument_exception(const std::string& option) 445 | : OptionParseException( 446 | "Option " + LQUOTE + option + RQUOTE + " is missing an argument" 447 | ) 448 | { 449 | } 450 | }; 451 | 452 | class option_requires_argument_exception : public OptionParseException 453 | { 454 | public: 455 | explicit option_requires_argument_exception(const std::string& option) 456 | : OptionParseException( 457 | "Option " + LQUOTE + option + RQUOTE + " requires an argument" 458 | ) 459 | { 460 | } 461 | }; 462 | 463 | class option_not_has_argument_exception : public OptionParseException 464 | { 465 | public: 466 | option_not_has_argument_exception 467 | ( 468 | const std::string& option, 469 | const std::string& arg 470 | ) 471 | : OptionParseException( 472 | "Option " + LQUOTE + option + RQUOTE + 473 | " does not take an argument, but argument " + 474 | LQUOTE + arg + RQUOTE + " given" 475 | ) 476 | { 477 | } 478 | }; 479 | 480 | class option_not_present_exception : public OptionParseException 481 | { 482 | public: 483 | explicit option_not_present_exception(const std::string& option) 484 | : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") 485 | { 486 | } 487 | }; 488 | 489 | class option_has_no_value_exception : public OptionException 490 | { 491 | public: 492 | explicit option_has_no_value_exception(const std::string& option) 493 | : OptionException( 494 | !option.empty() ? 495 | ("Option " + LQUOTE + option + RQUOTE + " has no value") : 496 | "Option has no value") 497 | { 498 | } 499 | }; 500 | 501 | class argument_incorrect_type : public OptionParseException 502 | { 503 | public: 504 | explicit argument_incorrect_type 505 | ( 506 | const std::string& arg 507 | ) 508 | : OptionParseException( 509 | "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" 510 | ) 511 | { 512 | } 513 | }; 514 | 515 | class option_required_exception : public OptionParseException 516 | { 517 | public: 518 | explicit option_required_exception(const std::string& option) 519 | : OptionParseException( 520 | "Option " + LQUOTE + option + RQUOTE + " is required but not present" 521 | ) 522 | { 523 | } 524 | }; 525 | 526 | template 527 | void throw_or_mimic(const std::string& text) 528 | { 529 | static_assert(std::is_base_of::value, 530 | "throw_or_mimic only works on std::exception and " 531 | "deriving classes"); 532 | 533 | #ifndef CXXOPTS_NO_EXCEPTIONS 534 | // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw 535 | throw T{text}; 536 | #else 537 | // Otherwise manually instantiate the exception, print what() to stderr, 538 | // and exit 539 | T exception{text}; 540 | std::cerr << exception.what() << std::endl; 541 | std::exit(EXIT_FAILURE); 542 | #endif 543 | } 544 | 545 | namespace values 546 | { 547 | namespace parser_tool 548 | { 549 | struct IntegerDesc 550 | { 551 | std::string negative = ""; 552 | std::string base = ""; 553 | std::string value = ""; 554 | }; 555 | struct ArguDesc { 556 | std::string arg_name = ""; 557 | bool grouping = false; 558 | bool set_value = false; 559 | std::string value = ""; 560 | }; 561 | #ifdef CXXOPTS_NO_REGEX 562 | inline IntegerDesc SplitInteger(const std::string &text) 563 | { 564 | if (text.empty()) 565 | { 566 | throw_or_mimic(text); 567 | } 568 | IntegerDesc desc; 569 | const char *pdata = text.c_str(); 570 | if (*pdata == '-') 571 | { 572 | pdata += 1; 573 | desc.negative = "-"; 574 | } 575 | if (strncmp(pdata, "0x", 2) == 0) 576 | { 577 | pdata += 2; 578 | desc.base = "0x"; 579 | } 580 | if (*pdata != '\0') 581 | { 582 | desc.value = std::string(pdata); 583 | } 584 | else 585 | { 586 | throw_or_mimic(text); 587 | } 588 | return desc; 589 | } 590 | 591 | inline bool IsTrueText(const std::string &text) 592 | { 593 | const char *pdata = text.c_str(); 594 | if (*pdata == 't' || *pdata == 'T') 595 | { 596 | pdata += 1; 597 | if (strncmp(pdata, "rue\0", 4) == 0) 598 | { 599 | return true; 600 | } 601 | } 602 | else if (strncmp(pdata, "1\0", 2) == 0) 603 | { 604 | return true; 605 | } 606 | return false; 607 | } 608 | 609 | inline bool IsFalseText(const std::string &text) 610 | { 611 | const char *pdata = text.c_str(); 612 | if (*pdata == 'f' || *pdata == 'F') 613 | { 614 | pdata += 1; 615 | if (strncmp(pdata, "alse\0", 5) == 0) 616 | { 617 | return true; 618 | } 619 | } 620 | else if (strncmp(pdata, "0\0", 2) == 0) 621 | { 622 | return true; 623 | } 624 | return false; 625 | } 626 | 627 | inline std::pair SplitSwitchDef(const std::string &text) 628 | { 629 | std::string short_sw, long_sw; 630 | const char *pdata = text.c_str(); 631 | if (isalnum(*pdata) && *(pdata + 1) == ',') { 632 | short_sw = std::string(1, *pdata); 633 | pdata += 2; 634 | } 635 | while (*pdata == ' ') { pdata += 1; } 636 | if (isalnum(*pdata)) { 637 | const char *store = pdata; 638 | pdata += 1; 639 | while (isalnum(*pdata) || *pdata == '-' || *pdata == '_') { 640 | pdata += 1; 641 | } 642 | if (*pdata == '\0') { 643 | long_sw = std::string(store, pdata - store); 644 | } else { 645 | throw_or_mimic(text); 646 | } 647 | } 648 | return std::pair(short_sw, long_sw); 649 | } 650 | 651 | inline ArguDesc ParseArgument(const char *arg, bool &matched) 652 | { 653 | ArguDesc argu_desc; 654 | const char *pdata = arg; 655 | matched = false; 656 | if (strncmp(pdata, "--", 2) == 0) 657 | { 658 | pdata += 2; 659 | if (isalnum(*pdata)) 660 | { 661 | argu_desc.arg_name.push_back(*pdata); 662 | pdata += 1; 663 | while (isalnum(*pdata) || *pdata == '-' || *pdata == '_') 664 | { 665 | argu_desc.arg_name.push_back(*pdata); 666 | pdata += 1; 667 | } 668 | if (argu_desc.arg_name.length() > 1) 669 | { 670 | if (*pdata == '=') 671 | { 672 | argu_desc.set_value = true; 673 | pdata += 1; 674 | if (*pdata != '\0') 675 | { 676 | argu_desc.value = std::string(pdata); 677 | } 678 | matched = true; 679 | } 680 | else if (*pdata == '\0') 681 | { 682 | matched = true; 683 | } 684 | } 685 | } 686 | } 687 | else if (strncmp(pdata, "-", 1) == 0) 688 | { 689 | pdata += 1; 690 | argu_desc.grouping = true; 691 | while (isalnum(*pdata)) 692 | { 693 | argu_desc.arg_name.push_back(*pdata); 694 | pdata += 1; 695 | } 696 | matched = !argu_desc.arg_name.empty() && *pdata == '\0'; 697 | } 698 | return argu_desc; 699 | } 700 | 701 | #else // CXXOPTS_NO_REGEX 702 | 703 | namespace 704 | { 705 | 706 | std::basic_regex integer_pattern 707 | ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); 708 | std::basic_regex truthy_pattern 709 | ("(t|T)(rue)?|1"); 710 | std::basic_regex falsy_pattern 711 | ("(f|F)(alse)?|0"); 712 | 713 | std::basic_regex option_matcher 714 | ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); 715 | std::basic_regex option_specifier 716 | ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); 717 | 718 | } // namespace 719 | 720 | inline IntegerDesc SplitInteger(const std::string &text) 721 | { 722 | std::smatch match; 723 | std::regex_match(text, match, integer_pattern); 724 | 725 | if (match.length() == 0) 726 | { 727 | throw_or_mimic(text); 728 | } 729 | 730 | IntegerDesc desc; 731 | desc.negative = match[1]; 732 | desc.base = match[2]; 733 | desc.value = match[3]; 734 | 735 | if (match.length(4) > 0) 736 | { 737 | desc.base = match[5]; 738 | desc.value = "0"; 739 | return desc; 740 | } 741 | 742 | return desc; 743 | } 744 | 745 | inline bool IsTrueText(const std::string &text) 746 | { 747 | std::smatch result; 748 | std::regex_match(text, result, truthy_pattern); 749 | return !result.empty(); 750 | } 751 | 752 | inline bool IsFalseText(const std::string &text) 753 | { 754 | std::smatch result; 755 | std::regex_match(text, result, falsy_pattern); 756 | return !result.empty(); 757 | } 758 | 759 | inline std::pair SplitSwitchDef(const std::string &text) 760 | { 761 | std::match_results result; 762 | std::regex_match(text.c_str(), result, option_specifier); 763 | if (result.empty()) 764 | { 765 | throw_or_mimic(text); 766 | } 767 | 768 | const std::string& short_sw = result[2]; 769 | const std::string& long_sw = result[3]; 770 | 771 | return std::pair(short_sw, long_sw); 772 | } 773 | 774 | inline ArguDesc ParseArgument(const char *arg, bool &matched) 775 | { 776 | std::match_results result; 777 | std::regex_match(arg, result, option_matcher); 778 | matched = !result.empty(); 779 | 780 | ArguDesc argu_desc; 781 | if (matched) { 782 | argu_desc.arg_name = result[1].str(); 783 | argu_desc.set_value = result[2].length() > 0; 784 | argu_desc.value = result[3].str(); 785 | if (result[4].length() > 0) 786 | { 787 | argu_desc.grouping = true; 788 | argu_desc.arg_name = result[4].str(); 789 | } 790 | } 791 | 792 | return argu_desc; 793 | } 794 | 795 | #endif // CXXOPTS_NO_REGEX 796 | #undef CXXOPTS_NO_REGEX 797 | } 798 | 799 | namespace detail 800 | { 801 | template 802 | struct SignedCheck; 803 | 804 | template 805 | struct SignedCheck 806 | { 807 | template 808 | void 809 | operator()(bool negative, U u, const std::string& text) 810 | { 811 | if (negative) 812 | { 813 | if (u > static_cast((std::numeric_limits::min)())) 814 | { 815 | throw_or_mimic(text); 816 | } 817 | } 818 | else 819 | { 820 | if (u > static_cast((std::numeric_limits::max)())) 821 | { 822 | throw_or_mimic(text); 823 | } 824 | } 825 | } 826 | }; 827 | 828 | template 829 | struct SignedCheck 830 | { 831 | template 832 | void 833 | operator()(bool, U, const std::string&) const {} 834 | }; 835 | 836 | template 837 | void 838 | check_signed_range(bool negative, U value, const std::string& text) 839 | { 840 | SignedCheck::is_signed>()(negative, value, text); 841 | } 842 | } // namespace detail 843 | 844 | template 845 | void 846 | checked_negate(R& r, T&& t, const std::string&, std::true_type) 847 | { 848 | // if we got to here, then `t` is a positive number that fits into 849 | // `R`. So to avoid MSVC C4146, we first cast it to `R`. 850 | // See https://github.com/jarro2783/cxxopts/issues/62 for more details. 851 | r = static_cast(-static_cast(t-1)-1); 852 | } 853 | 854 | template 855 | void 856 | checked_negate(R&, T&&, const std::string& text, std::false_type) 857 | { 858 | throw_or_mimic(text); 859 | } 860 | 861 | template 862 | void 863 | integer_parser(const std::string& text, T& value) 864 | { 865 | parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text); 866 | 867 | using US = typename std::make_unsigned::type; 868 | constexpr bool is_signed = std::numeric_limits::is_signed; 869 | 870 | const bool negative = int_desc.negative.length() > 0; 871 | const uint8_t base = int_desc.base.length() > 0 ? 16 : 10; 872 | const std::string & value_match = int_desc.value; 873 | 874 | US result = 0; 875 | 876 | for (char ch : value_match) 877 | { 878 | US digit = 0; 879 | 880 | if (ch >= '0' && ch <= '9') 881 | { 882 | digit = static_cast(ch - '0'); 883 | } 884 | else if (base == 16 && ch >= 'a' && ch <= 'f') 885 | { 886 | digit = static_cast(ch - 'a' + 10); 887 | } 888 | else if (base == 16 && ch >= 'A' && ch <= 'F') 889 | { 890 | digit = static_cast(ch - 'A' + 10); 891 | } 892 | else 893 | { 894 | throw_or_mimic(text); 895 | } 896 | 897 | const US next = static_cast(result * base + digit); 898 | if (result > next) 899 | { 900 | throw_or_mimic(text); 901 | } 902 | 903 | result = next; 904 | } 905 | 906 | detail::check_signed_range(negative, result, text); 907 | 908 | if (negative) 909 | { 910 | checked_negate(value, result, text, std::integral_constant()); 911 | } 912 | else 913 | { 914 | value = static_cast(result); 915 | } 916 | } 917 | 918 | template 919 | void stringstream_parser(const std::string& text, T& value) 920 | { 921 | std::stringstream in(text); 922 | in >> value; 923 | if (!in) { 924 | throw_or_mimic(text); 925 | } 926 | } 927 | 928 | template ::value>::type* = nullptr 930 | > 931 | void parse_value(const std::string& text, T& value) 932 | { 933 | integer_parser(text, value); 934 | } 935 | 936 | inline 937 | void 938 | parse_value(const std::string& text, bool& value) 939 | { 940 | if (parser_tool::IsTrueText(text)) 941 | { 942 | value = true; 943 | return; 944 | } 945 | 946 | if (parser_tool::IsFalseText(text)) 947 | { 948 | value = false; 949 | return; 950 | } 951 | 952 | throw_or_mimic(text); 953 | } 954 | 955 | inline 956 | void 957 | parse_value(const std::string& text, std::string& value) 958 | { 959 | value = text; 960 | } 961 | 962 | // The fallback parser. It uses the stringstream parser to parse all types 963 | // that have not been overloaded explicitly. It has to be placed in the 964 | // source code before all other more specialized templates. 965 | template ::value>::type* = nullptr 967 | > 968 | void 969 | parse_value(const std::string& text, T& value) { 970 | stringstream_parser(text, value); 971 | } 972 | 973 | template 974 | void 975 | parse_value(const std::string& text, std::vector& value) 976 | { 977 | std::stringstream in(text); 978 | std::string token; 979 | while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { 980 | T v; 981 | parse_value(token, v); 982 | value.emplace_back(std::move(v)); 983 | } 984 | } 985 | 986 | #ifdef CXXOPTS_HAS_OPTIONAL 987 | template 988 | void 989 | parse_value(const std::string& text, std::optional& value) 990 | { 991 | T result; 992 | parse_value(text, result); 993 | value = std::move(result); 994 | } 995 | #endif 996 | 997 | inline 998 | void parse_value(const std::string& text, char& c) 999 | { 1000 | if (text.length() != 1) 1001 | { 1002 | throw_or_mimic(text); 1003 | } 1004 | 1005 | c = text[0]; 1006 | } 1007 | 1008 | template 1009 | struct type_is_container 1010 | { 1011 | static constexpr bool value = false; 1012 | }; 1013 | 1014 | template 1015 | struct type_is_container> 1016 | { 1017 | static constexpr bool value = true; 1018 | }; 1019 | 1020 | template 1021 | class abstract_value : public Value 1022 | { 1023 | using Self = abstract_value; 1024 | 1025 | public: 1026 | abstract_value() 1027 | : m_result(std::make_shared()) 1028 | , m_store(m_result.get()) 1029 | { 1030 | } 1031 | 1032 | explicit abstract_value(T* t) 1033 | : m_store(t) 1034 | { 1035 | } 1036 | 1037 | ~abstract_value() override = default; 1038 | 1039 | abstract_value& operator=(const abstract_value&) = default; 1040 | 1041 | abstract_value(const abstract_value& rhs) 1042 | { 1043 | if (rhs.m_result) 1044 | { 1045 | m_result = std::make_shared(); 1046 | m_store = m_result.get(); 1047 | } 1048 | else 1049 | { 1050 | m_store = rhs.m_store; 1051 | } 1052 | 1053 | m_default = rhs.m_default; 1054 | m_implicit = rhs.m_implicit; 1055 | m_default_value = rhs.m_default_value; 1056 | m_implicit_value = rhs.m_implicit_value; 1057 | } 1058 | 1059 | void 1060 | parse(const std::string& text) const override 1061 | { 1062 | parse_value(text, *m_store); 1063 | } 1064 | 1065 | bool 1066 | is_container() const override 1067 | { 1068 | return type_is_container::value; 1069 | } 1070 | 1071 | void 1072 | parse() const override 1073 | { 1074 | parse_value(m_default_value, *m_store); 1075 | } 1076 | 1077 | bool 1078 | has_default() const override 1079 | { 1080 | return m_default; 1081 | } 1082 | 1083 | bool 1084 | has_implicit() const override 1085 | { 1086 | return m_implicit; 1087 | } 1088 | 1089 | std::shared_ptr 1090 | default_value(const std::string& value) override 1091 | { 1092 | m_default = true; 1093 | m_default_value = value; 1094 | return shared_from_this(); 1095 | } 1096 | 1097 | std::shared_ptr 1098 | implicit_value(const std::string& value) override 1099 | { 1100 | m_implicit = true; 1101 | m_implicit_value = value; 1102 | return shared_from_this(); 1103 | } 1104 | 1105 | std::shared_ptr 1106 | no_implicit_value() override 1107 | { 1108 | m_implicit = false; 1109 | return shared_from_this(); 1110 | } 1111 | 1112 | std::string 1113 | get_default_value() const override 1114 | { 1115 | return m_default_value; 1116 | } 1117 | 1118 | std::string 1119 | get_implicit_value() const override 1120 | { 1121 | return m_implicit_value; 1122 | } 1123 | 1124 | bool 1125 | is_boolean() const override 1126 | { 1127 | return std::is_same::value; 1128 | } 1129 | 1130 | const T& 1131 | get() const 1132 | { 1133 | if (m_store == nullptr) 1134 | { 1135 | return *m_result; 1136 | } 1137 | return *m_store; 1138 | } 1139 | 1140 | protected: 1141 | std::shared_ptr m_result{}; 1142 | T* m_store{}; 1143 | 1144 | bool m_default = false; 1145 | bool m_implicit = false; 1146 | 1147 | std::string m_default_value{}; 1148 | std::string m_implicit_value{}; 1149 | }; 1150 | 1151 | template 1152 | class standard_value : public abstract_value 1153 | { 1154 | public: 1155 | using abstract_value::abstract_value; 1156 | 1157 | CXXOPTS_NODISCARD 1158 | std::shared_ptr 1159 | clone() const override 1160 | { 1161 | return std::make_shared>(*this); 1162 | } 1163 | }; 1164 | 1165 | template <> 1166 | class standard_value : public abstract_value 1167 | { 1168 | public: 1169 | ~standard_value() override = default; 1170 | 1171 | standard_value() 1172 | { 1173 | set_default_and_implicit(); 1174 | } 1175 | 1176 | explicit standard_value(bool* b) 1177 | : abstract_value(b) 1178 | { 1179 | set_default_and_implicit(); 1180 | } 1181 | 1182 | std::shared_ptr 1183 | clone() const override 1184 | { 1185 | return std::make_shared>(*this); 1186 | } 1187 | 1188 | private: 1189 | 1190 | void 1191 | set_default_and_implicit() 1192 | { 1193 | m_default = true; 1194 | m_default_value = "false"; 1195 | m_implicit = true; 1196 | m_implicit_value = "true"; 1197 | } 1198 | }; 1199 | } // namespace values 1200 | 1201 | template 1202 | std::shared_ptr 1203 | value() 1204 | { 1205 | return std::make_shared>(); 1206 | } 1207 | 1208 | template 1209 | std::shared_ptr 1210 | value(T& t) 1211 | { 1212 | return std::make_shared>(&t); 1213 | } 1214 | 1215 | class OptionAdder; 1216 | 1217 | class OptionDetails 1218 | { 1219 | public: 1220 | OptionDetails 1221 | ( 1222 | std::string short_, 1223 | std::string long_, 1224 | String desc, 1225 | std::shared_ptr val 1226 | ) 1227 | : m_short(std::move(short_)) 1228 | , m_long(std::move(long_)) 1229 | , m_desc(std::move(desc)) 1230 | , m_value(std::move(val)) 1231 | , m_count(0) 1232 | { 1233 | m_hash = std::hash{}(m_long + m_short); 1234 | } 1235 | 1236 | OptionDetails(const OptionDetails& rhs) 1237 | : m_desc(rhs.m_desc) 1238 | , m_value(rhs.m_value->clone()) 1239 | , m_count(rhs.m_count) 1240 | { 1241 | } 1242 | 1243 | OptionDetails(OptionDetails&& rhs) = default; 1244 | 1245 | CXXOPTS_NODISCARD 1246 | const String& 1247 | description() const 1248 | { 1249 | return m_desc; 1250 | } 1251 | 1252 | CXXOPTS_NODISCARD 1253 | const Value& 1254 | value() const { 1255 | return *m_value; 1256 | } 1257 | 1258 | CXXOPTS_NODISCARD 1259 | std::shared_ptr 1260 | make_storage() const 1261 | { 1262 | return m_value->clone(); 1263 | } 1264 | 1265 | CXXOPTS_NODISCARD 1266 | const std::string& 1267 | short_name() const 1268 | { 1269 | return m_short; 1270 | } 1271 | 1272 | CXXOPTS_NODISCARD 1273 | const std::string& 1274 | long_name() const 1275 | { 1276 | return m_long; 1277 | } 1278 | 1279 | size_t 1280 | hash() const 1281 | { 1282 | return m_hash; 1283 | } 1284 | 1285 | private: 1286 | std::string m_short{}; 1287 | std::string m_long{}; 1288 | String m_desc{}; 1289 | std::shared_ptr m_value{}; 1290 | int m_count; 1291 | 1292 | size_t m_hash{}; 1293 | }; 1294 | 1295 | struct HelpOptionDetails 1296 | { 1297 | std::string s; 1298 | std::string l; 1299 | String desc; 1300 | bool has_default; 1301 | std::string default_value; 1302 | bool has_implicit; 1303 | std::string implicit_value; 1304 | std::string arg_help; 1305 | bool is_container; 1306 | bool is_boolean; 1307 | }; 1308 | 1309 | struct HelpGroupDetails 1310 | { 1311 | std::string name{}; 1312 | std::string description{}; 1313 | std::vector options{}; 1314 | }; 1315 | 1316 | class OptionValue 1317 | { 1318 | public: 1319 | void 1320 | parse 1321 | ( 1322 | const std::shared_ptr& details, 1323 | const std::string& text 1324 | ) 1325 | { 1326 | ensure_value(details); 1327 | ++m_count; 1328 | m_value->parse(text); 1329 | m_long_name = &details->long_name(); 1330 | } 1331 | 1332 | void 1333 | parse_default(const std::shared_ptr& details) 1334 | { 1335 | ensure_value(details); 1336 | m_default = true; 1337 | m_long_name = &details->long_name(); 1338 | m_value->parse(); 1339 | } 1340 | 1341 | void 1342 | parse_no_value(const std::shared_ptr& details) 1343 | { 1344 | m_long_name = &details->long_name(); 1345 | } 1346 | 1347 | #if defined(CXXOPTS_NULL_DEREF_IGNORE) 1348 | #pragma GCC diagnostic push 1349 | #pragma GCC diagnostic ignored "-Wnull-dereference" 1350 | #endif 1351 | 1352 | CXXOPTS_NODISCARD 1353 | size_t 1354 | count() const noexcept 1355 | { 1356 | return m_count; 1357 | } 1358 | 1359 | #if defined(CXXOPTS_NULL_DEREF_IGNORE) 1360 | #pragma GCC diagnostic pop 1361 | #endif 1362 | 1363 | // TODO: maybe default options should count towards the number of arguments 1364 | CXXOPTS_NODISCARD 1365 | bool 1366 | has_default() const noexcept 1367 | { 1368 | return m_default; 1369 | } 1370 | 1371 | template 1372 | const T& 1373 | as() const 1374 | { 1375 | if (m_value == nullptr) { 1376 | throw_or_mimic( 1377 | m_long_name == nullptr ? "" : *m_long_name); 1378 | } 1379 | 1380 | #ifdef CXXOPTS_NO_RTTI 1381 | return static_cast&>(*m_value).get(); 1382 | #else 1383 | return dynamic_cast&>(*m_value).get(); 1384 | #endif 1385 | } 1386 | 1387 | private: 1388 | void 1389 | ensure_value(const std::shared_ptr& details) 1390 | { 1391 | if (m_value == nullptr) 1392 | { 1393 | m_value = details->make_storage(); 1394 | } 1395 | } 1396 | 1397 | 1398 | const std::string* m_long_name = nullptr; 1399 | // Holding this pointer is safe, since OptionValue's only exist in key-value pairs, 1400 | // where the key has the string we point to. 1401 | std::shared_ptr m_value{}; 1402 | size_t m_count = 0; 1403 | bool m_default = false; 1404 | }; 1405 | 1406 | class KeyValue 1407 | { 1408 | public: 1409 | KeyValue(std::string key_, std::string value_) 1410 | : m_key(std::move(key_)) 1411 | , m_value(std::move(value_)) 1412 | { 1413 | } 1414 | 1415 | CXXOPTS_NODISCARD 1416 | const std::string& 1417 | key() const 1418 | { 1419 | return m_key; 1420 | } 1421 | 1422 | CXXOPTS_NODISCARD 1423 | const std::string& 1424 | value() const 1425 | { 1426 | return m_value; 1427 | } 1428 | 1429 | template 1430 | T 1431 | as() const 1432 | { 1433 | T result; 1434 | values::parse_value(m_value, result); 1435 | return result; 1436 | } 1437 | 1438 | private: 1439 | std::string m_key; 1440 | std::string m_value; 1441 | }; 1442 | 1443 | using ParsedHashMap = std::unordered_map; 1444 | using NameHashMap = std::unordered_map; 1445 | 1446 | class ParseResult 1447 | { 1448 | public: 1449 | 1450 | ParseResult() = default; 1451 | ParseResult(const ParseResult&) = default; 1452 | 1453 | ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector sequential, std::vector&& unmatched_args) 1454 | : m_keys(std::move(keys)) 1455 | , m_values(std::move(values)) 1456 | , m_sequential(std::move(sequential)) 1457 | , m_unmatched(std::move(unmatched_args)) 1458 | { 1459 | } 1460 | 1461 | ParseResult& operator=(ParseResult&&) = default; 1462 | ParseResult& operator=(const ParseResult&) = default; 1463 | 1464 | size_t 1465 | count(const std::string& o) const 1466 | { 1467 | auto iter = m_keys.find(o); 1468 | if (iter == m_keys.end()) 1469 | { 1470 | return 0; 1471 | } 1472 | 1473 | auto viter = m_values.find(iter->second); 1474 | 1475 | if (viter == m_values.end()) 1476 | { 1477 | return 0; 1478 | } 1479 | 1480 | return viter->second.count(); 1481 | } 1482 | 1483 | const OptionValue& 1484 | operator[](const std::string& option) const 1485 | { 1486 | auto iter = m_keys.find(option); 1487 | 1488 | if (iter == m_keys.end()) 1489 | { 1490 | throw_or_mimic(option); 1491 | } 1492 | 1493 | auto viter = m_values.find(iter->second); 1494 | 1495 | if (viter == m_values.end()) 1496 | { 1497 | throw_or_mimic(option); 1498 | } 1499 | 1500 | return viter->second; 1501 | } 1502 | 1503 | const std::vector& 1504 | arguments() const 1505 | { 1506 | return m_sequential; 1507 | } 1508 | 1509 | const std::vector& 1510 | unmatched() const 1511 | { 1512 | return m_unmatched; 1513 | } 1514 | 1515 | private: 1516 | NameHashMap m_keys{}; 1517 | ParsedHashMap m_values{}; 1518 | std::vector m_sequential{}; 1519 | std::vector m_unmatched{}; 1520 | }; 1521 | 1522 | struct Option 1523 | { 1524 | Option 1525 | ( 1526 | std::string opts, 1527 | std::string desc, 1528 | std::shared_ptr value = ::cxxopts::value(), 1529 | std::string arg_help = "" 1530 | ) 1531 | : opts_(std::move(opts)) 1532 | , desc_(std::move(desc)) 1533 | , value_(std::move(value)) 1534 | , arg_help_(std::move(arg_help)) 1535 | { 1536 | } 1537 | 1538 | std::string opts_; 1539 | std::string desc_; 1540 | std::shared_ptr value_; 1541 | std::string arg_help_; 1542 | }; 1543 | 1544 | using OptionMap = std::unordered_map>; 1545 | using PositionalList = std::vector; 1546 | using PositionalListIterator = PositionalList::const_iterator; 1547 | 1548 | class OptionParser 1549 | { 1550 | public: 1551 | OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised) 1552 | : m_options(options) 1553 | , m_positional(positional) 1554 | , m_allow_unrecognised(allow_unrecognised) 1555 | { 1556 | } 1557 | 1558 | ParseResult 1559 | parse(int argc, const char* const* argv); 1560 | 1561 | bool 1562 | consume_positional(const std::string& a, PositionalListIterator& next); 1563 | 1564 | void 1565 | checked_parse_arg 1566 | ( 1567 | int argc, 1568 | const char* const* argv, 1569 | int& current, 1570 | const std::shared_ptr& value, 1571 | const std::string& name 1572 | ); 1573 | 1574 | void 1575 | add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg); 1576 | 1577 | void 1578 | parse_option 1579 | ( 1580 | const std::shared_ptr& value, 1581 | const std::string& name, 1582 | const std::string& arg = "" 1583 | ); 1584 | 1585 | void 1586 | parse_default(const std::shared_ptr& details); 1587 | 1588 | void 1589 | parse_no_value(const std::shared_ptr& details); 1590 | 1591 | private: 1592 | 1593 | void finalise_aliases(); 1594 | 1595 | const OptionMap& m_options; 1596 | const PositionalList& m_positional; 1597 | 1598 | std::vector m_sequential{}; 1599 | bool m_allow_unrecognised; 1600 | 1601 | ParsedHashMap m_parsed{}; 1602 | NameHashMap m_keys{}; 1603 | }; 1604 | 1605 | class Options 1606 | { 1607 | public: 1608 | 1609 | explicit Options(std::string program, std::string help_string = "") 1610 | : m_program(std::move(program)) 1611 | , m_help_string(toLocalString(std::move(help_string))) 1612 | , m_custom_help("[OPTION...]") 1613 | , m_positional_help("positional parameters") 1614 | , m_show_positional(false) 1615 | , m_allow_unrecognised(false) 1616 | , m_width(76) 1617 | , m_tab_expansion(false) 1618 | , m_options(std::make_shared()) 1619 | { 1620 | } 1621 | 1622 | Options& 1623 | positional_help(std::string help_text) 1624 | { 1625 | m_positional_help = std::move(help_text); 1626 | return *this; 1627 | } 1628 | 1629 | Options& 1630 | custom_help(std::string help_text) 1631 | { 1632 | m_custom_help = std::move(help_text); 1633 | return *this; 1634 | } 1635 | 1636 | Options& 1637 | show_positional_help() 1638 | { 1639 | m_show_positional = true; 1640 | return *this; 1641 | } 1642 | 1643 | Options& 1644 | allow_unrecognised_options() 1645 | { 1646 | m_allow_unrecognised = true; 1647 | return *this; 1648 | } 1649 | 1650 | Options& 1651 | set_width(size_t width) 1652 | { 1653 | m_width = width; 1654 | return *this; 1655 | } 1656 | 1657 | Options& 1658 | set_tab_expansion(bool expansion=true) 1659 | { 1660 | m_tab_expansion = expansion; 1661 | return *this; 1662 | } 1663 | 1664 | ParseResult 1665 | parse(int argc, const char* const* argv); 1666 | 1667 | OptionAdder 1668 | add_options(std::string group = ""); 1669 | 1670 | void 1671 | add_options 1672 | ( 1673 | const std::string& group, 1674 | std::initializer_list