diff --git a/.clang-format b/.clang-format index 83daf56..b3b621d 100755 --- a/.clang-format +++ b/.clang-format @@ -1,7 +1,7 @@ --- Language: Cpp AccessModifierOffset: -4 -AlignAfterOpenBracket: DontAlign +AlignAfterOpenBracket: Align AlignConsecutiveAssignments: true AlignConsecutiveDeclarations: false AlignEscapedNewlinesLeft: false @@ -40,7 +40,7 @@ MaxEmptyLinesToKeep: 2 NamespaceIndentation: Inner PointerAlignment: Right ReflowComments: true -SortIncludes: true +SortIncludes: false SpaceAfterCStyleCast: true SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements @@ -53,5 +53,5 @@ SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp11 TabWidth: 4 -UseTab: ForIndentation +UseTab: Never ... diff --git a/.editorconfig b/.editorconfig index 7577ddd..08f403d 100755 --- a/.editorconfig +++ b/.editorconfig @@ -2,7 +2,7 @@ root = true [*] -indent_style = tab +indent_style = space indent_size = 4 end_of_line = lf charset = utf-8 @@ -10,12 +10,10 @@ trim_trailing_whitespace = true insert_final_newline = true [*.{html,ejs,css,sass,scss,js,yml}] -indent_style = space indent_size = 2 [*.md] trim_trailing_whitespace = false [{package.json,.travis.yml}] -indent_style = space indent_size = 2 diff --git a/.gitignore b/.gitignore index 9b38f12..b73f9ea 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,5 @@ # Directories build - +.vscode +.wercker diff --git a/README.md b/README.md index 1f008ec..f23f8c6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,146 @@ -# net -Modern C++ network programming library +# NET [![codecov](https://codecov.io/gh/c10k/net/branch/master/graph/badge.svg)](https://codecov.io/gh/c10k/net) + +![Logo](https://cloud.githubusercontent.com/assets/7630575/23608808/1e1de904-0291-11e7-8c6a-003f7a29e59c.png) + +#### Net is a High-level & Efficient Modern C++ library for Network Programming. + +[![wercker status](https://app.wercker.com/status/945fa8542bfa8066980fc234e4e91aee/m/ "wercker status")](https://app.wercker.com/project/byKey/945fa8542bfa8066980fc234e4e91aee) + +# Introduction + +It uses standard library functions with no other dependencies and provides a safer yet expressive version of original Berkeley Sockets API. Common mistakes are abstracted away giving a boost in productivity for library user. + +```cpp +#include "socket.hpp" +#include + +using namespace net; + +int main() +{ + try { + Socket s(Domain::IPv4, Type::TCP); + s.start("127.0.0.1", 24000); + while (true) { + const auto peer = s.accept(); + const auto msg = peer.recv(15); + std::cout << msg << '\n'; + } + } catch (std::exception &e) { + std::cerr << e.what() << '\n'; + } +} +``` + + +# Getting Started + +You don't need anything else other than a supported platform, a C++14 compiler and a few seconds to spare. The library is currently uses meson build system so first class dependency management is included for those using same. Otherwise just `git clone` the library, and pass the includes and sources to your compiler and you're good to go. + +## Documentation + +Please take a look inside `docs/` directory to find API and other documentation. + +## Build with Meson + +```bash +git clone https://github.com/c10k/net.git +cd net +mkdir build && cd build +meson .. && ninja # This will also create a dynamic lib +``` + +## Testing with GTest + +```bash +git clone https://github.com/c10k/net.git +cd net +mkdir build && cd build +meson .. && ninja +./test/testexe +``` + + +# Examples + +You can see more examples under `examples/` directory. + +## Echo TCP server + +```cpp +#include "socket.hpp" +#include + +using namespace net; + +int main() +{ + try { + Socket s(Domain::IPv4, Type::TCP); + s.start("127.0.0.1", 24000); + while (true) { + const auto peer = s.accept(); + const auto msg = peer.recv(15); + std::cout << msg << '\n'; + } + } catch (std::exception &e) { + std::cerr << e.what() << '\n'; + } +} +``` + +## Hello World TCP client + +```cpp +#include "socket.hpp" +#include + +using namespace net; + +int main() +{ + try { + Socket s(Domain::IPv4, Type::TCP); + s.connect("0.0.0.0", 24000); + s.send("Hello World!"); + } catch (std::exception &e) { + std::cerr << e.what() << '\n'; + } +} +``` + +## Concurrent TCP server with fork + +```cpp +#include "socket.hpp" +#include +#include + +using namespace net; + +int main() +{ + try { + signal(SIGCHLD, SIG_IGN); + Socket s(Domain::IPv4, Type::TCP); + s.start("127.0.0.1", 24001); + + while (true) { + const auto peer = s.accept(); + if (!fork()) { + std::cout << peer.recv(10) << '\n'; + return 0; + } + } + } catch (std::exception &e) { + std::cerr << e.what() << '\n'; + } +} +``` + +# TODO +- [ ] Proper namespacing in socket options +- [ ] Add more unit tests, increase coverage +- [ ] Add examples for non-blocking socket operations +- [ ] Add support for more protocols other than TCP, UDP, UNIX +- [ ] Add benchmarks against popular alternatives diff --git a/docs/socket.md b/docs/socket.md new file mode 100644 index 0000000..69d9b92 --- /dev/null +++ b/docs/socket.md @@ -0,0 +1,708 @@ + +## **low_write** + +Writes given _msg using _sockfd by calling _fn with args havingflags and destination socket address. + +``` + template + auto low_write(Fn &&_fn, const std::string &_msg, Args &&... args) const + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_fn|callable|Some callable that writes using socket descriptor.| +|_msg|string|Msg to write on sockfd.| +|args|parameter_pack|Flags, destination sockaddr objects and theirlengths.| + +### RETURN VALUE +|TYPE | DESCRIPTION | +|------|-------------| +|ssize_t|Status of writing _msg using socket descriptor / Numberof bytes written using socket descriptor.| + + + +___ + +## **low_read** + +Reads using sockfd by calling _fn with args having flags anddestination socket address. + +``` + template + auto low_read(Fn &&_fn, std::string &_str, Args &&... args) const + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_fn|callable|Some callable that reads using socket descriptor.| +|_str|string|String to store the data.| +|args|parameter_pack|Flags, destination sockaddr objects and theirlengths.| + +### RETURN VALUE +|TYPE | DESCRIPTION | +|------|-------------| +|ssize_t|Status of reading data using socket descriptor / Numberof bytes read using socket descriptor.| + + + +___ + +## **Socket** + +None + +``` + Socket(const int, Domain, Type, const void *) +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_sockfd|int|Descriptor representing a socket.| +|_domain|Domain|Socket domain.| +|_domain|Type|Socket type.| +|_addr|void *|Pointer to initialize appropriate member of Union ofnet::Socket.| + +### RETURN VALUE +[] + + +___ + +## **net::Socket** + +None + +``` + Socket(Domain _domain, Type _type, const int _proto = 0) + : sock_domain(_domain), sock_type(_type) + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_domain|domain|Socket domain.| +|_type|type|Socket type.| +|_proto|int|Socket protocol.| + +### RETURN VALUE +[] + + +___ + +## **net::Socket** + +None + +``` + Socket(Socket &&s) + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|s|Socket|Rvalue of type socket.| + +### RETURN VALUE +[] + + +___ + +## **getSocket** + +Get the socket descriptor in net::Socket. + +``` + auto getSocket() const noexcept +``` + +### PARAMETERS: +[] +### RETURN VALUE +|TYPE | DESCRIPTION | +|------|-------------| +|int|Socket descriptor for net::Socket.| + + + +___ + +## **getDomain** + +Get the Domain type of Socket. + +``` + auto getDomain() const noexcept +``` + +### PARAMETERS: +[] +### RETURN VALUE +|TYPE | DESCRIPTION | +|------|-------------| +|Domain|for net::Socket.| + + + +___ + +## **getType** + +Get the Protocol Type of Socket. + +``` + auto getType() const noexcept +``` + +### PARAMETERS: +[] +### RETURN VALUE +|TYPE | DESCRIPTION | +|------|-------------| +|Type|for net::Socket.| + + + +___ + +## **bind** + +Binds net::Socket to local address if successful else if Addressargument is invalid then throws invalid_argument exception else throwsruntime_error exception signalling that bind failed. Invokes the callableprovided to fill AddrIPv4 object. + +``` + template + auto bind(F _fn) -> decltype(_fn(std::declval()), void()) const + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_fn|callable|Some callable that takes arg of type AddrIPv4.| + +### RETURN VALUE +[] + + +___ + +## **bind** + +Binds net::Socket to local address if successful else if Addressargumentis invalid then throws invalid_argument exception else throwsruntime_error exception signalling that bind failed. Invokes the callableprovided to fill AddrIPv6 object. + +``` + template + auto bind(F _fn) -> decltype(_fn(std::declval()), void()) const + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_fn|callable|Some callable that takes arg of type AddrIPv6.| + +### RETURN VALUE +[] + + +___ + +## **bind** + +Binds net::Socket to local address if successful else if Addressargument is invalid then throws invalid_argument exception else throwsruntime_error exception signalling that bind failed. Invokes the callableprovided to fill AddrUnix object. + +``` + template + auto bind(F _fn) -> decltype(_fn(std::declval()), void()) const + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_fn|callable|Some callable that takes arg of type AddrUnix.| + +### RETURN VALUE +[] + + +___ + +## **connect** + +Connects net::Socket to address _addr:_port if successful elsethrows invalid_argument exception. + +``` + void connect(const char[], const int = 0, bool * = nullptr) +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_addr|char []|Ip address in case of ipv4 or ipv6 domain, and Pathin case of unix domain.| +|_port|int|Port number in case of AddrIPv4 or AddrIPv6.| +|_errorNB|bool *|To signal error in case of non-blocking connect.| + +### RETURN VALUE +[] + + +___ + +## **connect** + +Connects net::Socket to ipv4 peer if successful else throwsruntime_error exception.Throws invalid_argument exception in case of non-blocking net::Socket if_errorNB is missing.Throws invalid_argument exception if destination address given is invalid. + +``` + template + auto connect(F _fn, bool *_errorNB = nullptr) + -> decltype(_fn(std::declval()), void()) const + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_fn|callable|Some callable that takes arg of type AddrIPv4.| +|_errorNB|bool *|To signal error in case of non-blocking connect.| + +### RETURN VALUE +[] + + +___ + +## **connect** + +Connects net::Socket to ipv6 peer if successful else throwsruntime_error exception.Throws invalid_argument exception in case of non-blocking net::Socket if_errorNB is missing.Throws invalid_argument exception if destination address given is invalid.Invokes the callable provided to fill AddrIPv4 object. + +``` + template + auto connect(F _fn, bool *_errorNB = nullptr) + -> decltype(_fn(std::declval()), void()) const + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_fn|callable|Some callable that takes arg of type AddrIPv6.| +|_errorNB|bool *|To signal error in case of non-blocking connect.| + +### RETURN VALUE +[] + + +___ + +## **connect** + +Connects net::Socket to unix socket peer if successful else throwsruntime_error exception.Throws invalid_argument exception in case of non-blocking net::Socket if_errorNB is missing.Throws invalid_argument exception if destination address given is invalid.Invokes the callable provided to fill AddrIPv4 object. + +``` + template + auto connect(F _fn, bool *_errorNB = nullptr) + -> decltype(_fn(std::declval()), void()) const + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_fn|callable|Some callable that takes arg of type AddrUnix.| +|_errorNB|bool *|To signal error in case of non-blocking connect.| + +### RETURN VALUE +[] + + +___ + +## **start** + +Starts the net::Socket in listen mode on given ip address and givenport with given backlog if successful else throws runtime_error exception.Throws invalid_argument exception if given ip address or port are notvalid. + +``` + void start(const char[], const int = 0, const int = SOMAXCONN) +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_addr|char []|Ip address in case of ipv4 or ipv6 domain, and Pathin case of unix domain.| +|_port|int|Port number in case of ipv4 or ipv6.| +|_q|int|Size of backlog of listening socket.| + +### RETURN VALUE +[] + + +___ + +## **accept** + +Returns Socket object from connected sockets queue if successfulelse throws runtime_error exception.Throws invalid_argument exception in case of non-blocking net::Socket if_errorNB is missing. + +``` + Socket accept(bool * = nullptr) const +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_errorNB|bool *|To signal error in case of non-blocking accept.| + +### RETURN VALUE +|TYPE | DESCRIPTION | +|------|-------------| +|net::Socket|| + + + +___ + +## **write** + +Writes given string to Socket if successful else throwsruntime_error exception.Throws invalid_argument exception in case of non-blocking net::Socket if_errorNB is missing. + +``` + void write(const std::string &, bool * = nullptr) const +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_msg|string|String to be written to Socket.| +|_errorNB|bool *|To signal error in case of non-blocking write.| + +### RETURN VALUE +[] + + +___ + +## **read** + +Reads given number of bytes using Socket if successful else throwsruntime_error exception.Throws invalid_argument exception in case of non-blocking net::Socket if_errorNB is missing. + +``` + std::string read(const int, bool * = nullptr) const +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_bufSize|int|Number of bytes to be read using Socket.| +|_errorNB|bool *|To signal error in case of non-blocking read.| + +### RETURN VALUE +|TYPE | DESCRIPTION | +|------|-------------| +|string|String of _bufSize bytes read using Socket.| + + + +___ + +## **send** + +Sends given string using Socket if successful else throwsruntime_error exception.Throws invalid_argument exception in case of non-blocking net::Socket if_errorNB is missing. + +``` + void send(const std::string &, Send = Send::NONE, bool * = nullptr) const +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_msg|string|String to be sent using Socket.| +|_flags|send|Modify default behaviour of send.| +|_errorNB|bool *|To signal error in case of non-blocking send.| + +### RETURN VALUE +[] + + +___ + +## **send** + +Sends given string using Socket if successful else throwsruntime_error exception.Throws invalid_argument exception in case of non-blocking net::Socket if_errorNB is missing.Throws invalid_argument exception if destination address given is invalid.Invokes the callable provided to fill AddrIPv4 object. + +``` + template + auto send(const std::string &_msg, F _fn, Send _flags = Send::NONE, + bool *_errorNB = nullptr) const + -> decltype(_fn(std::declval()), void()) const + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_msg|string|String to be sent using Socket.| +|_fn|callable|Some callable that takes arg of type AddrIPv4 orvoid.| +|_flags|send|Modify default behaviour of send.| +|_errorNB|bool *|To signal error in case of non-blocking send.| + +### RETURN VALUE +[] + + +___ + +## **send** + +Sends given string using Socket if successful else throwsruntime_error exception.Throws invalid_argument exception in case of non-blocking net::Socket if_errorNB is missing.Throws invalid_argument exception if destination address given is invalid.Invokes the callable provided to fill AddrIPv6 object. + +``` + template + auto send(const std::string &_msg, F _fn, Send _flags = Send::NONE, + bool *_errorNB = nullptr) const + -> decltype(_fn(std::declval()), void()) const + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_msg|string|String to be sent using Socket.| +|_fn|callable|Some callable that takes arg of type AddrIPv6 orvoid.| +|_flags|send|Modify default behaviour of send.| +|_errorNB|bool *|To signal error in case of non-blocking send.| + +### RETURN VALUE +[] + + +___ + +## **send** + +Sends given string using Socket if successful else throwsruntime_error exception.Throws invalid_argument exception in case of non-blocking net::Socket if_errorNB is missing.Throws invalid_argument exception if destination address given is invalid.Invokes the callable provided to fill AddrUnix object. + +``` + template + auto send(const std::string &_msg, F _fn, Send _flags = Send::NONE, + bool *_errorNB = nullptr) const + -> decltype(_fn(std::declval()), void()) const + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_msg|string|String to be sent using Socket.| +|_fn|callable|Some callable that takes arg of type AddrUnix orvoid.| +|_flags|send|Modify default behaviour of send.| +|_errorNB|bool *|To signal error in case of non-blocking send.| + +### RETURN VALUE +[] + + +___ + +## **recv** + +Reads given number of bytes using Socket if successful else throwsruntime_error exception.Throws invalid_argument exception in case of non-blocking net::Socket if_errorNB is missing. + +``` + std::string recv(const int, Recv = Recv::NONE, bool * = nullptr) const +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_bufSize|int|Number of bytes to be read using Socket.| +|_flags|recv|Modify default behaviour of recv.| +|_errorNB|bool *|To signal error in case of non-blocking recv.| + +### RETURN VALUE +|TYPE | DESCRIPTION | +|------|-------------| +|string|String of _bufSize bytes read from socket.| + + + +___ + +## **recv** + +Reads given number of bytes using Socket if successful else throwsruntime_error exception.Throws invalid_argument exception in case of non-blocking net::Socket if_errorNB is missing.Throws invalid_argument exception if destination address given is invalid.Invokes the callable provided to return AddrIPv4 object from where msg hasbeen received. + +``` + template + auto recv(const int _numBytes, F _fn, Recv _flags = Recv::NONE, + bool *_errorNB = nullptr) const + -> decltype(_fn(std::declval()), std::string()) const + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_numBytes|int|Number of bytes to read.| +|_fn|callable|Some callable that takes arg of type AddrIPv4 orvoid.| +|_flags|recv|Modify default behaviour of recv.| +|_errorNB|bool *|To signal error in case of non-blocking recv.| + +### RETURN VALUE +[] + + +___ + +## **recv** + +Reads given number of bytes using Socket if successful else throwsruntime_error exception.Throws invalid_argument exception in case of non-blocking net::Socket if_errorNB is missing.Throws invalid_argument exception if destination address given is invalid.Invokes the callable provided to return AddrIPv6 object from where msg hasbeen received. + +``` + template + auto recv(const int _numBytes, F _fn, Recv _flags = Recv::NONE, + bool *_errorNB = nullptr) const + -> decltype(_fn(std::declval()), std::string()) const + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_numBytes|int|Number of bytes to read.| +|_fn|callable|Some callable that takes arg of type AddrIPv6 orvoid.| +|_flags|recv|Modify default behaviour of recv.| +|_errorNB|bool *|To signal error in case of non-blocking recv.| + +### RETURN VALUE +[] + + +___ + +## **recv** + +Reads given number of bytes using Socket if successful else throwsruntime_error exception.Throws invalid_argument exception in case of non-blocking net::Socket if_errorNB is missing.Throws invalid_argument exception if destination address given is invalid.Invokes the callable provided to return AddrUnix object from where msg hasbeen received. + +``` + template + auto recv(const int _numBytes, F _fn, Recv _flags = Recv::NONE, + bool *_errorNB = nullptr) const + -> decltype(_fn(std::declval()), std::string()) const + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_numBytes|int|Number of bytes to read.| +|_fn|callable|Some callable that takes arg of type AddrUnix orvoid.| +|_flags|recv|Modify default behaviour of recv.| +|_errorNB|bool *|To signal error in case of non-blocking recv.| + +### RETURN VALUE +[] + + +___ + +## **setOpt** + +Set a socket option from net::Opt for Socket using object of typenet::SockOpt. + +``` + void setOpt(Opt, SockOpt) const +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_opType|net::Opt|Option to set for Socket.| +|_opValue|net::SockOpt|socket option structure. Present insidesocket_family.hpp.| + +### RETURN VALUE +[] + + +___ + +## **getOpt** + +Get value of some socket option for Socket. + +``` + SockOpt getOpt(Opt) const +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_opType|net::Opt|Option of Socket whose value to get.| + +### RETURN VALUE +[] + + +___ + +## **stop** + +Shutdown Socket using net::shut. + +``` + void stop(Shut _s) const noexcept + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_s|net::shut|Option specifying which side of connection toshutdown for Socket.| + +### RETURN VALUE +[] + + +___ + +## **unlink** + +Unlinks the unix socket path. + +``` + bool unlink() const noexcept + +``` + +### PARAMETERS: +[] +### RETURN VALUE +[] + + +___ + +## **close** + +Closes the Socket for terminating connection. + +``` + bool close() const noexcept + +``` + +### PARAMETERS: +[] +### RETURN VALUE +[] + + +___ + \ No newline at end of file diff --git a/docs/socket_family.md b/docs/socket_family.md new file mode 100644 index 0000000..702fbef --- /dev/null +++ b/docs/socket_family.md @@ -0,0 +1,100 @@ + +## **getErrorMsg** + +Returns the standard human readable error message corresponding togiven errorNumber. + +``` + inline std::string getErrorMsg(const int errorNumber) + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|errorNumber|int|Error number whose string to return.| + +### RETURN VALUE +|TYPE | DESCRIPTION | +|------|-------------| +|string|Standard error string corresponding to givenerrorNumber.| + + + +___ + +## **construct** + +Fills the given AddrIPv4 structure object with given ip address andport. + +``` + inline int construct(AddrIPv4 &_addrStruct, const char _addr[], + const int _port) noexcept + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_addrStruct|AddrIPv4|Ipv4 structure object that needs to befilled with given ip address and port.| +|_addr|char []|Ip address which needs to be filled in the AddrIPv4structure object.| +|_port|int|Port number which needs to be filled in the AddrIPv4structure object.| + +### RETURN VALUE +|TYPE | DESCRIPTION | +|------|-------------| +|int|1 if sucessful, 0 if given ip address does not represent avalid ip address, -1 if some error occurred.| + + + +___ + +## **construct** + +Fills the given AddrIPv6 structure object with given ip address andport. + +``` + inline int construct(AddrIPv6 &_addrStruct, const char _addr[], + const int _port) noexcept + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_addrStruct|AddrIPv6|- Ipv6 structure object that needs to befilled with given ip address and port.| +|_addr|char []|Ip address which needs to be filled in the AddrIPv6structure object.| +|_port|int|Port number which needs to be filled in the AddrIPv6structure object.| + +### RETURN VALUE +|TYPE | DESCRIPTION | +|------|-------------| +|int|1 if sucessful, 0 if given ip address does not represent avalid ip address, -1 if some error occurred.| + + + +___ + +## **construct** + +Fills the given AddrUnix structure object with given address. + +``` + inline int construct(AddrUnix &_addrStruct, const char _addr[]) noexcept + +``` + +### PARAMETERS: +| NAME | TYPE | DESCRIPTION | +|------ | ------ | -------------| +|_addrStruct|AddrUnix|structure object that needs to be filledwith given path.| +|_addr|char []|Path which needs to be filled in the AddrUnixstructure object.| + +### RETURN VALUE +|TYPE | DESCRIPTION | +|------|-------------| +|int|Always returns 1.| + + + +___ + \ No newline at end of file diff --git a/examples/meson.build b/examples/meson.build index fb5d10f..9804225 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -1,5 +1,13 @@ -progs = [['net_demo', ['net_demo.cpp']]] +progs = [['socket_tcp_server', ['socket_tcp_server.cpp']], + ['socket_tcp_client', ['socket_tcp_client.cpp']], + ['socket_unix_udp_server',['socket_unix_udp_server.cpp']], + ['socket_unix_udp_client',['socket_unix_udp_client.cpp']], + ['socket_tcp_mt_server', ['socket_tcp_mt_server.cpp']], + ['socket_tcp_mt_client', ['socket_tcp_mt_client.cpp']], + ['socket_tcp_fork_server', ['socket_tcp_fork_server.cpp']], + ['socket_http_fixed_server', ['socket_http_fixed_server.cpp']]] foreach p : progs - executable(p[0], p[1], include_directories : inc, link_with : netlib) + executable(p[0], p[1], include_directories : inc, + link_with : netlib, dependencies: [thread_dep]) endforeach diff --git a/examples/net_demo.cpp b/examples/net_demo.cpp deleted file mode 100644 index 6506191..0000000 --- a/examples/net_demo.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "net.hpp" -#include - -int main(){ - std::cout << net::sayHello(); -} diff --git a/examples/socket_http_fixed_server.cpp b/examples/socket_http_fixed_server.cpp new file mode 100644 index 0000000..6253775 --- /dev/null +++ b/examples/socket_http_fixed_server.cpp @@ -0,0 +1,25 @@ +#include "socket.hpp" +#include + +using namespace net; + +int main() +{ + try { + Socket s(Domain::IPv4, Type::TCP); + s.start("127.0.0.1", 8000); + + std::string response = "HTTP/1.1 200 OK\r\n"; + response += "Content-Type: text/plain\r\n"; + response += "Content-Length: 11\r\n"; + response += "Connection: close\r\n"; + response += "\r\nHello World"; + + while (1) { + const auto peer = s.accept(); + peer.send(response); + } + } catch (std::exception &e) { + std::cerr << e.what() << '\n'; + } +} diff --git a/examples/socket_tcp_client.cpp b/examples/socket_tcp_client.cpp new file mode 100644 index 0000000..e3464a1 --- /dev/null +++ b/examples/socket_tcp_client.cpp @@ -0,0 +1,17 @@ +#include "socket.hpp" +#include + +using namespace net; + +int main() +{ + try { + + Socket s(Domain::IPv4, Type::TCP); + s.connect("0.0.0.0", 24000); + s.send("Hello World!"); + + } catch (std::exception &e) { + std::cerr << e.what() << '\n'; + } +} diff --git a/examples/socket_tcp_fork_server.cpp b/examples/socket_tcp_fork_server.cpp new file mode 100644 index 0000000..2805d59 --- /dev/null +++ b/examples/socket_tcp_fork_server.cpp @@ -0,0 +1,24 @@ +#include "socket.hpp" +#include +#include + +using namespace net; + +int main() +{ + try { + signal(SIGCHLD, SIG_IGN); + Socket s(Domain::IPv4, Type::TCP); + s.start("127.0.0.1", 24001); + + while (true) { + const auto peer = s.accept(); + if (!fork()) { + std::cout << peer.recv(10) << '\n'; + return 0; + } + } + } catch (std::exception &e) { + std::cerr << e.what() << '\n'; + } +} diff --git a/examples/socket_tcp_mt_client.cpp b/examples/socket_tcp_mt_client.cpp new file mode 100644 index 0000000..41ffc02 --- /dev/null +++ b/examples/socket_tcp_mt_client.cpp @@ -0,0 +1,32 @@ +#include "socket.hpp" +#include +#include +#include + +using namespace net; +using namespace std::chrono_literals; + + +void connect_send() +{ + try { + Socket s(Domain::IPv4, Type::TCP); + std::this_thread::sleep_for(2s); + s.connect("127.0.0.1", 24001); + s.send("123456789"); + } catch (std::exception &e) { + std::cerr << e.what() << '\n'; + } +} + + +int main() +{ + std::vector threads; + for (int i = 0; i < 100; ++i) { + threads.push_back(std::thread(connect_send)); + } + for (auto &t : threads) { + t.join(); + } +} diff --git a/examples/socket_tcp_mt_server.cpp b/examples/socket_tcp_mt_server.cpp new file mode 100644 index 0000000..844e347 --- /dev/null +++ b/examples/socket_tcp_mt_server.cpp @@ -0,0 +1,44 @@ +#include "socket.hpp" +#include +#include +#include +#include + +using namespace net; + +std::mutex m; +std::condition_variable cv; +bool accepted = false; + + +void worker_thread(Socket &s) +{ + try { + const auto peer = s.accept(); + std::unique_lock lock(m); + accepted = true; + lock.unlock(); + cv.notify_one(); + std::cout << peer.recv(10) << '\n'; + } catch (std::exception &e) { + std::cerr << e.what() << '\n'; + } +} + + +int main() +{ + try { + Socket s(Domain::IPv4, Type::TCP); + s.start("127.0.0.1", 24001); + + while (true) { + std::thread(worker_thread, std::ref(s)).detach(); + std::unique_lock lock(m); + cv.wait(lock, [] { return accepted; }); + accepted = false; + } + } catch (std::exception &e) { + std::cerr << e.what() << '\n'; + } +} diff --git a/examples/socket_tcp_server.cpp b/examples/socket_tcp_server.cpp new file mode 100644 index 0000000..c5e3141 --- /dev/null +++ b/examples/socket_tcp_server.cpp @@ -0,0 +1,20 @@ +#include "socket.hpp" +#include + +using namespace net; + +int main() +{ + try { + Socket s(Domain::IPv4, Type::TCP); + s.start("0.0.0.0", 24000); + + while (1) { + const auto peer = s.accept(); + const auto msg = peer.recv(15); + std::cout << msg << '\n'; + } + } catch (std::exception &e) { + std::cerr << e.what() << '\n'; + } +} diff --git a/examples/socket_unix_udp_client.cpp b/examples/socket_unix_udp_client.cpp new file mode 100644 index 0000000..8fb86ab --- /dev/null +++ b/examples/socket_unix_udp_client.cpp @@ -0,0 +1,23 @@ +#include "socket.hpp" +#include + +using namespace net; + +int main() +{ + try { + Socket unixClient(Domain::UNIX, Type::UDP); + std::string clientPath("/tmp/unixClient"); + std::string serverPath("/tmp/unixServer"); + + unixClient.bind([&](AddrUnix &s) { + return methods::construct(s, clientPath.c_str()); + }); + + unixClient.connect(serverPath.c_str()); + + unixClient.write("hello server"); + } catch (std::exception &e) { + std::cout << e.what() << '\n'; + } +} diff --git a/examples/socket_unix_udp_server.cpp b/examples/socket_unix_udp_server.cpp new file mode 100644 index 0000000..8c72bab --- /dev/null +++ b/examples/socket_unix_udp_server.cpp @@ -0,0 +1,23 @@ +#include "socket.hpp" +#include + +using namespace net; + +int main() +{ + try { + Socket unixServer(Domain::UNIX, Type::UDP); + std::string serverPath("/tmp/unixServer"); + + unixServer.bind([&](AddrUnix &s) { + return methods::construct(s, serverPath.c_str()); + }); + + const auto res = unixServer.read(12); + + std::cout << "Some client sent: " << res << '\n'; + + } catch (std::exception &e) { + std::cout << e.what() << '\n'; + } +} diff --git a/include/net.hpp b/include/net.hpp deleted file mode 100644 index d552348..0000000 --- a/include/net.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#include - -namespace net { -std::string sayHello(); -} diff --git a/include/socket.hpp b/include/socket.hpp new file mode 100644 index 0000000..32bbbc1 --- /dev/null +++ b/include/socket.hpp @@ -0,0 +1,882 @@ +#ifndef SOCKET_HPP +#define SOCKET_HPP + +#include "socket_family.hpp" +#include +#include +#include + + +namespace net { + +/** +* @class net::Socket +* @desc Socket class to create Berkeley sockets. +* Uses socket domains from domain enum in SF namespace from socket_family.hpp +* Uses socket types from type enum in SF namespace from socket_family.hpp +*/ +class Socket { +private: + union { + AddrStore store; + AddrIPv4 ipv4; + AddrIPv6 ipv6; + AddrUnix unix; + }; + int sockfd; + Domain sock_domain; + Type sock_type; + bool isClosed = false; + + + /** + * @method low_write + * @access private + * @desc Writes given _msg using _sockfd by calling _fn with args having + * flags and destination socket address. + * + * @param {callable} _fn Some callable that writes using socket descriptor. + * @param {string} _msg Msg to write on sockfd. + * @param {parameter_pack} args Flags, destination sockaddr objects and their + * lengths. + * @returns {ssize_t} Status of writing _msg using socket descriptor / Number + * of bytes written using socket descriptor. + */ + template + auto low_write(Fn &&_fn, const std::string &_msg, Args &&... args) const + { + ssize_t written = 0; + std::string::size_type count = 0; + do { + written = std::forward(_fn)(sockfd, _msg.c_str() + count, + _msg.length() - count, + std::forward(args)...); + count += written; + } while (count < _msg.length() && written > 0); + + return written; + } + + + /** + * @method low_read + * @access private + * @desc Reads using sockfd by calling _fn with args having flags and + * destination socket address. + * + * @param {callable} _fn Some callable that reads using socket descriptor. + * @param {string} _str String to store the data. + * @param {parameter_pack} args Flags, destination sockaddr objects and their + * lengths. + * @returns {ssize_t} Status of reading data using socket descriptor / Number + * of bytes read using socket descriptor. + */ + template + auto low_read(Fn &&_fn, std::string &_str, Args &&... args) const + { + const auto bufSize = _str.capacity(); + const auto buffer = std::make_unique(bufSize); + + const auto recvd = std::forward(_fn)(sockfd, buffer.get(), bufSize, + std::forward(args)...); + _str.append(buffer.get(), (recvd > 0) ? recvd : 0); + return recvd; + } + + + /** + * @construct Socket + * @access private + * @param {int} _sockfd Descriptor representing a socket. + * @param {Domain} _domain Socket domain. + * @param {Type} _domain Socket type. + * @param {void *} _addr Pointer to initialize appropriate member of Union of + * net::Socket. + */ + Socket(const int, Domain, Type, const void *); + + Socket(const Socket &) = delete; + Socket &operator=(const Socket &) = delete; + + +public: + /** + * @construct net::Socket + * @access public + * @param {domain} _domain Socket domain. + * @param {type} _type Socket type. + * @param {int} _proto Socket protocol. + */ + Socket(Domain _domain, Type _type, const int _proto = 0) + : sock_domain(_domain), sock_type(_type) + { + const auto d = static_cast(sock_domain); + const auto t = static_cast(sock_type); + + sockfd = socket(d, t, _proto); + if (sockfd < 0) { + const auto currErrno = errno; + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + + switch (sock_domain) { + case Domain::IPv4: ipv4.sin_family = AF_INET; break; + case Domain::IPv6: ipv6.sin6_family = AF_INET6; break; + case Domain::UNIX: unix.sun_family = AF_UNIX; break; + + default: store.ss_family = d; + } + } + + + /** + * @construct net::Socket + * @access public + * @param {Socket} s Rvalue of type socket. + */ + Socket(Socket &&s) + { + sockfd = s.sockfd; + sock_domain = s.sock_domain; + sock_type = s.sock_type; + + s.sockfd = -1; + + switch (s.sock_domain) { + case Domain::IPv4: ipv4 = s.ipv4; break; + case Domain::IPv6: ipv6 = s.ipv6; break; + case Domain::UNIX: unix = s.unix; break; + + default: store = s.store; + } + } + + + /** + * @method getSocket + * @access public + * @desc Get the socket descriptor in net::Socket. + * + * @returns {int} Socket descriptor for net::Socket. + */ + auto getSocket() const noexcept { return sockfd; } + + + /** + * @method getDomain + * @access public + * @desc Get the Domain type of Socket. + * + * @returns {Domain} for net::Socket. + */ + auto getDomain() const noexcept { return sock_domain; } + + + /** + * @method getType + * @access public + * @desc Get the Protocol Type of Socket. + * + * @returns {Type} for net::Socket. + */ + auto getType() const noexcept { return sock_type; } + + + /** + * @method bind + * @access public + * @desc Binds net::Socket to local address if successful else if Address + * argument is invalid then throws invalid_argument exception else throws + * runtime_error exception signalling that bind failed. Invokes the callable + * provided to fill AddrIPv4 object. + * + * @param {callable} _fn Some callable that takes arg of type AddrIPv4. + */ + template + auto bind(F _fn) -> decltype(_fn(std::declval()), void()) const + { + AddrIPv4 addr; + + auto res = _fn(addr); + if (res >= 1) { + res = ::bind(sockfd, (sockaddr *) &addr, sizeof(addr)); + res = (res == 0) ? 1 : res; + } + + const auto currErrno = errno; + if (res == -1) { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } else if (res == 0) { + throw std::invalid_argument("Address argument invalid"); + } + + ipv4 = addr; + } + + + /** + * @method bind + * @access public + * @desc Binds net::Socket to local address if successful else if Address + * argumentis invalid then throws invalid_argument exception else throws + * runtime_error exception signalling that bind failed. Invokes the callable + * provided to fill AddrIPv6 object. + * + * @param {callable} _fn Some callable that takes arg of type AddrIPv6. + */ + template + auto bind(F _fn) -> decltype(_fn(std::declval()), void()) const + { + AddrIPv6 addr; + + auto res = _fn(addr); + if (res >= 1) { + res = ::bind(sockfd, (sockaddr *) &addr, sizeof(addr)); + res = (res == 0) ? 1 : res; + } + + const auto currErrno = errno; + if (res == -1) { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } else if (res == 0) { + throw std::invalid_argument("Address argument invalid"); + } + + ipv6 = addr; + } + + + /** + * @method bind + * @access public + * @desc Binds net::Socket to local address if successful else if Address + * argument is invalid then throws invalid_argument exception else throws + * runtime_error exception signalling that bind failed. Invokes the callable + * provided to fill AddrUnix object. + * + * @param {callable} _fn Some callable that takes arg of type AddrUnix. + */ + template + auto bind(F _fn) -> decltype(_fn(std::declval()), void()) const + { + AddrUnix addr; + + auto res = _fn(addr); + if (res >= 1) { + res = ::bind(sockfd, (sockaddr *) &addr, sizeof(addr)); + res = (res == 0) ? 1 : res; + } + + const auto currErrno = errno; + if (res == -1) { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } else if (res == 0) { + throw std::invalid_argument("Address argument invalid"); + } + + unix = addr; + } + + + /** + * @method connect + * @access public + * @desc Connects net::Socket to address _addr:_port if successful else + * throws invalid_argument exception. + * + * @param {char []} _addr Ip address in case of ipv4 or ipv6 domain, and Path + * in case of unix domain. + * @param {int} _port Port number in case of AddrIPv4 or AddrIPv6. + * @param {bool *} _errorNB To signal error in case of non-blocking connect. + */ + void connect(const char[], const int = 0, bool * = nullptr); + + + /** + * @method connect + * @access public + * @desc Connects net::Socket to ipv4 peer if successful else throws + * runtime_error exception. + * Throws invalid_argument exception in case of non-blocking net::Socket if + * _errorNB is missing. + * Throws invalid_argument exception if destination address given is invalid. + * + * @param {callable} _fn Some callable that takes arg of type AddrIPv4. + * @param {bool *} _errorNB To signal error in case of non-blocking connect. + */ + template + auto connect(F _fn, bool *_errorNB = nullptr) + -> decltype(_fn(std::declval()), void()) const + { + AddrIPv4 addr; + + auto res = _fn(addr); + if (res >= 1) { + res = ::connect(sockfd, (sockaddr *) &addr, sizeof(addr)); + res = (res == 0) ? 1 : res; + } + + const auto currErrno = errno; + if (res == -1) { + if (currErrno == EINPROGRESS) { + if (_errorNB != nullptr) { + *_errorNB = true; + } else { + throw std::invalid_argument("errorNB argument missing"); + } + } else { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + } else if (res == 0) { + throw std::invalid_argument("Address argument invalid"); + } + } + + + /** + * @method connect + * @access public + * @desc Connects net::Socket to ipv6 peer if successful else throws + * runtime_error exception. + * Throws invalid_argument exception in case of non-blocking net::Socket if + * _errorNB is missing. + * Throws invalid_argument exception if destination address given is invalid. + * Invokes the callable provided to fill AddrIPv4 object. + * + * @param {callable} _fn Some callable that takes arg of type AddrIPv6. + * @param {bool *} _errorNB To signal error in case of non-blocking connect. + */ + template + auto connect(F _fn, bool *_errorNB = nullptr) + -> decltype(_fn(std::declval()), void()) const + { + AddrIPv6 addr; + + auto res = _fn(addr); + if (res >= 1) { + res = ::connect(sockfd, (sockaddr *) &addr, sizeof(addr)); + res = (res == 0) ? 1 : res; + } + + const auto currErrno = errno; + if (res == -1) { + if (currErrno == EINPROGRESS) { + if (_errorNB != nullptr) { + *_errorNB = true; + } else { + throw std::invalid_argument("errorNB argument missing"); + } + } else { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + } else if (res == 0) { + throw std::invalid_argument("Address argument invalid"); + } + } + + + /** + * @method connect + * @access public + * @desc Connects net::Socket to unix socket peer if successful else throws + * runtime_error exception. + * Throws invalid_argument exception in case of non-blocking net::Socket if + * _errorNB is missing. + * Throws invalid_argument exception if destination address given is invalid. + * Invokes the callable provided to fill AddrIPv4 object. + * + * @param {callable} _fn Some callable that takes arg of type AddrUnix. + * @param {bool *} _errorNB To signal error in case of non-blocking connect. + */ + template + auto connect(F _fn, bool *_errorNB = nullptr) + -> decltype(_fn(std::declval()), void()) const + { + AddrUnix addr; + + auto res = _fn(addr); + if (res >= 1) { + res = ::connect(sockfd, (sockaddr *) &addr, sizeof(addr)); + res = (res == 0) ? 1 : res; + } + + const auto currErrno = errno; + if (res == -1) { + if (currErrno == EINPROGRESS) { + if (_errorNB != nullptr) { + *_errorNB = true; + } else { + throw std::invalid_argument("errorNB argument missing"); + } + } else { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + } else if (res == 0) { + throw std::invalid_argument("Address argument invalid"); + } + } + + + /** + * @method start + * @access public + * @desc Starts the net::Socket in listen mode on given ip address and given + * port with given backlog if successful else throws runtime_error exception. + * Throws invalid_argument exception if given ip address or port are not + * valid. + * + * @param {char []} _addr Ip address in case of ipv4 or ipv6 domain, and Path + * in case of unix domain. + * @param {int} _port Port number in case of ipv4 or ipv6. + * @param {int} _q Size of backlog of listening socket. + */ + void start(const char[], const int = 0, const int = SOMAXCONN); + + + /** + * @method accept + * @access public + * @desc Returns Socket object from connected sockets queue if successful + * else throws runtime_error exception. + * Throws invalid_argument exception in case of non-blocking net::Socket if + * _errorNB is missing. + * + * @param {bool *} _errorNB To signal error in case of non-blocking accept. + * @returns {net::Socket} + */ + Socket accept(bool * = nullptr) const; + + + /** + * @method write + * @access public + * @desc Writes given string to Socket if successful else throws + * runtime_error exception. + * Throws invalid_argument exception in case of non-blocking net::Socket if + * _errorNB is missing. + * + * @param {string} _msg String to be written to Socket. + * @param {bool *} _errorNB To signal error in case of non-blocking write. + */ + void write(const std::string &, bool * = nullptr) const; + + + /** + * @method read + * @access public + * @desc Reads given number of bytes using Socket if successful else throws + * runtime_error exception. + * Throws invalid_argument exception in case of non-blocking net::Socket if + * _errorNB is missing. + * + * @param {int} _bufSize Number of bytes to be read using Socket. + * @param {bool *} _errorNB To signal error in case of non-blocking read. + * @returns {string} String of _bufSize bytes read using Socket. + */ + std::string read(const int, bool * = nullptr) const; + + + /** + * @method send + * @access public + * @desc Sends given string using Socket if successful else throws + * runtime_error exception. + * Throws invalid_argument exception in case of non-blocking net::Socket if + * _errorNB is missing. + * + * @param {string} _msg String to be sent using Socket. + * @param {send} _flags Modify default behaviour of send. + * @param {bool *} _errorNB To signal error in case of non-blocking send. + */ + void send(const std::string &, Send = Send::NONE, bool * = nullptr) const; + + + /** + * @method send + * @access public + * @desc Sends given string using Socket if successful else throws + * runtime_error exception. + * Throws invalid_argument exception in case of non-blocking net::Socket if + * _errorNB is missing. + * Throws invalid_argument exception if destination address given is invalid. + * Invokes the callable provided to fill AddrIPv4 object. + * + * @param {string} _msg String to be sent using Socket. + * @param {callable} _fn Some callable that takes arg of type AddrIPv4 or + * void. + * @param {send} _flags Modify default behaviour of send. + * @param {bool *} _errorNB To signal error in case of non-blocking send. + */ + template + auto send(const std::string &_msg, F _fn, Send _flags = Send::NONE, + bool *_errorNB = nullptr) const + -> decltype(_fn(std::declval()), void()) const + { + AddrIPv4 addr; + + const auto flags = static_cast(_flags); + const auto res = _fn(addr); + + if (res == 0) { + throw std::invalid_argument("Address argument invalid"); + } + + ssize_t sent = -1; + if (res >= 1) { + sent = low_write(::sendto, _msg, flags, (sockaddr *) &addr, + sizeof(addr)); + } + + const auto currErrno = errno; + if (res == -1 || sent == -1) { + if (currErrno == EAGAIN || currErrno == EWOULDBLOCK) { + if (_errorNB != nullptr) { + *_errorNB = true; + } else { + throw std::invalid_argument("errorNB argument missing"); + } + } else { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + } + } + + + /** + * @method send + * @access public + * @desc Sends given string using Socket if successful else throws + * runtime_error exception. + * Throws invalid_argument exception in case of non-blocking net::Socket if + * _errorNB is missing. + * Throws invalid_argument exception if destination address given is invalid. + * Invokes the callable provided to fill AddrIPv6 object. + * + * @param {string} _msg String to be sent using Socket. + * @param {callable} _fn Some callable that takes arg of type AddrIPv6 or + * void. + * @param {send} _flags Modify default behaviour of send. + * @param {bool *} _errorNB To signal error in case of non-blocking send. + */ + template + auto send(const std::string &_msg, F _fn, Send _flags = Send::NONE, + bool *_errorNB = nullptr) const + -> decltype(_fn(std::declval()), void()) const + { + AddrIPv6 addr; + + const auto flags = static_cast(_flags); + const auto res = _fn(addr); + + if (res == 0) { + throw std::invalid_argument("Address argument invalid"); + } + + ssize_t sent = -1; + if (res >= 1) { + sent = low_write(::sendto, _msg, flags, (sockaddr *) &addr, + sizeof(addr)); + } + + const auto currErrno = errno; + if (res == -1 || sent == -1) { + if (currErrno == EAGAIN || currErrno == EWOULDBLOCK) { + if (_errorNB != nullptr) { + *_errorNB = true; + } else { + throw std::invalid_argument("errorNB argument missing"); + } + } else { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + } + } + + + /** + * @method send + * @access public + * @desc Sends given string using Socket if successful else throws + * runtime_error exception. + * Throws invalid_argument exception in case of non-blocking net::Socket if + * _errorNB is missing. + * Throws invalid_argument exception if destination address given is invalid. + * Invokes the callable provided to fill AddrUnix object. + * + * @param {string} _msg String to be sent using Socket. + * @param {callable} _fn Some callable that takes arg of type AddrUnix or + * void. + * @param {send} _flags Modify default behaviour of send. + * @param {bool *} _errorNB To signal error in case of non-blocking send. + */ + template + auto send(const std::string &_msg, F _fn, Send _flags = Send::NONE, + bool *_errorNB = nullptr) const + -> decltype(_fn(std::declval()), void()) const + { + AddrUnix addr; + + const auto flags = static_cast(_flags); + const auto res = _fn(addr); + + if (res == 0) { + throw std::invalid_argument("Address argument invalid"); + } + + ssize_t sent = -1; + if (res >= 1) { + sent = low_write(::sendto, _msg, flags, (sockaddr *) &addr, + sizeof(addr)); + } + + const auto currErrno = errno; + if (res == -1 || sent == -1) { + if (currErrno == EAGAIN || currErrno == EWOULDBLOCK) { + if (_errorNB != nullptr) { + *_errorNB = true; + } else { + throw std::invalid_argument("errorNB argument missing"); + } + } else { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + } + } + + + /** + * @method recv + * @access public + * @desc Reads given number of bytes using Socket if successful else throws + * runtime_error exception. + * Throws invalid_argument exception in case of non-blocking net::Socket if + * _errorNB is missing. + * + * @param {int} _bufSize Number of bytes to be read using Socket. + * @param {recv} _flags Modify default behaviour of recv. + * @param {bool *} _errorNB To signal error in case of non-blocking recv. + * @returns {string} String of _bufSize bytes read from socket. + */ + std::string recv(const int, Recv = Recv::NONE, bool * = nullptr) const; + + + /** + * @method recv + * @access public + * @desc Reads given number of bytes using Socket if successful else throws + * runtime_error exception. + * Throws invalid_argument exception in case of non-blocking net::Socket if + * _errorNB is missing. + * Throws invalid_argument exception if destination address given is invalid. + * Invokes the callable provided to return AddrIPv4 object from where msg has + * been received. + * + * @param {int} _numBytes Number of bytes to read. + * @param {callable} _fn Some callable that takes arg of type AddrIPv4 or + * void. + * @param {recv} _flags Modify default behaviour of recv. + * @param {bool *} _errorNB To signal error in case of non-blocking recv. + */ + template + auto recv(const int _numBytes, F _fn, Recv _flags = Recv::NONE, + bool *_errorNB = nullptr) const + -> decltype(_fn(std::declval()), std::string()) const + { + AddrIPv4 addr; + std::string str; + str.reserve(_numBytes); + + const auto flags = static_cast(_flags); + socklen_t length = sizeof(addr); + + const auto recvd + = low_read(::recvfrom, str, flags, (sockaddr *) &addr, &length); + + const auto currErrno = errno; + if (recvd == -1) { + if (currErrno == EAGAIN || currErrno == EWOULDBLOCK) { + if (_errorNB != nullptr) { + *_errorNB = true; + } else { + throw std::invalid_argument("errorNB argument missing"); + } + } else { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + } + + _fn(addr); + return str; + } + + + /** + * @method recv + * @access public + * @desc Reads given number of bytes using Socket if successful else throws + * runtime_error exception. + * Throws invalid_argument exception in case of non-blocking net::Socket if + * _errorNB is missing. + * Throws invalid_argument exception if destination address given is invalid. + * Invokes the callable provided to return AddrIPv6 object from where msg has + * been received. + * + * @param {int} _numBytes Number of bytes to read. + * @param {callable} _fn Some callable that takes arg of type AddrIPv6 or + * void. + * @param {recv} _flags Modify default behaviour of recv. + * @param {bool *} _errorNB To signal error in case of non-blocking recv. + */ + template + auto recv(const int _numBytes, F _fn, Recv _flags = Recv::NONE, + bool *_errorNB = nullptr) const + -> decltype(_fn(std::declval()), std::string()) const + { + AddrIPv6 addr; + std::string str; + str.reserve(_numBytes); + + const auto flags = static_cast(_flags); + socklen_t length = sizeof(addr); + + const auto recvd + = low_read(::recvfrom, str, flags, (sockaddr *) &addr, &length); + + const auto currErrno = errno; + if (recvd == -1) { + if (currErrno == EAGAIN || currErrno == EWOULDBLOCK) { + if (_errorNB != nullptr) { + *_errorNB = true; + } else { + throw std::invalid_argument("errorNB argument missing"); + } + } else { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + } + + _fn(addr); + return str; + } + + + /** + * @method recv + * @access public + * @desc Reads given number of bytes using Socket if successful else throws + * runtime_error exception. + * Throws invalid_argument exception in case of non-blocking net::Socket if + * _errorNB is missing. + * Throws invalid_argument exception if destination address given is invalid. + * Invokes the callable provided to return AddrUnix object from where msg has + * been received. + * + * @param {int} _numBytes Number of bytes to read. + * @param {callable} _fn Some callable that takes arg of type AddrUnix or + * void. + * @param {recv} _flags Modify default behaviour of recv. + * @param {bool *} _errorNB To signal error in case of non-blocking recv. + */ + template + auto recv(const int _numBytes, F _fn, Recv _flags = Recv::NONE, + bool *_errorNB = nullptr) const + -> decltype(_fn(std::declval()), std::string()) const + { + AddrUnix addr; + std::string str; + str.reserve(_numBytes); + + const auto flags = static_cast(_flags); + socklen_t length = sizeof(addr); + + const auto recvd + = low_read(::recvfrom, str, flags, (sockaddr *) &addr, &length); + + const auto currErrno = errno; + if (recvd == -1) { + if (currErrno == EAGAIN || currErrno == EWOULDBLOCK) { + if (_errorNB != nullptr) { + *_errorNB = true; + } else { + throw std::invalid_argument("errorNB argument missing"); + } + } else { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + } + + _fn(addr); + return str; + } + + + /** + * @method setOpt + * @access public + * @desc Set a socket option from net::Opt for Socket using object of type + * net::SockOpt. + * + * @param {net::Opt} _opType Option to set for Socket. + * @param {net::SockOpt} _opValue socket option structure. Present inside + * socket_family.hpp. + */ + void setOpt(Opt, SockOpt) const; + + + /** + * @method getOpt + * @access public + * @desc Get value of some socket option for Socket. + * + * @param {net::Opt} _opType Option of Socket whose value to get. + */ + SockOpt getOpt(Opt) const; + + + /** + * @method stop + * @access public + * @desc Shutdown Socket using net::shut. + * + * @param {net::shut} _s Option specifying which side of connection to + * shutdown for Socket. + */ + void stop(Shut _s) const noexcept + { + shutdown(sockfd, static_cast(_s)); + } + + + /** + * @method unlink + * @access public + * @desc Unlinks the unix socket path. + */ + bool unlink() const noexcept + { + return (sock_domain == Domain::UNIX && !isClosed) + ? ::unlink(unix.sun_path) == 0 + : false; + } + + + /** + * @method close + * @access public + * @desc Closes the Socket for terminating connection. + */ + bool close() const noexcept + { + return (!isClosed) ? ::close(sockfd) == 0 : false; + } + + + ~Socket() noexcept + { + unlink(); + close(); + } +}; +} + +#endif diff --git a/include/socket_family.hpp b/include/socket_family.hpp new file mode 100644 index 0000000..023129a --- /dev/null +++ b/include/socket_family.hpp @@ -0,0 +1,178 @@ +#ifndef SOCKET_FAMILY_HPP +#define SOCKET_FAMILY_HPP + +#include +#include +#include +#include "socket_options.hpp" + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +} + + +namespace net { + +using AddrIPv4 = sockaddr_in; +using AddrIPv6 = sockaddr_in6; +using AddrUnix = sockaddr_un; +using AddrStore = sockaddr_storage; + +enum class Domain { + UNIX = AF_UNIX, + LOCAL = AF_UNIX, + IPv4 = AF_INET, + IPv6 = AF_INET6, + IPX = AF_IPX, + NETLINK = AF_NETLINK, + X25 = AF_X25, + AX25 = AF_AX25, + ATMPVC = AF_ATMPVC, + APPLETALK = AF_APPLETALK, + PACKET = AF_PACKET, + ALG = AF_ALG +}; + + +enum class Type { + TCP = SOCK_STREAM, + UDP = SOCK_DGRAM, + SEQPACKET = SOCK_SEQPACKET, + RAW = SOCK_RAW, + RDM = SOCK_RDM, + NONBLOCK = SOCK_NONBLOCK, + CLOEXEC = SOCK_CLOEXEC +}; + +enum class Shut { READ = SHUT_RD, WRITE = SHUT_WR, READWRITE = SHUT_RDWR }; + + +enum class Recv { + NONE = 0, + PEEK = MSG_PEEK, + OOB = MSG_OOB, + WAITALL = MSG_WAITALL +}; +inline constexpr Recv operator|(Recv a, Recv b) noexcept +{ + return static_cast(static_cast(a) | static_cast(b)); +} + +enum class Send { + NONE = 0, + EOR = MSG_EOR, + OOB = MSG_OOB, + NOSIGNAL = MSG_NOSIGNAL +}; +inline constexpr Send operator|(Send a, Send b) noexcept +{ + return static_cast(static_cast(a) | static_cast(b)); +} + + +namespace methods { + + /** + * @function getErrorMsg + * @desc Returns the standard human readable error message corresponding to + * given errorNumber. + * + * @param {int} errorNumber Error number whose string to return. + * @returns {string} Standard error string corresponding to given + * errorNumber. + */ + inline std::string getErrorMsg(const int errorNumber) + { + static std::mutex m; + std::lock_guard lock(m); + const char *errMsg = strerror(errorNumber); + std::string returnString(errMsg); + return returnString; + } + + /** + * @function construct + * @desc Fills the given AddrIPv4 structure object with given ip address and + * port. + * + * @param {AddrIPv4} _addrStruct Ipv4 structure object that needs to be + * filled with given ip address and port. + * @param {char []} _addr Ip address which needs to be filled in the AddrIPv4 + * structure object. + * @param {int} _port Port number which needs to be filled in the AddrIPv4 + * structure object. + * @returns {int} 1 if sucessful, 0 if given ip address does not represent a + * valid ip address, -1 if some error occurred. + */ + inline int construct(AddrIPv4 &_addrStruct, const char _addr[], + const int _port) noexcept + { + if (_port < 0 || _port > 65535) { + return 0; + } + + std::memset(&_addrStruct, 0, sizeof(_addrStruct)); + _addrStruct.sin_family = AF_INET; + _addrStruct.sin_port = htons(_port); + + return inet_pton(AF_INET, _addr, &_addrStruct.sin_addr); + } + + + /** + * @function construct + * @desc Fills the given AddrIPv6 structure object with given ip address and + * port. + * + * @param {AddrIPv6} _addrStruct - Ipv6 structure object that needs to be + * filled with given ip address and port. + * @param {char []} _addr Ip address which needs to be filled in the AddrIPv6 + * structure object. + * @param {int} _port Port number which needs to be filled in the AddrIPv6 + * structure object. + * @returns {int} 1 if sucessful, 0 if given ip address does not represent a + * valid ip address, -1 if some error occurred. + */ + inline int construct(AddrIPv6 &_addrStruct, const char _addr[], + const int _port) noexcept + { + // TODO: replace code with call to getaddrinfo() + if (_port < 0 || _port > 65535) { + return 0; + } + + std::memset(&_addrStruct, 0, sizeof(_addrStruct)); + _addrStruct.sin6_family = AF_INET6; + _addrStruct.sin6_port = htons(_port); + + return inet_pton(AF_INET6, _addr, &_addrStruct.sin6_addr); + } + + + /** + * @function construct + * @desc Fills the given AddrUnix structure object with given address. + * + * @param {AddrUnix} _addrStruct structure object that needs to be filled + * with given path. + * @param {char []} _addr Path which needs to be filled in the AddrUnix + * structure object. + * @returns {int} Always returns 1. + */ + inline int construct(AddrUnix &_addrStruct, const char _addr[]) noexcept + { + std::memset(&_addrStruct, 0, sizeof(_addrStruct)); + _addrStruct.sun_family = AF_UNIX; + std::strncpy(_addrStruct.sun_path, _addr, 108); + return 1; + } +} +} + +#endif diff --git a/include/socket_options.hpp b/include/socket_options.hpp new file mode 100644 index 0000000..f174e19 --- /dev/null +++ b/include/socket_options.hpp @@ -0,0 +1,164 @@ +#ifndef SOCKET_OPTIONS_HPP +#define SOCKET_OPTIONS_HPP + +#include +#include +#include + +extern "C" { +#include +} + +namespace net { + +enum class Opt { +#ifdef SO_BROADCAST + BROADCAST = SO_BROADCAST, +#endif +#ifdef SO_DEBUG + DEBUG = SO_DEBUG, +#endif +#ifdef SO_DONTROUTE + DONTROUTE = SO_DONTROUTE, +#endif +#ifdef SO_ERROR + ERROR = SO_ERROR, +#endif +#ifdef SO_KEEPALIVE + KEEPALIVE = SO_KEEPALIVE, +#endif +#ifdef SO_LINGER + LINGER = SO_LINGER, +#endif +#ifdef SO_OOBINLINE + OOBINLINE = SO_OOBINLINE, +#endif +#ifdef SO_RCVBUF + RCVBUF = SO_RCVBUF, +#endif +#ifdef SO_SNDBUF + SNDBUF = SO_SNDBUF, +#endif +#ifdef SO_RCVLOWAT + RCVLOWAT = SO_RCVLOWAT, +#endif +#ifdef SO_SNDLOWAT + SNDLOWAT = SO_SNDLOWAT, +#endif +#ifdef SO_RCVTIMEO + RCVTIMEO = SO_RCVTIMEO, +#endif +#ifdef SO_SNDTIMEO + SNDTIMEO = SO_SNDTIMEO, +#endif +#ifdef SO_REUSEADDR + REUSEADDR = SO_REUSEADDR, +#endif +#ifdef SO_REUSEPORT + REUSEPORT = SO_REUSEPORT, +#endif +#ifdef SO_TYPE + TYPE = SO_TYPE, +#endif +#ifdef SO_USELOOPBACK + USELOOPBACK = SO_USELOOPBACK, +#endif +#ifdef TCP_MAXSEG + MAXSEG = TCP_MAXSEG, +#endif +#ifdef TCP_NODELAY + NODELAY = TCP_NODELAY +#endif +}; + + +class SockOpt final { + union { + timeval t; + linger l; + int i; + }; + enum { TIME = 0, LINGER = 1, INT = 2 } type; + + SockOpt(int, int) = delete; + SockOpt() = delete; + +public: + SockOpt(const int _n) : i(_n), type(INT) {} + SockOpt(const bool _on, const int _linger) : type(LINGER) + { + l.l_onoff = _on ? 1 : 0; + l.l_linger = _linger; + } + SockOpt(const decltype(t.tv_sec) _seconds, + const decltype(t.tv_usec) _microseconds) + : type(TIME) + { + t.tv_sec = _seconds; + t.tv_usec = _microseconds; + } + + auto getType() const noexcept { return type; } + + auto getTime() const + { + return (type == TIME) ? std::make_pair(t.tv_sec, t.tv_usec) + : throw std::bad_cast(); + } + + auto getLinger() const + { + return (type == LINGER) + ? std::make_pair(static_cast(l.l_onoff), l.l_linger) + : throw std::bad_cast(); + } + + auto getValue() const { return (type == INT) ? i : throw std::bad_cast(); } +}; + + +inline bool operator==(const SockOpt &_lhs, const int _rhs) +{ + if (_lhs.getType() == 2) { + return _lhs.getValue() == _rhs; + } else { + return false; + } +} +inline bool operator==(const int _lhs, const SockOpt &_rhs) +{ + return _rhs == _lhs; +} + + +inline bool operator==(const SockOpt &_lhs, const linger _rhs) +{ + if (_lhs.getType() == 1) { + const auto lin = _lhs.getLinger(); + return (lin.first == _rhs.l_onoff && lin.second == _rhs.l_linger); + } else { + return false; + } +} +inline bool operator==(const linger _lhs, const SockOpt &_rhs) +{ + return _rhs == _lhs; +} + + +inline bool operator==(const SockOpt &_lhs, const timeval _rhs) +{ + if (_lhs.getType() == 0) { + const auto time = _lhs.getTime(); + return (time.first == _rhs.tv_sec && time.second == _rhs.tv_usec); + } else { + return false; + } +} +inline bool operator==(const timeval _lhs, const SockOpt &_rhs) +{ + return _rhs == _lhs; +} +} + +#endif diff --git a/meson.build b/meson.build index faf4ee8..8c687e1 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,8 @@ project('net', 'cpp', version : '0.1.0', default_options : ['cpp_std=c++14']) +add_global_arguments('-Wstrict-aliasing=2', language : 'cpp') + inc = include_directories('include') thread_dep = dependency('threads') diff --git a/src/meson.build b/src/meson.build index 2f9c9ff..44e1bb6 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,4 +1,4 @@ -prog_sources = ['net.cpp'] +prog_sources = ['socket.cpp'] netlib = library('net', prog_sources, include_directories: inc, dependencies: [thread_dep]) diff --git a/src/net.cpp b/src/net.cpp deleted file mode 100644 index 3e2e716..0000000 --- a/src/net.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "net.hpp" - -namespace net { -std::string sayHello() { return "Hello"; } -} diff --git a/src/socket.cpp b/src/socket.cpp new file mode 100644 index 0000000..8588f9d --- /dev/null +++ b/src/socket.cpp @@ -0,0 +1,396 @@ +#include "socket.hpp" + + +namespace net { + +Socket::Socket(const int _sockfd, Domain _domain, Type _type, const void *_addr) + : sockfd(_sockfd), sock_domain(_domain), sock_type(_type) +{ + std::memset(&store, 0, sizeof(store)); + + void *ptr = &store; + auto size = sizeof(store); + + switch (sock_domain) { + case Domain::IPv4: + ipv4.sin_family = AF_INET; + + ptr = &ipv4; + size = sizeof(ipv4); + break; + + case Domain::IPv6: + ipv6.sin6_family = AF_INET6; + + ptr = &ipv6; + size = sizeof(ipv6); + break; + + case Domain::UNIX: + unix.sun_family = AF_UNIX; + + ptr = &unix; + size = sizeof(unix); + break; + + default: store.ss_family = static_cast(sock_domain); + } + + sock_type = _type; + std::memcpy(ptr, _addr, size); +} + + +void Socket::start(const char _addr[], const int _port, const int _q) +{ + try { + switch (sock_domain) { + case Domain::IPv4: + bind([&](AddrIPv4 &s) { + return net::methods::construct(s, _addr, _port); + }); + break; + + case Domain::IPv6: + bind([&](AddrIPv6 &s) { + return net::methods::construct(s, _addr, _port); + }); + break; + + case Domain::UNIX: + bind([&](AddrUnix &s) { + return net::methods::construct(s, _addr); + }); + break; + + default: throw std::invalid_argument("Socket type not supported"); + } + + if (sock_type == Type::TCP || sock_type == Type::SEQPACKET) { + if (listen(sockfd, _q) < 0) { + const auto currErrno = errno; + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + } + } catch (...) { + throw; + } +} + + +void Socket::connect(const char _addr[], const int _port, bool *_errorNB) +{ + try { + switch (sock_domain) { + case Domain::IPv4: + connect( + [&](AddrIPv4 &s) { + return net::methods::construct(s, _addr, _port); + }, + _errorNB); + break; + + case Domain::IPv6: + connect( + [&](AddrIPv6 &s) { + return net::methods::construct(s, _addr, _port); + }, + _errorNB); + break; + + case Domain::UNIX: + connect( + [&](AddrUnix &s) { + return net::methods::construct(s, _addr); + }, + _errorNB); + break; + + default: throw std::invalid_argument("Socket type not supported"); + } + } catch (...) { + throw; + } +} + + +Socket Socket::accept(bool *_errorNB) const +{ + union { + AddrIPv4 ipv4; + AddrIPv6 ipv6; + AddrUnix unix; + AddrStore store; + }; + + std::memset(&store, 0, sizeof(store)); + + socklen_t addrSize = 0; + sockaddr *addrPtr = nullptr; + + switch (sock_domain) { + case Domain::IPv4: + ipv4.sin_family = AF_INET; + std::memset(&ipv4, 0, sizeof(ipv4)); + addrSize = sizeof(ipv4); + addrPtr = reinterpret_cast(&ipv4); + break; + + case Domain::IPv6: + ipv6.sin6_family = AF_INET6; + std::memset(&ipv6, 0, sizeof(ipv6)); + addrSize = sizeof(ipv6); + addrPtr = reinterpret_cast(&ipv6); + break; + + case Domain::UNIX: + unix.sun_family = AF_UNIX; + std::memset(&unix, 0, sizeof(unix)); + addrSize = sizeof(unix); + addrPtr = reinterpret_cast(&unix); + break; + + default: + store.ss_family = static_cast(sock_domain); + addrSize = sizeof(store); + addrPtr = reinterpret_cast(&store); + break; + } + + const auto client = ::accept(sockfd, addrPtr, &addrSize); + const auto currErrno = errno; + + if (client == -1) { + if (currErrno == EAGAIN || currErrno == EWOULDBLOCK) { + if (_errorNB != nullptr) { + *_errorNB = true; + } else { + throw std::invalid_argument("errorNB argument missing"); + } + } else { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + } + + return Socket(client, sock_domain, sock_type, addrPtr); +} + + +void Socket::write(const std::string &_msg, bool *_errorNB) const +{ + const auto written = low_write(::write, _msg); + + const auto currErrno = errno; + if (written == -1) { + if (currErrno == EAGAIN || currErrno == EWOULDBLOCK) { + if (_errorNB != nullptr) { + *_errorNB = true; + } else { + throw std::invalid_argument("errorNB argument missing"); + } + } else { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + } +} + + +void Socket::send(const std::string &_msg, Send _flags, bool *_errorNB) const +{ + const auto flags = static_cast(_flags); + const auto sent = low_write(::send, _msg, flags); + + const auto currErrno = errno; + if (sent == -1) { + if (currErrno == EAGAIN || currErrno == EWOULDBLOCK) { + if (_errorNB != nullptr) { + *_errorNB = true; + } else { + throw std::invalid_argument("errorNB argument missing"); + } + } else { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + } +} + + +std::string Socket::read(const int _numBytes, bool *_errorNB) const +{ + std::string str; + str.reserve(_numBytes); + + const auto recvd = low_read(::read, str); + + const auto currErrno = errno; + if (recvd == -1) { + if (currErrno == EAGAIN || currErrno == EWOULDBLOCK) { + if (_errorNB != nullptr) { + *_errorNB = true; + } else { + throw std::invalid_argument("errorNB argument missing"); + } + } else { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + } + + return str; +} + + +std::string Socket::recv(const int _numBytes, Recv _flags, bool *_errorNB) const +{ + std::string str; + str.reserve(_numBytes); + + const auto flags = static_cast(_flags); + const auto recvd = low_read(::recv, str, flags); + + const auto currErrno = errno; + if (recvd == -1) { + if (currErrno == EAGAIN || currErrno == EWOULDBLOCK) { + if (_errorNB != nullptr) { + *_errorNB = true; + } else { + throw std::invalid_argument("errorNB argument missing"); + } + } else { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + } + + return str; +} + + +void Socket::setOpt(Opt _opType, SockOpt _opValue) const +{ + enum type { TIME = 0, LINGER = 1, INT = 2 }; + auto res = -1; + + const auto optname = static_cast(_opType); + + switch (_opType) { + + case Opt::LINGER: { + if (_opValue.getType() != type::LINGER) { + throw std::invalid_argument("Invalid socket option"); + } + + const auto l = _opValue.getLinger(); + linger lin; + lin.l_onoff = l.first; + lin.l_linger = l.second; + const socklen_t len = sizeof(lin); + + res = setsockopt(sockfd, SOL_SOCKET, optname, &lin, len); + break; + } + + case Opt::RCVTIMEO: + case Opt::SNDTIMEO: { + if (_opValue.getType() != type::TIME) { + throw std::invalid_argument("Invalid socket option"); + } + + const auto t = _opValue.getTime(); + timeval time; + time.tv_sec = t.first; + time.tv_usec = t.second; + const socklen_t len = sizeof(time); + + res = setsockopt(sockfd, SOL_SOCKET, optname, &time, len); + break; + } + + case Opt::MAXSEG: + case Opt::NODELAY: { + if (_opValue.getType() != type::INT) { + throw std::invalid_argument("Invalid socket option"); + } + + const auto i = _opValue.getValue(); + const socklen_t len = sizeof(i); + + res = setsockopt(sockfd, IPPROTO_TCP, optname, &i, len); + break; + } + + default: { + if (_opValue.getType() != type::INT) { + throw std::invalid_argument("Invalid socket option"); + } + const auto i = _opValue.getValue(); + const socklen_t len = sizeof(i); + + res = setsockopt(sockfd, SOL_SOCKET, optname, &i, len); + break; + } + } + + const auto currErrno = errno; + if (res == -1) { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } +} + + +SockOpt Socket::getOpt(Opt _opType) const +{ + const auto optname = static_cast(_opType); + + switch (_opType) { + + case Opt::LINGER: { + linger l; + socklen_t len = sizeof(l); + const auto res = getsockopt(sockfd, SOL_SOCKET, optname, &l, &len); + const auto currErrno = errno; + if (res == -1) { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + SockOpt opt(static_cast(l.l_onoff), l.l_linger); + return opt; + } + + case Opt::RCVTIMEO: + case Opt::SNDTIMEO: { + timeval t; + socklen_t len = sizeof(t); + const auto res = getsockopt(sockfd, SOL_SOCKET, optname, &t, &len); + const auto currErrno = errno; + if (res == -1) { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + SockOpt opt(t.tv_sec, t.tv_usec); + return opt; + } + + case Opt::MAXSEG: + case Opt::NODELAY: { + int i; + socklen_t len = sizeof(i); + const auto res = getsockopt(sockfd, IPPROTO_TCP, optname, &i, &len); + const auto currErrno = errno; + if (res == -1) { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + SockOpt opt(i); + return opt; + } + + default: { + int i; + socklen_t len = sizeof(i); + const auto res = getsockopt(sockfd, SOL_SOCKET, optname, &i, &len); + const auto currErrno = errno; + if (res == -1) { + throw std::runtime_error(net::methods::getErrorMsg(currErrno)); + } + SockOpt opt(i); + return opt; + } + } +} +} diff --git a/test/meson.build b/test/meson.build index c4aa6a8..f182a70 100644 --- a/test/meson.build +++ b/test/meson.build @@ -1,7 +1,14 @@ -test_sources = ['test.cpp'] +test_sources = ['socket_bind_test.cpp', 'socket_constructor_test.cpp', + 'socket_options_test.cpp', 'socket_getSocket_test.cpp', + 'socket_send_test.cpp', 'socket_recv_test.cpp', 'socket_read_write_test.cpp', 'socket_connect_test.cpp'] testexe = executable('testexe', test_sources, include_directories : inc, link_with : netlib, dependencies : [gtest]) +sodebugtestexe = executable('sodebugtestexe', 'socket_option_debug_test.cpp', + include_directories : inc, link_with : netlib, + dependencies : [gtest]) + test('app test', testexe, args: '--gtest_color=yes') +test('app test', sodebugtestexe, args: '--gtest_color=yes') diff --git a/test/socket_bind_test.cpp b/test/socket_bind_test.cpp new file mode 100644 index 0000000..ee026a2 --- /dev/null +++ b/test/socket_bind_test.cpp @@ -0,0 +1,113 @@ +#include "socket.hpp" +#include + +using namespace net; + +TEST(Socket, Bind) +{ + Socket ipv4Socket(Domain::IPv4, Type::TCP); + ASSERT_NO_THROW(ipv4Socket.bind( + [](AddrIPv4 &s) { return methods::construct(s, "127.0.0.1", 0); })); + + Socket ipv4Socket2(Domain::IPv4, Type::TCP); + ASSERT_ANY_THROW(ipv4Socket2.bind( + [](AddrIPv4 &s) { return methods::construct(s, "127.0.0.1", 130000); })); + + + Socket sock_invalid_addr(Domain::IPv4, Type::TCP); + ASSERT_THROW(sock_invalid_addr.bind([](AddrIPv4 &s) { + return methods::construct(s, "256.0.0.0", 8000); + }), + std::invalid_argument); + + Socket sock_invalid_addr2(Domain::IPv4, Type::TCP); + ASSERT_THROW(sock_invalid_addr2.bind([](AddrIPv4 &s) { + return methods::construct(s, "255.0444.0.0", 8001); + }), + std::invalid_argument); + + Socket sock_bad_addr(Domain::IPv4, Type::TCP); + ASSERT_THROW(sock_bad_addr.bind([](AddrIPv4 &s) { return 0; }), + std::invalid_argument); + + Socket sock_bad_addr2(Domain::IPv4, Type::TCP); + ASSERT_THROW(sock_bad_addr2.bind([](AddrIPv4 &s) { return -1; }), + std::runtime_error); + + + Socket sock_bad_port(Domain::IPv4, Type::TCP); + ASSERT_ANY_THROW(sock_bad_port.bind( + [](AddrIPv4 &s) { return methods::construct(s, "0.0.0.0", 130000000); })); + + Socket sock_neg_port(Domain::IPv4, Type::TCP); + ASSERT_ANY_THROW(sock_neg_port.bind( + [](AddrIPv4 &s) { return methods::construct(s, "0.0.0.0", -1300000); })); + + + Socket sock_udp(Domain::IPv4, Type::UDP); + ASSERT_NO_THROW(sock_udp.bind( + [](AddrIPv4 &s) { return methods::construct(s, "127.0.0.0", 8000); })); + + + Socket sock_manual_fill(Domain::IPv4, Type::TCP); + ASSERT_NO_THROW(sock_manual_fill.bind([](AddrIPv4 &s) { + s.sin_family = AF_INET; + s.sin_addr.s_addr = htonl(INADDR_ANY); + s.sin_port = htons(8000); + return 1; + })); + + + Socket sock_liar_ipv4(Domain::IPv4, Type::TCP); + ASSERT_THROW(sock_liar_ipv4.bind([](AddrIPv6 &s) { + return methods::construct(s, "0:0:0:0:0:0:0:1", 14000); + }), + std::runtime_error); + + + Socket sock_bad_addr6(Domain::IPv6, Type::TCP); + ASSERT_THROW(sock_bad_addr6.bind([](AddrIPv6 &s) { return 0; }), + std::invalid_argument); + + + Socket sock_bad_addr62(Domain::IPv6, Type::TCP); + ASSERT_THROW(sock_bad_addr62.bind([](AddrIPv6 &s) { return -1; }), + std::runtime_error); + + Socket sock_correct6(Domain::IPv6, Type::TCP); + ASSERT_NO_THROW(sock_correct6.bind( + [](AddrIPv6 &s) { return methods::construct(s, "::1", 13000); })); + + + Socket badIpv6addr(Domain::IPv6, Type::TCP); + ASSERT_THROW(badIpv6addr.bind([](AddrIPv4 &s) { + return methods::construct(s, "0.0.0.0", 130000000); + }), + std::invalid_argument); + + + Socket badIpv6_4(Domain::IPv6, Type::TCP); + ASSERT_ANY_THROW(badIpv6_4.bind( + [](AddrIPv4 &s) { return methods::construct(s, "0:0:0:0:0:0:0:0", 0); })); + + + Socket badIpv6_42(Domain::IPv6, Type::TCP); + ASSERT_THROW(badIpv6_42.bind([](AddrIPv4 &s) { + return methods::construct(s, "::::::127.0.0.1", -1300000); + }), + std::invalid_argument); + + + Socket unixSocket(Domain::UNIX, Type::TCP); + std::string unixSocketPath("/tmp/unixSocketFile"); + unixSocket.bind([&](AddrUnix &s) { + return methods::construct(s, unixSocketPath.c_str()); + }); + AddrUnix actualUnixSocket; + socklen_t actualUnixSocketSize = sizeof(actualUnixSocket); + ASSERT_NE(getsockname(unixSocket.getSocket(), + (sockaddr *) &actualUnixSocket, + &actualUnixSocketSize), + -1); + EXPECT_EQ(actualUnixSocket.sun_path, unixSocketPath); +} diff --git a/test/socket_connect_test.cpp b/test/socket_connect_test.cpp new file mode 100644 index 0000000..c7456bd --- /dev/null +++ b/test/socket_connect_test.cpp @@ -0,0 +1,102 @@ +#include "socket.hpp" +#include +#include +#include + + +using namespace net; +using namespace std::chrono_literals; + +namespace { + +void runUnixServer(Socket &s, const char path[]) +{ + s.start(path); + if (s.getType() == Type::TCP) { + auto peer = s.accept(); + } +} + +void runNonUnixServer(Socket &s, const unsigned int port) +{ + std::string address; + switch (s.getDomain()) { + case Domain::IPv4: address = "127.0.0.1"; break; + case Domain::IPv6: address = "::1"; break; + + default: address = " "; + } + + try { + s.start(address.c_str(), port); + const auto peer = s.accept(); + } catch (std::exception &e) { + std::cerr << "Exception: " << e.what(); + } +} +} + +TEST(Socket, Connectv4) +{ + Socket server4(Domain::IPv4, Type::TCP); + + std::thread serverThread1(runNonUnixServer, std::ref(server4), 15010); + serverThread1.detach(); + std::this_thread::sleep_for(1s); + + Socket client4(Domain::IPv4, Type::TCP); + ASSERT_NO_THROW(client4.connect("127.0.0.1", 15010)); + client4.close(); +} + + +TEST(Socket, Connectv6) +{ + Socket server6(Domain::IPv6, Type::TCP); + std::thread serverThread1(runNonUnixServer, std::ref(server6), 15020); + serverThread1.detach(); + std::this_thread::sleep_for(1s); + + Socket client6(Domain::IPv6, Type::TCP); + ASSERT_NO_THROW(client6.connect("::1", 15020)); + client6.close(); +} + +TEST(Socket, ConnectUnixTCP) +{ + std::string unixPathServer("/tmp/unixSocketFileServer7"); + Socket serverUnixTCP(Domain::UNIX, Type::TCP); + std::thread serverThread1(runUnixServer, std::ref(serverUnixTCP), + unixPathServer.c_str()); + serverThread1.detach(); + std::this_thread::sleep_for(1s); + + std::string unixPathClient("/tmp/unixSocketFileClient7"); + Socket clientUnixTCP(Domain::UNIX, Type::TCP); + EXPECT_NO_THROW(clientUnixTCP.bind([&](AddrUnix &s) { + return methods::construct(s, unixPathClient.c_str()); + })); + EXPECT_NO_THROW(clientUnixTCP.connect([&](AddrUnix &s) { + return methods::construct(s, unixPathServer.c_str()); + });); + clientUnixTCP.close(); +} + +TEST(Socket, ConnectUnixUDP) +{ + std::string unixPathServer("/tmp/unixSocketFileServer2"); + Socket serverUnixUDP(Domain::UNIX, Type::UDP); + std::thread serverThread1( + [&]() { runUnixServer(serverUnixUDP, unixPathServer.c_str()); }); + serverThread1.detach(); + std::this_thread::sleep_for(1s); + + std::string unixPathClient("/tmp/unixSocketFileClient2"); + Socket clientUnixUDP(Domain::UNIX, Type::UDP); + EXPECT_NO_THROW(clientUnixUDP.bind([&](AddrUnix &s) { + return methods::construct(s, unixPathClient.c_str()); + })); + EXPECT_NO_THROW(clientUnixUDP.connect([&](AddrUnix &s) { + return methods::construct(s, unixPathServer.c_str()); + })); +} diff --git a/test/socket_constructor_test.cpp b/test/socket_constructor_test.cpp new file mode 100644 index 0000000..1f13069 --- /dev/null +++ b/test/socket_constructor_test.cpp @@ -0,0 +1,57 @@ +#include "socket.hpp" +#include + +using namespace net; + + +TEST(Socket, ConstructorIPv4) +{ + EXPECT_NO_THROW(Socket s(Domain::IPv4, Type::TCP)); + EXPECT_NO_THROW(Socket s(Domain::IPv4, Type::UDP)); + EXPECT_NO_THROW(Socket s(Domain::IPv4, Type::TCP, 6)); + EXPECT_NO_THROW(Socket s(Domain::IPv4, Type::SEQPACKET, 132)); + + EXPECT_ANY_THROW(Socket s(Domain::IPv4, Type::TCP, 41)); + EXPECT_ANY_THROW(Socket s(Domain::IPv4, Type::UDP, 6)); + + EXPECT_NO_THROW(Socket otherS(Socket(Domain::IPv4, Type::TCP))); + EXPECT_NO_THROW(Socket otherS(Socket(Domain::IPv4, Type::UDP))); +} + + +TEST(Socket, ConstructorIPv6) +{ + EXPECT_NO_THROW(Socket s(Domain::IPv6, Type::TCP)); + EXPECT_NO_THROW(Socket s(Domain::IPv6, Type::UDP)); + EXPECT_NO_THROW(Socket s(Domain::IPv6, Type::TCP, 6)); + EXPECT_NO_THROW(Socket s(Domain::IPv6, Type::SEQPACKET, 132)); + + EXPECT_ANY_THROW(Socket s(Domain::IPv6, Type::TCP, 41)); + EXPECT_ANY_THROW(Socket s(Domain::IPv6, Type::UDP, 6)); + + EXPECT_NO_THROW(Socket otherS(Socket(Domain::IPv6, Type::TCP))); + EXPECT_NO_THROW(Socket otherS(Socket(Domain::IPv6, Type::UDP))); +} + + +TEST(socket, ConstructorUnix) +{ + EXPECT_NO_THROW(Socket s(Domain::UNIX, Type::TCP)); + EXPECT_NO_THROW(Socket s(Domain::UNIX, Type::UDP)); + EXPECT_NO_THROW(Socket s(Domain::UNIX, Type::SEQPACKET)); + + EXPECT_ANY_THROW(Socket s(Domain::UNIX, Type::TCP, 3)); + + EXPECT_NO_THROW(Socket otherS(Socket(Domain::UNIX, Type::TCP))); + EXPECT_NO_THROW(Socket otherS(Socket(Domain::UNIX, Type::UDP))); +} + + +TEST(Socket, ConstructorRaw) +{ + EXPECT_NO_THROW(Socket s(Domain::IPv4, Type::RAW, 4)); + EXPECT_NO_THROW(Socket s(Domain::IPv6, Type::RAW, 4)); + + EXPECT_NO_THROW(Socket otherS(Socket(Domain::IPv4, Type::RAW, 4))); + EXPECT_NO_THROW(Socket otherS(Socket(Domain::IPv6, Type::RAW, 4))); +} diff --git a/test/socket_getSocket_test.cpp b/test/socket_getSocket_test.cpp new file mode 100644 index 0000000..b92b77d --- /dev/null +++ b/test/socket_getSocket_test.cpp @@ -0,0 +1,32 @@ +#include "socket.hpp" +#include + +using namespace net; + +TEST(Socket, GetSocket) +{ + try { + Socket s1(Domain::IPv4, Type::TCP); + Socket s2(Domain::IPv4, Type::UDP); + Socket s3(Domain::IPv4, Type::RAW, 4); + + Socket s4(Domain::IPv6, Type::TCP); + Socket s5(Domain::IPv6, Type::UDP); + Socket s6(Domain::IPv6, Type::RAW, 4); + + Socket s7(Domain::UNIX, Type::TCP); + Socket s8(Domain::UNIX, Type::UDP); + + ASSERT_GT(s1.getSocket(), 0); + ASSERT_GT(s2.getSocket(), 0); + ASSERT_GT(s3.getSocket(), 0); + ASSERT_GT(s4.getSocket(), 0); + ASSERT_GT(s5.getSocket(), 0); + ASSERT_GT(s6.getSocket(), 0); + ASSERT_GT(s7.getSocket(), 0); + ASSERT_GT(s8.getSocket(), 0); + + } catch (std::exception &e) { + FAIL() << e.what(); + } +} diff --git a/test/socket_option_debug_test.cpp b/test/socket_option_debug_test.cpp new file mode 100644 index 0000000..27b341f --- /dev/null +++ b/test/socket_option_debug_test.cpp @@ -0,0 +1,27 @@ +#include "socket.hpp" +#include + +using namespace net; + +TEST(SocketOptions, Debug) +{ + Socket s(Domain::IPv4, Type::UDP); + + int optval; + socklen_t optlen = sizeof(optval); + + SockOpt opt(1); + ASSERT_EQ(1, opt.getValue()); + + s.setOpt(Opt::DEBUG, opt); + ASSERT_EQ( + 0, getsockopt(s.getSocket(), SOL_SOCKET, SO_DEBUG, &optval, &optlen)); + ASSERT_EQ(1, optval); + + optval = 0; + ASSERT_EQ(0, + setsockopt(s.getSocket(), SOL_SOCKET, SO_DEBUG, &optval, optlen)); + + auto s2 = s.getOpt(Opt::DEBUG); + ASSERT_EQ(0, s2.getValue()); +} diff --git a/test/socket_options_test.cpp b/test/socket_options_test.cpp new file mode 100644 index 0000000..f2b0aa4 --- /dev/null +++ b/test/socket_options_test.cpp @@ -0,0 +1,300 @@ +#include "socket.hpp" +#include + +using namespace net; + +TEST(SocketOptions, GetType) +{ + SockOpt s1(1), s2(true, 5), s3(5L, 500L); + enum { TIME = 0, LINGER = 1, INT = 2 } type; + + ASSERT_EQ(s1.getType(), INT); + ASSERT_EQ(s2.getType(), LINGER); + ASSERT_EQ(s3.getType(), TIME); +} + + +TEST(SocketOptions, CastError) +{ + SockOpt s1(1), s2(true, 5), s3(5L, 500L); + + ASSERT_NO_THROW(s1.getValue()); + ASSERT_NO_THROW(s2.getLinger()); + ASSERT_NO_THROW(s3.getTime()); + + ASSERT_THROW(s1.getTime(), std::bad_cast); + ASSERT_THROW(s1.getLinger(), std::bad_cast); + + ASSERT_THROW(s2.getTime(), std::bad_cast); + ASSERT_THROW(s2.getValue(), std::bad_cast); + + ASSERT_THROW(s3.getValue(), std::bad_cast); + ASSERT_THROW(s3.getLinger(), std::bad_cast); +} + + +TEST(SocketOptions, EqualityTest) +{ + int value = 1; + linger l; + l.l_onoff = 1; + l.l_linger = 5; + timeval t; + t.tv_sec = 5; + t.tv_usec = 500; + + SockOpt opt1(1), opt2(true, 5), opt3(5L, 500L); + + ASSERT_EQ(value, opt1); + ASSERT_EQ(value, opt1.getValue()); + ASSERT_FALSE(l == opt1); + ASSERT_FALSE(t == opt1); + + ASSERT_EQ(l, opt2); + ASSERT_EQ(l.l_onoff, opt2.getLinger().first); + ASSERT_EQ(l.l_linger, opt2.getLinger().second); + ASSERT_FALSE(value == opt2); + ASSERT_FALSE(t == opt2); + + ASSERT_EQ(t, opt3); + ASSERT_EQ(t.tv_sec, opt3.getTime().first); + ASSERT_EQ(t.tv_usec, opt3.getTime().second); + ASSERT_FALSE(value == opt3); + ASSERT_FALSE(l == opt3); +} + + +TEST(SocketOptions, Broadcast) +{ + Socket s4(Domain::IPv4, Type::UDP); + Socket s6(Domain::IPv4, Type::UDP); + + SockOpt option(1); + int optval; + socklen_t optlen = sizeof(optval); + + ASSERT_EQ(1, option.getValue()); + + s4.setOpt(Opt::BROADCAST, option); + s6.setOpt(Opt::BROADCAST, option); + + EXPECT_EQ(0, getsockopt(s4.getSocket(), SOL_SOCKET, SO_BROADCAST, &optval, + &optlen)); + ASSERT_EQ(1, optval); + + EXPECT_EQ(0, getsockopt(s6.getSocket(), SOL_SOCKET, SO_BROADCAST, &optval, + &optlen)); + ASSERT_EQ(1, optval); + + + optval = 0; + EXPECT_EQ( + 0, setsockopt(s4.getSocket(), SOL_SOCKET, SO_BROADCAST, &optval, optlen)); + EXPECT_EQ( + 0, setsockopt(s6.getSocket(), SOL_SOCKET, SO_BROADCAST, &optval, optlen)); + + const auto opt4 = s4.getOpt(Opt::BROADCAST); + const auto opt6 = s6.getOpt(Opt::BROADCAST); + + ASSERT_EQ(0, opt4.getValue()); + ASSERT_EQ(0, opt6.getValue()); + + Socket tcpSocket(Domain::IPv4, Type::TCP); + SockOpt badOpt(1); + ASSERT_EQ(1, badOpt.getValue()); + + // Does nothing to TCP still valid + ASSERT_NO_THROW(tcpSocket.setOpt(Opt::BROADCAST, badOpt)); +} + + +TEST(SocketOptions, Linger) +{ + Socket s(Domain::IPv4, Type::TCP); + SockOpt opt(true, 30); + s.setOpt(Opt::LINGER, opt); + + linger lin; + socklen_t len = sizeof(lin); + + EXPECT_EQ(0, getsockopt(s.getSocket(), SOL_SOCKET, SO_LINGER, &lin, &len)); + ASSERT_EQ(lin, opt); + + lin.l_onoff = 1; + lin.l_linger = 2; + EXPECT_EQ(0, setsockopt(s.getSocket(), SOL_SOCKET, SO_LINGER, &lin, len)); + + SockOpt opt2(true, 2); + ASSERT_EQ(lin, opt2); + + const auto opt3 = s.getOpt(Opt::LINGER); + ASSERT_EQ(lin, opt3); +} + + +TEST(SocketOptions, DontRoute) +{ + Socket s(Domain::IPv4, Type::UDP); + + int optval; + socklen_t optlen = sizeof(optval); + + SockOpt opt(1); + s.setOpt(Opt::DONTROUTE, opt); + ASSERT_EQ( + 0, getsockopt(s.getSocket(), SOL_SOCKET, SO_DONTROUTE, &optval, &optlen)); + ASSERT_EQ(1, optval); + optval = 0; + + ASSERT_EQ( + 0, setsockopt(s.getSocket(), SOL_SOCKET, SO_DONTROUTE, &optval, optlen)); + const auto opt2 = s.getOpt(Opt::DONTROUTE); + ASSERT_EQ(0, opt2); +} + + +TEST(SocketOptions, KeepAlive) +{ + Socket s(Domain::IPv4, Type::UDP); + SockOpt opt(1); + + ASSERT_EQ(1, opt.getValue()); + s.setOpt(Opt::KEEPALIVE, opt); + + int optval; + socklen_t optlen = sizeof(optval); + + ASSERT_EQ( + 0, getsockopt(s.getSocket(), SOL_SOCKET, SO_KEEPALIVE, &optval, &optlen)); + ASSERT_EQ(1, optval); + optval = 0; + + ASSERT_EQ( + 0, setsockopt(s.getSocket(), SOL_SOCKET, SO_KEEPALIVE, &optval, optlen)); + const auto opt2 = s.getOpt(Opt::KEEPALIVE); + ASSERT_EQ(0, opt2); +} + + +TEST(SocketOptions, OOBInline) +{ + Socket s(Domain::IPv4, Type::UDP); + SockOpt opt(1); + ASSERT_EQ(1, opt.getValue()); + s.setOpt(Opt::OOBINLINE, opt); + int optval; + socklen_t optlen = sizeof(optval); + ASSERT_EQ( + 0, getsockopt(s.getSocket(), SOL_SOCKET, SO_OOBINLINE, &optval, &optlen)); + ASSERT_EQ(1, optval); + optval = 0; + + ASSERT_EQ( + 0, setsockopt(s.getSocket(), SOL_SOCKET, SO_OOBINLINE, &optval, optlen)); + + const auto opt2 = s.getOpt(Opt::OOBINLINE); + ASSERT_EQ(0, opt2); +} + + +TEST(SocketOptions, RCVLOWAT) +{ + Socket s(Domain::IPv4, Type::TCP); + SockOpt opt(100); + int optval; + socklen_t optlen = sizeof(optval); + ASSERT_EQ(100, opt.getValue()); + s.setOpt(Opt::RCVLOWAT, opt); + ASSERT_EQ( + 0, getsockopt(s.getSocket(), SOL_SOCKET, SO_RCVLOWAT, &optval, &optlen)); + ASSERT_EQ(100, optval); + optval = 10; + + ASSERT_EQ( + 0, setsockopt(s.getSocket(), SOL_SOCKET, SO_RCVLOWAT, &optval, optlen)); + + const auto opt2 = s.getOpt(Opt::RCVLOWAT); + ASSERT_EQ(10, opt2); +} + + +TEST(SocketOptions, RCVTIMEO) +{ + Socket s(Domain::IPv4, Type::TCP); + SockOpt opt(2L, 5000L); + s.setOpt(Opt::RCVTIMEO, opt); + + timeval t; + socklen_t len = sizeof(t); + EXPECT_EQ(0, getsockopt(s.getSocket(), SOL_SOCKET, SO_RCVTIMEO, &t, &len)); + + // No gurantee about tv_usec to be equal to what is set + ASSERT_EQ(t.tv_sec, opt.getTime().first); + + t.tv_sec = 3; + t.tv_usec = 500; + EXPECT_EQ(0, setsockopt(s.getSocket(), SOL_SOCKET, SO_RCVTIMEO, &t, len)); + + SockOpt opt2(3L, 500L); + ASSERT_EQ(t, opt2); + + const auto opt3 = s.getOpt(Opt::RCVTIMEO); + ASSERT_EQ(t.tv_sec, opt3.getTime().first); +} + + +TEST(SocketOptions, SNDTIMEO) +{ + Socket s(Domain::IPv4, Type::TCP); + + SockOpt opt(2L, 500L); + s.setOpt(Opt::SNDTIMEO, opt); + + timeval t; + socklen_t len = sizeof(t); + EXPECT_EQ(0, getsockopt(s.getSocket(), SOL_SOCKET, SO_SNDTIMEO, &t, &len)); + + // No gurantee about tv_usec to be equal to what is set + ASSERT_EQ(t.tv_sec, opt.getTime().first); + + t.tv_sec = 30; + t.tv_usec = 1000; + EXPECT_EQ(0, setsockopt(s.getSocket(), SOL_SOCKET, SO_SNDTIMEO, &t, len)); + + SockOpt opt2(30L, 1000L); + ASSERT_EQ(t, opt2); + + const auto opt3 = s.getOpt(Opt::SNDTIMEO); + ASSERT_EQ(t.tv_sec, opt3.getTime().first); +} + + +TEST(SocketOptions, MAXSEG) +{ + // This option ignores any value being set, atleast on my machine! + Socket s(Domain::IPv4, Type::TCP); + SockOpt opt(1024); + ASSERT_NO_THROW(s.setOpt(Opt::MAXSEG, opt)); + ASSERT_NO_THROW(s.getOpt(Opt::MAXSEG)); +} + + +TEST(SocketOptions, NODELAY) +{ + Socket s(Domain::IPv4, Type::TCP); + + int optval; + socklen_t optlen = sizeof(optval); + + SockOpt opt(1); + s.setOpt(Opt::NODELAY, opt); + ASSERT_EQ( + 0, getsockopt(s.getSocket(), IPPROTO_TCP, TCP_NODELAY, &optval, &optlen)); + ASSERT_EQ(1, optval); + optval = 0; + + ASSERT_EQ( + 0, setsockopt(s.getSocket(), IPPROTO_TCP, TCP_NODELAY, &optval, optlen)); + const auto opt2 = s.getOpt(Opt::NODELAY); + ASSERT_EQ(0, opt2); +} diff --git a/test/socket_read_write_test.cpp b/test/socket_read_write_test.cpp new file mode 100644 index 0000000..2a02b66 --- /dev/null +++ b/test/socket_read_write_test.cpp @@ -0,0 +1,198 @@ +#include "socket.hpp" +#include +#include +#include +#include + +using namespace net; +using namespace std::chrono_literals; + + +namespace readTest { + +const auto msgLen = 15000; +const std::string msg(readTest::msgLen, 'a'); +const std::string unixServerPath1("/tmp/unixServerPath10"); +const std::string unixServerPath2("/tmp/unixServerPath20"); +const std::string unixClientPath1("/tmp/unixClientPath10"); +const std::string unixClientPath2("/tmp/unixClientPath20"); + +void startUNIXServerTCP(const std::string path) +{ + Socket unixServer(Domain::UNIX, Type::TCP); + EXPECT_NO_THROW(unixServer.start(path.c_str())); + const auto peer = unixServer.accept(); + const auto res = peer.read(readTest::msgLen); + if (res == readTest::msg) { + peer.write("readTest::msg"); + } else { + peer.write(" "); + } + std::this_thread::sleep_for(1s); +} + +void startUNIXServerUDP(const std::string path) +{ + Socket unixServer(Domain::UNIX, Type::UDP); + EXPECT_NO_THROW(unixServer.bind( + [&](AddrUnix &s) { return methods::construct(s, path.c_str()); })); + + const auto res = unixServer.read(readTest::msgLen); + EXPECT_EQ(msg, res); +} + +void udpIPv4ServerProcessing(Socket &serverSocket) +{ + const auto res = serverSocket.read(readTest::msgLen); + EXPECT_EQ(res, readTest::msg); +} + +void udpIPv6ServerProcessing(Socket &serverSocket) +{ + const auto res = serverSocket.read(readTest::msgLen); + EXPECT_EQ(res, readTest::msg); +} + +void tcpIPv4ServerProcessing(Socket &serverSocket) +{ + const auto peer = serverSocket.accept(); + + const auto msg = peer.read(readTest::msgLen); + if (msg == readTest::msg) { + peer.write("readTest::msg"); + } else { + peer.write(" "); + } + std::this_thread::sleep_for(1s); +} + +void tcpIPv6ServerProcessing(Socket &serverSocket) +{ + const auto peer = serverSocket.accept(); + + const auto msg = peer.read(readTest::msgLen); + if (msg == readTest::msg) { + peer.write("readTest::msg"); + } else { + peer.write(" "); + } + std::this_thread::sleep_for(1s); +} + +void startTCPServerIPv6() +{ + Socket myServerSocket1(Domain::IPv6, Type::TCP); + myServerSocket1.start("::1", 20000); + tcpIPv6ServerProcessing(myServerSocket1); +} + + +void startTCPServerIPv4() +{ + Socket myServerSocket1(Domain::IPv4, Type::TCP); + myServerSocket1.start("127.0.0.1", 19000); + tcpIPv4ServerProcessing(myServerSocket1); +} + +void startUDPServerIPv6() +{ + Socket myServerSocket1(Domain::IPv6, Type::UDP); + myServerSocket1.start("::1", 20000); + udpIPv6ServerProcessing(myServerSocket1); +} + + +void startUDPServerIPv4() +{ + Socket myServerSocket1(Domain::IPv4, Type::UDP); + myServerSocket1.start("127.0.0.1", 19000); + udpIPv4ServerProcessing(myServerSocket1); +} +} + + +TEST(Socket, IPv4ReadWrite) +{ + std::thread tcpServerThreadIPv4(readTest::startTCPServerIPv4); + std::thread udpServerThreadIPv4(readTest::startUDPServerIPv4); + std::this_thread::sleep_for(1s); + + Socket tcpClient1(Domain::IPv4, Type::TCP); + EXPECT_EQ(tcpClient1.getType(), Type::TCP); + tcpClient1.connect("127.0.0.1", 19000); + + EXPECT_NO_THROW(tcpClient1.write(readTest::msg)); + EXPECT_EQ(tcpClient1.read(std::string("readTest::msg").size()), + "readTest::msg"); + + tcpClient1.close(); + + Socket udpClient1(Domain::IPv4, Type::UDP); + EXPECT_EQ(udpClient1.getType(), Type::UDP); + udpClient1.connect("127.0.0.1", 19000); + EXPECT_NO_THROW(udpClient1.write(readTest::msg)); + + tcpServerThreadIPv4.join(); + udpServerThreadIPv4.join(); +} + +TEST(Socket, IPv6ReadWrite) +{ + + std::thread tcpServerThreadIPv6(readTest::startTCPServerIPv6); + std::thread udpServerThreadIPv6(readTest::startUDPServerIPv6); + std::this_thread::sleep_for(1s); + + Socket tcpClient2(Domain::IPv6, Type::TCP); + EXPECT_EQ(tcpClient2.getType(), Type::TCP); + tcpClient2.connect("::1", 20000); + + EXPECT_NO_THROW(tcpClient2.write(readTest::msg)); + EXPECT_EQ(tcpClient2.read(std::string("readTest::msg").size()), + "readTest::msg"); + + tcpClient2.close(); + + Socket udpClient2(Domain::IPv6, Type::UDP); + EXPECT_EQ(udpClient2.getType(), Type::UDP); + udpClient2.connect("::1", 20000); + EXPECT_NO_THROW(udpClient2.write(readTest::msg)); + + tcpServerThreadIPv6.join(); + udpServerThreadIPv6.join(); +} + +TEST(Socket, UNIXReadWrite) +{ + + std::thread tcpServerThreadUnix(readTest::startUNIXServerTCP, + std::ref(readTest::unixServerPath1)); + std::thread udpServerThreadUnix(readTest::startUNIXServerUDP, + std::ref(readTest::unixServerPath2)); + std::this_thread::sleep_for(1s); + + Socket unixClient1(Domain::UNIX, Type::TCP); + EXPECT_EQ(unixClient1.getType(), Type::TCP); + EXPECT_NO_THROW(unixClient1.bind([&](AddrUnix &s) { + return methods::construct(s, readTest::unixClientPath1.c_str()); + })); + EXPECT_NO_THROW(unixClient1.connect(readTest::unixServerPath1.c_str())); + + EXPECT_NO_THROW(unixClient1.write(readTest::msg)); + EXPECT_EQ(unixClient1.read(std::string("readTest::msg").size()), + "readTest::msg"); + + unixClient1.close(); + + Socket unixClient2(Domain::UNIX, Type::UDP); + EXPECT_EQ(unixClient2.getType(), Type::UDP); + EXPECT_NO_THROW(unixClient2.bind([&](AddrUnix &s) { + return methods::construct(s, readTest::unixClientPath2.c_str()); + })); + EXPECT_NO_THROW(unixClient2.connect(readTest::unixServerPath2.c_str())); + + EXPECT_NO_THROW(unixClient2.write(readTest::msg)); + + tcpServerThreadUnix.join(); + udpServerThreadUnix.join(); +} diff --git a/test/socket_recv_test.cpp b/test/socket_recv_test.cpp new file mode 100644 index 0000000..2e5eb6b --- /dev/null +++ b/test/socket_recv_test.cpp @@ -0,0 +1,375 @@ +#include "socket.hpp" +#include +#include +#include +#include + +using namespace net; +using namespace std::chrono_literals; + + +namespace recvTest { + +const auto msgLen2 = 15000; +const std::string msg1(recvTest::msgLen2, 'a'); +const std::string msg2(recvTest::msgLen2, 'b'); +const std::string msg3(recvTest::msgLen2, 'c'); + +const std::string unixServerPath1("/tmp/unixServerPath1"); +const std::string unixServerPath2("/tmp/unixServerPath2"); +const std::string unixClientPath1("/tmp/unixClientPath1"); +const std::string unixClientPath2("/tmp/unixClientPath2"); + +std::string getPeerInfo(const Socket &peer) +{ + std::string peerInfo(150, ' '); + + AddrIPv4 actualPeerIp4; + AddrIPv6 actualPeerIp6; + AddrUnix actualPeerAddrUnix; + + bool someError(false); + int status = 0; + const char *status2 = nullptr; + auto d = peer.getDomain(); + + switch (d) { + + case Domain::IPv4: { + socklen_t actualPeerIpLen4 = sizeof(actualPeerIp4); + + status = getpeername(peer.getSocket(), (sockaddr *) &actualPeerIp4, + &actualPeerIpLen4); + + status2 + = inet_ntop(static_cast(d), &actualPeerIp4.sin_addr.s_addr, + &peerInfo.front(), peerInfo.size()); + + someError = (status == -1 || status2 == nullptr) ? true : false; + } break; + + case Domain::IPv6: { + socklen_t actualPeerIpLen6 = sizeof(actualPeerIp6); + + status = getpeername(peer.getSocket(), (sockaddr *) &actualPeerIp6, + &actualPeerIpLen6); + + status2 + = inet_ntop(static_cast(d), &actualPeerIp6.sin6_addr.s6_addr, + &peerInfo.front(), peerInfo.size()); + + someError = (status == -1 || status2 == nullptr) ? true : false; + } break; + + case Domain::UNIX: { + socklen_t actualPeerAddrLenUnix = sizeof(actualPeerAddrUnix); + + status + = getpeername(peer.getSocket(), (sockaddr *) &actualPeerAddrUnix, + &actualPeerAddrLenUnix); + + peerInfo = actualPeerAddrUnix.sun_path; + someError = (status == -1) ? true : false; + } break; + + default: break; + } + + if (someError) { + if (status == -1) { + throw std::runtime_error("Error in getpeername in getPeerInfo()"); + } + if (status2 == nullptr) { + throw std::runtime_error("Error in inet_ntop in getPeerInfo()"); + } + } + + peerInfo.erase(std::remove(peerInfo.begin(), peerInfo.end(), ' '), + peerInfo.end()); + return peerInfo; +} + +void startUNIXServerTCP(const std::string path) +{ + Socket unixServer(Domain::UNIX, Type::TCP); + EXPECT_NO_THROW(unixServer.start(path.c_str())); + const auto peer = unixServer.accept(); + const auto res = peer.recv(recvTest::msgLen2); + if (res == recvTest::msg1) { + peer.write("recvTest::msg1"); + } else { + peer.write(" "); + } + + const auto anotherRes = peer.recv(recvTest::msgLen2, [&](AddrUnix &s) { + const auto actualPeerAddr = getPeerInfo(peer); + std::string currentPeerAddr(s.sun_path); + EXPECT_EQ(actualPeerAddr, currentPeerAddr); + }); + if (anotherRes == recvTest::msg2) { + peer.write("recvTest::msg2"); + } else { + peer.write(" "); + } + + std::this_thread::sleep_for(1s); +} + +void startUNIXServerUDP(const std::string path) +{ + Socket unixServer(Domain::UNIX, Type::UDP); + EXPECT_NO_THROW(unixServer.bind( + [&](AddrUnix &s) { return methods::construct(s, path.c_str()); })); + + const auto res = unixServer.recv(recvTest::msgLen2, [](AddrUnix &s) { + std::string currentPeerAddr(s.sun_path); + EXPECT_EQ(unixClientPath2, currentPeerAddr); + }); + EXPECT_EQ(msg1, res); +} + +void udpIPv4ServerProcessing(Socket &serverSocket) +{ + const auto res = serverSocket.recv(recvTest::msgLen2, [](AddrIPv4 &s) { + std::string str(100, ' '); + const auto ptr + = inet_ntop(AF_INET, &s.sin_addr.s_addr, &str.front(), str.size()); + if (ptr != nullptr) { + str.erase(std::remove(str.begin(), str.end(), ' '), str.end()); + ASSERT_EQ(str.substr(0, str.size() - 1), "127.0.0.1"); + } else { + throw std::invalid_argument("Peer address not valid"); + } + }); + EXPECT_EQ(res, recvTest::msg1); +} + +void udpIPv6ServerProcessing(Socket &serverSocket) +{ + const auto res = serverSocket.recv(recvTest::msgLen2, [](AddrIPv6 &s) { + std::string str(100, ' '); + const auto ptr + = inet_ntop(AF_INET6, &s.sin6_addr.s6_addr, &str.front(), str.size()); + if (ptr != nullptr) { + str.erase(std::remove(str.begin(), str.end(), ' '), str.end()); + ASSERT_EQ(str.substr(0, str.size() - 1), "::1"); + } else { + throw std::invalid_argument("Peer address not valid"); + } + }); + EXPECT_EQ(res, recvTest::msg1); +} + +void tcpIPv4ServerProcessing(Socket &serverSocket) +{ + const auto peer = serverSocket.accept(); + + const auto msg = peer.read(recvTest::msgLen2); + if (msg == recvTest::msg1) { + peer.write("recvTest::msg1"); + } else { + peer.write(" "); + } + + const auto otherMsg = peer.recv(recvTest::msgLen2, [&](AddrIPv4 &s) { + + const auto actualPeerIp = getPeerInfo(peer); + + std::string str(100, ' '); + const auto ptr + = inet_ntop(AF_INET, &s.sin_addr.s_addr, &str.front(), str.size()); + if (ptr != nullptr) { + str.erase(std::remove(str.begin(), str.end(), ' '), str.end()); + EXPECT_NE(str.substr(0, str.size() - 1), actualPeerIp); + } else { + throw std::invalid_argument("Peer address not valid"); + } + }); + + if (otherMsg == recvTest::msg2) { + peer.send("recvTest::msg2"); + } else { + peer.send(" "); + } + + const auto yetAnotherMsg = peer.recv(recvTest::msgLen2); + if (yetAnotherMsg == recvTest::msg3) { + peer.send("recvTest::msg3"); + } else { + peer.send(" "); + } + std::this_thread::sleep_for(1s); +} + +void tcpIPv6ServerProcessing(Socket &serverSocket) +{ + const auto peer = serverSocket.accept(); + + const auto msg = peer.read(recvTest::msgLen2); + if (msg == recvTest::msg1) { + peer.write("recvTest::msg1"); + } else { + peer.write(" "); + } + + const auto otherMsg = peer.recv(recvTest::msgLen2, [&](AddrIPv6 &s) { + + const auto actualPeerIp = getPeerInfo(peer); + + std::string str(100, ' '); + const auto ptr + = inet_ntop(AF_INET6, &s.sin6_addr.s6_addr, &str.front(), str.size()); + if (ptr != nullptr) { + str.erase(std::remove(str.begin(), str.end(), ' '), str.end()); + EXPECT_NE(str.substr(0, str.size() - 1), actualPeerIp); + } else { + throw std::invalid_argument("Peer address not valid"); + } + }); + if (otherMsg == recvTest::msg2) { + peer.send("recvTest::msg2"); + } else { + peer.send(" "); + } + + const auto yetAnotherMsg = peer.recv(recvTest::msgLen2); + if (yetAnotherMsg == recvTest::msg3) { + peer.send("recvTest::msg3"); + } else { + peer.send(" "); + } + std::this_thread::sleep_for(1s); +} + +void startTCPServerIPv6() +{ + Socket myServerSocket1(Domain::IPv6, Type::TCP); + myServerSocket1.start("::1", 18000); + tcpIPv6ServerProcessing(myServerSocket1); +} + + +void startTCPServerIPv4() +{ + Socket myServerSocket1(Domain::IPv4, Type::TCP); + myServerSocket1.start("127.0.0.1", 17000); + tcpIPv4ServerProcessing(myServerSocket1); +} + +void startUDPServerIPv6() +{ + Socket myServerSocket1(Domain::IPv6, Type::UDP); + myServerSocket1.start("::1", 18000); + udpIPv6ServerProcessing(myServerSocket1); +} + + +void startUDPServerIPv4() +{ + Socket myServerSocket1(Domain::IPv4, Type::UDP); + myServerSocket1.start("127.0.0.1", 17000); + udpIPv4ServerProcessing(myServerSocket1); +} +} + + +TEST(Socket, IPv4Recv) +{ + std::thread tcpServerThreadIPv4(recvTest::startTCPServerIPv4); + std::thread udpServerThreadIPv4(recvTest::startUDPServerIPv4); + std::this_thread::sleep_for(1s); + + Socket tcpClient1(Domain::IPv4, Type::TCP); + tcpClient1.connect("127.0.0.1", 17000); + + EXPECT_NO_THROW(tcpClient1.send(recvTest::msg1)); + EXPECT_EQ(tcpClient1.read(4), "recvTest::msg1"); + + EXPECT_NO_THROW(tcpClient1.send(recvTest::msg2)); + EXPECT_EQ(tcpClient1.read(4), "recvTest::msg2"); + + EXPECT_NO_THROW(tcpClient1.send(recvTest::msg3)); + EXPECT_EQ(tcpClient1.read(4), "recvTest::msg3"); + + tcpClient1.close(); + + Socket udpClient1(Domain::IPv4, Type::UDP); + EXPECT_NO_THROW(udpClient1.send(recvTest::msg1, [](AddrIPv4 &s) { + return methods::construct(s, "127.0.0.1", 17000); + })); + + tcpServerThreadIPv4.join(); + udpServerThreadIPv4.join(); +} + +TEST(Socket, IPv6Recv) +{ + + std::thread tcpServerThreadIPv6(recvTest::startTCPServerIPv6); + std::thread udpServerThreadIPv6(recvTest::startUDPServerIPv6); + std::this_thread::sleep_for(1s); + + Socket tcpClient2(Domain::IPv6, Type::TCP); + tcpClient2.connect("::1", 18000); + + EXPECT_NO_THROW(tcpClient2.send(recvTest::msg1)); + EXPECT_EQ(tcpClient2.read(4), "recvTest::msg1"); + + EXPECT_NO_THROW(tcpClient2.send(recvTest::msg2)); + EXPECT_EQ(tcpClient2.read(4), "recvTest::msg2"); + + EXPECT_NO_THROW(tcpClient2.send(recvTest::msg3)); + EXPECT_EQ(tcpClient2.read(4), "recvTest::msg3"); + + tcpClient2.close(); + + Socket udpClient2(Domain::IPv6, Type::UDP); + EXPECT_NO_THROW(udpClient2.send(recvTest::msg1, [](AddrIPv6 &s) { + return methods::construct(s, "::1", 18000); + })); + + tcpServerThreadIPv6.join(); + udpServerThreadIPv6.join(); +} + +TEST(Socket, UNIXRecv) +{ + + std::thread tcpServerThreadUnix(recvTest::startUNIXServerTCP, + std::ref(recvTest::unixServerPath1)); + std::thread udpServerThreadUnix(recvTest::startUNIXServerUDP, + std::ref(recvTest::unixServerPath2)); + std::this_thread::sleep_for(1s); + + Socket unixClient1(Domain::UNIX, Type::TCP); + + EXPECT_NO_THROW(unixClient1.bind([&](AddrUnix &s) { + return methods::construct(s, recvTest::unixClientPath1.c_str()); + })); + EXPECT_NO_THROW(unixClient1.connect(recvTest::unixServerPath1.c_str())); + + EXPECT_NO_THROW(unixClient1.send(recvTest::msg1)); + EXPECT_EQ(unixClient1.read(std::string("recvTest::msg1").size()), + "recvTest::msg1"); + + EXPECT_NO_THROW(unixClient1.send(recvTest::msg2)); + EXPECT_EQ(unixClient1.read(std::string("recvTest::msg2").size()), + "recvTest::msg2"); + + EXPECT_ANY_THROW(unixClient1.send(recvTest::msg3, [&](AddrUnix &s) { + return methods::construct(s, recvTest::unixServerPath1.c_str()); + })); + + unixClient1.close(); + + Socket unixClient2(Domain::UNIX, Type::UDP); + EXPECT_NO_THROW(unixClient2.bind([&](AddrUnix &s) { + return methods::construct(s, recvTest::unixClientPath2.c_str()); + })); + EXPECT_NO_THROW(unixClient2.send(recvTest::msg1, [](AddrUnix &s) { + return methods::construct(s, recvTest::unixServerPath2.c_str()); + })); + + tcpServerThreadUnix.join(); + udpServerThreadUnix.join(); +} diff --git a/test/socket_send_test.cpp b/test/socket_send_test.cpp new file mode 100644 index 0000000..94787a9 --- /dev/null +++ b/test/socket_send_test.cpp @@ -0,0 +1,156 @@ +#include "socket.hpp" +#include +#include +#include +#include + +using namespace net; +using namespace std::chrono_literals; + + +namespace sendTest { + +const auto msgLen = 60000; +const std::string someString(sendTest::msgLen, 'a'); + +void udpIPv4ServerProcessing(Socket &serverSocket) +{ + const auto res = serverSocket.recv(sendTest::msgLen, [](AddrIPv4 &s) { + std::string str(100, ' '); + const auto ptr + = inet_ntop(AF_INET, &s.sin_addr.s_addr, &str.front(), str.size()); + if (ptr != nullptr) { + str.erase(std::remove(str.begin(), str.end(), ' '), str.end()); + ASSERT_EQ(str.substr(0, str.size() - 1), "127.0.0.1"); + } else { + throw std::invalid_argument("Peer address not valid"); + } + }); + EXPECT_EQ(res, sendTest::someString); +} + +void udpIPv6ServerProcessing(Socket &serverSocket) +{ + const auto res = serverSocket.recv(sendTest::msgLen, [](AddrIPv6 &s) { + std::string str(100, ' '); + const auto ptr + = inet_ntop(AF_INET6, &s.sin6_addr.s6_addr, &str.front(), str.size()); + if (ptr != nullptr) { + str.erase(std::remove(str.begin(), str.end(), ' '), str.end()); + ASSERT_EQ(str.substr(0, str.size() - 1), "::1"); + } else { + throw std::invalid_argument("Peer address not valid"); + } + }); + EXPECT_EQ(res, sendTest::someString); +} + +void tcpServerProcessing(Socket &serverSocket) +{ + const auto peer = serverSocket.accept(); + + auto recvd = 0; + while (!(recvd >= sendTest::msgLen)) { + auto msg = peer.read(1024); + recvd += msg.size(); + } + auto response = std::to_string(recvd); + peer.write(response); + + recvd = 0; + while (!(recvd >= sendTest::msgLen)) { + auto msg = peer.read(1024); + recvd += msg.size(); + } + response = std::to_string(recvd); + peer.send(response); + std::this_thread::sleep_for(1s); +} + +void startTCPServerIPv6() +{ + Socket myServerSocket(Domain::IPv6, Type::TCP); + myServerSocket.start("::1", 16000); + tcpServerProcessing(myServerSocket); +} + + +void startTCPServerIPv4() +{ + Socket myServerSocket(Domain::IPv4, Type::TCP); + myServerSocket.start("127.0.0.1", 15000); + tcpServerProcessing(myServerSocket); +} + +void startUDPServerIPv6() +{ + Socket myServerSocket(Domain::IPv6, Type::UDP); + myServerSocket.start("::1", 16000); + udpIPv6ServerProcessing(myServerSocket); +} + + +void startUDPServerIPv4() +{ + Socket myServerSocket(Domain::IPv4, Type::UDP); + myServerSocket.start("127.0.0.1", 15000); + udpIPv4ServerProcessing(myServerSocket); +} + +TEST(Socket, IPv4Send) +{ + std::thread tcpServerThreadIPv4(startTCPServerIPv4); + std::thread udpServerThreadIPv4(startUDPServerIPv4); + std::this_thread::sleep_for(1s); + + Socket tcpClient1(Domain::IPv4, Type::TCP); + tcpClient1.connect("127.0.0.1", 15000); + EXPECT_NO_THROW(tcpClient1.send(sendTest::someString)); + EXPECT_EQ(tcpClient1.read(5), std::to_string(sendTest::msgLen)); + EXPECT_NO_THROW(tcpClient1.send(sendTest::someString, [](AddrIPv4 &s) { + s.sin_family = AF_INET; + s.sin_port = htons(15000); + s.sin_addr.s_addr = htonl(INADDR_ANY); + return 1; + })); + EXPECT_EQ(tcpClient1.read(5), std::to_string(sendTest::msgLen)); + tcpClient1.close(); + + Socket udpClient1(Domain::IPv4, Type::UDP); + EXPECT_NO_THROW(udpClient1.send(sendTest::someString, [](AddrIPv4 &s) { + return methods::construct(s, "127.0.0.1", 15000); + })); + + tcpServerThreadIPv4.join(); + udpServerThreadIPv4.join(); +} + +TEST(Socket, IPv6Send) +{ + + std::thread tcpServerThreadIPv6(startTCPServerIPv6); + std::thread udpServerThreadIPv6(startUDPServerIPv6); + std::this_thread::sleep_for(1s); + + Socket tcpClient2(Domain::IPv6, Type::TCP); + tcpClient2.connect("::1", 16000); + EXPECT_NO_THROW(tcpClient2.send(sendTest::someString)); + EXPECT_EQ(tcpClient2.read(5), std::to_string(sendTest::msgLen)); + EXPECT_NO_THROW(tcpClient2.send(sendTest::someString, [](AddrIPv6 &s) { + s.sin6_family = AF_INET6; + s.sin6_port = htons(16000); + s.sin6_addr = in6addr_any; + return 1; + })); + EXPECT_EQ(tcpClient2.read(5), std::to_string(sendTest::msgLen)); + tcpClient2.close(); + + Socket udpClient2(Domain::IPv6, Type::UDP); + EXPECT_NO_THROW(udpClient2.send(sendTest::someString, [](AddrIPv6 &s) { + return methods::construct(s, "::1", 16000); + })); + + tcpServerThreadIPv6.join(); + udpServerThreadIPv6.join(); +} +} diff --git a/test/test.cpp b/test/test.cpp deleted file mode 100644 index 900bd46..0000000 --- a/test/test.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "net.hpp" -#include - -TEST(saysHello, returnCorrectString) -{ - - std::string s("Hello"); - ASSERT_EQ(s, net::sayHello()); -} diff --git a/wercker.yml b/wercker.yml new file mode 100644 index 0000000..fd908a2 --- /dev/null +++ b/wercker.yml @@ -0,0 +1,606 @@ +box: ubuntu + + +build-start: + + steps: + + - script: + name: report system + code: | + cat /proc/version + cat /etc/*release + + +build-gcc-default: + + steps: + + - script: + name: update system + code: | + export DEBIAN_FRONTEND=noninteractive + apt-get -y update && apt-get -y upgrade + apt-get install locales + export LANGUAGE=en_US.UTF-8 + export LANG=en_US.UTF-8 + export LC_ALL=en_US.UTF-8 + locale-gen en_US.UTF-8 + dpkg-reconfigure locales + + + - script: + name: install gcc + code: | + apt-get -y install build-essential gcc g++ + + - script: + name: echo gcc version + code: | + gcc --version + g++ --version + + - script: + name: install meson and ninja dependencies + code: | + apt-get -y install git curl wget python3 python3-pip pkg-config lcov + + - script: + name: install project dependencies + code: | + apt-get -y install libgtest-dev + + - script: + name: install meson + code: | + pip3 install meson + + - script: + name: install ninja build + code: | + git clone git://github.com/ninja-build/ninja.git && cd ninja + git checkout release + python3 ./configure.py --bootstrap + cp ninja /usr/bin/ninja + cd ../ + rm -rf ninja/ + + - script: + name: echo meson and ninja versions + code: | + meson --version + ninja --version + + - script: + name: build project + code: | + mkdir build && cd build + meson -Db_coverage=true --default-library=static .. + ninja + + - script: + name: test project + code: | + cd build + ./test/testexe + + - script: + name: generate coverage + code: | + cd build + ninja coverage-html + + - script: + name: upload coverage + code: | + cd build + bash <(curl -s https://codecov.io/bash) + + +build-clang-default: + + steps: + + - script: + name: update system + code: | + export DEBIAN_FRONTEND=noninteractive + apt-get -y update && apt-get -y upgrade + apt-get install locales + export LANGUAGE=en_US.UTF-8 + export LANG=en_US.UTF-8 + export LC_ALL=en_US.UTF-8 + locale-gen en_US.UTF-8 + dpkg-reconfigure locales + + + - script: + name: install clang + code: | + apt-get -y install build-essential clang + + - script: + name: echo clang version + code: | + clang --version + clang++ --version + + - script: + name: install meson and ninja dependencies + code: | + apt-get -y install git curl wget python3 python3-pip pkg-config + + - script: + name: install project dependencies + code: | + apt-get -y install libgtest-dev + + - script: + name: install meson + code: | + pip3 install meson + + - script: + name: install ninja build + code: | + git clone git://github.com/ninja-build/ninja.git && cd ninja + git checkout release + python3 ./configure.py --bootstrap + cp ninja /usr/bin/ninja + cd ../ + rm -rf ninja/ + + - script: + name: echo meson and ninja versions + code: | + meson --version + ninja --version + + - script: + name: remove gcc compiler + code: | + apt-get -y remove gcc + + - script: + name: build project + code: | + mkdir build && cd build + meson .. + ninja + + - script: + name: test project + code: | + cd build + ./test/testexe + + +build-gcc-5: + + steps: + + - script: + name: update system + code: | + export DEBIAN_FRONTEND=noninteractive + apt-get -y update && apt-get -y upgrade + apt-get install locales + export LANGUAGE=en_US.UTF-8 + export LANG=en_US.UTF-8 + export LC_ALL=en_US.UTF-8 + locale-gen en_US.UTF-8 + dpkg-reconfigure locales + apt-get -yq update && apt-get -yq upgrade + apt-get install -yq software-properties-common + add-apt-repository ppa:ubuntu-toolchain-r/test -y + + + - script: + name: install gcc 5 + code: | + apt-get -y install gcc-5 g++-5 + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 100 + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-5 100 + apt-get -y install build-essential + update-alternatives --config gcc + update-alternatives --config g++ + + - script: + name: install meson and ninja dependencies + code: | + apt-get -y install git curl wget python3 python3-pip pkg-config + + - script: + name: install project dependencies + code: | + apt-get -y install libgtest-dev + + - script: + name: install ninja build + code: | + git clone git://github.com/ninja-build/ninja.git && cd ninja + git checkout release + python3 ./configure.py --bootstrap + cp ninja /usr/bin/ninja + cd ../ + rm -rf ninja/ + + - script: + name: install meson + code: | + pip3 install meson + + - script: + name: echo meson and ninja versions + code: | + meson --version + ninja --version + + - script: + name: build project + code: | + mkdir build && cd build + meson .. + ninja + + - script: + name: test project + code: | + cd build + ./test/testexe + + +build-gcc-6: + + steps: + + - script: + name: update system + code: | + export DEBIAN_FRONTEND=noninteractive + apt-get -y update && apt-get -y upgrade + apt-get install locales + export LANGUAGE=en_US.UTF-8 + export LANG=en_US.UTF-8 + export LC_ALL=en_US.UTF-8 + locale-gen en_US.UTF-8 + dpkg-reconfigure locales + apt-get -yq update && apt-get -yq upgrade + apt-get install -yq software-properties-common + add-apt-repository ppa:ubuntu-toolchain-r/test -y + apt-get -y update && apt-get -y upgrade + + + - script: + name: install gcc 6 + code: | + apt-get -y install gcc-6 g++-6 + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 100 + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-6 100 + apt-get -y install build-essential + update-alternatives --config gcc + update-alternatives --config g++ + + - script: + name: echo gcc version + code: | + gcc --version + g++ --version + + - script: + name: install dependencies + code: | + apt-get -y install git curl wget python3 python3-pip pkg-config + apt-get -y install libgtest-dev + + - script: + name: install ninja build + code: | + git clone git://github.com/ninja-build/ninja.git && cd ninja + git checkout release + python3 ./configure.py --bootstrap + cp ninja /usr/bin/ninja + cd ../ + rm -rf ninja/ + + - script: + name: install meson + code: | + pip3 install meson + + - script: + name: echo meson and ninja versions + code: | + meson --version + ninja --version + + - script: + name: build project + code: | + mkdir build && cd build + meson .. + ninja + + - script: + name: test project + code: | + cd build + ./test/testexe + + +build-gcc-7: + + steps: + + - script: + name: update system + code: | + export DEBIAN_FRONTEND=noninteractive + apt-get -y update && apt-get -y upgrade + apt-get install locales + export LANGUAGE=en_US.UTF-8 + export LANG=en_US.UTF-8 + export LC_ALL=en_US.UTF-8 + locale-gen en_US.UTF-8 + dpkg-reconfigure locales + apt-get -yq update && apt-get -yq upgrade + apt-get install -yq software-properties-common + add-apt-repository ppa:ubuntu-toolchain-r/test -y + apt-get -y update && apt-get -y upgrade + + + - script: + name: install gcc 7 + code: | + apt-get -y install gcc-7 g++-7 + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 100 + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-7 100 + apt-get -y install build-essential + update-alternatives --config gcc + update-alternatives --config g++ + + - script: + name: echo gcc version + code: | + gcc --version + g++ --version + + - script: + name: install dependencies + code: | + apt-get -y install git curl wget python3 python3-pip pkg-config + apt-get -y install libgtest-dev + + - script: + name: install ninja build + code: | + git clone git://github.com/ninja-build/ninja.git && cd ninja + git checkout release + python3 ./configure.py --bootstrap + cp ninja /usr/bin/ninja + cd ../ + rm -rf ninja/ + + - script: + name: install meson + code: | + pip3 install meson + + - script: + name: echo meson and ninja versions + code: | + meson --version + ninja --version + + - script: + name: build project + code: | + mkdir build && cd build + meson .. + ninja + + - script: + name: test project + code: | + cd build + ./test/testexe + + +address-sanitizer: + + steps: + + - script: + name: update system + code: | + export DEBIAN_FRONTEND=noninteractive + apt-get -y update && apt-get -y upgrade + apt-get install locales + export LANGUAGE=en_US.UTF-8 + export LANG=en_US.UTF-8 + export LC_ALL=en_US.UTF-8 + locale-gen en_US.UTF-8 + dpkg-reconfigure locales + + + - script: + name: install clang + code: | + apt-get -y install build-essential clang + + - script: + name: echo clang version + code: | + clang --version + clang++ --version + + - script: + name: install meson and ninja dependencies + code: | + apt-get -y install git curl wget python3 python3-pip pkg-config + + - script: + name: install project dependencies + code: | + apt-get -y install libgtest-dev + + - script: + name: install meson + code: | + pip3 install meson + + - script: + name: install ninja build + code: | + git clone git://github.com/ninja-build/ninja.git && cd ninja + git checkout release + python3 ./configure.py --bootstrap + cp ninja /usr/bin/ninja + cd ../ + rm -rf ninja/ + + - script: + name: echo meson and ninja versions + code: | + meson --version + ninja --version + + - script: + name: remove gcc compiler + code: | + apt-get -y remove gcc + + - script: + name: build project + code: | + mkdir build && cd build + meson --default-library=static -Db_sanitize=address .. + ninja + + - script: + name: test project + code: | + cd build + ./test/testexe + + +valgrind-suite: + + steps: + + - script: + name: update system + code: | + export DEBIAN_FRONTEND=noninteractive + apt-get -y update && apt-get -y upgrade + apt-get install locales + export LANGUAGE=en_US.UTF-8 + export LANG=en_US.UTF-8 + export LC_ALL=en_US.UTF-8 + locale-gen en_US.UTF-8 + dpkg-reconfigure locales + apt-get -yq update && apt-get -yq upgrade + apt-get install -yq software-properties-common + add-apt-repository ppa:ubuntu-toolchain-r/test -y + apt-get -y update && apt-get -y upgrade + + + - script: + name: install gcc 6 + code: | + apt-get -y install gcc-6 g++-6 + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 100 + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-6 100 + apt-get -y install build-essential + update-alternatives --config gcc + update-alternatives --config g++ + + - script: + name: echo gcc version + code: | + gcc --version + g++ --version + + - script: + name: install dependencies + code: | + apt-get -y install git curl wget python3 python3-pip pkg-config valgrind + apt-get -y install libgtest-dev + + - script: + name: install ninja build + code: | + git clone git://github.com/ninja-build/ninja.git && cd ninja + git checkout release + python3 ./configure.py --bootstrap + cp ninja /usr/bin/ninja + cd ../ + rm -rf ninja/ + + - script: + name: install meson + code: | + pip3 install meson + + - script: + name: echo meson and ninja versions + code: | + meson --version + ninja --version + + - script: + name: build project + code: | + mkdir build && cd build + meson --default-library=static .. + ninja + + - script: + name: build valgrind + code: | + cd build + apt-get -y install subversion autotools-dev automake + apt-get -y remove valgrind + svn co svn://svn.valgrind.org/valgrind/trunk valgrind + cd valgrind + ./autogen.sh + ./configure + make + make install + cd .. + rm -rf valgrind/ + valgrind --version + + - script: + name: valgrind memcheck + code: | + cd build + valgrind ./test/testexe + + - script: + name: valgrind cachegrind + code: | + cd build + sleep 60 + valgrind --tool=cachegrind ./test/testexe + + - script: + name: valgrind massif + code: | + cd build + sleep 60 + valgrind --tool=massif ./test/testexe + + - script: + name: valgrind drd + code: | + cd build + sleep 60 + valgrind --tool=drd ./test/testexe + + - script: + name: valgrind helgrind + code: | + cd build + sleep 60 + valgrind --tool=helgrind ./test/testexe