Line data Source code
1 : // Copyright 2025 powersagitar
2 : //
3 : // Use of this source code is governed by an MIT-style
4 : // license that can be found in the LICENSE file or at
5 : // https://opensource.org/licenses/MIT.
6 :
7 : #ifndef TCP_H
8 : #define TCP_H
9 :
10 : #include <arpa/inet.h>
11 : #include <netinet/in.h>
12 : #include <sys/errno.h>
13 : #include <sys/socket.h>
14 : #include <unistd.h>
15 :
16 : #include "spdlog/spdlog.h"
17 :
18 : namespace http {
19 :
20 : namespace internals {
21 :
22 : constexpr int kNullSock = -1;
23 : constexpr int kHTTPPort = 80;
24 :
25 : } // namespace internals
26 :
27 : class TcpClient {
28 : public:
29 : TcpClient(const TcpClient &) = delete;
30 : TcpClient &operator=(const TcpClient &) = delete;
31 :
32 : TcpClient(TcpClient &&obj) noexcept { *this = std::move(obj); }
33 :
34 : TcpClient &operator=(TcpClient &&rhs) noexcept {
35 : if (this == &rhs) {
36 : return *this;
37 : }
38 :
39 : this->sockfd_ = rhs.sockfd_;
40 : rhs.sockfd_ = internals::kNullSock;
41 :
42 : return *this;
43 : }
44 :
45 1 : TcpClient(const int sockfd, const sockaddr_in addr,
46 : const socklen_t addr_len) noexcept
47 1 : : sockfd_(sockfd), addr_(addr), addr_len_(addr_len) {}
48 :
49 2 : ~TcpClient() noexcept {
50 1 : if (sockfd_ == internals::kNullSock) {
51 0 : return;
52 : }
53 :
54 1 : if (close(sockfd_) != 0) {
55 : // TODO: error handling / logging
56 : // throw std::system_error(
57 : // errno, std::system_category(),
58 : // "Failed to close client socket " + std::to_string(sockfd_));
59 : }
60 1 : }
61 :
62 1 : ssize_t Recv(void *const buffer, const size_t length,
63 : const int flags = 0) const noexcept {
64 1 : return recv(sockfd_, buffer, length, flags);
65 : }
66 :
67 : ssize_t Send(const void *const buffer, const size_t length,
68 : const int flags = 0) const noexcept {
69 : return send(sockfd_, buffer, length, flags);
70 : }
71 :
72 : [[nodiscard]] int sockfd() const noexcept { return sockfd_; }
73 :
74 : private:
75 : int sockfd_ = internals::kNullSock;
76 : sockaddr_in addr_{};
77 : socklen_t addr_len_{};
78 : };
79 :
80 : class TcpServer {
81 : public:
82 : /**
83 : * See man protocols(5)
84 : */
85 : enum class Protocol : int {
86 : kIP = 0,
87 : };
88 :
89 : /**
90 : * SocketServer is uniquely owned. No two instances may share the same socket.
91 : */
92 : TcpServer(const TcpServer &) = delete;
93 : TcpServer &operator=(const TcpServer &) = delete;
94 :
95 : TcpServer(TcpServer &&obj) noexcept { *this = std::move(obj); }
96 :
97 : TcpServer &operator=(TcpServer &&rhs) noexcept {
98 : if (this == &rhs) {
99 : return *this;
100 : }
101 :
102 : this->sockfd_ = rhs.sockfd_;
103 : rhs.sockfd_ = internals::kNullSock;
104 :
105 : return *this;
106 : }
107 :
108 : /**
109 : * See man socket(2)
110 : */
111 1 : TcpServer() noexcept
112 1 : : sockfd_(socket(PF_INET, SOCK_STREAM, static_cast<int>(Protocol::kIP))) {
113 1 : }
114 :
115 : /**
116 : * See man close(2)
117 : */
118 2 : ~TcpServer() noexcept {
119 1 : if (sockfd_ == internals::kNullSock) {
120 0 : return;
121 : }
122 :
123 1 : if (close(sockfd_) != 0) {
124 : // TODO: error handling / logging
125 : // throw std::system_error(
126 : // errno, std::system_category(),
127 : // "Failed to close server socket " + std::to_string(sockfd_));
128 : }
129 1 : }
130 :
131 : /**
132 : * See man bind(2)
133 : */
134 1 : [[nodiscard]] int Bind(const sa_family_t sin_family = AF_INET,
135 : const in_port_t sin_port = internals::kHTTPPort,
136 : const in_addr sin_addr = {INADDR_ANY}) const noexcept {
137 1 : sockaddr_in addr{.sin_family = sin_family,
138 : .sin_port = sin_port,
139 : .sin_addr = sin_addr,
140 1 : .sin_zero{0}};
141 :
142 : // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
143 1 : return bind(sockfd_, reinterpret_cast<sockaddr *>(&addr), sizeof(addr));
144 : }
145 :
146 : /**
147 : * See man listen(2)
148 : */
149 1 : [[nodiscard]] int Listen(const int backlog = 512) const noexcept {
150 1 : return listen(sockfd_, backlog);
151 : }
152 :
153 : /**
154 : * See man accept(2)
155 : */
156 1 : [[nodiscard]] TcpClient Accept() const {
157 1 : sockaddr_in addr{};
158 1 : socklen_t addr_len = sizeof addr;
159 :
160 : const int client_sockfd =
161 : // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
162 1 : accept(sockfd_, reinterpret_cast<sockaddr *>(&addr), &addr_len);
163 :
164 1 : if (client_sockfd == internals::kNullSock) {
165 0 : throw std::system_error(errno, std::system_category(),
166 0 : "Failed to accept client socket");
167 : }
168 :
169 2 : return {client_sockfd, addr, addr_len};
170 : }
171 :
172 : private:
173 : int sockfd_ = internals::kNullSock;
174 : };
175 :
176 : } // namespace http
177 :
178 : #endif // TCP_H
|