├── googletest-release-1.8.0.tar.gz ├── test ├── version_test.cc ├── testest.cc ├── str2lower_test.cc ├── testsvr.js ├── testcli.js ├── CMakeLists.txt ├── stest.cc ├── ctest.cc ├── split_path_query_test.cc ├── ahead_test.cc ├── first_line_parse_test.cc ├── base64_test.cc ├── is_numerichost_test.cc ├── sha1_test.cc ├── uri_parse_test.cc └── split_host_port_test.cc ├── LICENSE.txt ├── README-ja.md ├── README.md └── lwsock.hpp /googletest-release-1.8.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hfuj13/lwsock/HEAD/googletest-release-1.8.0.tar.gz -------------------------------------------------------------------------------- /test/version_test.cc: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | #include "lwsock.hpp" 4 | 5 | using namespace std; 6 | using namespace lwsock; 7 | 8 | TEST(Version, version) 9 | { 10 | std::string ver = Version; 11 | ASSERT_EQ("v1.4.1", ver); 12 | } 13 | -------------------------------------------------------------------------------- /test/testest.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "gtest/gtest.h" 4 | #include "lwsock.hpp" 5 | 6 | using namespace std; 7 | 8 | int main(int argc, char **argv) 9 | { 10 | printf("Running main() from gtest_main.cc\n"); 11 | testing::InitGoogleTest(&argc, argv); 12 | return RUN_ALL_TESTS(); 13 | } 14 | -------------------------------------------------------------------------------- /test/str2lower_test.cc: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | #include "lwsock.hpp" 4 | 5 | using namespace std; 6 | using namespace lwsock; 7 | 8 | TEST(str2lower, A) 9 | { 10 | auto actual = str2lower("A"); 11 | auto expected = "a"; 12 | ASSERT_EQ(expected, actual); 13 | } 14 | 15 | TEST(str2lower, ABCDEFGHIJKLMNOPQRSTUBWXYZ) 16 | { 17 | auto actual = str2lower("ABCDEFGHIJKLMNOPQRSTUBWXYZ"); 18 | auto expected = "abcdefghijklmnopqrstubwxyz"; 19 | ASSERT_EQ(expected, actual); 20 | } 21 | -------------------------------------------------------------------------------- /test/testsvr.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // npm install ws 4 | // node testsvr.js 5 | // 6 | // Enter ^C to end this. 7 | 8 | let WebSocketSvr = require('ws').Server; 9 | let wss = new WebSocketSvr({port: 12000}); 10 | wss.on('connection', (ws) => { 11 | ws.on('message', (data) => { 12 | console.log('recieved:'); 13 | console.log(' #[' + data + ']#'); 14 | console.log(' bin hex #[' + Buffer(data).toString('hex') + ']#'); 15 | }); 16 | 17 | ws.on('close', ()=>{ 18 | process.exit(); 19 | }); 20 | 21 | setTimeout(()=>{ 22 | ws.send('x y z'); 23 | }, 3000); 24 | 25 | }); 26 | -------------------------------------------------------------------------------- /test/testcli.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // npm install ws 4 | // node testcli.js 5 | // 6 | // Enter ^C to end this. 7 | 8 | let WebSocket = require('ws'); 9 | let wsock = new WebSocket('ws://localhost:22000/'); 10 | 11 | wsock.on('open', ()=>{ 12 | setTimeout(()=>{ 13 | wsock.send('x y z'); 14 | }, 1000); 15 | setTimeout(()=>{ 16 | wsock.close(); 17 | }, 2000); 18 | 19 | }); 20 | 21 | wsock.on('message', (data, flag)=>{ 22 | console.log('received:'); 23 | console.log(' #[' + data + ']#'); 24 | console.log(' bin hex #[' + Buffer(data).toString('hex') + ']#'); 25 | }); 26 | 27 | wsock.on('close', (code, reason)=>{ 28 | console.log('close the websocket code="' + code + '", reson="' + reason + '"'); 29 | }); 30 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.2) 2 | SET(CMAKE_CXX_COMPILER /usr/bin/g++) 3 | #SET(CMAKE_CXX_COMPILER /usr/bin/clang++) 4 | #SET(CMAKE_CXX_COMPILER /usr/bin/clang++-11) 5 | add_definitions("-g -Wall -std=c++14 -pthread") 6 | include_directories(..) 7 | include_directories(.) 8 | include_directories(include) 9 | 10 | add_executable( 11 | testest 12 | src/gtest-all.cc 13 | testest.cc 14 | version_test.cc 15 | base64_test.cc 16 | sha1_test.cc 17 | uri_parse_test.cc 18 | str2lower_test.cc 19 | ahead_test.cc 20 | first_line_parse_test.cc 21 | is_numerichost_test.cc 22 | split_host_port_test.cc 23 | ) 24 | target_link_libraries(testest pthread) 25 | 26 | # test client 27 | add_executable( 28 | ctest 29 | ctest.cc 30 | ) 31 | target_link_libraries(ctest pthread) 32 | 33 | # test server 34 | add_executable( 35 | stest 36 | stest.cc 37 | ) 38 | target_link_libraries(stest pthread) 39 | -------------------------------------------------------------------------------- /test/stest.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "lwsock.hpp" 3 | 4 | using namespace std; 5 | using namespace lwsock; 6 | 7 | // WebSocket Server Test Program. 8 | 9 | void worker(WebSocket&& ws) 10 | { 11 | ws.recv_req(); 12 | ws.send_res(); 13 | string msg1 = "d e f"; 14 | ws.send_msg_txt(msg1); 15 | auto rcvd = ws.recv_msg_txt(); 16 | cout << "rcvd #[" << rcvd.first << "]#" << endl; 17 | ws.send_close(1000); 18 | sleep(3); 19 | } 20 | 21 | int main(int argc, char* argv[]) 22 | { 23 | WebSocket ws(WebSocket::Mode::SERVER); 24 | ws.ostream4log(cout); 25 | ws.loglevel(LogLevel::DEBUG); 26 | 27 | ws.bind("ws://localhost:22000"); 28 | ws.listen(5); 29 | vector th_set; 30 | const int max = 2; 31 | for (int i = 0; i < max; ++i) { 32 | cout << "\n" << endl; 33 | WebSocket nws = ws.accept(); 34 | auto th = thread(worker, move(nws)); 35 | th_set.push_back(move(th)); 36 | } 37 | for (auto& th : th_set) { 38 | th.join(); 39 | } 40 | 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /test/ctest.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "lwsock.hpp" 6 | 7 | // WebSocket Client Test Program. 8 | 9 | using namespace std; 10 | using namespace lwsock; 11 | 12 | int main() 13 | { 14 | WebSocket ws(WebSocket::Mode::CLIENT); 15 | ws.ostream4log(cout); 16 | ws.loglevel(LogLevel::DEBUG); 17 | //ws.loglevel(LogLevel::ERROR); 18 | cout << as_int(ws.loglevel()) << endl; 19 | //ws.connect("ws://[::1]:10000"); 20 | //ws.connect("ws://127.0.0.1:10000"); 21 | //ws.connect("ws://localhost:8126/foo"); 22 | ws.connect("ws://localhost:12000"); 23 | //ws.connect("ws://192.168.30.10:10000"); 24 | //ws.connect("ws://www.google.com"); 25 | ws.send_req(); 26 | ws.recv_res(); 27 | 28 | vector msg1{{1, 2, 3}}; 29 | ws.send_msg_bin(msg1); 30 | 31 | string msg2 = "a b c"; 32 | ws.send_msg_txt(msg2); 33 | 34 | array msg3{{4, 5, 6}}; 35 | ws.send_msg_bin(msg3); 36 | 37 | cout << "==" << endl; 38 | auto rcvd = ws.recv_msg_txt(); 39 | cout << "rcvd #[" << rcvd.first << "]#" << endl; 40 | 41 | ws.send_close(1000); 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 hfuj13@gmail.com 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 | -------------------------------------------------------------------------------- /test/split_path_query_test.cc: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | #include "lwsock.hpp" 4 | 5 | using namespace std; 6 | using namespace lwsock; 7 | 8 | TEST(SPLIT_PATH_QUERY, split_path_query) 9 | { 10 | { string path_query("/path?aa=xx"); 11 | auto actual = split_path_query(path_query); 12 | ASSERT_EQ("/path", actual.first); 13 | ASSERT_EQ("?aa=xx", actual.second); 14 | } 15 | { string path_query("/path"); 16 | auto actual = split_path_query(path_query); 17 | ASSERT_EQ("/path", actual.first); 18 | ASSERT_EQ("", actual.second); 19 | } 20 | { string path_query("/"); 21 | auto actual = split_path_query(path_query); 22 | ASSERT_EQ("/", actual.first); 23 | ASSERT_EQ("", actual.second); 24 | } 25 | { string path_query("/?aa=xx"); 26 | auto actual = split_path_query(path_query); 27 | ASSERT_EQ("/", actual.first); 28 | ASSERT_EQ("?aa=xx", actual.second); 29 | } 30 | 31 | { string path_query("path?aa=xx"); 32 | auto actual = split_path_query(path_query); 33 | ASSERT_EQ("/path", actual.first); 34 | ASSERT_EQ("?aa=xx", actual.second); 35 | } 36 | { string path_query(""); 37 | auto actual = split_path_query(path_query); 38 | ASSERT_EQ("/", actual.first); 39 | ASSERT_EQ("", actual.second); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/ahead_test.cc: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | #include "lwsock.hpp" 4 | 5 | using namespace std; 6 | using namespace lwsock; 7 | 8 | TEST(AHead, ahead1) 9 | { 10 | AHead ahead; 11 | ahead.fin(1); 12 | ahead.rsv1(1); 13 | ahead.rsv2(1); 14 | ahead.rsv3(1); 15 | ahead.opcode(Opcode::BINARY); 16 | ahead.mask(1); 17 | ahead.payload_len(127); 18 | 19 | ASSERT_EQ(0xfff2, ahead.data()); 20 | } 21 | 22 | TEST(AHead, ahead2) 23 | { 24 | AHead ahead; 25 | ahead.payload_len(127); 26 | ahead.mask(1); 27 | ahead.opcode(Opcode::BINARY); 28 | ahead.rsv3(1); 29 | ahead.rsv2(1); 30 | ahead.rsv1(1); 31 | ahead.fin(1); 32 | 33 | ASSERT_EQ(0xfff2, ahead.data()); 34 | } 35 | 36 | TEST(AHead, ahead3) 37 | { 38 | AHead ahead; 39 | ahead.fin(1); 40 | ahead.rsv1(1); 41 | ahead.rsv2(1); 42 | ahead.rsv3(1); 43 | ahead.opcode(Opcode::BINARY); 44 | ahead.mask(1); 45 | ahead.payload_len(127); 46 | 47 | ahead.fin(0); 48 | ASSERT_EQ(0xff72, ahead.data()); 49 | ahead.fin(1); 50 | 51 | ahead.rsv1(0); 52 | ASSERT_EQ(0xffb2, ahead.data()); 53 | ahead.rsv1(1); 54 | 55 | ahead.rsv2(0); 56 | ASSERT_EQ(0xffd2, ahead.data()); 57 | ahead.rsv2(1); 58 | 59 | ahead.rsv3(0); 60 | ASSERT_EQ(0xffe2, ahead.data()); 61 | ahead.rsv3(1); 62 | 63 | ahead.opcode(Opcode::CONTINUE); 64 | ASSERT_EQ(0xfff0, ahead.data()); 65 | ahead.opcode(Opcode::BINARY); 66 | 67 | ahead.mask(0); 68 | ASSERT_EQ(0x7ff2, ahead.data()); 69 | ahead.mask(1); 70 | 71 | ahead.payload_len(7); 72 | ASSERT_EQ(0x87f2, ahead.data()); 73 | } 74 | -------------------------------------------------------------------------------- /test/first_line_parse_test.cc: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | #include "lwsock.hpp" 4 | 5 | using namespace std; 6 | using namespace lwsock; 7 | 8 | TEST(FIRST_LINE, REQUEST) 9 | { 10 | std::ostringstream re; 11 | re << R"(^GET +((/[^? ]*)(\?[^ ]*)?)? *HTTP/1\.1)"; 12 | { 13 | std::string src = "GET HTTP/1.1"; 14 | CRegex regex(re.str(), 20); 15 | auto tmp = regex.exec(src); 16 | ASSERT_EQ(1, tmp.size()); 17 | } 18 | { 19 | std::string src = "GET / HTTP/1.1"; 20 | CRegex regex(re.str(), 20); 21 | auto tmp = regex.exec(src); 22 | ASSERT_EQ(3, tmp.size()); 23 | ASSERT_EQ("/", tmp[2]); 24 | } 25 | { 26 | std::string src = "GET /path/a/b/c HTTP/1.1"; 27 | CRegex regex(re.str(), 20); 28 | auto tmp = regex.exec(src); 29 | ASSERT_EQ(3, tmp.size()); 30 | ASSERT_EQ("/path/a/b/c", tmp[2]); 31 | } 32 | { 33 | std::string src = "GET /path/a?b=1&c=2 HTTP/1.1"; 34 | CRegex regex(re.str(), 20); 35 | auto tmp = regex.exec(src); 36 | ASSERT_EQ(4, tmp.size()); 37 | ASSERT_EQ("/path/a?b=1&c=2", tmp[1]); 38 | ASSERT_EQ("/path/a", tmp[2]); 39 | ASSERT_EQ("?b=1&c=2", tmp[3]); 40 | } 41 | { 42 | std::string src = "GET /path?a&b=1&c=2 HTTP/1.1"; 43 | CRegex regex(re.str(), 20); 44 | auto tmp = regex.exec(src); 45 | ASSERT_EQ(4, tmp.size()); 46 | ASSERT_EQ("/path?a&b=1&c=2", tmp[1]); 47 | ASSERT_EQ("/path", tmp[2]); 48 | ASSERT_EQ("?a&b=1&c=2", tmp[3]); 49 | } 50 | { 51 | std::string src = "GET /?a&b=1&c=2 HTTP/1.1"; 52 | CRegex regex(re.str(), 20); 53 | auto tmp = regex.exec(src); 54 | ASSERT_EQ(4, tmp.size()); 55 | ASSERT_EQ("/?a&b=1&c=2", tmp[1]); 56 | ASSERT_EQ("/", tmp[2]); 57 | ASSERT_EQ("?a&b=1&c=2", tmp[3]); 58 | } 59 | } 60 | 61 | TEST(FIRST_LINE, RESPONSE) 62 | { 63 | { 64 | std::string src = "HTTP/1.1 101"; 65 | std::ostringstream re; 66 | re << R"(^HTTP/1.1 ([0-9]+)(.*)?)"; 67 | CRegex regex(re.str(), 20); 68 | auto tmp = regex.exec(src); 69 | ASSERT_LT(2, tmp.size()); 70 | ASSERT_EQ("101", tmp[1]); 71 | } 72 | 73 | { 74 | std::string src = "HTTP/1.1 101 aaaa"; 75 | std::ostringstream re; 76 | re << R"(^HTTP/1.1 ([0-9]+)(.*)?)"; 77 | CRegex regex(re.str(), 20); 78 | auto tmp = regex.exec(src); 79 | ASSERT_LT(2, tmp.size()); 80 | ASSERT_EQ("101", tmp[1]); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /README-ja.md: -------------------------------------------------------------------------------- 1 | ## LWSOCK 2 | 3 | * lwsock は C++ 用の WebSocket (RFC6455) ライブラリです。 4 | * L: ライブラリ、WSOCK: WebSocket を意味します。 5 | * このAPIは伝統的なTCPクライアント/サーバー用のBSD Socket APIに似ています。(connect, bind, listen, accept, send, recv) 6 | * IPv6 対応。 7 | * 他のライブラリに依存していません。(futuer maybe depend on openssl or libressl etc) 8 | * スレッド等の多重化IOは利用者自身で制御する必要があります。 9 | * lwsockは状態管理をしません(CONNECTING, OPEN, CLOSING, CLOSED等)。必要なら利用者自身で管理して下さい。 10 | * ドキュメント: https://github.com/hfuj13/lwsock/wiki 11 | 12 | ## 要件: 13 | 14 | * C++14以降 15 | * Linux 16 | * C++例外機能 (-fexceptions等) 17 | * リトルエンディアン 18 | 19 | ## テスターのビルド 20 | 21 | ``` 22 | $ tar xf googletest-release-1.8.0.tar.gz 23 | $ cd test 24 | $ ln -s ../googletest-release-1.8.0/googletest/include ./ 25 | $ ln -s ../googletest-release-1.8.0/googletest/src ./ 26 | $ mkdir build 27 | $ cd build 28 | $ cmake ../ 29 | $ make 30 | ``` 31 | 32 | ## 注 33 | 34 | * TLSはまだサポートしていません。 35 | * デフォルトでサポートしているオープニングハンドシェイクヘッダーは Host, Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Accept です。 36 | * もし他のヘッダーを使いたい場合は次のAPIを使って下さい: 37 | * send_req(const headers_t&) 38 | * send_res(const headers_t&) 39 | * send_req_manually(const handshake_t&) 40 | * send_res_manually(const handshake_t&) 41 | * もしAndroid NDKでlwsockを使う場合は、-fexceptions コンパイラオプションをセットし -Wexit-time-destructors をアンセットしたほうが良いです。 42 | 43 | ## 使用例 44 | 45 | ### サーバーサイド: 46 | 47 | ``` 48 | void worker(lwsock::WebSocket&& nws) 49 | { 50 | auto hs = nws.recv_req(); // returned handshake data 51 | nws.send_res(); 52 | std::string msg = "d e f"; 53 | nws.send_msg_txt(msg); 54 | auto rcvd = nws.recv_msg_txt(); 55 | std::cout << rcvd << std::endl; 56 | nws.send_close(1000); 57 | } 58 | 59 | lwsock::WebSocket s(lwsock::WebSocket::Mode::SERVER); 60 | //s.ostream4log(cout); 61 | s.bind("ws://hostname:22000"); 62 | //s.bind("ws://0.0.0.0:22000"); // IPv4 any address 63 | //s.bind("ws://[::]:22000"); // IPv6 only any address 64 | //s.bind("ws://[::].0.0.0.0:22000"); // IPv4 and IPv6 any address 65 | s.listen(5); 66 | std::vector th_set; 67 | for (int i = 0; i < 3; ++i) { 68 | lwsock::WebSocket nws = s.accept(); // blocking, return new WebSocket object 69 | auto th = std::thread(worker, std::move(nws)); 70 | th_set.push_back(std::move(th)); 71 | } 72 | for (auto& th : th_set) { 73 | th.join(); 74 | } 75 | ``` 76 | 77 | ### クライアントサイド: 78 | 79 | ``` 80 | lwsock::WebSocket c(lwsock::WebSocket::Mode::CLIENT); 81 | //c.ostream4log(cout); 82 | c.connect("ws://hostname:22000"); 83 | c.send_req(); 84 | auto hs = c.recv_res(); // returned handshake data 85 | std::string msg = "a b c"; 86 | c.send_msg_txt(msg); 87 | auto rcvd = ws.recv_msg_txt(); 88 | std::cout << rcvd << std::endl; 89 | ``` 90 | 91 | ## TODO 92 | 93 | * エラー値の再調整 94 | * ログの最適化 95 | * コードの整理 96 | * サンプルの改訂 97 | * TLSサポート 98 | * ソースファイルの分割 99 | -------------------------------------------------------------------------------- /test/base64_test.cc: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | #include "lwsock.hpp" 4 | 5 | using namespace std; 6 | using namespace lwsock; 7 | 8 | /* Test Vectors 9 | BASE64("") = "" 10 | BASE64("f") = "Zg==" 11 | BASE64("fo") = "Zm8=" 12 | BASE64("foo") = "Zm9v" 13 | BASE64("foob") = "Zm9vYg==" 14 | BASE64("fooba") = "Zm9vYmE=" 15 | BASE64("foobar") = "Zm9vYmFy" 16 | */ 17 | 18 | TEST(base64, rfc4648) 19 | { 20 | { 21 | string src(""); 22 | string encoded = b64encode(src.data(), src.size()); 23 | ASSERT_EQ("", encoded); 24 | vector decoded = b64decode(encoded); 25 | ASSERT_TRUE(equal(begin(src), end(src), begin(decoded))); 26 | } 27 | { 28 | string src("f"); 29 | string encoded = b64encode(src.data(), src.size()); 30 | ASSERT_EQ("Zg==", encoded); 31 | vector decoded = b64decode(encoded); 32 | ASSERT_TRUE(equal(begin(src), end(src), begin(decoded))); 33 | } 34 | { 35 | string src("fo"); 36 | string encoded = b64encode(src.data(), src.size()); 37 | ASSERT_EQ("Zm8=", encoded); 38 | vector decoded = b64decode(encoded); 39 | ASSERT_TRUE(equal(begin(src), end(src), begin(decoded))); 40 | } 41 | { 42 | string src("foo"); 43 | string encoded = b64encode(src.data(), src.size()); 44 | ASSERT_EQ("Zm9v", encoded); 45 | vector decoded = b64decode(encoded); 46 | ASSERT_TRUE(equal(begin(src), end(src), begin(decoded))); 47 | } 48 | { 49 | string src("foob"); 50 | string encoded = b64encode(src.data(), src.size()); 51 | ASSERT_EQ("Zm9vYg==", encoded); 52 | vector decoded = b64decode(encoded); 53 | ASSERT_TRUE(equal(begin(src), end(src), begin(decoded))); 54 | } 55 | { 56 | string src("fooba"); 57 | string encoded = b64encode(src.data(), src.size()); 58 | ASSERT_EQ("Zm9vYmE=", encoded); 59 | vector decoded = b64decode(encoded); 60 | ASSERT_TRUE(equal(begin(src), end(src), begin(decoded))); 61 | } 62 | { 63 | string src("foobar"); 64 | string encoded = b64encode(src.data(), src.size()); 65 | ASSERT_EQ("Zm9vYmFy", encoded); 66 | vector decoded = b64decode(encoded); 67 | ASSERT_TRUE(equal(begin(src), end(src), begin(decoded))); 68 | } 69 | } 70 | 71 | TEST(base64, rfc6455_16bytes) 72 | { 73 | { 74 | vector src{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}; 75 | string encoded = b64encode(src.data(), src.size()); 76 | ASSERT_EQ("AQIDBAUGBwgJCgsMDQ4PEA==", encoded); 77 | vector decoded = b64decode(encoded); 78 | ASSERT_TRUE(equal(begin(src), end(src), begin(decoded))); 79 | } 80 | { 81 | vector src{0xb3, 0x7a, 0x4f, 0x2c, 0xc0, 0x62, 0x4f, 0x16, 0x90, 0xf6, 0x46, 0x06, 0xcf, 0x38, 0x59, 0x45, 0xb2, 0xbe, 0xc4, 0xea}; 82 | string encoded = b64encode(src.data(), src.size()); 83 | ASSERT_EQ("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", encoded); 84 | vector decoded = b64decode(encoded); 85 | ASSERT_TRUE(equal(begin(src), end(src), begin(decoded))); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## LWSOCK 2 | 3 | * lwsock is a Library of WebSocket (RFC6455) for C++. 4 | * L: a Library, WSOCK: WebSocket 5 | * This API is like traditional BSD Socket API for TCP client / server. (connect, bind, listen, accept, send, recv) 6 | * IPv6 ready 7 | * not depend on other libraries. (futuer maybe depend on openssl or libressl etc) 8 | * You must control about multiple IO, thread etc by yourself. 9 | * lwsock doesn't management status (CONNECTING, OPEN, CLOSING, CLOSED etc). You must do it yourself. 10 | * Document: https://github.com/hfuj13/lwsock/wiki 11 | 12 | ## Require 13 | 14 | * C++14 or later 15 | * Linux 16 | * C++ Exception (-fexceptions etc.) 17 | * Little Endian 18 | 19 | ## Tester building 20 | 21 | ``` 22 | $ tar xf googletest-release-1.8.0.tar.gz 23 | $ cd test 24 | $ ln -s ../googletest-release-1.8.0/googletest/include ./ 25 | $ ln -s ../googletest-release-1.8.0/googletest/src ./ 26 | $ mkdir build 27 | $ cd build 28 | $ cmake ../ 29 | $ make 30 | ``` 31 | 32 | ## NOTE 33 | 34 | * not supported TLS yet. 35 | * Default supported opening handshake headers are Host, Upgrade, Connection, Sec-WebSocket-Key and Sec-WebSocket-Accept. 36 | * If you want to use other heaers, then use the following APIs: 37 | * send_req(const headers_t&) 38 | * send_res(const headers_t&) 39 | * send_req_manually(const handshake_t&) 40 | * send_res_manually(const handshake_t&) 41 | * If you use lwsock on Android NDK, then you should set -fexceptions and unset -Wexit-time-destructors Compiler options. 42 | 43 | ## For example 44 | 45 | ### server side: 46 | 47 | ``` 48 | void worker(lwsock::WebSocket&& nws) 49 | { 50 | auto hs = nws.recv_req(); // returned handshake data 51 | nws.send_res(); 52 | std::string msg = "d e f"; 53 | nws.send_msg_txt(msg); 54 | auto rcvd = nws.recv_msg_txt(); 55 | std::cout << rcvd << std::endl; 56 | nws.send_close(1000); 57 | } 58 | 59 | lwsock::WebSocket s(lwsock::WebSocket::Mode::SERVER); 60 | //s.ostream4log(cout); 61 | s.bind("ws://hostname:22000"); 62 | //s.bind("ws://0.0.0.0:22000"); // IPv4 any address 63 | //s.bind("ws://[::]:22000"); // IPv6 only any address 64 | //s.bind("ws://[::].0.0.0.0:22000"); // IPv4 and IPv6 any address 65 | s.listen(5); 66 | std::vector th_set; 67 | for (int i = 0; i < 3; ++i) { 68 | lwsock::WebSocket nws = s.accept(); // blocking, return new WebSocket object 69 | auto th = std::thread(worker, std::move(nws)); 70 | th_set.push_back(std::move(th)); 71 | } 72 | for (auto& th : th_set) { 73 | th.join(); 74 | } 75 | ``` 76 | 77 | ### client side: 78 | 79 | ``` 80 | lwsock::WebSocket c(lwsock::WebSocket::Mode::CLIENT); 81 | //c.ostream4log(cout); 82 | c.connect("ws://hostname:22000"); 83 | c.send_req(); 84 | auto hs = c.recv_res(); // returned handshake data 85 | std::string msg = "a b c"; 86 | c.send_msg_txt(msg); 87 | auto rcvd = ws.recv_msg_txt(); 88 | std::cout << rcvd << std::endl; 89 | ``` 90 | 91 | ## TODO 92 | 93 | * Readjust errors. 94 | * Organizing logs. 95 | * Organizing codes. 96 | * Revise sample. 97 | * Correspond to TLS. 98 | * Make files separable. 99 | -------------------------------------------------------------------------------- /test/is_numerichost_test.cc: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | #include "lwsock.hpp" 4 | 5 | using namespace std; 6 | using namespace lwsock; 7 | 8 | TEST(IS_NUMERICHOST, is_numerichost_ipv4) 9 | { 10 | { string host("0.0.0.0"); 11 | auto actual = is_numerichost(host); 12 | ASSERT_TRUE(actual); 13 | } 14 | { string host("192.168.0.1"); 15 | auto actual = is_numerichost(host); 16 | ASSERT_TRUE(actual); 17 | } 18 | } 19 | 20 | TEST(IS_NUMERICHOST, is_numerichost_ipv4ipv6) 21 | { 22 | { string host("::0.0.0.0"); 23 | auto actual = is_numerichost(host); 24 | ASSERT_TRUE(actual); 25 | } 26 | } 27 | 28 | TEST(IS_NUMERICHOST, is_numerichost_fqdn) 29 | { 30 | { string host("aa.bb.cc.dd"); 31 | auto actual = is_numerichost(host); 32 | ASSERT_FALSE(actual); 33 | } 34 | } 35 | 36 | TEST(IS_NUMERICHOST, is_numerichost_ipv6) 37 | { 38 | { string host("[1234:abcd:1234:1234:1234:1234:1234:1234]"); 39 | auto actual = is_numerichost(host); 40 | ASSERT_TRUE(actual); 41 | } 42 | { string host("1234:abcd:1234:1234:1234:1234:1234:1234"); 43 | auto actual = is_numerichost(host); 44 | ASSERT_TRUE(actual); 45 | } 46 | { string host("a::1"); 47 | auto actual = is_numerichost(host); 48 | ASSERT_TRUE(actual); 49 | } 50 | { string host("[a::1]"); 51 | auto actual = is_numerichost(host); 52 | ASSERT_TRUE(actual); 53 | } 54 | { string host("[::1]"); 55 | auto actual = is_numerichost(host); 56 | ASSERT_TRUE(actual); 57 | } 58 | { string host("::1"); 59 | auto actual = is_numerichost(host); 60 | ASSERT_TRUE(actual); 61 | } 62 | { string host("[::]"); 63 | auto actual = is_numerichost(host); 64 | ASSERT_TRUE(actual); 65 | } 66 | { string host("::"); 67 | auto actual = is_numerichost(host); 68 | ASSERT_TRUE(actual); 69 | } 70 | { string host("FF01:0:0:0:0:0:0:101"); 71 | auto actual = is_numerichost(host); 72 | ASSERT_TRUE(actual); 73 | } 74 | { string host("FF01::101"); 75 | auto actual = is_numerichost(host); 76 | ASSERT_TRUE(actual); 77 | } 78 | { string host("2001:DB8:0:0:8:800:200C:417A"); 79 | auto actual = is_numerichost(host); 80 | ASSERT_TRUE(actual); 81 | } 82 | { string host("2001:DB8::8:800:200C:417A"); 83 | auto actual = is_numerichost(host); 84 | ASSERT_TRUE(actual); 85 | } 86 | { string host("0:0:0:0:0:0:0:1"); 87 | auto actual = is_numerichost(host); 88 | ASSERT_TRUE(actual); 89 | } 90 | { string host("0:0:0:0:0:0:0:0"); 91 | auto actual = is_numerichost(host); 92 | ASSERT_TRUE(actual); 93 | } 94 | { string host("2001::aaaa:bbbb:cccc:1111"); 95 | auto actual = is_numerichost(host); 96 | ASSERT_TRUE(actual); 97 | } 98 | { string host("2001:0db8:0000:0000:1234:0000:0000:9abc"); 99 | auto actual = is_numerichost(host); 100 | ASSERT_TRUE(actual); 101 | } 102 | { string host("2001:db8::1234:0:0:9abc"); 103 | auto actual = is_numerichost(host); 104 | ASSERT_TRUE(actual); 105 | } 106 | { string host("2001:0db8:0000:0000:0000:0000:0000:9abc"); 107 | auto actual = is_numerichost(host); 108 | ASSERT_TRUE(actual); 109 | } 110 | { string host("2001:db8::9abc"); 111 | auto actual = is_numerichost(host); 112 | ASSERT_TRUE(actual); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /test/sha1_test.cc: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | #include "lwsock.hpp" 4 | 5 | using namespace std; 6 | using namespace lwsock; 7 | 8 | TEST(SHA1, rfc3174) 9 | { 10 | { 11 | string src("abc"); 12 | Sha1::Context_t ctx; 13 | Sha1::Input(ctx, reinterpret_cast(src.data()), src.size()); 14 | uint8_t actual[Sha1::SHA1_HASH_SIZE] = {0}; 15 | Sha1::Result(actual, sizeof actual, ctx); 16 | uint8_t expected[] = {0xA9, 0x99, 0x3E, 0x36, 0x47, 0x06, 0x81, 0x6A, 0xBA, 0x3E, 0x25, 0x71, 0x78, 0x50, 0xC2, 0x6C, 0x9C, 0xD0, 0xD8, 0x9D}; 17 | ASSERT_EQ(sizeof expected, sizeof actual); 18 | ASSERT_EQ(0, memcmp(expected, actual, sizeof actual)); 19 | } 20 | { 21 | string src("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); 22 | Sha1::Context_t ctx; 23 | Sha1::Input(ctx, reinterpret_cast(src.data()), src.size()); 24 | uint8_t actual[Sha1::SHA1_HASH_SIZE] = {0}; 25 | Sha1::Result(actual, sizeof actual, ctx); 26 | uint8_t expected[] = {0x84, 0x98, 0x3E, 0x44, 0x1C, 0x3B, 0xD2, 0x6E, 0xBA, 0xAE, 0x4A, 0xA1, 0xF9, 0x51, 0x29, 0xE5, 0xE5, 0x46, 0x70, 0xF1}; 27 | ASSERT_EQ(sizeof expected, sizeof actual); 28 | ASSERT_EQ(0, memcmp(expected, actual, sizeof actual)); 29 | } 30 | { 31 | string src("a"); 32 | Sha1::Context_t ctx; 33 | for (int i = 0; i < 1000000; ++i) { 34 | Sha1::Input(ctx, reinterpret_cast(src.data()), src.size()); 35 | } 36 | uint8_t actual[Sha1::SHA1_HASH_SIZE] = {0}; 37 | Sha1::Result(actual, sizeof actual, ctx); 38 | uint8_t expected[] = {0x34, 0xAA, 0x97, 0x3C, 0xD4, 0xC4, 0xDA, 0xA4, 0xF6, 0x1E, 0xEB, 0x2B, 0xDB, 0xAD, 0x27, 0x31, 0x65, 0x34, 0x01, 0x6F}; 39 | ASSERT_EQ(sizeof expected, sizeof actual); 40 | ASSERT_EQ(0, memcmp(expected, actual, sizeof actual)); 41 | } 42 | { 43 | string src("0123456701234567012345670123456701234567012345670123456701234567"); 44 | Sha1::Context_t ctx; 45 | for (int i = 0; i < 10; ++i) { 46 | Sha1::Input(ctx, reinterpret_cast(src.data()), src.size()); 47 | } 48 | uint8_t actual[Sha1::SHA1_HASH_SIZE] = {0}; 49 | Sha1::Result(actual, sizeof actual, ctx); 50 | uint8_t expected[] = {0xDE, 0xA3, 0x56, 0xA2, 0xCD, 0xDD, 0x90, 0xC7, 0xA7, 0xEC, 0xED, 0xC5, 0xEB, 0xB5, 0x63, 0x93, 0x4F, 0x46, 0x04, 0x52}; 51 | ASSERT_EQ(sizeof expected, sizeof actual); 52 | ASSERT_EQ(0, memcmp(expected, actual, sizeof actual)); 53 | } 54 | } 55 | TEST(SHA1, wikipedia) 56 | { 57 | { 58 | string src("The quick brown fox jumps over the lazy dog"); 59 | Sha1::Context_t ctx; 60 | Sha1::Input(ctx, reinterpret_cast(src.data()), src.size()); 61 | uint8_t actual[Sha1::SHA1_HASH_SIZE] = {0}; 62 | Sha1::Result(actual, sizeof actual, ctx); 63 | uint8_t expected[] = {0x2F, 0xD4, 0xE1, 0xC6, 0x7A, 0x2D, 0x28, 0xFC, 0xED, 0x84, 0x9E, 0xE1, 0xBB, 0x76, 0xE7, 0x39, 0x1B, 0x93, 0xEB, 0x12}; 64 | ASSERT_EQ(sizeof expected, sizeof actual); 65 | ASSERT_EQ(0, memcmp(expected, actual, sizeof actual)); 66 | } 67 | { 68 | string src("The quick brown fox jumps over the lazy cog"); 69 | Sha1::Context_t ctx; 70 | Sha1::Input(ctx, reinterpret_cast(src.data()), src.size()); 71 | uint8_t actual[Sha1::SHA1_HASH_SIZE] = {0}; 72 | Sha1::Result(actual, sizeof actual, ctx); 73 | uint8_t expected[] = {0xDE, 0x9F, 0x2C, 0x7F, 0xD2, 0x5E, 0x1B, 0x3A, 0xFA, 0xD3, 0xE8, 0x5A, 0x0B, 0xD1, 0x7D, 0x9B, 0x10, 0x0D, 0xB4, 0xB3}; 74 | ASSERT_EQ(sizeof expected, sizeof actual); 75 | ASSERT_EQ(0, memcmp(expected, actual, sizeof actual)); 76 | } 77 | { 78 | string src(""); 79 | Sha1::Context_t ctx; 80 | Sha1::Input(ctx, reinterpret_cast(src.data()), src.size()); 81 | uint8_t actual[Sha1::SHA1_HASH_SIZE] = {0}; 82 | Sha1::Result(actual, sizeof actual, ctx); 83 | uint8_t expected[] = {0xDA, 0x39, 0xA3, 0xEE, 0x5E, 0x6B, 0x4B, 0x0D, 0x32, 0x55, 0xBF, 0xEF, 0x95, 0x60, 0x18, 0x90, 0xAF, 0xD8, 0x07, 0x09}; 84 | ASSERT_EQ(sizeof expected, sizeof actual); 85 | ASSERT_EQ(0, memcmp(expected, actual, sizeof actual)); 86 | } 87 | } 88 | 89 | TEST(SHA1, rfc6455) 90 | { 91 | { 92 | string src("dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); 93 | Sha1::Context_t ctx; 94 | Sha1::Input(ctx, reinterpret_cast(src.data()), src.size()); 95 | uint8_t actual[Sha1::SHA1_HASH_SIZE] = {0}; 96 | Sha1::Result(actual, sizeof actual, ctx); 97 | uint8_t expected[] = {0xB3, 0x7A, 0x4F, 0x2C, 0xC0, 0x62, 0x4F, 0x16, 0x90, 0xF6, 0x46, 0x06, 0xCF, 0x38, 0x59, 0x45, 0xB2, 0xBE, 0xC4, 0xEA}; 98 | ASSERT_EQ(sizeof expected, sizeof actual); 99 | ASSERT_EQ(0, memcmp(expected, actual, sizeof actual)); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /test/uri_parse_test.cc: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | #include "lwsock.hpp" 4 | 5 | using namespace std; 6 | using namespace lwsock; 7 | 8 | TEST(URI_PARSE, split_hostport_pathquery) 9 | { 10 | { string uri("ws://[1234:abcd:1234:1234:1234:1234:1234:1234]:100/path?xx=zz"); 11 | auto actual = split_hostport_pathquery(uri); 12 | ASSERT_EQ("[1234:abcd:1234:1234:1234:1234:1234:1234]:100", actual.first); 13 | ASSERT_EQ("/path?xx=zz", actual.second); 14 | } 15 | { string uri("ws://[1234:abcd:1234:1234:1234:1234:1234:1234]:100"); 16 | auto actual = split_hostport_pathquery(uri); 17 | ASSERT_EQ("[1234:abcd:1234:1234:1234:1234:1234:1234]:100", actual.first); 18 | ASSERT_EQ("", actual.second); 19 | } 20 | { string uri("ws://[1234:abcd:1234:1234:1234:1234:1234:1234]:100/"); 21 | auto actual = split_hostport_pathquery(uri); 22 | ASSERT_EQ("[1234:abcd:1234:1234:1234:1234:1234:1234]:100", actual.first); 23 | ASSERT_EQ("/", actual.second); 24 | } 25 | { string uri("ws://[1234:abcd:1234:1234:1234:1234:1234:1234]/path?xx=zz"); 26 | auto actual = split_hostport_pathquery(uri); 27 | ASSERT_EQ("[1234:abcd:1234:1234:1234:1234:1234:1234]", actual.first); 28 | ASSERT_EQ("/path?xx=zz", actual.second); 29 | } 30 | { string uri("ws://[1234:abcd:1234:1234:1234:1234:1234:1234]"); 31 | auto actual = split_hostport_pathquery(uri); 32 | ASSERT_EQ("[1234:abcd:1234:1234:1234:1234:1234:1234]", actual.first); 33 | ASSERT_EQ("", actual.second); 34 | } 35 | { string uri("ws://[1234:abcd:1234:1234:1234:1234:1234:1234]/"); 36 | auto actual = split_hostport_pathquery(uri); 37 | ASSERT_EQ("[1234:abcd:1234:1234:1234:1234:1234:1234]", actual.first); 38 | ASSERT_EQ("/", actual.second); 39 | } 40 | { string uri("ws://1234:abcd:1234:1234:1234:1234:1234:1234"); 41 | auto actual = split_hostport_pathquery(uri); 42 | ASSERT_EQ("1234:abcd:1234:1234:1234:1234:1234:1234", actual.first); 43 | ASSERT_EQ("", actual.second); 44 | } 45 | { string uri("ws://1234:abcd:1234:1234:1234:1234:1234:1234/"); 46 | auto actual = split_hostport_pathquery(uri); 47 | ASSERT_EQ("1234:abcd:1234:1234:1234:1234:1234:1234", actual.first); 48 | ASSERT_EQ("/", actual.second); 49 | } 50 | 51 | { string uri("ws://aa.bb.cc.dd:100/path?xx=zz"); 52 | auto actual = split_hostport_pathquery(uri); 53 | ASSERT_EQ("aa.bb.cc.dd:100", actual.first); 54 | ASSERT_EQ("/path?xx=zz", actual.second); 55 | } 56 | { string uri("ws://aa.bb.cc.dd:100/"); 57 | auto actual = split_hostport_pathquery(uri); 58 | ASSERT_EQ("aa.bb.cc.dd:100", actual.first); 59 | ASSERT_EQ("/", actual.second); 60 | } 61 | { string uri("ws://aa.bb.cc.dd:100"); 62 | auto actual = split_hostport_pathquery(uri); 63 | ASSERT_EQ("aa.bb.cc.dd:100", actual.first); 64 | ASSERT_EQ("", actual.second); 65 | } 66 | { string uri("ws://aa.bb.cc.dd/path?xx=zz"); 67 | auto actual = split_hostport_pathquery(uri); 68 | ASSERT_EQ("aa.bb.cc.dd", actual.first); 69 | ASSERT_EQ("/path?xx=zz", actual.second); 70 | } 71 | { string uri("ws://aa.bb.cc.dd/"); 72 | auto actual = split_hostport_pathquery(uri); 73 | ASSERT_EQ("aa.bb.cc.dd", actual.first); 74 | ASSERT_EQ("/", actual.second); 75 | } 76 | { string uri("ws://aa.bb.cc.dd"); 77 | auto actual = split_hostport_pathquery(uri); 78 | ASSERT_EQ("aa.bb.cc.dd", actual.first); 79 | ASSERT_EQ("", actual.second); 80 | } 81 | 82 | { string uri("ws://192.168.0.1:100/path?xx=zz"); 83 | auto actual = split_hostport_pathquery(uri); 84 | ASSERT_EQ("192.168.0.1:100", actual.first); 85 | ASSERT_EQ("/path?xx=zz", actual.second); 86 | } 87 | { string uri("ws://192.168.0.1:100/"); 88 | auto actual = split_hostport_pathquery(uri); 89 | ASSERT_EQ("192.168.0.1:100", actual.first); 90 | ASSERT_EQ("/", actual.second); 91 | } 92 | { string uri("ws://192.168.0.1:100"); 93 | auto actual = split_hostport_pathquery(uri); 94 | ASSERT_EQ("192.168.0.1:100", actual.first); 95 | ASSERT_EQ("", actual.second); 96 | } 97 | { string uri("ws://192.168.0.1/path?xx=zz"); 98 | auto actual = split_hostport_pathquery(uri); 99 | ASSERT_EQ("192.168.0.1", actual.first); 100 | ASSERT_EQ("/path?xx=zz", actual.second); 101 | } 102 | { string uri("ws://192.168.0.1/"); 103 | auto actual = split_hostport_pathquery(uri); 104 | ASSERT_EQ("192.168.0.1", actual.first); 105 | ASSERT_EQ("/", actual.second); 106 | } 107 | { string uri("ws://192.168.0.1"); 108 | auto actual = split_hostport_pathquery(uri); 109 | ASSERT_EQ("192.168.0.1", actual.first); 110 | ASSERT_EQ("", actual.second); 111 | } 112 | 113 | { string uri("ws://[::]:100/path?xx=zz"); 114 | auto actual = split_hostport_pathquery(uri); 115 | ASSERT_EQ("[::]:100", actual.first); 116 | ASSERT_EQ("/path?xx=zz", actual.second); 117 | } 118 | { string uri("ws://[::]:100/"); 119 | auto actual = split_hostport_pathquery(uri); 120 | ASSERT_EQ("[::]:100", actual.first); 121 | ASSERT_EQ("/", actual.second); 122 | } 123 | { string uri("ws://[::]:100"); 124 | auto actual = split_hostport_pathquery(uri); 125 | ASSERT_EQ("[::]:100", actual.first); 126 | ASSERT_EQ("", actual.second); 127 | } 128 | { string uri("ws://[::]/path?xx=zz"); 129 | auto actual = split_hostport_pathquery(uri); 130 | ASSERT_EQ("[::]", actual.first); 131 | ASSERT_EQ("/path?xx=zz", actual.second); 132 | } 133 | { string uri("ws://[::]/"); 134 | auto actual = split_hostport_pathquery(uri); 135 | ASSERT_EQ("[::]", actual.first); 136 | ASSERT_EQ("/", actual.second); 137 | } 138 | { string uri("ws://[::]"); 139 | auto actual = split_hostport_pathquery(uri); 140 | ASSERT_EQ("[::]", actual.first); 141 | ASSERT_EQ("", actual.second); 142 | } 143 | { string uri("ws://::/"); 144 | auto actual = split_hostport_pathquery(uri); 145 | ASSERT_EQ("::", actual.first); 146 | ASSERT_EQ("/", actual.second); 147 | } 148 | { string uri("ws://::"); 149 | auto actual = split_hostport_pathquery(uri); 150 | ASSERT_EQ("::", actual.first); 151 | ASSERT_EQ("", actual.second); 152 | } 153 | 154 | { string uri("ws://[::].0.0.0.0:100/path?xx=zz"); 155 | auto actual = split_hostport_pathquery(uri); 156 | ASSERT_EQ("[::].0.0.0.0:100", actual.first); 157 | ASSERT_EQ("/path?xx=zz", actual.second); 158 | } 159 | { string uri("ws://[::].0.0.0.0:100/"); 160 | auto actual = split_hostport_pathquery(uri); 161 | ASSERT_EQ("[::].0.0.0.0:100", actual.first); 162 | ASSERT_EQ("/", actual.second); 163 | } 164 | { string uri("ws://[::].0.0.0.0:100"); 165 | auto actual = split_hostport_pathquery(uri); 166 | ASSERT_EQ("[::].0.0.0.0:100", actual.first); 167 | ASSERT_EQ("", actual.second); 168 | } 169 | { string uri("ws://[::].0.0.0.0/"); 170 | auto actual = split_hostport_pathquery(uri); 171 | ASSERT_EQ("[::].0.0.0.0", actual.first); 172 | ASSERT_EQ("/", actual.second); 173 | } 174 | { string uri("ws://[::].0.0.0.0"); 175 | auto actual = split_hostport_pathquery(uri); 176 | ASSERT_EQ("[::].0.0.0.0", actual.first); 177 | ASSERT_EQ("", actual.second); 178 | } 179 | 180 | { string uri("ws://::.0.0.0.0:100/path?xx=zz"); 181 | auto actual = split_hostport_pathquery(uri); 182 | ASSERT_EQ("::.0.0.0.0:100", actual.first); 183 | ASSERT_EQ("/path?xx=zz", actual.second); 184 | } 185 | { string uri("ws://::.0.0.0.0:100/"); 186 | auto actual = split_hostport_pathquery(uri); 187 | ASSERT_EQ("::.0.0.0.0:100", actual.first); 188 | ASSERT_EQ("/", actual.second); 189 | } 190 | { string uri("ws://::.0.0.0.0:100"); 191 | auto actual = split_hostport_pathquery(uri); 192 | ASSERT_EQ("::.0.0.0.0:100", actual.first); 193 | ASSERT_EQ("", actual.second); 194 | } 195 | { string uri("ws://::.0.0.0.0/path?xx=zz"); 196 | auto actual = split_hostport_pathquery(uri); 197 | ASSERT_EQ("::.0.0.0.0", actual.first); 198 | ASSERT_EQ("/path?xx=zz", actual.second); 199 | } 200 | { string uri("ws://::.0.0.0.0/"); 201 | auto actual = split_hostport_pathquery(uri); 202 | ASSERT_EQ("::.0.0.0.0", actual.first); 203 | ASSERT_EQ("/", actual.second); 204 | } 205 | { string uri("ws://::.0.0.0.0"); 206 | auto actual = split_hostport_pathquery(uri); 207 | ASSERT_EQ("::.0.0.0.0", actual.first); 208 | ASSERT_EQ("", actual.second); 209 | } 210 | { string uri("ws://0.0.0.0:100/path?xx=zz"); 211 | auto actual = split_hostport_pathquery(uri); 212 | ASSERT_EQ("0.0.0.0:100", actual.first); 213 | ASSERT_EQ("/path?xx=zz", actual.second); 214 | } 215 | { string uri("ws://0.0.0.0:100/"); 216 | auto actual = split_hostport_pathquery(uri); 217 | ASSERT_EQ("0.0.0.0:100", actual.first); 218 | ASSERT_EQ("/", actual.second); 219 | } 220 | { string uri("ws://0.0.0.0:100"); 221 | auto actual = split_hostport_pathquery(uri); 222 | ASSERT_EQ("0.0.0.0:100", actual.first); 223 | ASSERT_EQ("", actual.second); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /test/split_host_port_test.cc: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | #include "lwsock.hpp" 4 | 5 | using namespace std; 6 | using namespace lwsock; 7 | 8 | TEST(SPLIT_HOST_PORT, split_host_port) 9 | { 10 | { string host_port("127.0.0.1:100"); 11 | auto actual = split_host_port(host_port); 12 | ASSERT_EQ("127.0.0.1", actual.first); 13 | ASSERT_EQ("100", actual.second); 14 | } 15 | { string host_port("0.0.0.0:100"); 16 | auto actual = split_host_port(host_port); 17 | ASSERT_EQ("0.0.0.0", actual.first); 18 | ASSERT_EQ("100", actual.second); 19 | } 20 | { string host_port("127.0.0.1"); 21 | auto actual = split_host_port(host_port); 22 | ASSERT_EQ("127.0.0.1", actual.first); 23 | ASSERT_EQ("", actual.second); 24 | } 25 | { string host_port("0.0.0.0"); 26 | auto actual = split_host_port(host_port); 27 | ASSERT_EQ("0.0.0.0", actual.first); 28 | ASSERT_EQ("", actual.second); 29 | } 30 | 31 | { string host_port("[2001:0db8:1234:5678:90ab:cdef:0000:0000]:100"); 32 | auto actual = split_host_port(host_port); 33 | ASSERT_EQ("2001:0db8:1234:5678:90ab:cdef:0000:0000", actual.first); 34 | ASSERT_EQ("100", actual.second); 35 | } 36 | { string host_port("[2001:0db8:1234:5678:90ab:cdef:0000:0000]"); 37 | auto actual = split_host_port(host_port); 38 | ASSERT_EQ("2001:0db8:1234:5678:90ab:cdef:0000:0000", actual.first); 39 | ASSERT_EQ("", actual.second); 40 | } 41 | { string host_port("2001:0db8:1234:5678:90ab:cdef:0000:0000"); 42 | auto actual = split_host_port(host_port); 43 | ASSERT_EQ("2001:0db8:1234:5678:90ab:cdef:0000:0000", actual.first); 44 | ASSERT_EQ("", actual.second); 45 | } 46 | { string host_port("[2001:0db8:0000:0000:3456:0000:0000:0000]:100"); 47 | auto actual = split_host_port(host_port); 48 | ASSERT_EQ("2001:0db8:0000:0000:3456:0000:0000:0000", actual.first); 49 | ASSERT_EQ("100", actual.second); 50 | } 51 | { string host_port("[2001:0db8:0000:0000:3456:0000:0000:0000]"); 52 | auto actual = split_host_port(host_port); 53 | ASSERT_EQ("2001:0db8:0000:0000:3456:0000:0000:0000", actual.first); 54 | ASSERT_EQ("", actual.second); 55 | } 56 | { string host_port("2001:0db8:0000:0000:3456:0000:0000:0000"); 57 | auto actual = split_host_port(host_port); 58 | ASSERT_EQ("2001:0db8:0000:0000:3456:0000:0000:0000", actual.first); 59 | ASSERT_EQ("", actual.second); 60 | } 61 | { string host_port("[2001:0db8:0000:0000:3456::]:100"); 62 | auto actual = split_host_port(host_port); 63 | ASSERT_EQ("2001:0db8:0000:0000:3456::", actual.first); 64 | ASSERT_EQ("100", actual.second); 65 | } 66 | { string host_port("[2001:0db8:0000:0000:3456::]"); 67 | auto actual = split_host_port(host_port); 68 | ASSERT_EQ("2001:0db8:0000:0000:3456::", actual.first); 69 | ASSERT_EQ("", actual.second); 70 | } 71 | { string host_port("2001:0db8:0000:0000:3456::"); 72 | auto actual = split_host_port(host_port); 73 | ASSERT_EQ("2001:0db8:0000:0000:3456::", actual.first); 74 | ASSERT_EQ("", actual.second); 75 | } 76 | { string host_port("[2001:0db8::3456:0000:0000:0000]:100"); 77 | auto actual = split_host_port(host_port); 78 | ASSERT_EQ("2001:0db8::3456:0000:0000:0000", actual.first); 79 | ASSERT_EQ("100", actual.second); 80 | } 81 | { string host_port("[2001:0db8::3456:0000:0000:0000]"); 82 | auto actual = split_host_port(host_port); 83 | ASSERT_EQ("2001:0db8::3456:0000:0000:0000", actual.first); 84 | ASSERT_EQ("", actual.second); 85 | } 86 | { string host_port("2001:0db8::3456:0000:0000:0000"); 87 | auto actual = split_host_port(host_port); 88 | ASSERT_EQ("2001:0db8::3456:0000:0000:0000", actual.first); 89 | ASSERT_EQ("", actual.second); 90 | } 91 | { string host_port("[2001:0db8:0000:0000:3456::]:100"); 92 | auto actual = split_host_port(host_port); 93 | ASSERT_EQ("2001:0db8:0000:0000:3456::", actual.first); 94 | ASSERT_EQ("100", actual.second); 95 | } 96 | { string host_port("[2001:0db8:0000:0000:3456::]"); 97 | auto actual = split_host_port(host_port); 98 | ASSERT_EQ("2001:0db8:0000:0000:3456::", actual.first); 99 | ASSERT_EQ("", actual.second); 100 | } 101 | { string host_port("2001:0db8:0000:0000:3456::"); 102 | auto actual = split_host_port(host_port); 103 | ASSERT_EQ("2001:0db8:0000:0000:3456::", actual.first); 104 | ASSERT_EQ("", actual.second); 105 | } 106 | { string host_port("[2001:db8:0:0:3456::]:100"); 107 | auto actual = split_host_port(host_port); 108 | ASSERT_EQ("2001:db8:0:0:3456::", actual.first); 109 | ASSERT_EQ("100", actual.second); 110 | } 111 | { string host_port("[2001:db8:0:0:3456::]"); 112 | auto actual = split_host_port(host_port); 113 | ASSERT_EQ("2001:db8:0:0:3456::", actual.first); 114 | ASSERT_EQ("", actual.second); 115 | } 116 | { string host_port("2001:db8:0:0:3456::"); 117 | auto actual = split_host_port(host_port); 118 | ASSERT_EQ("2001:db8:0:0:3456::", actual.first); 119 | ASSERT_EQ("", actual.second); 120 | } 121 | { string host_port("[2001:db8::1]:100"); 122 | auto actual = split_host_port(host_port); 123 | ASSERT_EQ("2001:db8::1", actual.first); 124 | ASSERT_EQ("100", actual.second); 125 | } 126 | { string host_port("[2001:db8::1]"); 127 | auto actual = split_host_port(host_port); 128 | ASSERT_EQ("2001:db8::1", actual.first); 129 | ASSERT_EQ("", actual.second); 130 | } 131 | { string host_port("2001:db8::1"); 132 | auto actual = split_host_port(host_port); 133 | ASSERT_EQ("2001:db8::1", actual.first); 134 | ASSERT_EQ("", actual.second); 135 | } 136 | { string host_port("[2001:db8::2:1]:100"); 137 | auto actual = split_host_port(host_port); 138 | ASSERT_EQ("2001:db8::2:1", actual.first); 139 | ASSERT_EQ("100", actual.second); 140 | } 141 | { string host_port("[2001:db8::2:1]"); 142 | auto actual = split_host_port(host_port); 143 | ASSERT_EQ("2001:db8::2:1", actual.first); 144 | ASSERT_EQ("", actual.second); 145 | } 146 | { string host_port("2001:db8::2:1"); 147 | auto actual = split_host_port(host_port); 148 | ASSERT_EQ("2001:db8::2:1", actual.first); 149 | ASSERT_EQ("", actual.second); 150 | } 151 | { string host_port("[2001:db8:1:1:1::]:100"); 152 | auto actual = split_host_port(host_port); 153 | ASSERT_EQ("2001:db8:1:1:1::", actual.first); 154 | ASSERT_EQ("100", actual.second); 155 | } 156 | { string host_port("[2001:db8:1:1:1::]"); 157 | auto actual = split_host_port(host_port); 158 | ASSERT_EQ("2001:db8:1:1:1::", actual.first); 159 | ASSERT_EQ("", actual.second); 160 | } 161 | { string host_port("2001:db8:1:1:1::"); 162 | auto actual = split_host_port(host_port); 163 | ASSERT_EQ("2001:db8:1:1:1::", actual.first); 164 | ASSERT_EQ("", actual.second); 165 | } 166 | { string host_port("[2001:db8:0:1:1:1:1:1]:100"); 167 | auto actual = split_host_port(host_port); 168 | ASSERT_EQ("2001:db8:0:1:1:1:1:1", actual.first); 169 | ASSERT_EQ("100", actual.second); 170 | } 171 | { string host_port("[2001:db8:0:1:1:1:1:1]"); 172 | auto actual = split_host_port(host_port); 173 | ASSERT_EQ("2001:db8:0:1:1:1:1:1", actual.first); 174 | ASSERT_EQ("", actual.second); 175 | } 176 | { string host_port("2001:db8:0:1:1:1:1:1"); 177 | auto actual = split_host_port(host_port); 178 | ASSERT_EQ("2001:db8:0:1:1:1:1:1", actual.first); 179 | ASSERT_EQ("", actual.second); 180 | } 181 | { string host_port("[2001:0:0:1::1]:100"); 182 | auto actual = split_host_port(host_port); 183 | ASSERT_EQ("2001:0:0:1::1", actual.first); 184 | ASSERT_EQ("100", actual.second); 185 | } 186 | { string host_port("[2001:0:0:1::1]"); 187 | auto actual = split_host_port(host_port); 188 | ASSERT_EQ("2001:0:0:1::1", actual.first); 189 | ASSERT_EQ("", actual.second); 190 | } 191 | { string host_port("2001:0:0:1::1"); 192 | auto actual = split_host_port(host_port); 193 | ASSERT_EQ("2001:0:0:1::1", actual.first); 194 | ASSERT_EQ("", actual.second); 195 | } 196 | { string host_port("[2001:db8::1:0:0:1]:100"); 197 | auto actual = split_host_port(host_port); 198 | ASSERT_EQ("2001:db8::1:0:0:1", actual.first); 199 | ASSERT_EQ("100", actual.second); 200 | } 201 | { string host_port("[2001:db8::1:0:0:1]"); 202 | auto actual = split_host_port(host_port); 203 | ASSERT_EQ("2001:db8::1:0:0:1", actual.first); 204 | ASSERT_EQ("", actual.second); 205 | } 206 | { string host_port("2001:db8::1:0:0:1"); 207 | auto actual = split_host_port(host_port); 208 | ASSERT_EQ("2001:db8::1:0:0:1", actual.first); 209 | ASSERT_EQ("", actual.second); 210 | } 211 | { string host_port("[2001:db8::abcd:ef12]:100"); 212 | auto actual = split_host_port(host_port); 213 | ASSERT_EQ("2001:db8::abcd:ef12", actual.first); 214 | ASSERT_EQ("100", actual.second); 215 | } 216 | { string host_port("[2001:db8::abcd:ef12]"); 217 | auto actual = split_host_port(host_port); 218 | ASSERT_EQ("2001:db8::abcd:ef12", actual.first); 219 | ASSERT_EQ("", actual.second); 220 | } 221 | { string host_port("2001:db8::abcd:ef12"); 222 | auto actual = split_host_port(host_port); 223 | ASSERT_EQ("2001:db8::abcd:ef12", actual.first); 224 | ASSERT_EQ("", actual.second); 225 | } 226 | 227 | { string host_port("[::0]:0.0.0.0:100"); 228 | auto actual = split_host_port(host_port); 229 | ASSERT_EQ("", actual.first); 230 | ASSERT_EQ("100", actual.second); 231 | } 232 | { string host_port("[::0]:0.0.0.0"); 233 | auto actual = split_host_port(host_port); 234 | ASSERT_EQ("", actual.first); 235 | ASSERT_EQ("", actual.second); 236 | } 237 | { string host_port("::0.0.0.0"); 238 | auto actual = split_host_port(host_port); 239 | ASSERT_EQ("::0.0.0.0", actual.first); 240 | ASSERT_EQ("", actual.second); 241 | } 242 | { string host_port("aa.bb.cc.dd:100"); 243 | auto actual = split_host_port(host_port); 244 | ASSERT_EQ("aa.bb.cc.dd", actual.first); 245 | ASSERT_EQ("100", actual.second); 246 | } 247 | { string host_port("aa.bb.cc.dd"); 248 | auto actual = split_host_port(host_port); 249 | ASSERT_EQ("aa.bb.cc.dd", actual.first); 250 | ASSERT_EQ("", actual.second); 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /lwsock.hpp: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (C) 2016 hfuj13@gmail.com 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 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | 56 | namespace lwsock { 57 | 58 | // var string: variable name 59 | // func_name string: function name 60 | // param string: parameter 61 | #define DECLARE_CALLEE(var, func_name, param) \ 62 | std::ostringstream var; \ 63 | { var << func_name << param; } 64 | 65 | 66 | constexpr char Version[] = "v1.4.1"; 67 | constexpr char B64chs[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 68 | constexpr char GUID[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 69 | constexpr char EOL[] = "\r\n"; // end of line 70 | constexpr uint8_t Magic[] = {49, 49, 104, 102, 117, 106, 49, 51}; 71 | 72 | /// WebSocket(RFC6455) Opcode enum 73 | enum class Opcode { 74 | CONTINUE = 0x0, 75 | TEXT = 0x1, 76 | BINARY = 0x2, 77 | CLOSE = 0x8, 78 | PING = 0x9, 79 | PONG = 0xa, 80 | }; 81 | 82 | /// @brief lwsock error code 83 | /// 84 | /// negative value. (positive value is system error) 85 | /// @todo readjust errorcode 86 | enum class LwsockErrc: int32_t { 87 | NO_ERROR = 0, 88 | COULD_NOT_OPEN_AVAILABLE_SOCKET = -102, 89 | SOCKET_CLOSED = -103, 90 | INVALID_HANDSHAKE = -104, 91 | FRAME_ERROR = -106, 92 | INVALID_PARAM = -107, 93 | INVALID_AF = -108, 94 | INVALID_MODE = -109, 95 | BAD_MESSAGE = -110, 96 | TIMED_OUT = -111, 97 | }; 98 | 99 | enum class LogLevel: int32_t { 100 | #if 0 101 | DEBUG = 1, 102 | DEBUG, 103 | INFO, 104 | WARNING, 105 | ERROR, 106 | #else 107 | UNDER_LVL = 0, 108 | VERBOSE, 109 | DEBUG, 110 | INFO, 111 | WARNING, 112 | ERROR, 113 | SILENT, 114 | OVER_LVL 115 | #endif 116 | }; 117 | 118 | /// @brief get emum class value as int 119 | /// 120 | /// @param [in] value 121 | /// @retval int value 122 | template auto as_int(const T value) -> typename std::underlying_type::type 123 | { 124 | return static_cast::type>(value); 125 | } 126 | 127 | /// @brief transform the adress family to string 128 | /// 129 | /// @pram [in] af: AF_INET, AF_INET6, AF_UNSPEC 130 | /// @retval string 131 | inline std::string af2str(int af) 132 | { 133 | std::string str; 134 | switch (af) { 135 | case AF_INET: 136 | str = "AF_INET"; 137 | break; 138 | case AF_INET6: 139 | str = "AF_INET6"; 140 | break; 141 | case AF_UNSPEC: 142 | str = "AF_UNSPEC"; 143 | break; 144 | default: 145 | str = std::to_string(af); 146 | break; 147 | } 148 | return str; 149 | } 150 | 151 | /// @brief get now timestamp. UTC only yet 152 | /// 153 | /// @param [in] parenthesis: ture: output with '[' and ']'
154 | // false : output with raw 155 | /// @retval string transformed timestamp (e.g. [2016-12-11T13:24:13.058] or 2016-12-11T13:24:13.058 etc.). the output format is like the ISO8601 (that is it include milliseconds) 156 | /// @todo correspond TIME ZONE 157 | inline std::string now_timestamp(bool parenthesis) 158 | { 159 | std::chrono::time_point tp = std::chrono::system_clock::now(); 160 | //std::chrono::nanoseconds nsec_since_epoch = std::chrono::duration_cast(tp.time_since_epoch()); 161 | std::chrono::milliseconds msec_since_epoch = std::chrono::duration_cast(tp.time_since_epoch()); 162 | //std::chrono::milliseconds msec_since_epoch = std::chrono::duration_cast(nsec_since_epoch); 163 | std::chrono::seconds sec = std::chrono::duration_cast(msec_since_epoch); 164 | 165 | std::time_t tt = sec.count(); 166 | std::size_t msec = msec_since_epoch.count() % 1000; 167 | //std::size_t nsec = nsec_since_epoch.count() % (1000 * 1000); 168 | 169 | struct tm stm = {0}; 170 | tzset(); 171 | gmtime_r(&tt, &stm); 172 | 173 | std::ostringstream oss; 174 | 175 | oss << (stm.tm_year+1900) << '-' 176 | << std::setw(2) << std::setfill('0') << (stm.tm_mon+1) << '-' 177 | << std::setw(2) << std::setfill('0') << stm.tm_mday 178 | << 'T' 179 | << std::setw(2) << std::setfill('0') << stm.tm_hour 180 | << ':' 181 | << std::setw(2) << std::setfill('0') << stm.tm_min 182 | << ':' 183 | << std::setw(2) << std::setfill('0') << stm.tm_sec 184 | << '.' << std::setw(3) << std::setfill('0') << msec 185 | //<< std::setw(3) << std::setfill('0') << nsec 186 | ; 187 | 188 | std::string str; 189 | if (parenthesis) { 190 | str += "[" + oss.str() + "]"; 191 | } 192 | else { 193 | str += oss.str(); 194 | } 195 | 196 | return str; 197 | } 198 | 199 | /// @brief get timestamp by cloed parenthesis 200 | /// 201 | /// @retval string transformed timestamp (e.g. [2016-12-11T13:24:13.058]) 202 | inline std::string now_timestamp() 203 | { 204 | return now_timestamp(true); 205 | } 206 | 207 | /// @brief trim specified characters 208 | /// 209 | /// @param [in] str: string 210 | /// @param [in] charset: character set (specified by a string) what you want to delete 211 | /// @retval trimed string 212 | inline std::string trim(const std::string& str, const std::string& charset) 213 | { 214 | std::string::size_type p0 = str.find_first_not_of(charset); 215 | if (p0 == std::string::npos) { 216 | p0 = 0; 217 | } 218 | std::string::size_type p1 = str.find_last_not_of(charset); 219 | 220 | std::string result = str.substr(p0, p1 - p0 + 1); 221 | return result; 222 | } 223 | 224 | /// @brief trim white spaces 225 | /// 226 | /// @param [in] str: string 227 | /// @retval trimed string 228 | inline std::string trim(const std::string& str) 229 | { 230 | return trim(str, " \t\v\r\n"); 231 | } 232 | 233 | /// @brief transform the string to the lowercase string 234 | /// 235 | /// @param [in] str: string 236 | /// @retval lower case string 237 | inline std::string str2lower(const std::string& str) 238 | { 239 | std::string result; 240 | std::transform(std::begin(str), std::end(str), std::back_inserter(result), ::tolower); 241 | return result; 242 | } 243 | 244 | // a logger 245 | class alog final { 246 | public: 247 | class scoped final { 248 | public: 249 | scoped() = delete; 250 | scoped(const scoped&) = default; 251 | scoped(scoped&&) = default; 252 | 253 | scoped(const std::string& str) 254 | : scoped(LogLevel::DEBUG, str) {} 255 | scoped(LogLevel loglevel, const std::string& str) 256 | : loglevel_(loglevel), oss_(str) 257 | { 258 | log_(loglevel_) << "[[[[ " << oss_.str() << std::endl; 259 | } 260 | ~scoped() 261 | { 262 | log_(loglevel_) << "]]]] " << oss_.str() << std::endl; 263 | } 264 | std::string str() 265 | { 266 | return oss_.str(); 267 | } 268 | scoped& clear() 269 | { 270 | oss_.str(""); 271 | return *this; 272 | } 273 | template friend std::ostream& operator<<(scoped& slog, const T& rhs); 274 | 275 | private: 276 | alog& log_ = alog::get_instance(); 277 | LogLevel loglevel_ = LogLevel::DEBUG; 278 | std::ostringstream oss_; 279 | }; 280 | 281 | static alog& get_instance() 282 | { 283 | return get_instance(nullptr); 284 | } 285 | static alog& get_instance(std::ostream& ost) 286 | { 287 | return get_instance(&ost); 288 | } 289 | 290 | bool operator==(const alog& rhs) const 291 | { 292 | return &rhs == this || (rhs.level_ == level_ && rhs.ost_ == ost_); 293 | } 294 | bool operator!=(const alog& rhs) const 295 | { 296 | return (rhs.level_ != level_ || rhs.ost_ != ost_); 297 | } 298 | template static std::string format(const std::string& fmt, Args... args) 299 | { 300 | std::string buff; // Used only for dynamic area control. 301 | int ret = snprintf(&buff[0], buff.capacity(), fmt.c_str(), args...); 302 | if (ret >= buff.capacity()) { 303 | buff.reserve(ret+1); 304 | ret = snprintf(&buff[0], buff.capacity(), fmt.c_str(), args...); 305 | } 306 | else if (ret < 0) { 307 | abort(); 308 | } 309 | std::string str(buff.c_str()); 310 | return str; 311 | 312 | } 313 | 314 | // for verbose 315 | std::ostream& v() 316 | { 317 | return (*this)(LogLevel::VERBOSE) << "[V]"; 318 | } 319 | template std::ostream& v(const std::string& fmt, Args... args) 320 | { 321 | return v() << format(fmt, args...) << std::flush; 322 | } 323 | 324 | // for debug 325 | std::ostream& d() 326 | { 327 | return (*this)(LogLevel::DEBUG) << "[D]"; 328 | } 329 | template std::ostream& d(const std::string& fmt, Args... args) 330 | { 331 | return d() << format(fmt, args...) << std::flush; 332 | } 333 | 334 | // for info 335 | std::ostream& i() 336 | { 337 | return (*this)(LogLevel::INFO) << "[I]"; 338 | } 339 | template std::ostream& i(const std::string& fmt, Args... args) 340 | { 341 | return i() << format(fmt, args...) << std::flush; 342 | } 343 | 344 | // for warning 345 | std::ostream& w() 346 | { 347 | return (*this)(LogLevel::WARNING) << "[W]"; 348 | } 349 | template std::ostream& w(const std::string& fmt, Args... args) 350 | { 351 | return w() << format(fmt, args...) << std::flush; 352 | } 353 | 354 | // for error 355 | std::ostream& e() 356 | { 357 | return (*this)(LogLevel::ERROR) << "[E]"; 358 | } 359 | template std::ostream& e(const std::string& fmt, Args... args) 360 | { 361 | return e() << format(fmt, args...) << std::flush; 362 | } 363 | 364 | // ログレベル設定が何であっても強制的に出力する 365 | std::ostream& force() 366 | { 367 | return (*this)(); 368 | } 369 | template std::ostream& force(const std::string& fmt, Args... args) 370 | { 371 | return force() << format(fmt, args...) << std::flush; 372 | } 373 | 374 | template friend std::ostream& operator<<(alog& log, const T& rhs); 375 | 376 | alog& level(LogLevel lvl) 377 | { 378 | assert(LogLevel::UNDER_LVL < lvl && lvl < LogLevel::OVER_LVL); 379 | if (lvl <= LogLevel::UNDER_LVL || LogLevel::OVER_LVL <= lvl) { 380 | abort(); 381 | } 382 | 383 | level_ = lvl; 384 | return *this; 385 | } 386 | LogLevel level() 387 | { 388 | return level_; 389 | } 390 | 391 | alog& ostream(std::ostream& ost) 392 | { 393 | ost_ = &ost; 394 | return *this; 395 | } 396 | 397 | private: 398 | alog() = default; 399 | alog& operator=(const alog&) = delete; 400 | 401 | std::ostream& output() 402 | { 403 | return (*ost_) << now_timestamp() << "[thd:" << std::this_thread::get_id() << "] "; 404 | } 405 | 406 | std::ostream& operator()() 407 | { 408 | return output(); 409 | } 410 | std::ostream& operator()(LogLevel lvl) 411 | { 412 | return lvl >= level_ ? output() : null_ost_; 413 | } 414 | 415 | static alog& get_instance(std::ostream* ost) 416 | { 417 | static alog log; 418 | if (ost != nullptr) { 419 | log.ost_ = ost; 420 | } 421 | return log; 422 | } 423 | 424 | std::ostream null_ost_{nullptr}; // /dev/null like ostream 425 | LogLevel level_ = LogLevel::SILENT; 426 | std::ostream* ost_ = &null_ost_; 427 | }; 428 | 429 | template std::ostream& operator<<(alog& log, const T& rhs) 430 | { 431 | return (*log.ost_) << rhs; 432 | } 433 | 434 | template std::ostream& operator<<(alog::scoped& slog, const T& rhs) 435 | { 436 | return slog.oss_ << rhs; 437 | } 438 | 439 | /// @brief Error class 440 | /// 441 | /// this class contains errno(3), getaddrinfo(3)'s error, regcom(3)'s error or lwsock Errcode. 442 | class Error final { 443 | public: 444 | Error() = delete; 445 | Error(const Error&) = default; 446 | Error(Error&&) = default; 447 | Error(int errcode, uint32_t line, const std::string& what_arg) 448 | : errcode_(errcode), line_(line) 449 | { 450 | std::ostringstream oss; 451 | if (line_ > 0) { 452 | oss << "line:" << line_ << ". " << "errcode=" << errcode_ << ". " << what_arg; 453 | what_ = oss.str(); 454 | } 455 | else { 456 | oss << "errcode=" << errcode_ << ". " << what_arg; 457 | what_ = oss.str(); 458 | } 459 | alog& log = alog::get_instance(); 460 | log.e() << what_ << std::endl; 461 | } 462 | Error(int errcode, uint32_t line) 463 | : Error(errcode, line, "") 464 | { } 465 | Error(int errcode, const std::string& what_arg) 466 | : Error(errcode, 0, what_arg) 467 | { } 468 | explicit Error(int errcode) 469 | : Error(errcode, 0, "") 470 | { } 471 | 472 | ~Error() = default; 473 | 474 | /// @brief get error code 475 | /// 476 | /// @retval error code. errno(3), getaddrinfo(3), regcomp(3), lwsock Errcode. 477 | int code() 478 | { 479 | return errcode_; 480 | } 481 | 482 | void prefix(const std::string& prefix) 483 | { 484 | what_ = prefix + what_; 485 | } 486 | 487 | /// @brief get reason string 488 | /// 489 | /// @retval reason string 490 | const char* what() const 491 | { 492 | return what_.c_str(); 493 | } 494 | private: 495 | int errcode_ = 0; 496 | uint32_t line_ = 0; // the line when the error occurred 497 | std::string what_; 498 | }; 499 | 500 | class CRegexException final: public std::exception { 501 | public: 502 | CRegexException() = delete; 503 | CRegexException(const CRegexException&) = default; 504 | CRegexException(CRegexException&&) = default; 505 | explicit CRegexException(const Error& error) 506 | : error_(error) 507 | { } 508 | 509 | ~CRegexException() = default; 510 | 511 | const char* what() const noexcept override 512 | { 513 | error_.prefix("CRegexException: "); 514 | return error_.what(); 515 | } 516 | 517 | /// @brief get exception code (error code etc.) 518 | /// 519 | /// @retavl error code 520 | virtual int code() 521 | { 522 | return error_.code(); 523 | } 524 | private: 525 | mutable Error error_; 526 | }; 527 | 528 | class GetaddrinfoException final: public std::exception { 529 | public: 530 | GetaddrinfoException() = delete; 531 | GetaddrinfoException(const GetaddrinfoException&) = default; 532 | GetaddrinfoException(GetaddrinfoException&&) = default; 533 | explicit GetaddrinfoException(const Error& error) 534 | : error_(error) 535 | { } 536 | 537 | ~GetaddrinfoException() = default; 538 | 539 | const char* what() const noexcept override 540 | { 541 | error_.prefix("GetaddrinfoException: "); 542 | return error_.what(); 543 | } 544 | 545 | /// @brief get exception code (error code etc.) 546 | /// 547 | /// @retavl error code 548 | virtual int code() 549 | { 550 | return error_.code(); 551 | } 552 | private: 553 | mutable Error error_; 554 | }; 555 | 556 | /// @brief libray error exception class 557 | class LwsockException final: public std::exception { 558 | public: 559 | LwsockException() = delete; 560 | LwsockException(const LwsockException&) = default; 561 | LwsockException(LwsockException&&) = default; 562 | explicit LwsockException(const Error& error) 563 | : error_(error) 564 | { } 565 | 566 | ~LwsockException() = default; 567 | 568 | const char* what() const noexcept override 569 | { 570 | error_.prefix("LwsockException: "); 571 | return error_.what(); 572 | } 573 | 574 | /// @brief get exception code (error code etc.) 575 | /// 576 | /// @retavl error code 577 | virtual int code() 578 | { 579 | return error_.code(); 580 | } 581 | private: 582 | mutable Error error_; 583 | }; 584 | 585 | /// @brief system_error exception class. this is a wrapper class because i want to output logs. 586 | /// 587 | class SystemErrorException final: public std::exception { 588 | public: 589 | SystemErrorException() = delete; 590 | SystemErrorException(const SystemErrorException&) = default; 591 | SystemErrorException(SystemErrorException&&) = default; 592 | explicit SystemErrorException(const Error& error) 593 | : error_(error) 594 | { } 595 | 596 | ~SystemErrorException() = default; 597 | 598 | const char* what() const noexcept override 599 | { 600 | error_.prefix("SystemErrorException: "); 601 | return error_.what(); 602 | } 603 | 604 | /// @brief get exception code (error code etc.) 605 | /// 606 | /// @retavl error code 607 | virtual int code() 608 | { 609 | return error_.code(); 610 | } 611 | private: 612 | mutable Error error_; 613 | }; 614 | 615 | /// @brief regex(3) wrapper 616 | /// 617 | /// regex(3) wrapper class. because the std::regex, depending on the version of android it does not work properly. 618 | class CRegex final { 619 | public: 620 | CRegex() = delete; 621 | 622 | /// @exception CRegexException 623 | CRegex(const std::string& re, size_t nmatch) 624 | : nmatch_(nmatch) 625 | { 626 | alog& log = alog::get_instance(); 627 | //log(LogLevel::DEBUG) << "CRegex(re=\"" << re << "\", nmatch=" << nmatch << ')' << std::endl; 628 | log.d() << "CRegex(re=\"" << re << "\", nmatch=" << nmatch << ')' << std::endl; 629 | 630 | int err = regcomp(®buff_, re.c_str(), REG_EXTENDED); 631 | if (err != 0) { 632 | std::ostringstream oss; 633 | char errbuf[256] = {0}; 634 | regerror(err, ®buff_, errbuf, sizeof errbuf); 635 | oss << "CRegex(re=\"" << re << "\", nmatch=" << nmatch << ") " << errbuf; 636 | throw CRegexException(Error(err, oss.str())); 637 | } 638 | } 639 | ~CRegex() 640 | { 641 | regfree(®buff_); 642 | } 643 | 644 | /// @brief execute regex 645 | /// 646 | /// @param [in] src: string for regex 647 | /// @retval matched string set. if empty then no matched 648 | std::vector exec(const std::string& src) 649 | { 650 | std::vector matched; 651 | std::vector match(nmatch_, {-1, -1}); 652 | int err = regexec(®buff_, src.c_str(), match.size(), &match[0], 0); 653 | if (err != 0) { 654 | return matched; 655 | } 656 | for (auto& elm : match) { 657 | int start = elm.rm_so; 658 | int end = elm.rm_eo; 659 | if (start == -1 || end == -1) { 660 | continue; 661 | } 662 | std::string str(std::begin(src)+start, std::begin(src)+end); 663 | matched.push_back(str); 664 | } 665 | return matched; 666 | } 667 | private: 668 | regex_t regbuff_{}; 669 | size_t nmatch_ = 0; 670 | }; 671 | 672 | /// @brief base64 encoder 673 | /// 674 | /// This referred the https://opensource.apple.com/source/QuickTimeStreamingServer/QuickTimeStreamingServer-452/CommonUtilitiesLib/base64.c 675 | /// @param [in] src_data: array or vector 676 | /// @param [in] src_data_sz: *src_data size. bytes 677 | /// @retval base64 encoded string 678 | inline std::string b64encode(const void* src_data, int src_data_sz) 679 | { 680 | assert(src_data_sz >= 0); 681 | std::string dst; 682 | const uint8_t* src = static_cast(src_data); 683 | int idx = 0; 684 | 685 | for (; idx < src_data_sz - 2; idx += 3) { 686 | dst += B64chs[(src[idx] >> 2) & 0x3F]; 687 | dst += B64chs[((src[idx] & 0x3) << 4) | ((src[idx + 1] & 0xF0) >> 4)]; 688 | dst += B64chs[((src[idx + 1] & 0xF) << 2) | ((src[idx + 2] & 0xC0) >> 6)]; 689 | dst += B64chs[src[idx + 2] & 0x3F]; 690 | } 691 | if (idx < src_data_sz) { 692 | dst += B64chs[(src[idx] >> 2) & 0x3F]; 693 | if (idx == (src_data_sz - 1)) { 694 | dst += B64chs[((src[idx] & 0x3) << 4)]; 695 | dst += '='; 696 | } 697 | else { 698 | dst += B64chs[((src[idx] & 0x3) << 4) | ((src[idx + 1] & 0xF0) >> 4)]; 699 | dst += B64chs[((src[idx + 1] & 0xF) << 2)]; 700 | } 701 | dst += '='; 702 | } 703 | 704 | return dst; 705 | } 706 | 707 | /// @brief base64 decoder 708 | /// 709 | /// @param [in] src: base64 encoded string 710 | /// @retval base64 decoded data 711 | /// @exception LwsockException 712 | inline std::vector b64decode(const std::string& src) 713 | { 714 | DECLARE_CALLEE(callee, __func__, "(src=\"" << src << "\")"); 715 | 716 | if (src.size() % 4 != 0) { 717 | int err = as_int(LwsockErrc::INVALID_PARAM); 718 | std::ostringstream oss; 719 | oss << callee.str() << " src.size()=" << src.size() << " is illegal."; 720 | throw LwsockException(Error(err, oss.str())); 721 | } 722 | constexpr int BLOCK_SZ = 4; 723 | std::vector dst; 724 | for (size_t i = 0; i < src.size(); i += BLOCK_SZ) { 725 | const char* ptr = &src[i]; 726 | std::array tmp; 727 | uint8_t value[BLOCK_SZ] = {0}; 728 | int j = 0; 729 | for (; j < BLOCK_SZ; ++j) { 730 | if (std::isupper(ptr[j])) { 731 | value[j] = ptr[j] - 65; 732 | } 733 | else if (std::islower(ptr[j])) { 734 | value[j] = ptr[j] - 71; 735 | } 736 | else if (std::isdigit(ptr[j])) { 737 | value[j] = ptr[j] + 4; 738 | } 739 | else if (ptr[j] == '+') { 740 | value[j] = ptr[j] + 19; 741 | } 742 | else if (ptr[j] == '/') { 743 | value[j] = ptr[j] + 16; 744 | } 745 | else if (ptr[j] == '=') { 746 | break; 747 | } 748 | else { 749 | int err = as_int(LwsockErrc::INVALID_PARAM); 750 | std::ostringstream oss; 751 | char ch = ptr[j]; 752 | oss << callee.str() << " illegal char='" << ch << '\''; 753 | throw LwsockException(Error(err, oss.str())); 754 | } 755 | } 756 | tmp[0] = value[0] << 2 | value[1] >> 4; 757 | tmp[1] = value[1] << 4 | value[2] >> 2; 758 | tmp[2] = value[2] << 6 | value[3]; 759 | std::copy(std::begin(tmp), std::begin(tmp) + j - 1, std::back_inserter(dst)); 760 | } 761 | return dst; 762 | } 763 | 764 | /// @brief sha1 class 765 | /// 766 | /// This referred the RFC3174 Section 7. 767 | class Sha1 final { 768 | public: 769 | static constexpr int SHA1_HASH_SIZE = 20; 770 | static constexpr int MESSAGE_BLOCK_SIZE = 64; // 512-bit message blocks 771 | enum { 772 | shaSuccess = 0, 773 | shaNull, /* Null pointer parameter */ 774 | shaInputTooLong, /* input data too long */ 775 | shaStateError /* called Input after Result */ 776 | }; 777 | Sha1() = delete; 778 | Sha1(const Sha1&) = delete; 779 | Sha1(Sha1&&) = delete; 780 | 781 | // This will hold context information for the SHA-1 hashing operation 782 | class Context_t final { 783 | public: 784 | Context_t() 785 | { 786 | Intermediate_Hash[0] = 0x67452301; 787 | Intermediate_Hash[1] = 0xEFCDAB89; 788 | Intermediate_Hash[2] = 0x98BADCFE; 789 | Intermediate_Hash[3] = 0x10325476; 790 | Intermediate_Hash[4] = 0xC3D2E1F0; 791 | } 792 | Context_t(const Context_t&) = default; 793 | Context_t(Context_t&&) = default; 794 | 795 | ~Context_t() = default; 796 | 797 | Context_t& operator=(const Context_t&) = default; 798 | Context_t& operator=(Context_t&&) = default; 799 | 800 | uint32_t Intermediate_Hash[SHA1_HASH_SIZE / 4]; /* Message Digest */ 801 | uint32_t Length_Low = 0; /* Message length in bits */ 802 | uint32_t Length_High = 0; /* Message length in bits */ 803 | int_least16_t Message_Block_Index = 0; 804 | uint8_t Message_Block[MESSAGE_BLOCK_SIZE]; /* 512-bit message blocks */ 805 | }; 806 | 807 | static int32_t Input(Context_t& dst, const void* message_array, int length) 808 | { 809 | assert(message_array != nullptr); 810 | assert(length >= 0); 811 | 812 | const uint8_t* p = static_cast(message_array); 813 | 814 | for (int i = 0; length > 0; --length, ++i) { 815 | dst.Message_Block[dst.Message_Block_Index++] = (p[i] & 0xFF); 816 | dst.Length_Low += 8; 817 | if (dst.Length_Low == 0) { 818 | dst.Length_High++; 819 | if (dst.Length_High == 0) { 820 | /* Message is too long */ 821 | return EMSGSIZE; 822 | } 823 | } 824 | 825 | if (dst.Message_Block_Index == MESSAGE_BLOCK_SIZE) { 826 | dst = SHA1ProcessMessageBlock(dst); 827 | } 828 | } 829 | 830 | return 0;; 831 | } 832 | 833 | static int32_t Result(uint8_t* Message_Digest, size_t sz, const Context_t& context) 834 | { 835 | assert(Message_Digest != nullptr); 836 | assert(sz == SHA1_HASH_SIZE); 837 | 838 | Context_t ctx = SHA1PadMessage(context); 839 | for (int i = 0; i < MESSAGE_BLOCK_SIZE; ++i) { 840 | /* message may be sensitive, clear it out */ 841 | ctx.Message_Block[i] = 0; 842 | } 843 | 844 | // and clear length 845 | ctx.Length_Low = 0; 846 | ctx.Length_High = 0; 847 | 848 | for (size_t i = 0; i < sz; ++i) { 849 | Message_Digest[i] = ctx.Intermediate_Hash[i>>2] >> 8 * ( 3 - ( i & 0x03 ) ); 850 | } 851 | 852 | return 0; 853 | } 854 | 855 | private: 856 | // SHA1ProcessMessageBlock 857 | // Description: 858 | // This function will process the next 512 bits of the message 859 | // stored in the Message_Block array. 860 | // Comments: 861 | // Many of the variable names in this code, especially the 862 | // single character names, were used because those were the 863 | // names used in the publication. 864 | static Context_t SHA1ProcessMessageBlock(const Context_t& context) 865 | { 866 | Context_t ctx(context); 867 | constexpr uint32_t K[] = { // Constants defined in SHA-1 868 | 0x5A827999, 869 | 0x6ED9EBA1, 870 | 0x8F1BBCDC, 871 | 0xCA62C1D6 872 | }; 873 | int t = 0; // Loop counter 874 | uint32_t temp = 0; // Temporary word value 875 | uint32_t W[80] = {0}; // Word sequence 876 | uint32_t A = 0, B = 0, C = 0, D = 0, E = 0; // Word buffers 877 | 878 | // Initialize the first 16 words in the array W 879 | for (t = 0; t < 16; ++t) { 880 | W[t] = ctx.Message_Block[t * 4] << 24; 881 | W[t] |= ctx.Message_Block[t * 4 + 1] << 16; 882 | W[t] |= ctx.Message_Block[t * 4 + 2] << 8; 883 | W[t] |= ctx.Message_Block[t * 4 + 3]; 884 | } 885 | for (t = 16; t < 80; ++t) { 886 | W[t] = SHA1CircularShift(1, W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]); 887 | } 888 | A = ctx.Intermediate_Hash[0]; 889 | B = ctx.Intermediate_Hash[1]; 890 | C = ctx.Intermediate_Hash[2]; 891 | D = ctx.Intermediate_Hash[3]; 892 | E = ctx.Intermediate_Hash[4]; 893 | for (t = 0; t < 20; ++t) { 894 | temp = SHA1CircularShift(5, A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; 895 | E = D; 896 | D = C; 897 | C = SHA1CircularShift(30,B); 898 | B = A; 899 | A = temp; 900 | } 901 | for (t = 20; t < 40; ++t) { 902 | temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1]; 903 | E = D; 904 | D = C; 905 | C = SHA1CircularShift(30,B); 906 | B = A; 907 | A = temp; 908 | } 909 | 910 | for (t = 40; t < 60; ++t) { 911 | temp = SHA1CircularShift(5,A) + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; 912 | E = D; 913 | D = C; 914 | C = SHA1CircularShift(30,B); 915 | B = A; 916 | A = temp; 917 | } 918 | 919 | for (t = 60; t < 80; ++t) { 920 | temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3]; 921 | E = D; 922 | D = C; 923 | C = SHA1CircularShift(30,B); 924 | B = A; 925 | A = temp; 926 | } 927 | 928 | ctx.Intermediate_Hash[0] += A; 929 | ctx.Intermediate_Hash[1] += B; 930 | ctx.Intermediate_Hash[2] += C; 931 | ctx.Intermediate_Hash[3] += D; 932 | ctx.Intermediate_Hash[4] += E; 933 | ctx.Message_Block_Index = 0; 934 | 935 | return ctx; 936 | } 937 | /* 938 | * SHA1PadMessage 939 | * 940 | * Description: 941 | * According to the standard, the message must be padded to an even 942 | * 512 bits. The first padding bit must be a '1'. The last 64 943 | * bits represent the length of the original message. All bits in 944 | * between should be 0. This function will pad the message 945 | * according to those rules by filling the Message_Block array 946 | * accordingly. It will also call the ProcessMessageBlock function 947 | * provided appropriately. When it returns, it can be assumed that 948 | * the message digest has been computed. 949 | * 950 | * Parameters: 951 | * context: [in/out] 952 | * The context to pad 953 | * ProcessMessageBlock: [in] 954 | * The appropriate SHA*ProcessMessageBlock function 955 | * Returns: 956 | * Nothing. 957 | * 958 | */ 959 | static Context_t SHA1PadMessage(const Context_t& context) 960 | { 961 | Context_t ctx(context); 962 | 963 | // Check to see if the current message block is too small to hold 964 | // the initial padding bits and length. If so, we will pad the 965 | // block, process it, and then continue padding into a second 966 | // block. 967 | if (ctx.Message_Block_Index > 55) { 968 | ctx.Message_Block[ctx.Message_Block_Index++] = 0x80; 969 | while (ctx.Message_Block_Index < MESSAGE_BLOCK_SIZE) { 970 | ctx.Message_Block[ctx.Message_Block_Index++] = 0; 971 | } 972 | 973 | ctx = SHA1ProcessMessageBlock(ctx); 974 | 975 | while (ctx.Message_Block_Index < 56) { 976 | ctx.Message_Block[ctx.Message_Block_Index++] = 0; 977 | } 978 | } 979 | else { 980 | ctx.Message_Block[ctx.Message_Block_Index++] = 0x80; 981 | while (ctx.Message_Block_Index < 56) { 982 | ctx.Message_Block[ctx.Message_Block_Index++] = 0; 983 | } 984 | } 985 | 986 | /* 987 | * Store the message length as the last 8 octets 988 | */ 989 | ctx.Message_Block[56] = ctx.Length_High >> 24; 990 | ctx.Message_Block[57] = ctx.Length_High >> 16; 991 | ctx.Message_Block[58] = ctx.Length_High >> 8; 992 | ctx.Message_Block[59] = ctx.Length_High; 993 | ctx.Message_Block[60] = ctx.Length_Low >> 24; 994 | ctx.Message_Block[61] = ctx.Length_Low >> 16; 995 | ctx.Message_Block[62] = ctx.Length_Low >> 8; 996 | ctx.Message_Block[63] = ctx.Length_Low; 997 | 998 | ctx = SHA1ProcessMessageBlock(ctx); 999 | return ctx; 1000 | } 1001 | 1002 | // Define the SHA1 circular left shift macro 1003 | static uint32_t SHA1CircularShift(uint32_t bits, uint32_t word) 1004 | { 1005 | return (((word) << (bits)) | ((word) >> (32-(bits)))); 1006 | } 1007 | }; 1008 | 1009 | /// @brief check it is numerichost 1010 | /// 1011 | /// @param [in] host: host 1012 | /// @retval true it is ipaddress (numeric host) 1013 | /// @retval false it is hostname (e.g. FQDN) 1014 | /// @exception CRegexException 1015 | inline bool is_numerichost(const std::string& host) 1016 | { 1017 | std::string trimed_host(trim(host, "[]")); 1018 | #if 0 1019 | // struct addrinfo hints = {0}; 1020 | // struct addrinfo* res0 = nullptr; 1021 | // hints.ai_family = AF_UNSPEC; 1022 | // hints.ai_socktype = SOCK_STREAM; 1023 | // hints.ai_flags = AI_NUMERICHOST; 1024 | // int ret = ::getaddrinfo(trimed_host.c_str(), "80", &hints, &res0); 1025 | // freeaddrinfo(res0); 1026 | // return ret == 0; 1027 | #else 1028 | uint8_t tmp[sizeof(struct in6_addr)] = {0}; 1029 | int ret = inet_pton(AF_INET, trimed_host.c_str(), tmp); 1030 | if (ret != 1) { 1031 | ret = inet_pton(AF_INET6, trimed_host.c_str(), tmp); 1032 | } 1033 | return ret == 1; 1034 | #endif 1035 | } 1036 | 1037 | /// @brief split into host_port part and path_query part 1038 | /// 1039 | /// @param [in] uri: uri 1040 | /// @retval pair::first: host_port
1041 | /// pair::second: path_query 1042 | /// @exception CRegexException, LwsockException 1043 | inline std::pair split_hostport_pathquery(const std::string& uri) 1044 | { 1045 | DECLARE_CALLEE(callee, __func__, "(uri=\"" << uri << "\")"); 1046 | alog::scoped slog(LogLevel::DEBUG, callee.str()); 1047 | 1048 | std::string re = R"(^ws://([][0-9A-Za-z\.:\-]+)(/.*)?)"; 1049 | size_t nmatch = 4; 1050 | 1051 | std::pair hostport_pathquery; 1052 | 1053 | CRegex regex(re, nmatch); 1054 | auto result = regex.exec(uri); 1055 | switch (result.size()) { 1056 | case 3: 1057 | hostport_pathquery.second = result[2]; 1058 | //[[fallthrough]]; 1059 | case 2: 1060 | hostport_pathquery.first = result[1]; 1061 | break; 1062 | default: 1063 | { 1064 | int err = as_int(LwsockErrc::INVALID_PARAM); 1065 | std::ostringstream oss; 1066 | oss << callee.str() << " invalid uri."; 1067 | throw LwsockException(Error(err, __LINE__, oss.str())); 1068 | } 1069 | break; 1070 | } 1071 | 1072 | alog& log = alog::get_instance(); 1073 | log.d() << " hostport=\"" << hostport_pathquery.first << "\"\n"; 1074 | log.d() << " pathquery=\"" << hostport_pathquery.second << '\"'<< std::endl; 1075 | 1076 | slog.clear() << __func__ << "(...)"; 1077 | 1078 | return hostport_pathquery; 1079 | } 1080 | 1081 | /// @brief split into path part and query part 1082 | /// 1083 | /// @param [in] path_query: path and query string. (e.g. /aaa/bbb/ccc?i=1&j=2) 1084 | /// @retval pair::first: path
1085 | /// pair::second: query 1086 | /// @exception CRegexException, LwsockExrepss 1087 | inline std::pair split_path_query(const std::string& path_query_str) 1088 | { 1089 | DECLARE_CALLEE(callee, __func__, "(path_query_str=\"" << path_query_str << "\")"); 1090 | alog::scoped slog(LogLevel::DEBUG, callee.str()); 1091 | 1092 | std::string re = R"((/?[^? ]*)(\?[^ ]*)?)"; 1093 | size_t nmatch = 4; 1094 | 1095 | std::pair path_query; 1096 | CRegex regex(re, nmatch); 1097 | auto result = regex.exec(path_query_str); 1098 | if (result.size() == 0) 1099 | { return path_query; } 1100 | switch (result.size()) { 1101 | case 3: 1102 | path_query.second = result[2]; 1103 | //[[fallthrough]]; 1104 | case 2: 1105 | path_query.first = result[1][0] != '/' ? "/" + result[1] : result[1]; 1106 | break; 1107 | default: 1108 | { int err = as_int(LwsockErrc::INVALID_PARAM); 1109 | std::ostringstream oss; 1110 | oss << callee.str() << " invalid path_query_str."; 1111 | throw LwsockException(Error(err, __LINE__, oss.str())); 1112 | } 1113 | break; 1114 | } 1115 | 1116 | alog& log = alog::get_instance(); 1117 | log.d() << " path=\"" << path_query.first << "\"\n"; 1118 | log.d() << " query=\"" << path_query.second << '\"'<< std::endl; 1119 | 1120 | slog.clear() << __func__ << "(...)"; 1121 | return path_query; 1122 | } 1123 | 1124 | /// @brief split into host part and port number part. 1125 | /// 1126 | /// @param [in] host_port_str: host and port string. (e.g. aaa.bbb.ccc:12000, 192.168.0.1:12000 etc.) 1127 | /// @retval pair::first: host
1128 | /// pair::second: port number 1129 | /// @exception CRegexException, LwsockExrepss 1130 | inline std::pair split_host_port(const std::string& host_port_str) 1131 | { 1132 | DECLARE_CALLEE(callee, __func__, "(host_port_str=\"" << host_port_str << "\")"); 1133 | alog::scoped slog(LogLevel::DEBUG, callee.str()); 1134 | 1135 | std::pair host_port; 1136 | 1137 | std::string anyaddr = "[::0]:0.0.0.0"; 1138 | if (host_port_str.find(anyaddr) != std::string::npos) { 1139 | host_port.first = ""; // anyaddr node 1140 | if (host_port_str.length() > anyaddr.length()) { 1141 | host_port.second = host_port_str.substr(anyaddr.length()+1); 1142 | } 1143 | } 1144 | else if (host_port_str.find("[") != std::string::npos) { // maybe host part is numeric IPv6 1145 | std::string re = R"((\[.*\])(:[0-9]{1,5})?)"; 1146 | size_t nmatch = 4; 1147 | CRegex regex(re, nmatch); 1148 | std::vector tmp = regex.exec(host_port_str); 1149 | switch (tmp.size()) { 1150 | case 3: 1151 | host_port.second = tmp[2].at(0) == ':' ? tmp[2].substr(1) : tmp[2]; 1152 | //[[fallthrough]]; 1153 | case 2: 1154 | host_port.first = trim(tmp[1], "[]"); 1155 | break; 1156 | default: 1157 | { 1158 | int err = as_int(LwsockErrc::INVALID_PARAM); 1159 | std::ostringstream oss; 1160 | oss << callee.str() << " invalid host_port_str."; 1161 | throw LwsockException(Error(err, __LINE__, oss.str())); 1162 | } 1163 | break; 1164 | } 1165 | } 1166 | else { 1167 | // There aren't collons. 1168 | // hostname 1169 | // IPv4 1170 | // There is one collon. 1171 | // hostname:port 1172 | // IPv4:port 1173 | // There are two or more collons. 1174 | // IPv6 1175 | int cnt = std::count(std::begin(host_port_str), std::end(host_port_str), ':'); 1176 | switch (cnt) { 1177 | case 0: 1178 | host_port.first = host_port_str; 1179 | break; 1180 | case 1: 1181 | { 1182 | std::string::size_type pos = host_port_str.find_last_of(':'); 1183 | host_port.first = host_port_str.substr(0, pos); 1184 | host_port.second = host_port_str.substr(pos+1); 1185 | } 1186 | break; 1187 | default: 1188 | host_port.first = host_port_str; 1189 | break; 1190 | } 1191 | } 1192 | 1193 | alog& log = alog::get_instance(); 1194 | log.d() << " host=\"" << host_port.first << "\"\n"; 1195 | log.d() << " port=\"" << host_port.second << '\"'<< std::endl; 1196 | 1197 | slog.clear() << __func__ << "()"; 1198 | return host_port; 1199 | } 1200 | 1201 | 1202 | // RFC6455 Section 5.2 1203 | // 0 1 2 3 1204 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 1205 | // +-+-+-+-+-------+-+-------------+-------------------------------+ 1206 | // |F|R|R|R| opcode|M| Payload len | Extended payload length | 1207 | // |I|S|S|S| (4) |A| (7) | (16/64) | 1208 | // |N|V|V|V| |S| | (if payload len==126/127) | 1209 | // | |1|2|3| |K| | | 1210 | // +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + 1211 | // | Extended payload length continued, if payload len == 127 | 1212 | // + - - - - - - - - - - - - - - - +-------------------------------+ 1213 | // | |Masking-key, if MASK set to 1 | 1214 | // +-------------------------------+-------------------------------+ 1215 | // | Masking-key (continued) | Payload Data | 1216 | // +-------------------------------- - - - - - - - - - - - - - - - + 1217 | // : Payload Data continued ... : 1218 | // + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + 1219 | // | Payload Data continued ... | 1220 | // +---------------------------------------------------------------+ 1221 | 1222 | /// @brief AHead class 1223 | /// 1224 | /// A part of the Header class. fin,rsv1,rsv2,rsv3,opcode,payload_len 1225 | /// Little Endian is assumed 1226 | class AHead final { 1227 | public: 1228 | AHead() = default; 1229 | AHead(const AHead&) = default; 1230 | AHead(AHead&&) = default; 1231 | explicit AHead(uint16_t data) // data is network byte order 1232 | : data_(data) { } 1233 | 1234 | ~AHead() = default; 1235 | 1236 | AHead& operator=(const AHead&) = default; 1237 | AHead& operator=(AHead&&) = default; 1238 | 1239 | /// @brief get raw data 1240 | /// 1241 | /// @retval raw data 1242 | uint16_t data() 1243 | { 1244 | return data_; 1245 | } 1246 | 1247 | /// @brief get the pointer for data 1248 | /// 1249 | /// @retval the pointer for data 1250 | uint16_t* data_ptr() 1251 | { 1252 | return &data_; 1253 | } 1254 | 1255 | /// @brief get size for data 1256 | /// 1257 | /// @retval size for data 1258 | size_t size() 1259 | { 1260 | return sizeof data_; 1261 | } 1262 | 1263 | /// @brief set fin bit 1264 | /// 1265 | /// @param [in] val: fin bit value. 1 or 0 1266 | /// @retval reference of *this 1267 | /// @note if you set !0 (e.g. 100) then set 1 1268 | AHead& fin(int val) 1269 | { 1270 | int v = val == 0 ? 0 : 1; 1271 | data_ = (data_ & 0xff7f) | (v << 7); 1272 | return *this; 1273 | } 1274 | 1275 | /// @brief get fin bit 1276 | /// 1277 | /// @retval fin bit value 1278 | int fin() 1279 | { 1280 | return (data_ & 0x0080) >> 7; 1281 | } 1282 | 1283 | /// @brief set rsv1 bit 1284 | /// 1285 | /// @param [in] val: fin bit value. 1 or 0 1286 | /// @retval reference of *this 1287 | /// @note if you set !0 (e.g. 100) then set 1 1288 | AHead& rsv1(int val) 1289 | { 1290 | int v = val == 0 ? 0 : 1; 1291 | data_ = (data_ & 0xffbf) | (v << 6); 1292 | return *this; 1293 | } 1294 | 1295 | /// @brief get rsv1 bit 1296 | /// 1297 | /// @retval rsv1 bit value 1298 | int rsv1() 1299 | { 1300 | return (data_ & 0x0040) >> 6; 1301 | } 1302 | 1303 | /// @brief set rsv2 bit 1304 | /// 1305 | /// @param [in] val: rsv2 bit value. 1 or 0 1306 | /// @retval reference of *this 1307 | /// @note if you set !0 (e.g. 100) then set 1 1308 | AHead& rsv2(int val) 1309 | { 1310 | int v = val == 0 ? 0 : 1; 1311 | data_ = (data_ & 0xffdf) | (v << 5); 1312 | return *this; 1313 | } 1314 | 1315 | /// @brief get rsv2 bit 1316 | /// 1317 | /// @retval rsv2 bit value 1318 | int rsv2() 1319 | { 1320 | return (data_ & 0x0020) >> 5; 1321 | } 1322 | 1323 | /// @brief set rsv3 bit 1324 | /// 1325 | /// @param [in] val: rsv3 bit value. 1 or 0 1326 | /// @retval reference of *this 1327 | /// @note if you set !0 (e.g. 100) then set 1 1328 | AHead& rsv3(int val) 1329 | { 1330 | int v = val == 0 ? 0 : 1; 1331 | data_ = (data_ & 0xffef) | (v << 4); 1332 | return *this; 1333 | } 1334 | 1335 | /// @brief get rsv3 bit 1336 | /// 1337 | /// @retval rsv3 bit value 1338 | int rsv3() 1339 | { 1340 | return (data_ & 0x0010) >> 4; 1341 | } 1342 | 1343 | /// @brief set opcode 1344 | /// 1345 | /// @param [in] val: opcode value 1346 | /// @retval reference of *this 1347 | AHead& opcode(Opcode val) 1348 | { 1349 | data_ = (data_ & 0xfff0) | static_cast(val); 1350 | return *this; 1351 | } 1352 | 1353 | /// @brief get opcode 1354 | /// 1355 | /// @retval opcode value 1356 | Opcode opcode() 1357 | { 1358 | return static_cast(data_ & 0x000f); 1359 | } 1360 | 1361 | /// @brief set mask bit 1362 | /// 1363 | /// @param [in] val: mask bit value. 1 or 0 1364 | /// @retval reference of *this 1365 | /// @note if you set !0 (e.g. 100) then set 1 1366 | AHead& mask(int val) 1367 | { 1368 | int v = val == 0 ? 0 : 1; 1369 | data_ = (data_ & 0x7fff) | (v << 15); 1370 | return *this; 1371 | } 1372 | 1373 | /// @brief get mask bit 1374 | /// 1375 | /// @retval mask bit value 1376 | int mask() 1377 | { 1378 | return (data_ & 0x8000) >> 15; 1379 | } 1380 | 1381 | /// @brief set payload len field value 1382 | /// 1383 | /// @param [in] val: payload length. it is less than equal 127 1384 | /// @retval reference of *this 1385 | AHead& payload_len(int val) 1386 | { 1387 | assert(val <= 127); 1388 | data_ = (data_ & 0x80ff) | (val << 8); 1389 | return *this; 1390 | } 1391 | 1392 | /// @brief get payload length field value 1393 | /// 1394 | /// @retval payload length field value 1395 | int payload_len() 1396 | { 1397 | return (data_ & 0x7f00) >> 8; 1398 | } 1399 | 1400 | private: 1401 | uint16_t data_ = 0; // network byte order 1402 | }; 1403 | 1404 | /// @brief Sockaddr class 1405 | /// 1406 | /// sockaddr structure utility class 1407 | class Sockaddr final { 1408 | public: 1409 | Sockaddr() = default; 1410 | Sockaddr(const Sockaddr&) = default; 1411 | Sockaddr(Sockaddr&&) = default; 1412 | explicit Sockaddr(const struct sockaddr_storage& addr) 1413 | { 1414 | uaddr_.storage = addr; 1415 | } 1416 | 1417 | /// @brief constructer 1418 | /// 1419 | /// @param [in] saddr: a pointer for struct socaddr instance 1420 | /// @param [in] addrlen: saddr object size. bytes 1421 | /// @exception LwsockException 1422 | Sockaddr(const struct sockaddr* saddr, socklen_t addrlen) 1423 | { 1424 | if (sizeof uaddr_.storage < static_cast(addrlen)) { 1425 | int err = as_int(LwsockErrc::INVALID_PARAM); 1426 | std::ostringstream oss; 1427 | oss << "Sockaddr(saddr=" << std::hex << saddr << ", addrlen=" << std::dec << addrlen << ") addrlen is too big. [requier addrlen <= sizeof(struct sockaddr_storage)]"; 1428 | throw LwsockException(Error(err, __LINE__, oss.str())); 1429 | } 1430 | ::memcpy(&uaddr_.storage, saddr, addrlen); 1431 | } 1432 | ~Sockaddr() = default; 1433 | 1434 | Sockaddr& operator=(const Sockaddr&) = default; 1435 | Sockaddr& operator=(Sockaddr&&) = default; 1436 | 1437 | /// @brief get address family. (e.g. AF_INET, AF_INET6 etc.) 1438 | /// 1439 | /// @retval AF_INET: IPv4 1440 | /// @retval AF_INET6: IPv6 1441 | /// @exception LwsockException 1442 | int af() 1443 | { 1444 | if (ipaddr_.empty()) { 1445 | ip(); 1446 | } 1447 | return uaddr_.saddr.sa_family; 1448 | } 1449 | 1450 | /// @brief get ip address. result of inet_ntop(3) 1451 | /// 1452 | /// @retval ip address string 1453 | /// @exception LwsockException 1454 | std::string ip() 1455 | { 1456 | if (!ipaddr_.empty()) { 1457 | return ipaddr_; 1458 | } 1459 | char tmp[INET6_ADDRSTRLEN] = {0}; 1460 | socklen_t len = sizeof tmp; 1461 | switch (uaddr_.saddr.sa_family) { 1462 | case AF_INET: 1463 | inet_ntop(uaddr_.saddr.sa_family, &uaddr_.in.sin_addr, tmp, len); 1464 | port_ = ntohs(uaddr_.in.sin_port); 1465 | break; 1466 | case AF_INET6: 1467 | inet_ntop(uaddr_.saddr.sa_family, &uaddr_.in6.sin6_addr, tmp, len); 1468 | port_ = ntohs(uaddr_.in6.sin6_port); 1469 | break; 1470 | default: 1471 | { 1472 | int err = as_int(LwsockErrc::INVALID_AF); 1473 | std::ostringstream oss; 1474 | oss << "Sockaddr::ip()" << ". sockaddr::sa_family=" << af2str(uaddr_.saddr.sa_family); 1475 | throw LwsockException(Error(err, __LINE__, oss.str())); 1476 | } 1477 | break; 1478 | } 1479 | ipaddr_ = tmp; 1480 | return ipaddr_; 1481 | } 1482 | 1483 | /// @brief get port number 1484 | /// 1485 | /// @exception LwsockException 1486 | uint16_t port() 1487 | { 1488 | if (ipaddr_.empty()) { 1489 | ip(); 1490 | } 1491 | return port_; 1492 | } 1493 | private: 1494 | union { 1495 | struct sockaddr_storage storage = {0}; 1496 | struct sockaddr saddr; 1497 | struct sockaddr_in in; 1498 | struct sockaddr_in6 in6; 1499 | } uaddr_; // union adder 1500 | std::string ipaddr_; 1501 | uint16_t port_ = 0; 1502 | }; 1503 | 1504 | /// @brief Timespec data class 1505 | class Timespec final { 1506 | public: 1507 | enum { 1508 | TIMEOUT_NOSPEC = -1, ///< timeout no specification. it depend on the system 1509 | }; 1510 | 1511 | Timespec() = default; 1512 | 1513 | /// @brief constructer that the milliseconds specify 1514 | /// 1515 | /// @param [in] msec: millisecond or TIMEOUT_NOSPEC 1516 | /// @exception LwsockException 1517 | explicit Timespec(int32_t msec) 1518 | : msec_(msec) 1519 | { 1520 | if (msec == TIMEOUT_NOSPEC) { 1521 | return; 1522 | } 1523 | 1524 | if (msec > TIMEOUT_NOSPEC) { 1525 | tm_ = std::make_unique(timespec{msec / 1000, msec % 1000 * 1000000}); 1526 | } 1527 | else { 1528 | throw LwsockException(Error(as_int(LwsockErrc::INVALID_PARAM), __LINE__)); 1529 | } 1530 | } 1531 | ~Timespec() = default; 1532 | 1533 | bool operator==(int32_t msec) const 1534 | { 1535 | return msec_ == msec; 1536 | } 1537 | bool operator!=(int32_t msec) const 1538 | { 1539 | return !(*this == msec); 1540 | } 1541 | bool operator>(int32_t msec) const 1542 | { 1543 | return msec_ > msec; 1544 | } 1545 | bool operator<(int32_t msec) const 1546 | { 1547 | return !(*this > msec); 1548 | } 1549 | bool operator>=(int32_t msec) const 1550 | { 1551 | return msec_ >= msec; 1552 | } 1553 | bool operator<=(int32_t msec) const 1554 | { 1555 | return msec_ <= msec; 1556 | } 1557 | 1558 | /// @brief get a pointer of struct timespec instance 1559 | /// 1560 | /// @retval a pointer of struct timespec instance 1561 | const struct timespec* ptr() const 1562 | { 1563 | return tm_.get(); 1564 | } 1565 | 1566 | /// @brief transform to string 1567 | /// 1568 | /// @retval "struct timespec" string representation. (e.g. {10, 123} or NOSPEC etc.) 1569 | std::string to_string() const 1570 | { 1571 | if (msec_ == TIMEOUT_NOSPEC) { 1572 | return "NOSPEC"; 1573 | } 1574 | else { 1575 | return "{" + std::to_string(tm_->tv_sec) + ", " + std::to_string(tm_->tv_nsec) + "}"; 1576 | } 1577 | } 1578 | private: 1579 | int32_t msec_ = TIMEOUT_NOSPEC; 1580 | std::unique_ptr tm_ = nullptr; 1581 | }; 1582 | 1583 | #define WSMETHOD "WebSocket::" << __func__ 1584 | 1585 | /// @brierf WebSocket class 1586 | class WebSocket final { 1587 | public: 1588 | /// Mode enum 1589 | enum class Mode { 1590 | NONE = -1, 1591 | CLIENT = 0, 1592 | SERVER, 1593 | }; 1594 | 1595 | /// opening handshake headers type 1596 | /// 1597 | /// vector: pair
1598 | /// pair::first: header name
1599 | /// pair::second: value (it does not include \r\n) 1600 | using headers_t = std::vector>; 1601 | 1602 | /// opening handshake type 1603 | /// 1604 | /// first: requset line or status line (it does not include \r\n)
1605 | /// second: headers_t 1606 | using handshake_t = std::pair; 1607 | 1608 | WebSocket() = default; 1609 | WebSocket(const WebSocket&) = delete; 1610 | WebSocket(WebSocket&& ws) noexcept 1611 | { 1612 | mode_ = ws.mode_; 1613 | sfd_ = ws.sfd_; 1614 | ws.sfd_ = -1; 1615 | bind_sfds_ = std::move(ws.bind_sfds_); 1616 | host_ = std::move(ws.host_); 1617 | port_ = ws.port_; 1618 | 1619 | path_ = std::move(ws.path_); 1620 | query_ = std::move(ws.query_); 1621 | nonce_ = std::move(ws.nonce_); 1622 | recved_rest_buff_ = std::move(ws.recved_rest_buff_); 1623 | remote_ = std::move(ws.remote_); 1624 | } 1625 | 1626 | // @param [in] mode: Mode::NONE, Mode::CLIENT, Mode::SERVER 1627 | explicit WebSocket(Mode mode) 1628 | : mode_(mode) 1629 | { } 1630 | 1631 | ~WebSocket() 1632 | { 1633 | if (sfd_ != -1) { 1634 | close(sfd_); 1635 | } 1636 | if (!bind_sfds_.empty()) { 1637 | for (auto& sfd : bind_sfds_) { 1638 | close(sfd); 1639 | } 1640 | } 1641 | } 1642 | 1643 | WebSocket& operator=(const WebSocket &) = delete; 1644 | WebSocket& operator=(WebSocket&& rhs) noexcept 1645 | { 1646 | mode_ = rhs.mode_; 1647 | sfd_ = rhs.sfd_; 1648 | rhs.sfd_ = -1; 1649 | host_ = std::move(rhs.host_); 1650 | port_ = rhs.port_; 1651 | path_ = std::move(rhs.path_); 1652 | query_ = std::move(rhs.query_); 1653 | nonce_ = std::move(rhs.nonce_); 1654 | recved_rest_buff_ = std::move(rhs.recved_rest_buff_); 1655 | remote_ = std::move(rhs.remote_); 1656 | 1657 | return *this; 1658 | } 1659 | 1660 | /// @brief bind address that uri specify.
1661 | /// this use getaddrinfo(3) for specified uri, then open sockets and bind addresses. 1662 | /// 1663 | /// @param [in] uri: WebSocket URI
1664 | /// uri ::= "ws://" host (":" port)? path ("?" query)?
1665 | /// host ::= hostname | IPv4_dot_decimal | IPv6_colon_hex
1666 | /// @retval reference of *this 1667 | /// @exception CRegexException, GetaddrinfoException, LwsockExrepss 1668 | WebSocket& bind(const std::string& uri) 1669 | { 1670 | return bind(uri, AF_UNSPEC); 1671 | } 1672 | 1673 | /// @brief bind address that uri specify.
1674 | /// if you use hostname for uri and want to specify IPv4 or IPv6, you should use this method. 1675 | /// 1676 | /// @param [in] uri: WebSocket URI
1677 | /// uri ::= "ws://" host (":" port)? path ("?" query)?
1678 | /// host ::= hostname | IPv4_dot_decimal | IPv6_colon_hex
1679 | /// @pram [in] af: AF_INET or AF_INET6
1680 | /// if you want to specify that use IPv4 or IPv6 then you set this param. 1681 | /// @retval reference of *this 1682 | /// @exception CRegexException, GetaddrinfoException, LwsockExrepss 1683 | WebSocket& bind(const std::string& uri, int af) 1684 | { 1685 | assert(!uri.empty()); 1686 | assert(sfd_ == -1); 1687 | 1688 | DECLARE_CALLEE(callee, WSMETHOD, "(uri=\"" << uri << "\", af=" << af2str(af) << ")"); 1689 | alog::scoped slog(callee.str()); 1690 | 1691 | if (mode_ != Mode::SERVER) { 1692 | int err = as_int(LwsockErrc::INVALID_MODE); 1693 | std::ostringstream oss; 1694 | oss << callee.str() << " invalid mode. expect Mode::SERVER."; 1695 | throw LwsockException(Error(err, __LINE__, oss.str())); 1696 | } 1697 | 1698 | if (uri.empty()) { 1699 | int err = as_int(LwsockErrc::INVALID_PARAM); 1700 | std::ostringstream oss; 1701 | oss << callee.str() << " invalid uri."; 1702 | throw LwsockException(Error(err, __LINE__, oss.str())); 1703 | } 1704 | 1705 | if (af != AF_UNSPEC && af != AF_INET && af != AF_INET6) { 1706 | int err = as_int(LwsockErrc::INVALID_PARAM); 1707 | std::ostringstream oss; 1708 | oss << callee.str() << " invalid af=" << af2str(af); 1709 | throw LwsockException(Error(err, __LINE__, oss.str())); 1710 | } 1711 | 1712 | std::pair host_port; 1713 | std::pair hostport_pathquery; 1714 | try { 1715 | // split into host_port part and path_query part. 1716 | hostport_pathquery = split_hostport_pathquery(uri); 1717 | 1718 | // split into host part and port number part. 1719 | host_port = split_host_port(hostport_pathquery.first); 1720 | } 1721 | catch (LwsockException& e) { 1722 | int err = as_int(LwsockErrc::INVALID_PARAM); 1723 | std::ostringstream oss; 1724 | oss << callee.str() << " invalid uri."; 1725 | throw LwsockException(Error(err, __LINE__, oss.str())); 1726 | } 1727 | 1728 | // split into path part and query part. 1729 | std::pair path_query = split_path_query(hostport_pathquery.second); 1730 | path_ = std::move(path_query.first); 1731 | query_ = std::move(path_query.second); 1732 | 1733 | host_ = host_port.first; 1734 | try { 1735 | port_ = host_port.second.empty() ? 80 : std::stoi(host_port.second); 1736 | } 1737 | catch (std::invalid_argument& e) { 1738 | int err = as_int(LwsockErrc::INVALID_PARAM); 1739 | std::ostringstream oss; 1740 | oss << callee.str() << " invalid port number=" << host_port.second; 1741 | throw LwsockException(Error(err, __LINE__, oss.str())); 1742 | } 1743 | if (port_ > 65535) { 1744 | int err = as_int(LwsockErrc::INVALID_PARAM); 1745 | std::ostringstream oss; 1746 | oss << callee.str() << " invalid port number=" << host_port.second; 1747 | throw LwsockException(Error(err, __LINE__, oss.str())); 1748 | } 1749 | 1750 | log_.i() 1751 | << "host_=\"" << host_ << '\"' << ", port=" << port_ << ", path_=\"" << path_ << '\"' << ", query=\"" << query_ << '\"' 1752 | << std::endl 1753 | ; 1754 | 1755 | struct addrinfo hints = {0}; 1756 | struct addrinfo* res0 = nullptr; 1757 | struct addrinfo* res = nullptr; 1758 | hints.ai_flags |= AI_PASSIVE; 1759 | hints.ai_flags |= is_numerichost(host_) ? AI_NUMERICHOST : hints.ai_flags; 1760 | hints.ai_family 1761 | = af == AF_INET ? AF_INET 1762 | : af == AF_INET6 ? AF_INET6 1763 | : AF_UNSPEC 1764 | ; 1765 | hints.ai_socktype = SOCK_STREAM; 1766 | 1767 | int ret = ::getaddrinfo(host_.empty() ? NULL : host_.c_str(), std::to_string(port_).c_str(), &hints, &res0); 1768 | if (ret != 0) { 1769 | int err = ret; 1770 | std::ostringstream oss; 1771 | oss << callee.str() << " getaddrinfo(node=\"" << host_ << "\", port=" << port_ << ")"; 1772 | throw GetaddrinfoException(Error(err, __LINE__, oss.str())); 1773 | } 1774 | 1775 | for (res = res0; res != nullptr; res = res->ai_next) { 1776 | int sfd = ::socket(res->ai_family, res->ai_socktype, res->ai_protocol); 1777 | if (sfd < 0) { 1778 | int err = errno; 1779 | log_.w() << "::socket(" << res->ai_family << ", " << res->ai_socktype << ", " << res->ai_protocol << ") error=" << err << ". " << strerror(err) << ". Try next." << std::endl; 1780 | continue; 1781 | } 1782 | log_.d() << "::socket() sfd=" << sfd << std::endl; 1783 | 1784 | int on = 1; 1785 | if (res->ai_family == AF_INET6) { 1786 | setsockopt(sfd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof on); 1787 | } 1788 | setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof on); 1789 | setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on); 1790 | #if defined(SO_REUSEPORT) 1791 | setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof on); 1792 | #endif 1793 | 1794 | Sockaddr saddr(res->ai_addr, res->ai_addrlen); 1795 | ret = ::bind(sfd, res->ai_addr, res->ai_addrlen); 1796 | if (ret < 0) { 1797 | int err = errno; 1798 | log_.w() << "::bind(sfd=" << sfd << ", ip=\"" << saddr.ip() << "\", port=" << saddr.port() << ") error=" << err << ". " << strerror(err) << ". closed socket. Try next." << std::endl; 1799 | close_socket(sfd); 1800 | continue; 1801 | } 1802 | log_.i() << "::bind(sfd=" << sfd << ", ip=\"" << saddr.ip() << "\", port=" << saddr.port() << ")" << std::endl; 1803 | 1804 | bind_sfds_.push_back(sfd); 1805 | } 1806 | freeaddrinfo(res); 1807 | 1808 | if (bind_sfds_.empty()) { 1809 | int err = as_int(LwsockErrc::COULD_NOT_OPEN_AVAILABLE_SOCKET); 1810 | std::ostringstream oss; 1811 | oss << callee.str() << " could not bind(2) any sockets."; 1812 | throw LwsockException(Error(err, __LINE__, oss.str())); 1813 | } 1814 | 1815 | return *this; 1816 | } 1817 | 1818 | /// @brief listen(2) each binded sockets 1819 | /// 1820 | /// @param [in] backlog: listen(2)'s backlog 1821 | /// @retval reference of *this 1822 | /// @exception SystemErrorException 1823 | WebSocket& listen(int backlog) 1824 | { 1825 | assert(mode_ == Mode::SERVER); 1826 | assert(!bind_sfds_.empty()); 1827 | 1828 | DECLARE_CALLEE(callee, WSMETHOD, "(backlog=" << backlog << ")"); 1829 | alog::scoped slog(callee.str()); 1830 | 1831 | std::for_each(std::begin(bind_sfds_), std::end(bind_sfds_), [&](int sfd){ 1832 | int ret = ::listen(sfd, backlog); 1833 | if (ret != 0) { 1834 | int err = errno; 1835 | std::ostringstream oss; 1836 | oss << callee.str() << "::listen(sfd=" << sfd << ", backlog=" << backlog << ")"; 1837 | throw SystemErrorException(Error(err, __LINE__, oss.str())); 1838 | } 1839 | log_.i() << "::listen(sfd=" << sfd << ", backlog=" << backlog << ")" << std::endl; 1840 | }); 1841 | 1842 | return *this; 1843 | } 1844 | 1845 | /// @brief accept socket 1846 | /// 1847 | /// @retval a new WebSocket instance 1848 | /// @exception LwsockException, SystemErrorException 1849 | WebSocket accept() 1850 | { 1851 | assert(mode_ == Mode::SERVER); 1852 | assert(!bind_sfds_.empty()); 1853 | 1854 | DECLARE_CALLEE(callee, WSMETHOD, "()"); 1855 | alog::scoped slog(callee.str()); 1856 | 1857 | fd_set rfds; 1858 | FD_ZERO(&rfds); 1859 | int maxsfd = -1; 1860 | 1861 | log_.i() << "::pselect() wait sfds="; 1862 | for (size_t i = 0; i < bind_sfds_.size(); ++i) { 1863 | int sfd = bind_sfds_[i]; 1864 | FD_SET(sfd, &rfds); 1865 | maxsfd = std::max(maxsfd, sfd); 1866 | log_ << sfd << (i != bind_sfds_.size()-1 ? "," : ""); 1867 | } 1868 | log_ << '\n' << std::flush; 1869 | 1870 | int nfds = maxsfd + 1; 1871 | int ret = pselect(nfds, &rfds, nullptr, nullptr, nullptr, nullptr); 1872 | if (ret == -1) { 1873 | int err = errno; 1874 | std::ostringstream oss; 1875 | oss << callee.str() << " ::pselect(nfds=" << nfds << ", ...)"; 1876 | throw SystemErrorException(Error(err, __LINE__, oss.str())); 1877 | } 1878 | 1879 | auto ite = std::find_if(std::begin(bind_sfds_), std::end(bind_sfds_), [&rfds](int sfd){ 1880 | if (FD_ISSET(sfd, &rfds)) { 1881 | return true; 1882 | } 1883 | else { 1884 | return false; 1885 | } 1886 | }); 1887 | int sfd = *ite; 1888 | 1889 | struct sockaddr_storage remote = {0}; 1890 | socklen_t addrlen = sizeof remote; 1891 | //log_(LogLevel::INFO) << "::accept(sfd=" << sfd << ", ...)\n"; 1892 | int newsfd = ::accept(sfd, (struct sockaddr*)&remote, &addrlen); 1893 | if (newsfd < 0) { 1894 | int err = errno; 1895 | std::ostringstream oss; 1896 | oss << callee.str() << " ::accept(sfd=" << sfd << ", ...)"; 1897 | throw SystemErrorException(Error(err, __LINE__, oss.str())); 1898 | } 1899 | WebSocket ws(Mode::SERVER); 1900 | ws.sfd_ = newsfd; 1901 | ws.host_ = host_; 1902 | ws.port_ = port_; 1903 | ws.path_ = path_; 1904 | ws.query_ = query_; 1905 | remote_ = Sockaddr(remote); 1906 | 1907 | log_.i() << "::accept(sfd=" << sfd << ", ...) newsfd=" << newsfd << ", remote=" << remote_.ip() << ", port=" << remote_.port() << std::endl; 1908 | return ws; 1909 | } 1910 | 1911 | /// @brief accept socket 1912 | /// 1913 | /// @param [out] remote: this is set in with the address of the peer socket 1914 | /// @retval a new WebSocket instance 1915 | /// @exception LwsockException, SystemErrorException 1916 | WebSocket accept(Sockaddr& remote) 1917 | { 1918 | WebSocket nws = accept(); // newer Websocket instance 1919 | remote = nws.remote_; 1920 | return nws; 1921 | } 1922 | 1923 | /// @brief receive a opening handshake request message. blocking receive 1924 | /// 1925 | /// @retval received handshake message parameters 1926 | /// @exception CRegexException, LwsockExrepss, SystemErrorException 1927 | handshake_t recv_req() 1928 | { 1929 | return recv_req(Timespec()); 1930 | } 1931 | 1932 | /// @brief receive opening handshake request message with timeout.
1933 | /// recv_req internally calls recv(2) multiple times. timeout is effective that times. 1934 | /// 1935 | /// @param [in] timeout: specify timeout. Timespec instance 1936 | /// @retval received handshake message parameters 1937 | /// @exception CRegexException, LwsockExrepss, SystemErrorException 1938 | handshake_t recv_req(const Timespec& timeout) 1939 | { 1940 | assert(sfd_ != -1); 1941 | assert(mode_ == Mode::SERVER); 1942 | 1943 | DECLARE_CALLEE(callee, WSMETHOD, "(timeout=" << timeout.to_string() << ")"); 1944 | alog::scoped slog(callee.str()); 1945 | 1946 | std::string recved_response = recv_until_eoh(sfd_, timeout); 1947 | log_.d() << '\"' << recved_response << '\"'<< std::endl; 1948 | 1949 | handshake_t handshake_data; 1950 | try { 1951 | handshake_data = parse_handshake_msg(recved_response); 1952 | } 1953 | catch (LwsockException& e) { 1954 | int err = as_int(LwsockErrc::INVALID_HANDSHAKE); 1955 | std::ostringstream oss; 1956 | oss << callee.str() << " INVALID_HANDSHAKE. send 404 and close socket=" << sfd_; 1957 | handshake_t handshake; 1958 | handshake.first = "HTTP/1.1 400 Bad Request"; 1959 | send_res_manually(handshake); 1960 | close_socket(sfd_); // 10.7 when the endpoint sees an opening handshake that does not correspond to the values it is expecting, the endpoint MAY drop the TCP connection. 1961 | throw LwsockException(Error(err, __LINE__, oss.str())); 1962 | } 1963 | 1964 | CRegex regex(R"(^GET +((/[^? ]*)(\?[^ ]*)?)? *HTTP/1\.1)", 20); 1965 | auto tmp = regex.exec(handshake_data.first); 1966 | if (tmp.size() < 1) { 1967 | int err = as_int(LwsockErrc::INVALID_HANDSHAKE); 1968 | std::ostringstream oss; 1969 | oss << callee.str() << " INVALID_HANDSHAKE first_line=\"" << handshake_data.first << "\". send 404 and close socket=" << sfd_; 1970 | handshake_t handshake; 1971 | handshake.first = "HTTP/1.1 400 Bad Request"; 1972 | send_res_manually(handshake); 1973 | close_socket(sfd_); // 10.7 when the endpoint sees an opening handshake that does not correspond to the values it is expecting, the endpoint MAY drop the TCP connection. 1974 | throw LwsockException(Error(err, __LINE__, oss.str())); 1975 | } 1976 | 1977 | // if the request path differ expecting path, then respond 404. 1978 | std::pair path_query = split_path_query(tmp[1]); 1979 | if (path_query.first != path_) { 1980 | int err = as_int(LwsockErrc::INVALID_HANDSHAKE); 1981 | std::ostringstream oss; 1982 | oss << callee.str() << " INVALID_HANDSHAKE path=\"" << path_query.first << "\". require path=" << path_ << ". send 404 and close socket=" << sfd_; 1983 | handshake_t handshake; 1984 | handshake.first = "HTTP/1.1 400 Bad Request"; 1985 | send_res_manually(handshake); 1986 | close_socket(sfd_); // 10.7 when the endpoint sees an opening handshake that does not correspond to the values it is expecting, the endpoint MAY drop the TCP connection. 1987 | throw LwsockException(Error(err, __LINE__, oss.str())); 1988 | } 1989 | 1990 | auto ite4origin = std::find_if(std::begin(handshake_data.second), std::end(handshake_data.second), [](std::pair& headervalue){ 1991 | if (str2lower(headervalue.first) == str2lower("Origin")) { 1992 | return true; 1993 | } 1994 | else { 1995 | return false; 1996 | } 1997 | }); 1998 | if (ite4origin != std::end(handshake_data.second)) { 1999 | origin_ = ite4origin->second; 2000 | } 2001 | 2002 | try { 2003 | check_request_headers(handshake_data.second); 2004 | } 2005 | catch (LwsockException& e) { 2006 | std::ostringstream oss; 2007 | oss << callee.str() << e.what() << " received a bad request from the client, then send 400 response and close socekt=" << sfd_; 2008 | handshake_t handshake; 2009 | handshake.first = "HTTP/1.1 400 Bad Request"; 2010 | send_res_manually(handshake); 2011 | close_socket(sfd_); // 10.7 when the endpoint sees an opening handshake that does not correspond to the values it is expecting, the endpoint MAY drop the TCP connection. 2012 | throw LwsockException(Error(e.code(), oss.str())); 2013 | } 2014 | 2015 | return handshake_data; 2016 | } 2017 | 2018 | /// @brief send an opening handshake response message.
2019 | /// send default heades. they are Host, Upgrade, Connection, Sec-WebSocket-Key and Sec-WebSocket-Accept. 2020 | /// 2021 | /// @retval sent a message 2022 | /// @exception SystemErrorException 2023 | std::string send_res() 2024 | { 2025 | return send_res(headers_t{}); 2026 | } 2027 | 2028 | /// @brief send opening handshake response with other headers.
2029 | /// if you want to send that add other headers to default headers, then use this api. 2030 | /// 2031 | /// @param [in] otherheaders: other headers 2032 | /// @retval sent a message 2033 | /// @exception SystemErrorException 2034 | std::string send_res(const headers_t& otherheaders) 2035 | { 2036 | assert(sfd_ != -1); 2037 | assert(mode_ == Mode::SERVER); 2038 | 2039 | DECLARE_CALLEE(callee, WSMETHOD, "() otherheaders cnt=" << otherheaders.size()); 2040 | alog::scoped slog(callee.str()); 2041 | 2042 | handshake_t handshake; 2043 | handshake.first = "HTTP/1.1 101 Switching Protocols\r\n"; 2044 | 2045 | headers_t headers; 2046 | headers.push_back({"Upgrade", "websocket"}); 2047 | headers.push_back({"Connection", "Upgrade"}); 2048 | 2049 | std::string key = make_key(nonce_, GUID); 2050 | headers.push_back({"Sec-WebSocket-Accept", key}); 2051 | if (!otherheaders.empty()) { 2052 | std::copy(std::begin(otherheaders), std::end(otherheaders), std::back_inserter(headers)); 2053 | } 2054 | 2055 | handshake.second = headers; 2056 | 2057 | return send_ohandshake(handshake); 2058 | } 2059 | 2060 | /// @brief send an opening handshake response message that is set completely manual. 2061 | /// 2062 | /// @param [in] handshake: handshake message parameters 2063 | /// @retval sent a message 2064 | /// @exception SystemErrorException 2065 | std::string send_res_manually(const handshake_t& handshake) 2066 | { 2067 | return send_ohandshake(handshake); 2068 | } 2069 | 2070 | /// @brief connect to the server 2071 | /// 2072 | /// @param [in] uri: WebSocket URI
2073 | /// uri ::= "ws://" host (":" port)? path ("?" query)?
2074 | /// host ::= hostname | IPv4_dot_decimal | IPv6_colon_hex
2075 | /// @retval reference of *this 2076 | /// @exception CRegexException, GetaddrinfoException, LwsockExrepss, SystemErrorException 2077 | WebSocket& connect(const std::string& uri) 2078 | { 2079 | return connect(uri, Timespec()); 2080 | } 2081 | 2082 | /// @brief connect to the server with timeout 2083 | /// 2084 | /// @param [in] uri: connect to uri.
2085 | /// uri ::= "ws://" host (":" port)? path ("?" query)?
2086 | /// host ::= hostname | IPv4_dot_decimal | IPv6_colon_hex
2087 | /// @param [in] timeout: specify timeout. Timespec instance 2088 | /// @retval reference of *this 2089 | /// @exception CRegexException, GetaddrinfoException, LwsockExrepss, SystemErrorException 2090 | WebSocket& connect(const std::string& uri, const Timespec& timeout) 2091 | { 2092 | return connect(uri, AF_UNSPEC, timeout); 2093 | } 2094 | 2095 | /// @brief connect to the server with timeout. and if you use hostname for uri and want to specify IPv4 or IPv6, you should use this method. 2096 | /// 2097 | /// @param [in] uri: connect to uri.
2098 | /// uri ::= "ws://" host (":" port)? path ("?" query)?
2099 | /// host ::= hostname | IPv4_dot_decimal | IPv6_colon_hex
2100 | /// @pram [in] af: AF_INET or AF_INET6
2101 | /// if you want to specify that use IPv4 or IPv6 then you set this param. 2102 | /// @param [in] timeout: specify timeout. Timespec instance 2103 | /// @retval reference of *this 2104 | /// @exception CRegexException, GetaddrinfoException, LwsockExrepss, SystemErrorException 2105 | /// @remarks 2106 | WebSocket& connect(const std::string& uri, int af, const Timespec& timeout) 2107 | { 2108 | assert(mode_ == Mode::CLIENT); 2109 | assert(af == AF_INET || af == AF_INET6 || af == AF_UNSPEC); 2110 | assert(!uri.empty()); 2111 | assert(sfd_ == -1); 2112 | 2113 | DECLARE_CALLEE(callee, WSMETHOD, "(uri=\"" << uri << "\", af=" << af2str(af) << ", timeout=" << timeout.to_string() << ')'); 2114 | alog::scoped slog(callee.str()); 2115 | 2116 | // define a function that it set nonblocking/blocking to sfd. 2117 | // If nonblock is true, sfd is sat nonblocking. 2118 | // If nonblock is false, sfd is sat blocking. 2119 | auto sfd_nonblock = [](int sfd, bool nonblock) -> int { 2120 | int val = nonblock; 2121 | int ret = ioctl(sfd, FIONBIO, &val); 2122 | return ret; 2123 | }; 2124 | 2125 | std::pair host_port; 2126 | std::pair hostport_pathquery; 2127 | try { 2128 | // split into host_port part and path_query part. 2129 | hostport_pathquery = split_hostport_pathquery(uri); 2130 | 2131 | // split into host part and port number part. 2132 | host_port = split_host_port(hostport_pathquery.first); 2133 | } 2134 | catch (LwsockException& e) { 2135 | int err = as_int(LwsockErrc::INVALID_PARAM); 2136 | std::ostringstream oss; 2137 | oss << callee.str() << " invalid uri."; 2138 | throw LwsockException(Error(err, __LINE__, oss.str())); 2139 | } 2140 | 2141 | // split into path part and query part. 2142 | std::pair path_query = split_path_query(hostport_pathquery.second); 2143 | //path_ = std::move(path_query.first); 2144 | path_ = path_query.first; 2145 | query_ = std::move(path_query.second); 2146 | 2147 | host_ = host_port.first; 2148 | try { 2149 | port_ = host_port.second.empty() ? 80 : std::stoi(host_port.second); 2150 | } 2151 | catch (std::invalid_argument& e) { 2152 | int err = as_int(LwsockErrc::INVALID_PARAM); 2153 | std::ostringstream oss; 2154 | oss << callee.str() << " invalid port number=" << host_port.second; 2155 | throw LwsockException(Error(err, __LINE__, oss.str())); 2156 | } 2157 | if (port_ > 65535) { 2158 | int err = as_int(LwsockErrc::INVALID_PARAM); 2159 | std::ostringstream oss; 2160 | oss << callee.str() << " invalid port number=" << host_port.second; 2161 | throw LwsockException(Error(err, __LINE__, oss.str())); 2162 | } 2163 | 2164 | log_.i() << "host=\"" << host_ << "\", port=" << port_ << ", path=\"" << path_ << "\", query=\"" << query_ << '\"' << std::endl; 2165 | 2166 | int available_sfd = -1; 2167 | struct addrinfo hints = {0}; 2168 | struct addrinfo* res0 = nullptr; 2169 | struct addrinfo* res = nullptr; 2170 | hints.ai_flags += is_numerichost(host_) ? AI_NUMERICHOST : hints.ai_flags; 2171 | hints.ai_family 2172 | = af == AF_INET ? AF_INET 2173 | : af == AF_INET6 ? AF_INET6 2174 | : AF_UNSPEC 2175 | ; 2176 | hints.ai_socktype = SOCK_STREAM; 2177 | 2178 | int ret = ::getaddrinfo(host_.c_str(), std::to_string(port_).c_str(), &hints, &res0); 2179 | if (ret != 0) { 2180 | int err = ret; 2181 | std::ostringstream oss; 2182 | oss << callee.str() << " getaddrinfo(node=\"" << host_ << "\", port=" << port_ << ")"; 2183 | throw GetaddrinfoException(Error(err, __LINE__, oss.str())); 2184 | } 2185 | for (res = res0; res != nullptr; res = res->ai_next) { 2186 | int sfd = ::socket(res->ai_family, res->ai_socktype, res->ai_protocol); 2187 | if (sfd < 0) { 2188 | int err = errno; 2189 | log_.w() << "socket(" << res->ai_family << ", " << res->ai_socktype << ", " << res->ai_protocol << ") error=" << err << ". " << strerror(err) << ". Try next." << std::endl; 2190 | continue; 2191 | } 2192 | log_.d() << "socket() opened sfd=" << sfd << std::endl; 2193 | 2194 | int on = 1; 2195 | if (res->ai_family == AF_INET6) { 2196 | setsockopt(sfd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof on); 2197 | } 2198 | setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof on); 2199 | sfd_nonblock(sfd, true); // set nonblocking mode 2200 | 2201 | ret = ::connect(sfd, res->ai_addr, res->ai_addrlen); 2202 | if (ret == 0) { 2203 | sfd_nonblock(sfd, false); // reset blocking mode 2204 | Sockaddr saddr(res->ai_addr, res->ai_addrlen); 2205 | log_.d() << "::connect(sfd=" << sfd << ", ip=\"" << saddr.ip() << "\", port=" << saddr.port() << ") success" << std::endl; 2206 | available_sfd = sfd; 2207 | break; 2208 | } 2209 | else { 2210 | int err = errno; 2211 | if (err == EINPROGRESS) { 2212 | Sockaddr saddr(res->ai_addr, res->ai_addrlen); 2213 | 2214 | fd_set rfd; 2215 | FD_ZERO(&rfd); 2216 | FD_SET(sfd, &rfd); 2217 | fd_set wfd; 2218 | FD_ZERO(&wfd); 2219 | FD_SET(sfd, &wfd); 2220 | ret = 0; 2221 | log_.d() << "::connect(sfd=" << sfd << ", ip=\"" << saddr.ip() << "\", port=" << saddr.port() << ", timeout=" << timeout.to_string() << ')' << std::endl; 2222 | int nfds = sfd + 1; 2223 | ret = pselect(nfds, &rfd, &wfd, nullptr, timeout.ptr(), nullptr); 2224 | if (ret == -1) { 2225 | int err = errno; 2226 | std::ostringstream oss; 2227 | oss << callee.str() << " ::pselect(nfds=" << nfds << ", ...)"; 2228 | close_socket(sfd); 2229 | throw SystemErrorException(Error(err, __LINE__, oss.str())); 2230 | } 2231 | else if (ret == 0) { 2232 | log_.w() << "::connect() is timeouted, try next." << std::endl; 2233 | close_socket(sfd); 2234 | break; // try a next connection 2235 | } 2236 | else { 2237 | if (FD_ISSET(sfd, &rfd)) { 2238 | char tmp[8]; // '8' has no particular meaning. 2239 | int err = 0; 2240 | ret = recv(sfd, tmp, sizeof tmp, 0); 2241 | if (ret < 0) { 2242 | err = errno; 2243 | } 2244 | Sockaddr saddr(res->ai_addr, res->ai_addrlen); 2245 | if (ret == 0) { 2246 | log_.w() << "::connect(sfd=" << sfd << ", ip=\"" << saddr.ip() << "\", port=" << saddr.port() << ") is closed from the server. Try next" << std::endl; 2247 | } 2248 | else { 2249 | log_.w() << "::connect(sfd=" << sfd << ", ip=\"" << saddr.ip() << "\", port=" << saddr.port() << ") error=" << err << ". " << strerror(err) << ". Try next" << std::endl; 2250 | } 2251 | close_socket(sfd); 2252 | continue; 2253 | } 2254 | if (FD_ISSET(sfd, &wfd)) { 2255 | // connect successed 2256 | sfd_nonblock(sfd, false); // set blocking mode 2257 | available_sfd = sfd; 2258 | remote_ = Sockaddr(res->ai_addr, res->ai_addrlen); 2259 | break; 2260 | } 2261 | 2262 | throw SystemErrorException(Error(EBADE, __LINE__, "FD_ISSET() result is an unexpected.")); 2263 | } 2264 | } 2265 | else { 2266 | close_socket(sfd); 2267 | Sockaddr saddr(res->ai_addr, res->ai_addrlen); 2268 | log_.w() << "::connect(sfd=" << sfd << ", ip=\"" << saddr.ip() << "\", port=" << saddr.port() << ") error=" << err << ". " << strerror(err) << ". closed socket. Try next." << std::endl; 2269 | 2270 | } 2271 | } 2272 | } 2273 | freeaddrinfo(res); 2274 | 2275 | if (available_sfd == -1) { 2276 | int err = as_int(LwsockErrc::COULD_NOT_OPEN_AVAILABLE_SOCKET); 2277 | std::ostringstream oss; 2278 | oss << callee.str() << " COULD_NOT_OPEN_AVAILABLE_SOCKET."; 2279 | throw LwsockException(Error(err, __LINE__, oss.str())); 2280 | } 2281 | 2282 | sfd_ = available_sfd; 2283 | log_.i() << WSMETHOD << "(sfd=" << sfd_ << ") connect success." << std::endl; 2284 | 2285 | return *this; 2286 | } 2287 | 2288 | /// @brief send an opening handshake request message 2289 | /// 2290 | /// @retval sent a message 2291 | std::string send_req() 2292 | { 2293 | return send_req(headers_t{}); 2294 | } 2295 | 2296 | /// @brief send an opening handshake request message with other headers 2297 | /// 2298 | /// @param [in] otherheaders: other headers 2299 | /// @retval sent a message 2300 | /// @exception SystemErrorException 2301 | std::string send_req(const headers_t& otherheaders) 2302 | { 2303 | assert(sfd_ != -1); 2304 | assert(mode_ == Mode::CLIENT); 2305 | 2306 | DECLARE_CALLEE(callee, WSMETHOD, "() otherheaders cnt=" << otherheaders.size()); 2307 | alog::scoped slog(callee.str()); 2308 | 2309 | std::ostringstream first_line; 2310 | first_line << "GET " << path_ << query_ << " HTTP/1.1" << EOL; 2311 | 2312 | handshake_t handshake; 2313 | handshake.first = first_line.str(); 2314 | 2315 | headers_t headers; 2316 | headers.push_back({"Host", (port_ == 80) ? host_ : (host_ + ":" + std::to_string(port_))}); 2317 | headers.push_back({"Upgrade", "websocket"}); 2318 | headers.push_back({"Connection", "Upgrade"}); 2319 | nonce_ = make_nonce(); 2320 | headers.push_back({"Sec-WebSocket-Key", nonce_}); 2321 | headers.push_back({"Sec-WebSocket-Version", "13"}); 2322 | if (!otherheaders.empty()) { 2323 | std::copy(std::begin(otherheaders), std::end(otherheaders), std::back_inserter(headers)); 2324 | } 2325 | 2326 | handshake.second = headers; 2327 | 2328 | return send_ohandshake(handshake); 2329 | } 2330 | 2331 | /// @brief send an opening handshake request message that is set completely manual. 2332 | /// 2333 | /// @param [in] handshake: handshake message parameters 2334 | /// @retval sent a message 2335 | /// @exception SystemErrorException 2336 | std::string send_req_manually(const handshake_t& handshake) 2337 | { 2338 | return send_ohandshake(handshake); 2339 | } 2340 | 2341 | /// @brief receive an opening handshake response message 2342 | /// 2343 | /// @retval pair::first: received handshake message parameters
2344 | /// pair::second: status code of the 1st line
2345 | /// @exception CRegexException, LwsockExrepss, SystemErrorException 2346 | std::pair recv_res() 2347 | { 2348 | return recv_res(Timespec()); 2349 | } 2350 | 2351 | /// @brief receive an opening handshake response message with timeout 2352 | /// 2353 | /// @param [in] timeout: specify timeout. Timespec instance 2354 | /// @retval pair::first: received handshake params
2355 | /// pair::second: status code
2356 | /// @exception CRegexException, LwsockExrepss, SystemErrorException 2357 | std::pair recv_res(const Timespec& timeout) 2358 | { 2359 | assert(sfd_ != -1); 2360 | 2361 | DECLARE_CALLEE(callee, WSMETHOD, "(timeout=" << timeout.to_string() << ")"); 2362 | alog::scoped slog(callee.str()); 2363 | 2364 | std::string recved_response = recv_until_eoh(sfd_, timeout); 2365 | 2366 | handshake_t handshake_data = parse_handshake_msg(recved_response); 2367 | CRegex regex(R"(^HTTP/1.1 ([0-9]+)(.*)?)", 20); 2368 | std::vector tmp = regex.exec(handshake_data.first); 2369 | 2370 | if (tmp.size() < 3) { 2371 | int err = as_int(LwsockErrc::INVALID_HANDSHAKE); 2372 | std::ostringstream oss; 2373 | oss << callee.str() << " INVALID_HANDSHAKE first_line=\"" << handshake_data.first << "\", then send CLOSE frame and close socket=" << sfd_; 2374 | send_close(1002); 2375 | close_socket(sfd_); // 10.7 when the endpoint sees an opening handshake that does not correspond to the values it is expecting, the endpoint MAY drop the TCP connection. 2376 | throw LwsockException(Error(err, __LINE__, oss.str())); 2377 | } 2378 | int32_t status_code = std::stoi(tmp[1]); 2379 | if (status_code != 101) { 2380 | return std::pair {{}, status_code}; 2381 | } 2382 | 2383 | try { 2384 | check_response_headers(handshake_data.second); 2385 | } 2386 | catch (LwsockException& e) { 2387 | std::ostringstream oss; 2388 | oss << callee.str() << e.what() << ", then send CLOSE frame and close socket=" << sfd_; 2389 | send_close(1002); 2390 | close_socket(sfd_); // 10.7 when the endpoint sees an opening handshake that does not correspond to the values it is expecting, the endpoint MAY drop the TCP connection. 2391 | throw LwsockException(Error(e.code(), __LINE__, oss.str())); 2392 | } 2393 | 2394 | log_.d() << handshake_data.first << std::endl; 2395 | for (auto& elm : handshake_data.second) { 2396 | log_.d() << elm.first << ':' << elm.second << '\n'; 2397 | } 2398 | log_.d() << std::endl; 2399 | 2400 | return std::pair(handshake_data, status_code); 2401 | } 2402 | 2403 | /// @brief send a websocket text message to the remote 2404 | /// 2405 | /// @param [in] payload_data: WebSocket payload data 2406 | /// @retval sent data size. bytes 2407 | /// @exception SystemErrorException 2408 | ssize_t send_msg_txt(const std::string& payload_data) 2409 | { 2410 | return send_msg(Opcode::TEXT, payload_data.data(), payload_data.size()); 2411 | } 2412 | 2413 | /// @brief send a websocket binary message to the remote 2414 | /// 2415 | /// @param [in] payload_data: WebSocket payload data 2416 | /// @retval sent data size. bytes 2417 | /// @exception SystemErrorException 2418 | ssize_t send_msg_bin(const std::vector& payload_data) 2419 | { 2420 | return send_msg(Opcode::BINARY, payload_data.data(), payload_data.size()); 2421 | } 2422 | 2423 | /// @brief send a websocket binary message to the remote 2424 | /// 2425 | /// @param [in] payload_data: websocket payload data 2426 | /// @retval sent data size. bytes 2427 | /// @exception SystemErrorException 2428 | template ssize_t send_msg_bin(const std::array& payload_data) 2429 | { 2430 | return send_msg(Opcode::BINARY, payload_data.data(), payload_data.size()); 2431 | } 2432 | 2433 | /// @brief receive a websocket text message from the remote 2434 | /// 2435 | /// @retval pair::first: a received string message
2436 | /// pair::second: status code when recieved a CLOSE frame
2437 | /// @exception CRegexException, LwsockExrepss, SystemErrorException 2438 | std::pair recv_msg_txt() 2439 | { 2440 | return recv_msg_txt(Timespec()); 2441 | } 2442 | 2443 | /// @brief receive a websocket text message from the remote with timeout 2444 | /// 2445 | /// @param [in] timeout: specify timeout. Timespec instance 2446 | /// @retval pair::first: a received string message
2447 | /// pair::second: status code when recieved a CLOSE frame
2448 | /// @exception LwsockException, SystemErrorException 2449 | std::pair recv_msg_txt(const Timespec& timeout) 2450 | { 2451 | assert(sfd_ != -1); 2452 | 2453 | DECLARE_CALLEE(callee, WSMETHOD, "(timeout=" << timeout.to_string() << ")"); 2454 | alog::scoped slog(callee.str()); 2455 | 2456 | std::pair result = recv_msg(timeout); 2457 | 2458 | return result; 2459 | } 2460 | 2461 | /// @brief receive a websocket binary message from the remote 2462 | /// 2463 | /// @retval pair::first: a received binary message
2464 | /// pair::second: status code when recieved a CLOSE frame
2465 | /// @exception LwsockException, SystemErrorException 2466 | std::pair, int32_t> recv_msg_bin() 2467 | { 2468 | return recv_msg_bin(Timespec()); 2469 | } 2470 | 2471 | /// @brief receive a websocket binary message from the remote with timeout 2472 | /// 2473 | /// @param [in] timeout: specify timeout. Timespec instance 2474 | /// @retval pair::first: a received binary message
2475 | /// pair::second: status code when recieved a CLOSE
2476 | /// @exception LwsockException, SystemErrorException 2477 | std::pair, int32_t> recv_msg_bin(const Timespec& timeout) 2478 | { 2479 | assert(sfd_ != -1); 2480 | 2481 | DECLARE_CALLEE(callee, WSMETHOD, "(timeout=" << timeout.to_string() << ")"); 2482 | alog::scoped slog(callee.str()); 2483 | 2484 | std::pair, int32_t> result = recv_msg>(timeout); 2485 | return result; 2486 | } 2487 | 2488 | /// @brief send a PING frame 2489 | /// 2490 | /// @retval sent data size. bytes 2491 | /// @exception SystemErrorException 2492 | ssize_t send_ping() 2493 | { 2494 | return send_msg(Opcode::PING, "", 0); 2495 | } 2496 | 2497 | /// @brief send a PING frame with a text app data 2498 | /// 2499 | /// @param [in] app_data: app data 2500 | /// @retval sent data size. bytes 2501 | /// @exception SystemErrorException 2502 | ssize_t send_ping(const std::string& app_data) 2503 | { 2504 | return send_msg(Opcode::PING, app_data.data(), app_data.size()); 2505 | } 2506 | 2507 | /// @brief send PING frame with a binary app data 2508 | /// 2509 | /// @param [in] app_data: app data 2510 | /// @retval sent size. bytes 2511 | /// @exception SystemErrorException 2512 | ssize_t send_ping(const std::vector& app_data) 2513 | { 2514 | return send_msg(Opcode::PING, app_data.data(), app_data.size()); 2515 | } 2516 | 2517 | /// @brief send PING frame with a binary app data 2518 | /// 2519 | /// @param [in] app_data: app data 2520 | /// @retval sent size. bytes 2521 | /// @exception SystemErrorException 2522 | template ssize_t send_ping(const std::array& app_data) 2523 | { 2524 | return send_msg(Opcode::PING, app_data.data(), app_data.size()); 2525 | } 2526 | 2527 | /// @brief send a PONG frame 2528 | /// 2529 | /// @retval sent size. bytes 2530 | /// @exception SystemErrorException 2531 | ssize_t send_pong() 2532 | { 2533 | return send_msg(Opcode::PONG, nullptr, 0); 2534 | } 2535 | 2536 | /// @brief send a PONG frame with a text app data 2537 | /// 2538 | /// @param [in] app_data: app data 2539 | /// @retval sent size. bytes 2540 | /// @exception SystemErrorException 2541 | ssize_t send_pong(const std::string& app_data) 2542 | { 2543 | DECLARE_CALLEE(callee, WSMETHOD, "(app_data=0x" << std::hex << app_data << ")"); 2544 | alog::scoped slog(callee.str()); 2545 | 2546 | return send_msg(Opcode::PONG, app_data.data(), app_data.size()); 2547 | } 2548 | 2549 | /// @brief send a PONG frame with a binary app data 2550 | /// 2551 | /// @param [in] app_data: app data 2552 | /// @retval sent size. bytes 2553 | /// @exception SystemErrorException 2554 | ssize_t send_pong(const std::vector& app_data) 2555 | { 2556 | DECLARE_CALLEE(callee, WSMETHOD, "(app_data=0x" << std::hex << &app_data << ")"); 2557 | alog::scoped slog(callee.str()); 2558 | 2559 | return send_msg(Opcode::PONG, app_data.data(), app_data.size()); 2560 | } 2561 | 2562 | /// @brief send a PONG frame with a binary app data 2563 | /// 2564 | /// @param [in] app_data: app data 2565 | /// @retval sent size. bytes 2566 | /// @exception SystemErrorException 2567 | template ssize_t send_pong(const std::array& app_data) 2568 | { 2569 | DECLARE_CALLEE(callee, WSMETHOD, "(app_data=0x" << std::hex << &app_data << ")"); 2570 | alog::scoped slog(callee.str()); 2571 | 2572 | return send_msg(Opcode::PONG, app_data.data(), app_data.size()); 2573 | } 2574 | 2575 | /// @brief send CLOSE frame. send CLOSE frame, then wait a response (maybe CLOSE frame) or wait closing socket from the remote. 2576 | /// 2577 | /// @param [in] status_code: status code 2578 | /// @exception LwsockException, SystemErrorException 2579 | void send_close(const uint16_t status_code) 2580 | { 2581 | send_close(status_code, "", Timespec()); 2582 | } 2583 | 2584 | /// @brief send CLOSE frame. send CLOSE frame, then wait a response (maybe CLOSE frame) or wait closing socket from the remote. 2585 | /// 2586 | /// @param [in] status_code: status code 2587 | /// @param [in] reason: reason string 2588 | /// @exception LwsockException, SystemErrorException 2589 | void send_close(const uint16_t status_code, const std::string& reason) 2590 | { 2591 | send_close(status_code, reason, Timespec()); 2592 | } 2593 | 2594 | /// @brief send CLOSE frame with timeout. send CLOSE frame, then wait a response (maybe CLOSE frame) or wait closing socket from the remote. 2595 | /// 2596 | /// @param [in] status_code: status code 2597 | /// @param [in] reason: reason 2598 | /// @param [in] timeout: specify timeout. Timespec instance 2599 | /// @exception LwsockException, SystemErrorException 2600 | void send_close(const uint16_t status_code, const std::string& reason, const Timespec& timeout) 2601 | { 2602 | DECLARE_CALLEE(callee, WSMETHOD, "(status_code=" << status_code << ", reason=\"" << reason << "\", timeout=" << timeout.to_string() << ")"); 2603 | alog::scoped slog(callee.str()); 2604 | 2605 | std::vector appdata(sizeof status_code + reason.size()); 2606 | { 2607 | uint16_t be_scode = htons(status_code); // big endian status code 2608 | uint8_t* p = &appdata[0]; 2609 | ::memcpy(p, &be_scode, sizeof be_scode); 2610 | p += sizeof be_scode; 2611 | ::memcpy(p, reason.data(), reason.size()); 2612 | } 2613 | try { 2614 | send_msg(Opcode::CLOSE, appdata.data(), appdata.size()); 2615 | } 2616 | catch (SystemErrorException& see) { 2617 | if (see.code() == EBADF || see.code() == EPIPE) { 2618 | ; // nop 2619 | } 2620 | else { 2621 | throw see; 2622 | } 2623 | } 2624 | close_websocket(sfd_, timeout); 2625 | } 2626 | 2627 | /// @brief get Sockaddr about the remote 2628 | /// 2629 | /// @retval Sockaddr 2630 | Sockaddr remote() 2631 | { 2632 | return remote_; 2633 | } 2634 | 2635 | /// @brief get the request path 2636 | /// 2637 | /// @retval path (e.g. "/path/a/b/c") 2638 | std::string path() 2639 | { 2640 | return path_; 2641 | } 2642 | 2643 | /// @brief get the request query parameters. 2644 | /// 2645 | /// @retval query (e.g. "?aa=123&bb=xyz") 2646 | std::string query() 2647 | { 2648 | return query_; 2649 | } 2650 | 2651 | /// @brief get the Origin header's value in the request headers, if client is a web browser. 2652 | /// 2653 | /// @retval a value of Origin header 2654 | std::string origin() 2655 | { 2656 | return origin_; 2657 | } 2658 | 2659 | 2660 | /// @brief get the raw sockfd that connected or accepted. you must not close it. 2661 | /// 2662 | /// @retval raw sockfd 2663 | /// @note: you must not close the socket 2664 | int sfd_ref() 2665 | { 2666 | return sfd_; 2667 | } 2668 | 2669 | /// @brief get the raw sockfd that connected or accepted. 2670 | /// 2671 | /// @retval raw sockfd 2672 | /// @note: you must close the socket yourself when sockfd was no necessary 2673 | int sfd_mv() 2674 | { 2675 | int sfd = sfd_; 2676 | init(); 2677 | return sfd; 2678 | } 2679 | 2680 | /// @brief get reference of binded sockfds 2681 | /// 2682 | /// @retval sockfds 2683 | /// @note: you must not close the socket 2684 | const std::vector& bind_sfds() 2685 | { 2686 | return bind_sfds_; 2687 | } 2688 | 2689 | /// @brief set ostream for log. output log to the ostream 2690 | /// 2691 | /// @param [in] ost: ostream 2692 | /// @retval reference of *this 2693 | WebSocket& ostream4log(std::ostream& ost) 2694 | { 2695 | log_.ostream(ost); 2696 | return *this; 2697 | } 2698 | 2699 | /// @briaf set log level 2700 | /// 2701 | /// @param [in] lvl: log level 2702 | /// @retval reference of *this 2703 | WebSocket& loglevel(LogLevel lvl) 2704 | { 2705 | log_.level(lvl); 2706 | return *this; 2707 | } 2708 | 2709 | /// @brief get now log level 2710 | 2711 | /// @retval now log level 2712 | LogLevel loglevel() 2713 | { 2714 | return log_.level(); 2715 | } 2716 | 2717 | private: 2718 | void init() 2719 | { 2720 | //mode_ = Mode::NONE; 2721 | sfd_ = -1; 2722 | host_.clear(); 2723 | port_ = 0; 2724 | path_.clear(); 2725 | query_.clear(); 2726 | nonce_.clear(); 2727 | recved_rest_buff_ = std::vector(); 2728 | remote_ = Sockaddr(); 2729 | } 2730 | 2731 | /// @brief close sockfd 2732 | /// 2733 | /// @param [in out] sfd: sockfd 2734 | void close_socket(int& sfd) 2735 | { 2736 | DECLARE_CALLEE(callee, WSMETHOD, "(sfd=" << sfd << ")"); 2737 | if (sfd != -1) { 2738 | alog::scoped slog(LogLevel::DEBUG, callee.str()); 2739 | 2740 | log_.i() << "::close(sfd=" << sfd << ')' << std::endl; 2741 | 2742 | int ret = ::close(sfd); 2743 | if (ret == -1) { 2744 | int err = errno; 2745 | std::ostringstream oss; 2746 | oss << "::close(sfd=" << sfd << ')' << std::endl; 2747 | Error(err, oss.str()); 2748 | } 2749 | 2750 | sfd = -1; 2751 | 2752 | if (!recved_rest_buff_.empty()) { 2753 | recved_rest_buff_.clear(); 2754 | } 2755 | } 2756 | } 2757 | 2758 | /// @brief close websocket with timeout. refered RFC6455 7.1.1. 2759 | /// 2760 | /// @param [in out] sfd: sockfd 2761 | /// @param [in] timeout: specify timeout. Timespec instance 2762 | /// @exception SystemErrorException 2763 | void close_websocket(int& sfd, const Timespec& timeout) 2764 | { 2765 | DECLARE_CALLEE(callee, WSMETHOD, "(sfd=" << sfd << ", timeout=" << timeout.to_string() << ")"); 2766 | alog::scoped slog(LogLevel::DEBUG, callee.str()); 2767 | 2768 | if (sfd == -1) { 2769 | return; 2770 | } 2771 | 2772 | if (::shutdown(sfd, SHUT_WR) == 0) { 2773 | uint8_t buff[16] = {0}; 2774 | try { 2775 | recv_with_timeout(sfd, buff, sizeof buff, timeout); 2776 | } 2777 | catch (SystemErrorException& see) { 2778 | if (see.code() == EBADF || see.code() == ECONNREFUSED) { 2779 | ; // nop 2780 | } 2781 | else { 2782 | throw see; 2783 | } 2784 | } 2785 | } 2786 | close_socket(sfd); 2787 | } 2788 | 2789 | /// @brief send opening handshake 2790 | /// 2791 | /// @param [in] handshake_data: handshake data 2792 | /// @retval string transformed handshake data 2793 | /// @exception SystemErrorException 2794 | std::string send_ohandshake(const handshake_t& handshake_data) 2795 | { 2796 | DECLARE_CALLEE(callee, WSMETHOD, "(handshake_data=" << std::hex << &handshake_data << ")"); 2797 | alog::scoped slog(LogLevel::DEBUG, callee.str()); 2798 | 2799 | std::string first_line = handshake_data.first; 2800 | headers_t headers = handshake_data.second; 2801 | 2802 | std::ostringstream oss; 2803 | oss << first_line; 2804 | for (auto& header : headers) { 2805 | oss << header.first << ": " << header.second << EOL; 2806 | } 2807 | oss << EOL; 2808 | //log_(LogLevel::DEBUG) << "\"" << oss.str() << "\" size=" << std::dec << oss.str().size() << std::endl; 2809 | log_.d() << '\"' << oss.str() << '\"' << std::endl; 2810 | 2811 | size_t ret = send_fill(sfd_, oss.str().c_str(), oss.str().size()); 2812 | assert(ret == oss.str().size()); 2813 | log_.d() << "sent size=" << oss.str().size() << std::endl; 2814 | return oss.str(); 2815 | } 2816 | 2817 | /// @brief receive a message with timeout 2818 | /// 2819 | /// @param [in] timeout: specify timeout. Timespec instance 2820 | /// @retval pair::first: received a message. if websocket was closed from the remote, then size()==0
2821 | /// pair::second: staus code when websocket is closed from the remote
2822 | /// @exception LwsockException, SystemErrorException 2823 | template std::pair recv_msg(const Timespec& timeout) 2824 | { 2825 | static_assert(std::is_base_of::value || std::is_base_of>::value, "Require T is std::string or std::vector"); 2826 | 2827 | assert(sfd_ != -1); 2828 | assert(timeout >= -1); 2829 | 2830 | DECLARE_CALLEE(callee, WSMETHOD, "(timeout=" << timeout.to_string() << ")"); 2831 | alog::scoped slog(LogLevel::DEBUG, callee.str()); 2832 | 2833 | std::pair result{{}, 0}; 2834 | AHead ahead; 2835 | bool txtflg = false; 2836 | do { 2837 | log_.d() << " [[ receive a part of header ..." << std::endl; 2838 | ssize_t ret = recv_fill(sfd_, ahead.data_ptr(), ahead.size(), timeout); 2839 | if (ret == 0) { // socket is closed. 2840 | result.first.clear(); 2841 | result.second = 1006; // RFC6455 7.1.5. "The WebSocket Connection Close Code is considered to be 1006." 2842 | close_socket(sfd_); 2843 | return result; 2844 | } 2845 | log_.d() << " ]] receive a part of header...result=" 2846 | << " raw=0x" << std::hex << std::setw(4) << std::setfill('0') << ahead.data() << std::dec 2847 | << ", fin=" << ahead.fin() << ", rsv1=" << ahead.rsv1() << ", rsv2=" << ahead.rsv2() << ", rsv3=" << ahead.rsv3() 2848 | << ", opcode=0x" << std::hex << std::setw(2) << std::setfill('0') << as_int(ahead.opcode()) << std::setw(0) << std::dec 2849 | << ", mask=" << ahead.mask() 2850 | << ", payload_len=" << ahead.payload_len() << std::endl 2851 | ; 2852 | 2853 | if (ahead.opcode() == Opcode::TEXT) { 2854 | txtflg = true; 2855 | } 2856 | if (ahead.rsv1() != 0 || ahead.rsv2() != 0 || ahead.rsv3() != 0) { 2857 | int err = as_int(LwsockErrc::FRAME_ERROR); 2858 | std::ostringstream oss; 2859 | oss << callee.str() << " rsv1=" << ahead.rsv1() << ", rsv2=" << ahead.rsv2() << ", rsv3=" << ahead.rsv3(); 2860 | log_.w() << oss.str() << std::endl; 2861 | close_websocket(sfd_, timeout); 2862 | throw LwsockException(Error(err, __LINE__, oss.str())); 2863 | } 2864 | 2865 | uint64_t payload_len = 0; 2866 | switch (ahead.payload_len()) { 2867 | case 126: 2868 | { 2869 | uint16_t tmp = 0; 2870 | ret = recv_fill(sfd_, &tmp, sizeof tmp, timeout); 2871 | // TODO ret <= 0 case 2872 | payload_len = ntohs(tmp); 2873 | } 2874 | break; 2875 | case 127: 2876 | { 2877 | uint64_t tmp = 0; 2878 | // for environment without be64ton() or betoh64() 2879 | auto be2h64 = [](uint64_t be) -> uint64_t { 2880 | uint64_t ret = 0; 2881 | uint32_t* p32_1 = reinterpret_cast(&be); 2882 | uint32_t* p32_2 = p32_1 + 1; 2883 | uint32_t le_1 = ntohl(*p32_1); 2884 | uint32_t le_2 = ntohl(*p32_2); 2885 | uint32_t* r1 = reinterpret_cast(&ret); 2886 | uint32_t* r2 = r1 + 1; 2887 | ::memcpy(r1, &le_2, sizeof le_2); 2888 | ::memcpy(r2, &le_1, sizeof le_1); 2889 | return ret; 2890 | }; 2891 | ret = recv_fill(sfd_, &tmp, sizeof tmp, timeout); 2892 | // TODO ret <= 0 case 2893 | payload_len = be2h64(tmp); 2894 | } 2895 | break; 2896 | default: 2897 | payload_len = ahead.payload_len(); 2898 | break; 2899 | } 2900 | log_.d() << " eventually payload len=" << payload_len << std::endl; 2901 | 2902 | if ((mode_ == Mode::SERVER && ahead.mask() == 0) || (mode_ == Mode::CLIENT && ahead.mask() == 1)) { 2903 | int err = as_int(LwsockErrc::BAD_MESSAGE); 2904 | std::ostringstream oss; 2905 | oss << callee.str() << "received invalid maskbit=" << ahead.mask() << ", then send CLOSE 1002 frame, then close socket=" << sfd_; 2906 | send_close(1002); 2907 | close_socket(sfd_); 2908 | throw LwsockException(Error(err, __LINE__, oss.str())); 2909 | } 2910 | 2911 | uint32_t masking_key = 0; 2912 | if (ahead.mask()) { 2913 | log_.d() << " [[ receive masking key..." << std::endl; 2914 | ret = recv_fill(sfd_, &masking_key, sizeof masking_key, timeout); 2915 | // TODO ret == 0 case 2916 | log_.d() << " ]] receive masking key...raw=0x" << std::hex << std::setw(8) << std::setfill('0') << masking_key << std::endl; 2917 | } 2918 | 2919 | // receive payload data 2920 | std::vector tmp_recved(payload_len); 2921 | ret = recv_fill(sfd_, &tmp_recved[0], tmp_recved.size(), timeout); 2922 | // TODO ret == 0 case 2923 | 2924 | std::vector payload_data; 2925 | if (ahead.mask()) { 2926 | payload_data = mask_data(tmp_recved.data(), tmp_recved.size(), masking_key); 2927 | } 2928 | else { 2929 | payload_data = std::move(tmp_recved); 2930 | } 2931 | 2932 | switch (ahead.opcode()) { 2933 | case Opcode::CONTINUE: 2934 | case Opcode::TEXT: 2935 | case Opcode::BINARY: 2936 | break; 2937 | 2938 | case Opcode::PING: 2939 | { 2940 | log_.i() << "received Ping frame. app_data_sz=" << payload_data.size() << ", then send PONG" << std::endl; 2941 | send_pong(payload_data); 2942 | } 2943 | continue; 2944 | case Opcode::PONG: 2945 | log_.i() << "received Pong frame. app_data_sz=" << payload_data.size() << std::endl; 2946 | continue; 2947 | case Opcode::CLOSE: 2948 | { 2949 | uint16_t scode = 0; // status code; 2950 | std::string reason; 2951 | if (payload_data.size() > 0) { 2952 | uint16_t be_scode = 0; // big endian status code 2953 | ::memcpy(&be_scode, payload_data.data(), sizeof be_scode); 2954 | scode = ntohs(be_scode); // status code 2955 | log_.i() << "received CLOSE frame from the remote, status_code=" << std::dec << scode << ", then send CLOSE" << std::endl; 2956 | result.first.clear(); 2957 | result.second = scode; 2958 | } 2959 | else { 2960 | log_.i() << "received CLOSE frame from the remote, status_code is none," << ", then send CLOSE" << std::endl; 2961 | result.first.clear(); 2962 | result.second = 1005; 2963 | reason = "RFC6455 7.1.5. \"If this Close control frame contains no status code, The WebSocket Connection Close Code is considered to be 1005.\""; 2964 | } 2965 | try { 2966 | if (scode != 0) { 2967 | send_close(scode, reason, timeout); 2968 | } 2969 | else { 2970 | send_close(timeout); 2971 | } 2972 | } 2973 | catch (SystemErrorException& e) { 2974 | if (e.code() == EBADF || e.code() == EPIPE) { 2975 | ; // nop. socket is closed already 2976 | } 2977 | else { 2978 | throw e; 2979 | } 2980 | } 2981 | close_socket(sfd_); 2982 | } 2983 | continue; 2984 | 2985 | default: // faild 2986 | // TODO 2987 | close_socket(sfd_); 2988 | continue; 2989 | } 2990 | // append received data 2991 | std::copy(std::begin(payload_data), std::end(payload_data), std::back_inserter(result.first)); 2992 | } while (ahead.fin() == 0 && ahead.opcode() != Opcode::CLOSE); 2993 | 2994 | if (txtflg) { 2995 | // TODO 2996 | // Check if result is UTF-8 data. 2997 | } 2998 | 2999 | return result; 3000 | } 3001 | 3002 | /// @brief send a message 3003 | /// 3004 | /// @pwaram [in] opcode: opcode 3005 | /// @param [in] payload_data_org : extension data + app data 3006 | /// @param [in] payload_data_sz: payload_data_org object size. bytes 3007 | /// @retval sent size 3008 | /// @exception SystemErrorException 3009 | ssize_t send_msg(Opcode opcode, const void* payload_data_org, const size_t payload_data_sz) 3010 | { 3011 | assert(mode_ == Mode::CLIENT || mode_ == Mode::SERVER); 3012 | assert(sfd_ != -1); 3013 | assert(opcode == Opcode::TEXT || opcode == Opcode::BINARY || opcode == Opcode::CLOSE || opcode == Opcode::PING); 3014 | 3015 | DECLARE_CALLEE(callee, WSMETHOD, "(opcode=0x" << std::hex << std::setw(2) << std::setfill('0') << as_int(opcode) << std::setw(0) << ", payload_data_org=" << payload_data_org << ", payload_data_sz=" << std::dec << payload_data_sz << ")"); 3016 | alog::scoped slog(LogLevel::DEBUG, callee.str()); 3017 | 3018 | AHead ahead; 3019 | ahead.fin(1); 3020 | ahead.opcode(opcode); 3021 | ahead.mask(mode_ == Mode::CLIENT ? 1 : 0); 3022 | 3023 | union { 3024 | uint16_t bit16; 3025 | uint64_t bit64; 3026 | } ext_payload_len = {0}; 3027 | if (payload_data_sz <= 125) { 3028 | ahead.payload_len(payload_data_sz); 3029 | } 3030 | else if (payload_data_sz <= 0xffff) { 3031 | ahead.payload_len(126); 3032 | ext_payload_len.bit16 = htons(payload_data_sz); 3033 | } 3034 | else { 3035 | ahead.payload_len(127); 3036 | ext_payload_len.bit64 = htobe64(payload_data_sz); 3037 | } 3038 | uint64_t frame_sz 3039 | = ahead.size() 3040 | + (ahead.payload_len() <= 125 ? 0 : ahead.payload_len() == 126 ? sizeof ext_payload_len.bit16 : sizeof ext_payload_len.bit64) // Extended payload length 3041 | + (mode_ == Mode::CLIENT ? 4 : 0) // for masking key 3042 | + payload_data_sz 3043 | ; 3044 | 3045 | std::vector frame(frame_sz); 3046 | uint8_t* p = &frame[0]; 3047 | 3048 | ::memcpy(p, ahead.data_ptr(), ahead.size()); 3049 | p += ahead.size(); 3050 | 3051 | if (ahead.payload_len() == 126) { 3052 | ::memcpy(p, &ext_payload_len.bit16, sizeof ext_payload_len.bit16); 3053 | p += sizeof ext_payload_len.bit16; 3054 | } 3055 | else if (ahead.payload_len() == 127) { 3056 | ::memcpy(p, &ext_payload_len.bit64, sizeof ext_payload_len.bit64); 3057 | p += sizeof ext_payload_len.bit64; 3058 | } 3059 | 3060 | if (mode_ == Mode::CLIENT) { 3061 | std::mt19937 rd; 3062 | std::uniform_int_distribution dist(0, 0xffffffff); 3063 | uint32_t masking_key = dist(rd); 3064 | 3065 | ::memcpy(p, &masking_key, sizeof masking_key); 3066 | p += sizeof masking_key; 3067 | 3068 | std::vector payload_data = mask_data(payload_data_org, payload_data_sz, masking_key); 3069 | ::memcpy(p, payload_data.data(), payload_data_sz); 3070 | } 3071 | else { 3072 | ::memcpy(p, payload_data_org, payload_data_sz); 3073 | } 3074 | 3075 | ssize_t sentsz = send_fill(sfd_, frame.data(), frame.size()); 3076 | 3077 | slog.clear() << "WebSocket::send_msg(opcode=0x" << std::hex << std::setw(2) << std::setfill('0') << as_int(opcode) << std::dec << ", ...) total sent size=" << sentsz; 3078 | 3079 | return sentsz; 3080 | } 3081 | 3082 | /// @brief send data untill specified size 3083 | /// 3084 | /// @param [in] sfd: sockfd 3085 | /// @param [in] buff: data pointer 3086 | /// @param [in] buffsz: buff object size 3087 | /// @retval sent size 3088 | /// @exception SystemErrorException 3089 | ssize_t send_fill(int sfd, const void* buff, const size_t buffsz) 3090 | { 3091 | DECLARE_CALLEE(callee, WSMETHOD, "(sfd=" << sfd << ", buff=" << std::hex << buff << ", buffsz=" << std::dec << buffsz << ")"); 3092 | alog::scoped slog(LogLevel::DEBUG, callee.str()); 3093 | 3094 | const uint8_t* ptr = static_cast(buff); 3095 | size_t sent_sz = 0; 3096 | 3097 | while (sent_sz < buffsz) { 3098 | int ret = ::send(sfd, ptr, buffsz - sent_sz, MSG_NOSIGNAL); 3099 | if (ret < 0) { 3100 | int err = errno; 3101 | std::ostringstream oss; 3102 | oss << callee.str() << " ::send(sfd=" << sfd << ", ...)"; 3103 | throw SystemErrorException(Error(err, __LINE__, oss.str())); 3104 | } 3105 | 3106 | ptr += ret; 3107 | sent_sz += ret; 3108 | 3109 | // Urge context switching. 3110 | struct timespec ts{0, 1}; 3111 | nanosleep(&ts, nullptr); 3112 | } 3113 | 3114 | slog.clear() << WSMETHOD << "(sfd=" << sfd << ", ...) result=" << sent_sz; 3115 | return sent_sz; 3116 | } 3117 | 3118 | /// @brief recv(2) with timeout. 3119 | /// 3120 | /// @param [in] sfd: sockfd 3121 | /// @param [out] buff: buffer pointer 3122 | /// @param [in] buffsz: buffer size 3123 | /// @param [in] timeout: specify timeout. Timespec instance 3124 | /// @reval > 0 received size 3125 | /// @reval ==0 socket was closed 3126 | /// @exception SystemErrorException 3127 | ssize_t recv_with_timeout(int sfd, void* buff, size_t buffsz, const Timespec& timeout) 3128 | { 3129 | DECLARE_CALLEE(callee, WSMETHOD, "(sfd=" << sfd << ", buff=" << std::hex << buff << ", buffsz=" << std::dec << buffsz << ", timeout=" << timeout.to_string() << ")"); 3130 | alog::scoped slog(LogLevel::DEBUG, callee.str()); 3131 | 3132 | fd_set rfd; 3133 | FD_ZERO(&rfd); 3134 | FD_SET(sfd, &rfd); 3135 | int nfds = sfd + 1; 3136 | int ret = pselect(nfds, &rfd, nullptr, nullptr, timeout.ptr(), nullptr); 3137 | if (ret == 0) { 3138 | int err = as_int(LwsockErrc::TIMED_OUT); 3139 | std::ostringstream oss; 3140 | oss << callee.str() << " ::peslect(nfds=" << nfds << ", ...) TIMED OUT."; 3141 | throw SystemErrorException(Error(err, __LINE__, oss.str())); 3142 | } 3143 | else if (ret == -1) { 3144 | int err = errno; 3145 | std::ostringstream oss; 3146 | oss << callee.str() << " ::pselect(nfds=" << nfds << ", ...) errno=" << err; 3147 | throw SystemErrorException(Error(err, __LINE__, oss.str())); 3148 | } 3149 | 3150 | ssize_t result = recv(sfd, buff, buffsz, 0); 3151 | if (result == -1) { 3152 | int err = errno; 3153 | std::ostringstream oss; 3154 | oss << callee.str() << " ::recv(sfd=" << sfd << ", ...) error."; 3155 | throw SystemErrorException(Error(err, __LINE__, oss.str())); 3156 | } 3157 | 3158 | slog.clear() << WSMETHOD << "(sfd=" << sfd << ", ...) result=" << result; 3159 | return result; 3160 | } 3161 | 3162 | /// @brief receive untill specified size with timeout 3163 | /// 3164 | /// @param [in] sfd: sockfd 3165 | /// @param [out] buff: buffer's pointer 3166 | /// @param [in] expect_sz: expect size 3167 | /// @param [in] timeout: specify timeout. Timespec instance 3168 | /// @reval > 0 received size 3169 | /// @reval ==0 socket was closed 3170 | /// @exception LwsockException, SystemErrorException 3171 | ssize_t recv_fill(int sfd, void* buff, const size_t expect_sz, const Timespec& timeout) 3172 | { 3173 | assert(sfd != -1); 3174 | 3175 | DECLARE_CALLEE(callee, WSMETHOD, "(sfd=" << sfd << ", buff=" << std::hex << buff << ", expect_sz=" << std::dec << expect_sz << ", timeout=" << timeout.to_string() << ")"); 3176 | alog::scoped slog(LogLevel::DEBUG, callee.str()); 3177 | 3178 | uint8_t* ptr = static_cast(buff); 3179 | size_t recved_sz = 0; 3180 | 3181 | // if received data when opening handshake is rest, then copy it 3182 | log_.d() << " recved_rest_buff.size()=" << recved_rest_buff_.size() << std::endl; 3183 | if (!recved_rest_buff_.empty()) { 3184 | size_t sz = recved_rest_buff_.size(); 3185 | if (sz > expect_sz) { 3186 | memcpy(ptr, &recved_rest_buff_[0], expect_sz); 3187 | std::vector rest(std::begin(recved_rest_buff_)+expect_sz, std::end(recved_rest_buff_)); 3188 | recved_rest_buff_ = std::move(rest); 3189 | return expect_sz; 3190 | } 3191 | memcpy(ptr, &recved_rest_buff_[0], sz); 3192 | recved_rest_buff_.clear(); 3193 | ptr += sz; 3194 | recved_sz += sz; 3195 | } 3196 | 3197 | ssize_t ret = 0; 3198 | while (recved_sz < expect_sz && (ret = recv_with_timeout(sfd, ptr, expect_sz - recved_sz, timeout)) > 0) { 3199 | ptr += ret; 3200 | recved_sz += ret; 3201 | 3202 | // Urge context switching. 3203 | struct timespec ts{0, 1}; 3204 | nanosleep(&ts, nullptr); 3205 | } 3206 | 3207 | ret = recved_sz == expect_sz ? recved_sz : ret; 3208 | 3209 | slog.clear() << "WebSocket::recv_fill(sfd=" << sfd << ", ...) result=" << ret; 3210 | return ret; 3211 | } 3212 | 3213 | /// @brief receive untill CRLFCRLF. if there is data after CRLFCRLF, save it 3214 | /// 3215 | /// @param [in] sfd: sockfd 3216 | /// @param [in] timeout: specify timeout. Timespec instance 3217 | /// @retval received data 3218 | /// @exception LwsockException, SystemErrorException 3219 | std::string recv_until_eoh(int sfd, const Timespec& timeout) 3220 | { 3221 | assert(recved_rest_buff_.empty()); 3222 | 3223 | DECLARE_CALLEE(callee, WSMETHOD, "(sfd=" << sfd << ", timeout=" << timeout.to_string() << ")"); 3224 | alog::scoped slog(LogLevel::DEBUG, callee.str()); 3225 | 3226 | constexpr std::string::size_type NPOS = std::string::npos; 3227 | std::string recved_msg; 3228 | 3229 | constexpr char EOH[] = "\r\n\r\n"; // end of header 3230 | std::string::size_type pos = NPOS; 3231 | while ((pos = recved_msg.find(EOH)) == NPOS) { 3232 | char tmp[512] = {0}; 3233 | ssize_t ret = recv_with_timeout(sfd, tmp, (sizeof tmp) -1, timeout); // -1 is that tmp[] has at least '\0'. 3234 | if (ret == 0) { 3235 | int err = as_int(LwsockErrc::SOCKET_CLOSED); 3236 | std::ostringstream oss; 3237 | oss << callee.str() << " socket was closed from the remote."; 3238 | throw LwsockException(Error(err, __LINE__, oss.str())); 3239 | } 3240 | recved_msg += tmp; 3241 | 3242 | // Urge context switching. 3243 | struct timespec ts{0, 1}; 3244 | nanosleep(&ts, nullptr); 3245 | } 3246 | 3247 | constexpr int eohsz = sizeof EOH -1; // end of header size 3248 | std::string result = recved_msg.substr(0, pos + eohsz); // result data include CRLFCRLF 3249 | 3250 | // if there is data after crlfcrl, save that data to recved_rest_buff_ 3251 | if (pos + eohsz < recved_msg.size()) { 3252 | std::copy(std::begin(recved_msg) + pos + eohsz, std::end(recved_msg), std::back_inserter(recved_rest_buff_)); 3253 | } 3254 | 3255 | log_.d() << result << std::endl; 3256 | 3257 | return result; 3258 | } 3259 | 3260 | /// @brief send empty body CLOSE frame. 3261 | /// 3262 | /// @param [in] timeout: specify timeout. Timespec instance 3263 | /// this function is when receiving empty body CLOSE frame, then called. 3264 | /// @exception LwsockException, SystemErrorException 3265 | void send_close(const Timespec& timeout) 3266 | { 3267 | DECLARE_CALLEE(callee, WSMETHOD, "(timeout=" << timeout.to_string() << ")"); 3268 | alog::scoped slog(LogLevel::DEBUG, callee.str()); 3269 | send_msg(Opcode::CLOSE, nullptr, 0); 3270 | close_websocket(sfd_, timeout); 3271 | } 3272 | 3273 | /// @brief split headers 3274 | /// 3275 | /// @param [in] lines_msg: headers string 3276 | /// @retval splited headers 3277 | std::vector> split_headers(const std::string& lines_msg) 3278 | { 3279 | DECLARE_CALLEE(callee, WSMETHOD, "(...)"); 3280 | alog::scoped slog(LogLevel::DEBUG, callee.str()); 3281 | 3282 | using size_type = std::string::size_type; 3283 | constexpr size_type NPOS(std::string::npos); 3284 | 3285 | std::vector> headers; 3286 | for (size_type p0 = 0, pos = NPOS; (pos = lines_msg.find(EOL, p0)) != NPOS; p0 = pos + 2) { 3287 | std::string line = lines_msg.substr(p0, pos - p0); 3288 | if (line.empty()) { 3289 | break; 3290 | } 3291 | size_type p = line.find_first_of(':'); 3292 | std::string header_name = line.substr(0, p); 3293 | std::string value = p == NPOS ? "" : trim(line.substr(p+1)); 3294 | log_.d() << " header_name=\"" << header_name << "\", value=\"" << value << '\"' << std::endl; 3295 | headers.push_back(std::make_pair(std::move(header_name), std::move(value))); 3296 | } 3297 | 3298 | return headers; 3299 | } 3300 | 3301 | /// @brief check response headers 3302 | /// 3303 | /// @param [in] hlines: splited headers 3304 | /// @exception LwsockException 3305 | void check_response_headers(const std::vector>& hv_lines) 3306 | { 3307 | DECLARE_CALLEE(callee, WSMETHOD, "(\n"); 3308 | for (auto& e : hv_lines) { 3309 | callee << " " << e.first << ": " << e.second << '\n'; 3310 | } 3311 | callee << ")"; 3312 | alog::scoped slog(LogLevel::DEBUG, callee.str()); 3313 | 3314 | // check "Upgrade" header 3315 | { 3316 | std::string header_name = "Upgrade"; 3317 | auto ite = std::find_if(std::begin(hv_lines), std::end(hv_lines), [&header_name](auto& hv){ 3318 | if (str2lower(hv.first) == str2lower(header_name)) 3319 | { return true; } 3320 | else 3321 | { return false; } 3322 | }); 3323 | if (ite == std::end(hv_lines)) { 3324 | std::ostringstream oss; 3325 | oss << " \"" << header_name << "\" header is not found."; 3326 | throw LwsockException(Error(as_int(LwsockErrc::INVALID_HANDSHAKE), __LINE__, oss.str())); 3327 | } 3328 | if (str2lower(ite->second) != "websocket") { 3329 | std::ostringstream oss; 3330 | oss << " \"" << header_name << ": " << ite->second << "\" dose not include \"websocket\"."; 3331 | throw LwsockException(Error(as_int(LwsockErrc::INVALID_HANDSHAKE), __LINE__, oss.str())); 3332 | } 3333 | } 3334 | 3335 | // check "Connection" header 3336 | { 3337 | std::string header_name = "Connection"; 3338 | auto ite = std::find_if(std::begin(hv_lines), std::end(hv_lines), [&header_name](auto& hv){ 3339 | if (str2lower(hv.first) == str2lower(header_name)) 3340 | { return true; } 3341 | else 3342 | { return false; } 3343 | }); 3344 | if (ite == std::end(hv_lines)) { 3345 | std::ostringstream oss; 3346 | oss << " \"" << header_name << "\" header is not found."; 3347 | throw LwsockException(Error(as_int(LwsockErrc::INVALID_HANDSHAKE), __LINE__, oss.str())); 3348 | } 3349 | std::string str = str2lower(ite->second); 3350 | std::string token = str2lower("Upgrade"); 3351 | if (str.find(token) == std::string::npos) { 3352 | std::ostringstream oss; 3353 | oss << " \"" << header_name << ": " << ite->second << "\" dose not include \"Upgrade\"."; 3354 | throw LwsockException(Error(as_int(LwsockErrc::INVALID_HANDSHAKE), __LINE__, oss.str())); 3355 | } 3356 | } 3357 | 3358 | // check "Sec-WebSocket-Accept" 3359 | { 3360 | std::string header_name = "Sec-WebSocket-Accept"; 3361 | auto ite = std::find_if(std::begin(hv_lines), std::end(hv_lines), [&header_name](auto& hv){ 3362 | if (str2lower(hv.first) == str2lower(header_name)) 3363 | { return true; } 3364 | else 3365 | { return false; } 3366 | }); 3367 | if (ite == std::end(hv_lines)) { 3368 | std::ostringstream oss; 3369 | oss << " \"" << header_name << "\" header is not found."; 3370 | throw LwsockException(Error(as_int(LwsockErrc::INVALID_HANDSHAKE), __LINE__, oss.str())); 3371 | } 3372 | std::string key = make_key(nonce_, GUID); 3373 | if (ite->second != key) { 3374 | std::ostringstream oss; 3375 | oss << " invalid \"Sec-WebSocket-Accept: " << ite->second << '\"'; 3376 | throw LwsockException(Error(as_int(LwsockErrc::INVALID_HANDSHAKE), __LINE__, oss.str())); 3377 | } 3378 | } 3379 | 3380 | slog.clear() << "WebSocket::check_response_headers() ok"; 3381 | } 3382 | 3383 | /// @brief check request headers 3384 | /// 3385 | /// @param [in] hlines: splited headers 3386 | /// @exception LwsockException 3387 | void check_request_headers(const std::vector>& hv_lines) 3388 | { 3389 | DECLARE_CALLEE(callee, WSMETHOD, "(\n"); 3390 | for (auto& e : hv_lines) { 3391 | callee << " \"" << e.first << "\": \"" << e.second << "\"\n"; 3392 | } 3393 | callee << ")"; 3394 | alog::scoped slog(LogLevel::DEBUG, callee.str()); 3395 | slog.clear() << WSMETHOD << "(...)"; 3396 | 3397 | // check "Host" header existing 3398 | { auto ite = std::find_if(std::begin(hv_lines), std::end(hv_lines), [](auto& hv){ 3399 | if (str2lower(hv.first) == str2lower("Host")) { 3400 | return true; 3401 | } 3402 | else { 3403 | return false; 3404 | } 3405 | }); 3406 | if (ite == std::end(hv_lines)) { 3407 | std::ostringstream oss; 3408 | oss << " \"Host\" header is not found."; 3409 | throw LwsockException(Error(as_int(LwsockErrc::INVALID_HANDSHAKE), oss.str())); 3410 | } 3411 | } 3412 | 3413 | // extract values of "Upgrade" header. it header is possible multiple. 3414 | { 3415 | std::vector values; // "Upgrade" Header's values 3416 | std::for_each(std::begin(hv_lines), std::end(hv_lines), [&values](auto& hvs){ 3417 | if (str2lower(hvs.first) == str2lower("Upgrade")) { 3418 | values.push_back(hvs.second); 3419 | } 3420 | }); 3421 | if (values.empty()) { 3422 | std::ostringstream oss; 3423 | oss << " \"Upgrade\" header is not found."; 3424 | throw LwsockException(Error(as_int(LwsockErrc::INVALID_HANDSHAKE), oss.str())); 3425 | } 3426 | auto ite = std::find_if(std::begin(values), std::end(values), [](const std::string& value){ 3427 | auto str = str2lower(value); 3428 | if (str.find("websocket") != std::string::npos) { 3429 | return true; 3430 | } 3431 | else { 3432 | return false; 3433 | } 3434 | }); 3435 | if (ite == std::end(values)) { 3436 | std::ostringstream oss; 3437 | oss << " \"Upgrade\" header does not have the value of \"websocket\"."; 3438 | throw LwsockException(Error(as_int(LwsockErrc::INVALID_HANDSHAKE), oss.str())); 3439 | } 3440 | } 3441 | 3442 | // extract values of "Connection" header. it header is possible multiple. 3443 | { 3444 | std::vector values; // "Connection" Header's values 3445 | std::for_each(std::begin(hv_lines), std::end(hv_lines), [&values](auto& hv){ 3446 | if (str2lower(hv.first) == str2lower("Connection")) { 3447 | values.push_back(hv.second); 3448 | } 3449 | }); 3450 | if (values.empty()) { 3451 | std::ostringstream oss; 3452 | oss << " \"Connection\" header is not found."; 3453 | throw LwsockException(Error(as_int(LwsockErrc::INVALID_HANDSHAKE), oss.str())); 3454 | } 3455 | auto ite = std::find_if(std::begin(values), std::end(values), [](const std::string& value){ 3456 | std::string str = str2lower(value); 3457 | std::string token = str2lower("Upgrade"); 3458 | if (str.find(token) != std::string::npos) { 3459 | return true; 3460 | } 3461 | else { 3462 | return false; 3463 | } 3464 | }); 3465 | if (ite == std::end(values)) { 3466 | std::ostringstream oss; 3467 | oss << " \"Connection\" header does not include the value of \"Upgrade\"."; 3468 | throw LwsockException(Error(as_int(LwsockErrc::INVALID_HANDSHAKE), oss.str())); 3469 | } 3470 | } 3471 | 3472 | // search "Sec-WebSocket-Key" 3473 | { 3474 | auto sec_websocket_key_line = std::find_if(std::begin(hv_lines), std::end(hv_lines), [](auto& hv){ 3475 | if (str2lower(hv.first) == str2lower("Sec-WebSocket-Key")) { 3476 | return true; 3477 | } 3478 | else { 3479 | return false; 3480 | } 3481 | }); 3482 | if (sec_websocket_key_line == std::end(hv_lines)) { 3483 | std::ostringstream oss; 3484 | oss << " \"Sec-WebSocket-Key\" header is not found."; 3485 | throw LwsockException(Error(as_int(LwsockErrc::INVALID_HANDSHAKE), oss.str())); 3486 | } 3487 | std::vector value = b64decode(sec_websocket_key_line->second); 3488 | if (value.size() != 16) { 3489 | std::ostringstream oss; 3490 | oss << " \"Sec-WebSocket-Key\" header is invalid size: " << std::to_string(value.size()); 3491 | throw LwsockException(Error(as_int(LwsockErrc::INVALID_HANDSHAKE), oss.str())); 3492 | } 3493 | nonce_ = sec_websocket_key_line->second; 3494 | } 3495 | 3496 | // extract values of "Sec-WebSocket-Version" header. it header is possible multiple. 3497 | { 3498 | std::vector values; // "Sec-WebSocket-Version" Header's values 3499 | std::for_each(std::begin(hv_lines), std::end(hv_lines), [&values](auto& hvs){ 3500 | if (str2lower(hvs.first) == str2lower("Sec-WebSocket-Version")) { 3501 | values.push_back(hvs.second); 3502 | } 3503 | }); 3504 | if (values.empty()) { 3505 | std::ostringstream oss; 3506 | oss << " \"Sec-WebSocket-Version\" header is not found."; 3507 | throw LwsockException(Error(as_int(LwsockErrc::INVALID_HANDSHAKE), oss.str())); 3508 | } 3509 | auto ite = std::find_if(std::begin(values), std::end(values), [](const std::string& value){ 3510 | std::string str = str2lower(value); 3511 | std::string token = "13"; 3512 | if (str.find(token) != std::string::npos) { 3513 | return true; 3514 | } 3515 | else { 3516 | return false; 3517 | } 3518 | }); 3519 | if (ite == std::end(values)) { 3520 | std::ostringstream oss; 3521 | oss << " \"Sec-WebSocket-Version\" header does not include \"13\"."; 3522 | throw LwsockException(Error(as_int(LwsockErrc::INVALID_HANDSHAKE), oss.str())); 3523 | } 3524 | } 3525 | } 3526 | 3527 | /// @biref make a nonce for a opening handshake 3528 | /// 3529 | /// @param nonce 3530 | std::string make_nonce() 3531 | { 3532 | std::mt19937 rd; 3533 | std::uniform_int_distribution dist(0, 0xffffffffffffffff); 3534 | uint64_t tmp = dist(rd); 3535 | uint8_t x1[16] = {0}; 3536 | ::memcpy(&x1[0], &tmp, sizeof tmp); 3537 | ::memcpy(&x1[8], &Magic[0], (sizeof x1)-8); 3538 | std::string nonce = b64encode(x1, sizeof x1); 3539 | return nonce; 3540 | } 3541 | 3542 | /// @brief make a key for a opening handshake 3543 | /// 3544 | /// @param key 3545 | std::string make_key(const std::string& nonce, const std::string& guid) 3546 | { 3547 | std::string key = nonce + guid; 3548 | Sha1::Context_t ctx; 3549 | Sha1::Input(ctx, key.data(), key.size()); 3550 | uint8_t sha1val[Sha1::SHA1_HASH_SIZE] = {0}; 3551 | Sha1::Result(sha1val, sizeof sha1val, ctx); 3552 | std::string b64 = b64encode(sha1val, sizeof sha1val); 3553 | return b64; 3554 | } 3555 | 3556 | /// @brief parse a opening handshake message 3557 | /// 3558 | /// @param [in] received handshake message 3559 | /// @retval parsed handshake message 3560 | /// @exception LwsockException 3561 | handshake_t parse_handshake_msg(const std::string& handshake_msg) 3562 | { 3563 | using size_type = std::string::size_type; 3564 | size_type pos = handshake_msg.find(EOL); 3565 | if (pos == std::string::npos) { 3566 | int err = as_int(LwsockErrc::INVALID_HANDSHAKE); 3567 | std::ostringstream oss; 3568 | oss << "invliad handshake=\"" << handshake_msg << '\"'; 3569 | throw LwsockException(Error(err, oss.str())); 3570 | } 3571 | std::string first_line = handshake_msg.substr(0, pos); 3572 | size_type headers_start_pos = pos + (sizeof EOL -1); // -1 is '\0' 3573 | headers_t headers = split_headers(handshake_msg.substr(headers_start_pos)); 3574 | 3575 | return handshake_t{first_line, headers}; 3576 | } 3577 | 3578 | /// @brief mask data 3579 | /// 3580 | /// @param [in] src: data pointer 3581 | /// @param [in] src_sz: data size. bytes 3582 | /// @param [in] masking_key: masking key 3583 | /// @retval masked data 3584 | std::vector mask_data(const void* src, size_t src_sz, uint32_t masking_key) 3585 | { 3586 | std::vector masked_data(src_sz); 3587 | const uint8_t* p0 = static_cast(src); 3588 | const uint8_t* p1 = reinterpret_cast(&masking_key); 3589 | for (size_t i = 0; i < src_sz; ++i) { 3590 | uint8_t j = i % 4; 3591 | uint8_t ti = p0[i] ^ p1[j]; 3592 | masked_data[i] = ti; 3593 | } 3594 | return masked_data; 3595 | } 3596 | 3597 | alog& log_ = alog::get_instance(); 3598 | Mode mode_ = Mode::NONE; 3599 | int sfd_ = -1; 3600 | std::vector bind_sfds_; 3601 | std::string host_; 3602 | uint32_t port_ = 0; 3603 | std::string path_; 3604 | std::string query_; 3605 | std::string nonce_; 3606 | std::string origin_; 3607 | std::vector recved_rest_buff_; 3608 | Sockaddr remote_; 3609 | }; 3610 | 3611 | } // namespace lwsock 3612 | --------------------------------------------------------------------------------