Skip to content

Commit fb9f7b7

Browse files
authored
TCP+TLS Client's use mutable|const buffers for send/recv return values (#392)
* TCP+TLS Client's use mutable|const buffers for send/recv return values * This adds another templated parameter to send/recv for tls|tcp clients on the return value type so the returned buffers contain the same element_type instead of just char. * Add udp peer as well Closes #390
1 parent c301d7d commit fb9f7b7

File tree

7 files changed

+79
-69
lines changed

7 files changed

+79
-69
lines changed

include/coro/concepts/buffer.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ concept const_buffer = requires(const type t)
2222
{ t.data() } -> std::same_as<const typename std::remove_pointer_t<decltype(t.data())>*>;
2323
};
2424

25+
template<const_buffer buffer_type>
26+
struct const_buffer_traits
27+
{
28+
using element_type = std::add_const_t<std::remove_pointer_t<decltype(std::declval<buffer_type>().data())>>;
29+
};
30+
2531
template<typename type>
2632
concept mutable_buffer = requires(type t)
2733
{
@@ -36,6 +42,13 @@ concept mutable_buffer = requires(type t)
3642
// We check the return type of `data()` to be a non-const pointer to the underlying type
3743
{ t.data() } -> std::same_as<typename std::remove_pointer_t<decltype(t.data())>*>;
3844
};
45+
46+
template<mutable_buffer buffer_type>
47+
struct mutable_buffer_traits
48+
{
49+
using element_type = std::remove_pointer_t<decltype(std::declval<buffer_type>().data())>;
50+
};
51+
3952
// clang-format on
4053

4154
} // namespace coro::concepts

include/coro/net/tcp/client.hpp

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class client
3030
};
3131

3232
/**
33-
* Creates a new tcp client that can connect to an ip address + port. By default the socket
33+
* Creates a new tcp client that can connect to an ip address + port. By default, the socket
3434
* created will be in non-blocking mode, meaning that any sending or receiving of data should
3535
* poll for event readiness prior.
3636
* @param scheduler The io scheduler to drive the tcp client.
@@ -43,7 +43,7 @@ class client
4343
.port = 8080,
4444
});
4545
client(const client& other);
46-
client(client&& other);
46+
client(client&& other) noexcept;
4747
auto operator=(const client& other) noexcept -> client&;
4848
auto operator=(client&& other) noexcept -> client&;
4949
~client();
@@ -52,8 +52,8 @@ class client
5252
* @return The tcp socket this client is using.
5353
* @{
5454
**/
55-
auto socket() -> net::socket& { return m_socket; }
56-
auto socket() const -> const net::socket& { return m_socket; }
55+
[[nodiscard]] auto socket() -> net::socket& { return m_socket; }
56+
[[nodiscard]] auto socket() const -> const net::socket& { return m_socket; }
5757
/** @} */
5858

5959
/**
@@ -66,81 +66,78 @@ class client
6666

6767
/**
6868
* Polls for the given operation on this client's tcp socket. This should be done prior to
69-
* calling recv and after a send that doesn't send the entire buffer.
69+
* calling recv and after a send call that doesn't send the entire buffer.
7070
* @param op The poll operation to perform, use read for incoming data and write for outgoing.
7171
* @param timeout The amount of time to wait for the poll event to be ready. Use zero for infinte timeout.
7272
* @return The status result of th poll operation. When poll_status::event is returned then the
7373
* event operation is ready.
7474
*/
75-
auto poll(coro::poll_op op, std::chrono::milliseconds timeout = std::chrono::milliseconds{0})
75+
auto poll(const coro::poll_op op, const std::chrono::milliseconds timeout = std::chrono::milliseconds{0})
7676
-> coro::task<poll_status>
7777
{
7878
return m_io_scheduler->poll(m_socket, op, timeout);
7979
}
8080

8181
/**
82-
* Receives incoming data into the given buffer. By default since all tcp client sockets are set
82+
* Receives incoming data into the given buffer. By default, since all tcp client sockets are set
8383
* to non-blocking use co_await poll() to determine when data is ready to be received.
8484
* @param buffer Received bytes are written into this buffer up to the buffers size.
85-
* @return The status of the recv call and a span of the bytes recevied (if any). The span of
85+
* @return The status of the recv call and a span of the bytes received (if any). The span of
8686
* bytes will be a subspan or full span of the given input buffer.
8787
*/
88-
template<concepts::mutable_buffer buffer_type>
89-
auto recv(buffer_type&& buffer) -> std::pair<recv_status, std::span<char>>
88+
template<concepts::mutable_buffer buffer_type, typename element_type = typename concepts::mutable_buffer_traits<buffer_type>::element_type>
89+
auto recv(buffer_type&& buffer) -> std::pair<recv_status, std::span<element_type>>
9090
{
9191
// If the user requested zero bytes, just return.
9292
if (buffer.empty())
9393
{
94-
return {recv_status::ok, std::span<char>{}};
94+
return {recv_status::ok, std::span<element_type>{}};
9595
}
9696

9797
auto bytes_recv = ::recv(m_socket.native_handle(), buffer.data(), buffer.size(), 0);
9898
if (bytes_recv > 0)
9999
{
100-
// Ok, we've recieved some data.
101-
return {recv_status::ok, std::span<char>{buffer.data(), static_cast<size_t>(bytes_recv)}};
100+
// Ok, we've received some data.
101+
return {recv_status::ok, std::span<element_type>{buffer.data(), static_cast<size_t>(bytes_recv)}};
102102
}
103-
else if (bytes_recv == 0)
103+
104+
if (bytes_recv == 0)
104105
{
105106
// On TCP stream sockets 0 indicates the connection has been closed by the peer.
106-
return {recv_status::closed, std::span<char>{}};
107-
}
108-
else
109-
{
110-
// Report the error to the user.
111-
return {static_cast<recv_status>(errno), std::span<char>{}};
107+
return {recv_status::closed, std::span<element_type>{}};
112108
}
109+
110+
// Report the error to the user.
111+
return {static_cast<recv_status>(errno), std::span<element_type>{}};
113112
}
114113

115114
/**
116-
* Sends outgoing data from the given buffer. If a partial write occurs then use co_await poll()
115+
* Sends outgoing data from the given buffer. If a partial write occurs then use co_await poll()
117116
* to determine when the tcp client socket is ready to be written to again. On partial writes
118117
* the status will be 'ok' and the span returned will be non-empty, it will contain the buffer
119118
* span data that was not written to the client's socket.
120119
* @param buffer The data to write on the tcp socket.
121120
* @return The status of the send call and a span of any remaining bytes not sent. If all bytes
122121
* were successfully sent the status will be 'ok' and the remaining span will be empty.
123122
*/
124-
template<concepts::const_buffer buffer_type>
125-
auto send(const buffer_type& buffer) -> std::pair<send_status, std::span<const char>>
123+
template<concepts::const_buffer buffer_type, typename element_type = typename concepts::const_buffer_traits<buffer_type>::element_type>
124+
auto send(const buffer_type& buffer) -> std::pair<send_status, std::span<element_type>>
126125
{
127126
// If the user requested zero bytes, just return.
128127
if (buffer.empty())
129128
{
130-
return {send_status::ok, std::span<const char>{buffer.data(), buffer.size()}};
129+
return {send_status::ok, std::span<element_type>{buffer.data(), buffer.size()}};
131130
}
132131

133132
auto bytes_sent = ::send(m_socket.native_handle(), buffer.data(), buffer.size(), 0);
134133
if (bytes_sent >= 0)
135134
{
136135
// Some or all of the bytes were written.
137-
return {send_status::ok, std::span<const char>{buffer.data() + bytes_sent, buffer.size() - bytes_sent}};
138-
}
139-
else
140-
{
141-
// Due to the error none of the bytes were written.
142-
return {static_cast<send_status>(errno), std::span<const char>{buffer.data(), buffer.size()}};
136+
return {send_status::ok, std::span<element_type>{buffer.data() + bytes_sent, buffer.size() - bytes_sent}};
143137
}
138+
139+
// Due to the error none of the bytes were written.
140+
return {static_cast<send_status>(errno), std::span<element_type>{buffer.data(), buffer.size()}};
144141
}
145142

146143
private:
@@ -154,7 +151,7 @@ class client
154151
options m_options{};
155152
/// The tcp socket.
156153
net::socket m_socket{-1};
157-
/// Cache the status of the connect in the event the user calls connect() again.
154+
/// Cache the status of the connect call in the event the user calls connect() again.
158155
std::optional<net::connect_status> m_connect_status{std::nullopt};
159156
};
160157

include/coro/net/tls/client.hpp

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class client
3434
};
3535

3636
/**
37-
* Creates a new tls client that can connect to an ip address + port. By default the socket
37+
* Creates a new tls client that can connect to an ip address + port. By default, the socket
3838
* created will be in non-blocking mode, meaning that any sending or receiving of data should
3939
* be polled for event readiness prior.
4040
* @param scheduler The io scheduler to drive the tls client.
@@ -49,7 +49,7 @@ class client
4949
.port = 8080,
5050
});
5151
client(const client&) = delete;
52-
client(client&& other);
52+
client(client&& other) noexcept;
5353
auto operator=(const client&) noexcept -> client& = delete;
5454
auto operator=(client&& other) noexcept -> client&;
5555
~client();
@@ -58,8 +58,8 @@ class client
5858
* @return The tcp socket this client is using.
5959
* @{
6060
**/
61-
auto socket() -> net::socket& { return m_socket; }
62-
auto socket() const -> const net::socket& { return m_socket; }
61+
[[nodiscard]] auto socket() -> net::socket& { return m_socket; }
62+
[[nodiscard]] auto socket() const -> const net::socket& { return m_socket; }
6363
/** @} */
6464

6565
/**
@@ -77,13 +77,13 @@ class client
7777
* @return The status of the recv call and a span of the bytes received (if any). The span of
7878
* bytes will be a subspan or full span of the given input buffer.
7979
*/
80-
template<concepts::mutable_buffer buffer_type>
80+
template<concepts::mutable_buffer buffer_type, typename element_type = typename concepts::mutable_buffer_traits<buffer_type>::element_type>
8181
auto recv(buffer_type& buffer, std::optional<std::chrono::milliseconds> timeout = std::nullopt)
82-
-> coro::task<std::pair<recv_status, std::span<char>>>
82+
-> coro::task<std::pair<recv_status, std::span<element_type>>>
8383
{
8484
if (buffer.empty())
8585
{
86-
co_return {recv_status::buffer_is_empty, std::span<char>{}};
86+
co_return {recv_status::buffer_is_empty, std::span<element_type>{}};
8787
}
8888

8989
auto* tls = m_tls_info.m_tls_ptr.get();
@@ -105,7 +105,7 @@ class client
105105
t -= duration;
106106
if (t <= std::chrono::microseconds{0})
107107
{
108-
co_return {recv_status::timeout, std::span<char>{}};
108+
co_return {recv_status::timeout, std::span<element_type>{}};
109109
}
110110
}
111111

@@ -119,11 +119,11 @@ class client
119119
case poll_status::event:
120120
break;
121121
case poll_status::timeout:
122-
co_return {recv_status::timeout, std::span<char>{}};
122+
co_return {recv_status::timeout, std::span<element_type>{}};
123123
case poll_status::error:
124-
co_return {recv_status::error, std::span<char>{}};
124+
co_return {recv_status::error, std::span<element_type>{}};
125125
case poll_status::closed:
126-
co_return {recv_status::closed, std::span<char>{}};
126+
co_return {recv_status::closed, std::span<element_type>{}};
127127
}
128128

129129
size_t bytes_recv{0};
@@ -148,32 +148,32 @@ class client
148148
}
149149
else
150150
{
151-
co_return {static_cast<recv_status>(err), std::span<char>{}};
151+
co_return {static_cast<recv_status>(err), std::span<element_type>{}};
152152
}
153153
}
154154
else
155155
{
156-
co_return {recv_status::ok, std::span<char>{buffer.data(), static_cast<size_t>(bytes_recv)}};
156+
co_return {recv_status::ok, std::span<element_type>{buffer.data(), static_cast<size_t>(bytes_recv)}};
157157
}
158158
}
159159
}
160160

161161
/**
162162
* Sends outgoing data from the given buffer. If a partial write occurs then the returned span will
163-
* contain a view into the unsent bytes. This function will automatically call for writeability on the socket.
163+
* contain a view into the unsent bytes. This function will automatically call for write-ability on the socket.
164164
* @param buffer The data to write on the tls socket.
165165
* @param timeout The amount of time to send the data before timing out.
166166
* @return The status of the send call and a span of any remaining bytes not sent. If all bytes
167167
* were successfully sent the status will be 'ok' and the remaining span will be empty.
168168
*/
169-
template<concepts::const_buffer buffer_type>
169+
template<concepts::const_buffer buffer_type, typename element_type = typename concepts::const_buffer_traits<buffer_type>::element_type>
170170
auto send(const buffer_type& buffer, std::optional<std::chrono::milliseconds> timeout = std::nullopt)
171-
-> coro::task<std::pair<send_status, std::span<const char>>>
171+
-> coro::task<std::pair<send_status, std::span<element_type>>>
172172
{
173173
// Make sure there is data to send.
174174
if (buffer.empty())
175175
{
176-
co_return {send_status::buffer_is_empty, std::span<const char>{buffer.data(), buffer.size()}};
176+
co_return {send_status::buffer_is_empty, std::span<element_type>{buffer.data(), buffer.size()}};
177177
}
178178

179179
auto* tls = m_tls_info.m_tls_ptr.get();
@@ -195,7 +195,7 @@ class client
195195
t -= duration;
196196
if (t <= std::chrono::microseconds{0})
197197
{
198-
co_return {send_status::timeout, std::span<char>{}};
198+
co_return {send_status::timeout, std::span<element_type>{}};
199199
}
200200
}
201201

@@ -209,11 +209,11 @@ class client
209209
case poll_status::event:
210210
break;
211211
case poll_status::timeout:
212-
co_return {send_status::timeout, std::span<char>{}};
212+
co_return {send_status::timeout, std::span<element_type>{}};
213213
case poll_status::error:
214-
co_return {send_status::error, std::span<char>{}};
214+
co_return {send_status::error, std::span<element_type>{}};
215215
case poll_status::closed:
216-
co_return {send_status::closed, std::span<char>{}};
216+
co_return {send_status::closed, std::span<element_type>{}};
217217
}
218218

219219
size_t bytes_sent{0};
@@ -239,13 +239,13 @@ class client
239239
}
240240
else
241241
{
242-
co_return {static_cast<send_status>(err), std::span<char>{}};
242+
co_return {static_cast<send_status>(err), std::span<element_type>{}};
243243
}
244244
}
245245
else
246246
{
247247
co_return {
248-
send_status::ok, std::span<const char>{buffer.data() + bytes_sent, buffer.size() - bytes_sent}};
248+
send_status::ok, std::span<element_type>{buffer.data() + bytes_sent, buffer.size() - bytes_sent}};
249249
}
250250
}
251251
}

include/coro/net/udp/peer.hpp

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class peer
6060
auto socket() const noexcept -> const net::socket& { return m_socket; }
6161

6262
/**
63-
* @param op The poll operation to perform on the udp socket. Note that if this is a send only
63+
* @param op The poll operation to perform on the udp socket. Note that if this is a send call only
6464
* udp socket (did not bind) then polling for read will not work.
6565
* @param timeout The timeout for the poll operation to be ready.
6666
* @return The result status of the poll operation.
@@ -77,12 +77,12 @@ class peer
7777
* @return The status of send call and a span view of any data that wasn't sent. This data if
7878
* un-sent will correspond to bytes at the end of the given buffer.
7979
*/
80-
template<concepts::const_buffer buffer_type>
81-
auto sendto(const info& peer_info, const buffer_type& buffer) -> std::pair<send_status, std::span<const char>>
80+
template<concepts::const_buffer buffer_type, typename element_type = typename concepts::const_buffer_traits<buffer_type>::element_type>
81+
auto sendto(const info& peer_info, const buffer_type& buffer) -> std::pair<send_status, std::span<element_type>>
8282
{
8383
if (buffer.empty())
8484
{
85-
return {send_status::ok, std::span<const char>{}};
85+
return {send_status::ok, std::span<element_type>{}};
8686
}
8787

8888
sockaddr_in peer{};
@@ -97,11 +97,11 @@ class peer
9797

9898
if (bytes_sent >= 0)
9999
{
100-
return {send_status::ok, std::span<const char>{buffer.data() + bytes_sent, buffer.size() - bytes_sent}};
100+
return {send_status::ok, std::span<element_type>{buffer.data() + bytes_sent, buffer.size() - bytes_sent}};
101101
}
102102
else
103103
{
104-
return {static_cast<send_status>(errno), std::span<const char>{}};
104+
return {static_cast<send_status>(errno), std::span<element_type>{}};
105105
}
106106
}
107107

@@ -112,13 +112,13 @@ class peer
112112
* always start at the beggining of the buffer but depending on how large the data was
113113
* it might not fill the entire buffer.
114114
*/
115-
template<concepts::mutable_buffer buffer_type>
116-
auto recvfrom(buffer_type&& buffer) -> std::tuple<recv_status, peer::info, std::span<char>>
115+
template<concepts::mutable_buffer buffer_type, typename element_type = typename concepts::mutable_buffer_traits<buffer_type>::element_type>
116+
auto recvfrom(buffer_type&& buffer) -> std::tuple<recv_status, peer::info, std::span<element_type>>
117117
{
118118
// The user must bind locally to be able to receive packets.
119119
if (!m_bound)
120120
{
121-
return {recv_status::udp_not_bound, peer::info{}, std::span<char>{}};
121+
return {recv_status::udp_not_bound, peer::info{}, std::span<element_type>{}};
122122
}
123123

124124
sockaddr_in peer{};
@@ -129,7 +129,7 @@ class peer
129129

130130
if (bytes_read < 0)
131131
{
132-
return {static_cast<recv_status>(errno), peer::info{}, std::span<char>{}};
132+
return {static_cast<recv_status>(errno), peer::info{}, std::span<element_type>{}};
133133
}
134134

135135
std::span<const uint8_t> ip_addr_view{
@@ -142,7 +142,7 @@ class peer
142142
peer::info{
143143
.address = net::ip_address{ip_addr_view, static_cast<net::domain_t>(peer.sin_family)},
144144
.port = ntohs(peer.sin_port)},
145-
std::span<char>{buffer.data(), static_cast<size_t>(bytes_read)}};
145+
std::span<element_type>{buffer.data(), static_cast<size_t>(bytes_read)}};
146146
}
147147

148148
private:

0 commit comments

Comments
 (0)