1 /* $Id: ncbi_uv_nghttp2.cpp 632910 2021-06-09 19:11:25Z ivanov $
2 * ===========================================================================
3 *
4 * PUBLIC DOMAIN NOTICE
5 * National Center for Biotechnology Information
6 *
7 * This software/database is a "United States Government Work" under the
8 * terms of the United States Copyright Act. It was written as part of
9 * the author's official duties as a United States Government employee and
10 * thus cannot be copyrighted. This software/database is freely available
11 * to the public for use. The National Library of Medicine and the U.S.
12 * Government have not placed any restriction on its use or reproduction.
13 *
14 * Although all reasonable efforts have been taken to ensure the accuracy
15 * and reliability of the software and data, the NLM and the U.S.
16 * Government do not and cannot warrant the performance or results that
17 * may be obtained by using this software or data. The NLM and the U.S.
18 * Government disclaim all warranties, express or implied, including
19 * warranties of performance, merchantability or fitness for any particular
20 * purpose.
21 *
22 * Please cite the author in any work or product based on this material.
23 *
24 * ===========================================================================
25 *
26 * Authors: Rafael Sadyrov
27 *
28 */
29
30 #include <ncbi_pch.hpp>
31
32 #include "connect_misc_impl.hpp"
33
34 #include <connect/impl/ncbi_uv_nghttp2.hpp>
35 #include <connect/ncbi_socket.hpp>
36 #include <connect/ncbi_tls.h>
37
38 #include <corelib/ncbiapp.hpp>
39 #include <corelib/request_ctx.hpp>
40
41 BEGIN_NCBI_SCOPE
42
43 #define NCBI_UV_WRITE_TRACE(message) _TRACE(message)
44 #define NCBI_UV_TCP_TRACE(message) _TRACE(message)
45 #define NCBI_NGHTTP2_SESSION_TRACE(message) _TRACE(message)
46 #define NCBI_UVNGHTTP2_TLS_TRACE(message) _TRACE(message)
47 #define NCBI_UVNGHTTP2_SESSION_TRACE(message) _TRACE(message)
48
49 using namespace NCBI_XCONNECT;
50
51 template <typename T, enable_if_t<is_signed<T>::value, T>>
operator ()(T e)52 const char* SUvNgHttp2_Error::SMbedTlsStr::operator()(T e)
53 {
54 mbedtls_strerror(static_cast<int>(e), data(), size());
55 return data();
56 }
57
SUv_Write(void * user_data,size_t buf_size)58 SUv_Write::SUv_Write(void* user_data, size_t buf_size) :
59 m_UserData(user_data),
60 m_BufSize(buf_size)
61 {
62 NewBuffer();
63 NCBI_UV_WRITE_TRACE(this << " created");
64 }
65
Write(uv_stream_t * handle,uv_write_cb cb)66 int SUv_Write::Write(uv_stream_t* handle, uv_write_cb cb)
67 {
68 _ASSERT(m_CurrentBuffer);
69 auto& request = m_CurrentBuffer->request;
70 auto& data = m_CurrentBuffer->data;
71 auto& in_progress = m_CurrentBuffer->in_progress;
72
73 _ASSERT(!in_progress);
74
75 if (data.empty()) {
76 NCBI_UV_WRITE_TRACE(this << " empty write");
77 return 0;
78 }
79
80 uv_buf_t buf;
81 buf.base = data.data();
82 buf.len = static_cast<decltype(buf.len)>(data.size());
83
84 auto try_rv = uv_try_write(handle, &buf, 1);
85
86 // If immediately sent everything
87 if (try_rv == static_cast<int>(data.size())) {
88 NCBI_UV_WRITE_TRACE(this << '/' << &request << " try-wrote: " << try_rv);
89 data.clear();
90 return 0;
91
92 // If sent partially
93 } else if (try_rv > 0) {
94 NCBI_UV_WRITE_TRACE(this << '/' << &request << " try-wrote partially: " << try_rv);
95 _ASSERT(try_rv < static_cast<int>(data.size()));
96 buf.base += try_rv;
97 buf.len -= try_rv;
98
99 // If unexpected error
100 } else if (try_rv != UV_EAGAIN) {
101 NCBI_UV_WRITE_TRACE(this << '/' << &request << " try-write failed: " << SUvNgHttp2_Error::LibuvStr(try_rv));
102 return try_rv;
103 }
104
105 auto rv = uv_write(&request, handle, &buf, 1, cb);
106
107 if (rv < 0) {
108 NCBI_UV_WRITE_TRACE(this << '/' << &request << " pre-write failed: " << SUvNgHttp2_Error::LibuvStr(rv));
109 return rv;
110 }
111
112 NCBI_UV_WRITE_TRACE(this << '/' << &request << " writing: " << data.size());
113 in_progress = true;
114
115 // Looking for unused buffer
116 for (auto& buffer : m_Buffers) {
117 if (!buffer.in_progress) {
118 _ASSERT(buffer.data.empty());
119
120 NCBI_UV_WRITE_TRACE(this << '/' << &buffer.request << " switching to");
121 m_CurrentBuffer = &buffer;
122 return 0;
123 }
124 }
125
126 // Need more buffers
127 NewBuffer();
128 return 0;
129 }
130
OnWrite(uv_write_t * req)131 void SUv_Write::OnWrite(uv_write_t* req)
132 {
133 for (auto& buffer : m_Buffers) {
134 if (&buffer.request == req) {
135 _ASSERT(buffer.data.size());
136 _ASSERT(buffer.in_progress);
137
138 NCBI_UV_WRITE_TRACE(this << '/' << req << " wrote");
139 buffer.data.clear();
140 buffer.in_progress = false;
141 return;
142 }
143 }
144
145 _TROUBLE;
146 }
147
Reset()148 void SUv_Write::Reset()
149 {
150 NCBI_UV_WRITE_TRACE(this << " reset");
151
152 for (auto& buffer : m_Buffers) {
153 buffer.data.clear();
154 buffer.in_progress = false;
155 }
156 }
157
NewBuffer()158 void SUv_Write::NewBuffer()
159 {
160 m_Buffers.emplace_front();
161 m_CurrentBuffer = &m_Buffers.front();
162
163 NCBI_UV_WRITE_TRACE(this << '/' << &m_CurrentBuffer->request << " new buffer");
164 m_CurrentBuffer->request.data = m_UserData;
165 m_CurrentBuffer->data.reserve(m_BufSize);
166 }
167
168
SUv_Connect(void * user_data,const SSocketAddress & address)169 SUv_Connect::SUv_Connect(void* user_data, const SSocketAddress& address)
170 {
171 m_Request.data = user_data;
172
173 m_Address.sin_family = AF_INET;
174 m_Address.sin_addr.s_addr = address.host;
175 m_Address.sin_port = CSocketAPI::HostToNetShort(address.port);
176 #ifdef HAVE_SIN_LEN
177 m_Address.sin_len = sizeof(m_Address);
178 #endif
179 }
180
operator ()(uv_tcp_t * handle,uv_connect_cb cb)181 int SUv_Connect::operator()(uv_tcp_t* handle, uv_connect_cb cb)
182 {
183 return uv_tcp_connect(&m_Request, handle, reinterpret_cast<sockaddr*>(&m_Address), cb);
184 }
185
186
SUv_Tcp(uv_loop_t * l,const SSocketAddress & address,size_t rd_buf_size,size_t wr_buf_size,TConnectCb connect_cb,TReadCb rcb,TWriteCb write_cb)187 SUv_Tcp::SUv_Tcp(uv_loop_t *l, const SSocketAddress& address, size_t rd_buf_size, size_t wr_buf_size,
188 TConnectCb connect_cb, TReadCb rcb, TWriteCb write_cb) :
189 SUv_Handle<uv_tcp_t>(s_OnClose),
190 m_Loop(l),
191 m_Connect(this, address),
192 m_Write(this, wr_buf_size),
193 m_ConnectCb(connect_cb),
194 m_ReadCb(rcb),
195 m_WriteCb(write_cb)
196 {
197 data = this;
198 m_ReadBuffer.reserve(rd_buf_size);
199
200 NCBI_UV_TCP_TRACE(this << " created");
201 }
202
Write()203 int SUv_Tcp::Write()
204 {
205 if (m_State == eClosed) {
206 auto rv = uv_tcp_init(m_Loop, this);
207
208 if (rv < 0) {
209 NCBI_UV_TCP_TRACE(this << " init failed: " << SUvNgHttp2_Error::LibuvStr(rv));
210 return rv;
211 }
212
213 rv = m_Connect(this, s_OnConnect);
214
215 if (rv < 0) {
216 NCBI_UV_TCP_TRACE(this << " pre-connect failed: " << SUvNgHttp2_Error::LibuvStr(rv));
217 Close();
218 return rv;
219 }
220
221 NCBI_UV_TCP_TRACE(this << " connecting");
222 m_State = eConnecting;
223 }
224
225 if (m_State == eConnected) {
226 auto rv = m_Write.Write((uv_stream_t*)this, s_OnWrite);
227
228 if (rv < 0) {
229 NCBI_UV_TCP_TRACE(this << " pre-write failed: " << SUvNgHttp2_Error::LibuvStr(rv));
230 Close();
231 return rv;
232 }
233
234 NCBI_UV_TCP_TRACE(this << " writing");
235 }
236
237 return 0;
238 }
239
Close()240 void SUv_Tcp::Close()
241 {
242 if (m_State == eConnected) {
243 auto rv = uv_read_stop(reinterpret_cast<uv_stream_t*>(this));
244
245 if (rv < 0) {
246 NCBI_UV_TCP_TRACE(this << " read stop failed: " << SUvNgHttp2_Error::LibuvStr(rv));
247 } else {
248 NCBI_UV_TCP_TRACE(this << " read stopped");
249 }
250 }
251
252 m_Write.Reset();
253
254 if ((m_State != eClosing) && (m_State != eClosed)) {
255 NCBI_UV_TCP_TRACE(this << " closing");
256 m_State = eClosing;
257 SUv_Handle<uv_tcp_t>::Close();
258 } else {
259 NCBI_UV_TCP_TRACE(this << " already closing/closed");
260 }
261 }
262
OnConnect(uv_connect_t *,int status)263 void SUv_Tcp::OnConnect(uv_connect_t*, int status)
264 {
265 if (status >= 0) {
266 status = uv_tcp_nodelay(this, 1);
267
268 if (status >= 0) {
269 status = uv_read_start((uv_stream_t*)this, s_OnAlloc, s_OnRead);
270
271 if (status >= 0) {
272 NCBI_UV_TCP_TRACE(this << " connected");
273 m_State = eConnected;
274 m_ConnectCb(status);
275 return;
276 } else {
277 NCBI_UV_TCP_TRACE(this << " read start failed: " << SUvNgHttp2_Error::LibuvStr(status));
278 }
279 } else {
280 NCBI_UV_TCP_TRACE(this << " nodelay failed: " << SUvNgHttp2_Error::LibuvStr(status));
281 }
282 } else {
283 NCBI_UV_TCP_TRACE(this << " connect failed: " << SUvNgHttp2_Error::LibuvStr(status));
284 }
285
286 Close();
287 m_ConnectCb(status);
288 }
289
OnAlloc(uv_handle_t *,size_t suggested_size,uv_buf_t * buf)290 void SUv_Tcp::OnAlloc(uv_handle_t*, size_t suggested_size, uv_buf_t* buf)
291 {
292 m_ReadBuffer.resize(suggested_size);
293 buf->base = m_ReadBuffer.data();
294 buf->len = static_cast<decltype(buf->len)>(m_ReadBuffer.size());
295 }
296
OnRead(uv_stream_t *,ssize_t nread,const uv_buf_t * buf)297 void SUv_Tcp::OnRead(uv_stream_t*, ssize_t nread, const uv_buf_t* buf)
298 {
299 if (nread < 0) {
300 NCBI_UV_TCP_TRACE(this << " read failed: " << SUvNgHttp2_Error::LibuvStr(nread));
301 Close();
302 } else {
303 NCBI_UV_TCP_TRACE(this << " read: " << nread);
304 }
305
306 m_ReadCb(buf->base, nread);
307 }
308
OnWrite(uv_write_t * req,int status)309 void SUv_Tcp::OnWrite(uv_write_t* req, int status)
310 {
311 if (status < 0) {
312 NCBI_UV_TCP_TRACE(this << '/' << req << " write failed: " << SUvNgHttp2_Error::LibuvStr(status));
313 Close();
314 } else {
315 NCBI_UV_TCP_TRACE(this << '/' << req << " wrote");
316 m_Write.OnWrite(req);
317 }
318
319 m_WriteCb(status);
320 }
321
OnClose(uv_handle_t *)322 void SUv_Tcp::OnClose(uv_handle_t*)
323 {
324 NCBI_UV_TCP_TRACE(this << " closed");
325 m_State = eClosed;
326 }
327
328 struct SUvNgHttp2_UserAgentImpl : string
329 {
330 SUvNgHttp2_UserAgentImpl();
331 };
332
SUvNgHttp2_UserAgentImpl()333 SUvNgHttp2_UserAgentImpl::SUvNgHttp2_UserAgentImpl()
334 {
335 if (auto app = CNcbiApplication::InstanceGuard()) {
336 const auto& full_version = app->GetFullVersion();
337 const auto& app_version = full_version.GetVersionInfo();
338 const auto pkg_version = full_version.GetPackageVersion();
339
340 assign(app->GetProgramDisplayName());
341 append(1, '/');
342
343 if (app_version.IsAny() && !pkg_version.IsAny()) {
344 append(1, 'p');
345 append(pkg_version.Print());
346 } else {
347 append(app_version.Print());
348 }
349 } else {
350 assign("UNKNOWN/UNKNOWN");
351 }
352
353 append(" NcbiCxxToolkit/"
354 #if defined(NCBI_PRODUCTION_VER)
355 "P" NCBI_AS_STRING(NCBI_PRODUCTION_VER)
356 #elif defined(NCBI_SUBVERSION_REVISION)
357 "r" NCBI_AS_STRING(NCBI_SUBVERSION_REVISION)
358 #if defined(NCBI_DEVELOPMENT_VER)
359 ".D" NCBI_AS_STRING(NCBI_DEVELOPMENT_VER)
360 #elif defined(NCBI_SC_VERSION)
361 ".SC" NCBI_AS_STRING(NCBI_SC_VERSION)
362 #endif
363 #else
364 "UNKNOWN"
365 #endif
366 );
367 }
368
Init()369 string SUvNgHttp2_UserAgent::Init()
370 {
371 return SUvNgHttp2_UserAgentImpl();
372 }
373
SNgHttp2_Session(void * user_data,uint32_t max_streams,nghttp2_on_data_chunk_recv_callback on_data,nghttp2_on_stream_close_callback on_stream_close,nghttp2_on_header_callback on_header,nghttp2_error_callback on_error,nghttp2_on_frame_recv_callback on_frame_recv)374 SNgHttp2_Session::SNgHttp2_Session(void* user_data, uint32_t max_streams,
375 nghttp2_on_data_chunk_recv_callback on_data,
376 nghttp2_on_stream_close_callback on_stream_close,
377 nghttp2_on_header_callback on_header,
378 nghttp2_error_callback on_error,
379 nghttp2_on_frame_recv_callback on_frame_recv) :
380 m_UserData(user_data),
381 m_OnData(on_data),
382 m_OnStreamClose(on_stream_close),
383 m_OnHeader(on_header),
384 m_OnError(on_error),
385 m_OnFrameRecv(on_frame_recv),
386 m_MaxStreams(max_streams, max_streams)
387 {
388 NCBI_NGHTTP2_SESSION_TRACE(this << " created");
389 }
390
Init()391 int SNgHttp2_Session::Init()
392 {
393 if (m_Session) return 0;
394
395 nghttp2_session_callbacks* callbacks;
396 nghttp2_session_callbacks_new(&callbacks);
397
398 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, m_OnData);
399 nghttp2_session_callbacks_set_on_stream_close_callback( callbacks, m_OnStreamClose);
400 nghttp2_session_callbacks_set_on_header_callback( callbacks, m_OnHeader);
401 nghttp2_session_callbacks_set_error_callback( callbacks, m_OnError);
402 if (m_OnFrameRecv) nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, m_OnFrameRecv);
403
404 nghttp2_session_client_new(&m_Session, callbacks, m_UserData);
405 nghttp2_session_callbacks_del(callbacks);
406
407 nghttp2_settings_entry iv[1] = {
408 {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, m_MaxStreams.second}
409 };
410
411 /* client 24 bytes magic string will be sent by nghttp2 library */
412 if (auto rv = nghttp2_submit_settings(m_Session, NGHTTP2_FLAG_NONE, iv, sizeof(iv) / sizeof(iv[0]))) {
413 NCBI_NGHTTP2_SESSION_TRACE(this << " submit settings failed: " << SUvNgHttp2_Error::NgHttp2Str(rv));
414 return x_DelOnError(rv);
415 }
416
417 NCBI_NGHTTP2_SESSION_TRACE(this << " initialized");
418 auto max_streams = nghttp2_session_get_remote_settings(m_Session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
419 m_MaxStreams.first = min(max_streams, m_MaxStreams.second);
420 return 0;
421 }
422
Del()423 void SNgHttp2_Session::Del()
424 {
425 if (!m_Session) {
426 NCBI_NGHTTP2_SESSION_TRACE(this << " already terminated");
427 return;
428 }
429
430 auto rv = nghttp2_session_terminate_session(m_Session, NGHTTP2_NO_ERROR);
431
432 if (rv) {
433 NCBI_NGHTTP2_SESSION_TRACE(this << " terminate failed: " << SUvNgHttp2_Error::NgHttp2Str(rv));
434 } else {
435 NCBI_NGHTTP2_SESSION_TRACE(this << " terminated");
436 }
437
438 x_DelOnError(-1);
439 }
440
Submit(const nghttp2_nv * nva,size_t nvlen,nghttp2_data_provider * data_prd)441 int32_t SNgHttp2_Session::Submit(const nghttp2_nv *nva, size_t nvlen, nghttp2_data_provider* data_prd)
442 {
443 if (auto rv = Init()) return rv;
444
445 auto rv = nghttp2_submit_request(m_Session, nullptr, nva, nvlen, data_prd, nullptr);
446
447 if (rv < 0) {
448 NCBI_NGHTTP2_SESSION_TRACE(this << " submit failed: " << SUvNgHttp2_Error::NgHttp2Str(rv));
449 } else {
450 NCBI_NGHTTP2_SESSION_TRACE(this << " submitted");
451 }
452
453 return x_DelOnError(rv);
454 }
455
Resume(int32_t stream_id)456 int SNgHttp2_Session::Resume(int32_t stream_id)
457 {
458 if (auto rv = Init()) return rv;
459
460 auto rv = nghttp2_session_resume_data(m_Session, stream_id);
461
462 if (rv < 0) {
463 NCBI_NGHTTP2_SESSION_TRACE(this << " resume failed: " << SUvNgHttp2_Error::NgHttp2Str(rv));
464 } else {
465 NCBI_NGHTTP2_SESSION_TRACE(this << " resumed");
466 }
467
468 return x_DelOnError(rv);
469 }
470
Send(vector<char> & buffer)471 ssize_t SNgHttp2_Session::Send(vector<char>& buffer)
472 {
473 if (auto rv = Init()) return rv;
474
475 _DEBUG_ARG(ssize_t total = 0);
476
477 while (nghttp2_session_want_write(m_Session)) {
478 const uint8_t* data;
479 auto rv = nghttp2_session_mem_send(m_Session, &data);
480
481 if (rv > 0) {
482 buffer.insert(buffer.end(), data, data + rv);
483 _DEBUG_CODE(total += rv;);
484
485 } else if (rv < 0) {
486 NCBI_NGHTTP2_SESSION_TRACE(this << " send failed: " << SUvNgHttp2_Error::NgHttp2Str(rv));
487 return x_DelOnError(rv);
488
489 } else {
490 NCBI_NGHTTP2_SESSION_TRACE(this << " sent: " << total);
491 return eOkay;
492 }
493 }
494
495 if (nghttp2_session_want_read(m_Session) == 0) {
496 NCBI_NGHTTP2_SESSION_TRACE(this << " does not want to write and read");
497 x_DelOnError(-1);
498 return eWantsClose;
499 }
500
501 NCBI_NGHTTP2_SESSION_TRACE(this << " does not want to write");
502 return eOkay;
503 }
504
Recv(const uint8_t * buffer,size_t size)505 ssize_t SNgHttp2_Session::Recv(const uint8_t* buffer, size_t size)
506 {
507 if (auto rv = Init()) return rv;
508
509 ssize_t total = 0;
510
511 while (size > 0) {
512 auto rv = nghttp2_session_mem_recv(m_Session, buffer, size);
513
514 if (rv > 0) {
515 buffer += rv;
516 size -= rv;
517 total += rv;
518
519 } else {
520 NCBI_NGHTTP2_SESSION_TRACE(this << " receive failed: " << SUvNgHttp2_Error::NgHttp2Str(rv));
521 return x_DelOnError(rv);
522 }
523 }
524
525 NCBI_NGHTTP2_SESSION_TRACE(this << " received: " << total);
526 return total;
527 }
528
529 struct SUvNgHttp2_TlsNoOp : SUvNgHttp2_Tls
530 {
SUvNgHttp2_TlsNoOpSUvNgHttp2_TlsNoOp531 SUvNgHttp2_TlsNoOp(TGetWriteBuf get_write_buf) : m_GetWriteBuf(get_write_buf) {}
532
ReadSUvNgHttp2_TlsNoOp533 int Read(const char*& buf, ssize_t& nread) override
534 {
535 m_IncomingData = exchange(buf, buf + nread);
536 return static_cast<int>(exchange(nread, 0));
537 }
538
WriteSUvNgHttp2_TlsNoOp539 int Write() override { return 0; }
CloseSUvNgHttp2_TlsNoOp540 int Close() override { return 0; }
541
GetReadBufferSUvNgHttp2_TlsNoOp542 const char* GetReadBuffer() override { return m_IncomingData; }
GetWriteBufferSUvNgHttp2_TlsNoOp543 vector<char>& GetWriteBuffer() override { return m_GetWriteBuf(); }
544
545 private:
546 const char* m_IncomingData = nullptr;
547 TGetWriteBuf m_GetWriteBuf;
548 };
549
550 struct SUvNgHttp2_TlsImpl : SUvNgHttp2_Tls
551 {
552 SUvNgHttp2_TlsImpl(const SSocketAddress& address, size_t rd_buf_size, size_t wr_buf_size, TGetWriteBuf get_write_buf);
553 ~SUvNgHttp2_TlsImpl() override;
554
555 int Read(const char*& buf, ssize_t& nread) override;
556 int Write() override;
557 int Close() override;
558
GetReadBufferSUvNgHttp2_TlsImpl559 const char* GetReadBuffer() override { return m_ReadBuffer.data(); }
GetWriteBufferSUvNgHttp2_TlsImpl560 vector<char>& GetWriteBuffer() override { return m_WriteBuffer; }
561
562 private:
563 // Provides scope-restricted access to incoming TLS data
564 struct SIncomingData : pair<const char**, ssize_t*>
565 {
operator ()SUvNgHttp2_TlsImpl::SIncomingData::SReset566 struct SReset { void operator()(first_type* p) const { *p = nullptr; } };
operator ()SUvNgHttp2_TlsImpl::SIncomingData567 auto operator()(first_type b, second_type l) { first = b; second = l; return unique_ptr<first_type, SReset>(&first); }
568 };
569
570 SUvNgHttp2_TlsImpl(const SUvNgHttp2_TlsImpl&) = delete;
571 SUvNgHttp2_TlsImpl(SUvNgHttp2_TlsImpl&&) = delete;
572
573 SUvNgHttp2_TlsImpl& operator=(const SUvNgHttp2_TlsImpl&) = delete;
574 SUvNgHttp2_TlsImpl& operator=(SUvNgHttp2_TlsImpl&&) = delete;
575
576 int Init();
577 int GetReady();
578
579 int OnRecv(unsigned char* buf, size_t len);
580 int OnSend(const unsigned char* buf, size_t len);
581
GetThatSUvNgHttp2_TlsImpl582 static SUvNgHttp2_TlsImpl* GetThat(void* ctx)
583 {
584 _ASSERT(ctx);
585 return static_cast<SUvNgHttp2_TlsImpl*>(ctx);
586 }
587
s_OnRecvSUvNgHttp2_TlsImpl588 static int s_OnRecv(void* ctx, unsigned char* buf, size_t len)
589 {
590 return GetThat(ctx)->OnRecv(buf, len);
591 }
592
s_OnSendSUvNgHttp2_TlsImpl593 static int s_OnSend(void* ctx, const unsigned char* buf, size_t len)
594 {
595 return GetThat(ctx)->OnSend(buf, len);
596 }
597
598 enum { eInitialized, eReady, eClosed } m_State = eInitialized;
599
600 vector<char> m_ReadBuffer;
601 vector<char> m_WriteBuffer;
602 SIncomingData m_IncomingData;
603 TGetWriteBuf m_GetWriteBuf;
604
605 mbedtls_ssl_context m_Ssl;
606 mbedtls_ssl_config m_Conf;
607 mbedtls_ctr_drbg_context m_CtrDrbg;
608 mbedtls_entropy_context m_Entropy;
609 array<const char*, 2> m_Protocols;
610 };
611
s_WantReadOrWrite(int rv)612 bool s_WantReadOrWrite(int rv)
613 {
614 return (rv == MBEDTLS_ERR_SSL_WANT_READ) || (rv == MBEDTLS_ERR_SSL_WANT_WRITE);
615 }
616
SUvNgHttp2_TlsImpl(const SSocketAddress & address,size_t rd_buf_size,size_t wr_buf_size,TGetWriteBuf get_write_buf)617 SUvNgHttp2_TlsImpl::SUvNgHttp2_TlsImpl(const SSocketAddress& address, size_t rd_buf_size, size_t wr_buf_size, TGetWriteBuf get_write_buf) :
618 m_ReadBuffer(rd_buf_size),
619 m_GetWriteBuf(get_write_buf),
620 m_Protocols({ "h2", nullptr })
621 {
622 NCBI_UVNGHTTP2_TLS_TRACE(this << " created");
623 m_WriteBuffer.reserve(wr_buf_size),
624
625 SOCK_SetupSSLEx(NcbiSetupTls);
626 mbedtls_ssl_config_init(&m_Conf);
627
628 auto c_rv = mbedtls_ssl_config_defaults(&m_Conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT);
629
630 if (c_rv) {
631 NCBI_UVNGHTTP2_TLS_TRACE(this << " mbedtls_ssl_config_defaults: " << SUvNgHttp2_Error::MbedTlsStr(c_rv));
632 return;
633 }
634
635 mbedtls_ssl_conf_authmode(&m_Conf, MBEDTLS_SSL_VERIFY_NONE);
636 mbedtls_entropy_init(&m_Entropy);
637 mbedtls_ctr_drbg_init(&m_CtrDrbg);
638
639 auto d_rv = mbedtls_ctr_drbg_seed(&m_CtrDrbg, mbedtls_entropy_func, &m_Entropy, nullptr, 0);
640
641 if (d_rv) {
642 NCBI_UVNGHTTP2_TLS_TRACE(this << " mbedtls_ctr_drbg_seed: " << SUvNgHttp2_Error::MbedTlsStr(d_rv));
643 return;
644 }
645
646 mbedtls_ssl_conf_rng(&m_Conf, mbedtls_ctr_drbg_random, &m_CtrDrbg);
647 mbedtls_ssl_conf_alpn_protocols(&m_Conf, m_Protocols.data());
648 mbedtls_ssl_init(&m_Ssl);
649
650 auto s_rv = mbedtls_ssl_setup(&m_Ssl, &m_Conf);
651
652 if (s_rv) {
653 NCBI_UVNGHTTP2_TLS_TRACE(this << " mbedtls_ssl_setup: " << SUvNgHttp2_Error::MbedTlsStr(s_rv));
654 return;
655 }
656
657 const auto host_name = address.GetHostName();
658 auto h_rv = mbedtls_ssl_set_hostname(&m_Ssl, host_name.c_str());
659
660 if (h_rv) {
661 NCBI_UVNGHTTP2_TLS_TRACE(this << " mbedtls_ssl_set_hostname: " << SUvNgHttp2_Error::MbedTlsStr(h_rv));
662 return;
663 }
664
665 mbedtls_ssl_set_bio(&m_Ssl, this, s_OnSend, s_OnRecv, nullptr);
666 }
667
~SUvNgHttp2_TlsImpl()668 SUvNgHttp2_TlsImpl::~SUvNgHttp2_TlsImpl()
669 {
670 mbedtls_entropy_free(&m_Entropy);
671 mbedtls_ctr_drbg_free(&m_CtrDrbg);
672 mbedtls_ssl_config_free(&m_Conf);
673 mbedtls_ssl_free(&m_Ssl);
674 }
675
Init()676 int SUvNgHttp2_TlsImpl::Init()
677 {
678 switch (m_State)
679 {
680 case eInitialized:
681 return GetReady();
682
683 case eReady:
684 return 0;
685
686 case eClosed:
687 break;
688 }
689
690 m_WriteBuffer.clear();
691 auto rv = mbedtls_ssl_session_reset(&m_Ssl);
692
693 if (rv < 0) {
694 NCBI_UVNGHTTP2_TLS_TRACE(this << " reset: " << SUvNgHttp2_Error::MbedTlsStr(rv));
695 } else {
696 NCBI_UVNGHTTP2_TLS_TRACE(this << " reset: " << rv);
697 m_State = eInitialized;
698 }
699
700 return rv;
701 }
702
GetReady()703 int SUvNgHttp2_TlsImpl::GetReady()
704 {
705 auto hs_rv = mbedtls_ssl_handshake(&m_Ssl);
706
707 if (hs_rv < 0) {
708 NCBI_UVNGHTTP2_TLS_TRACE(this << " handshake: " << SUvNgHttp2_Error::MbedTlsStr(hs_rv));
709 return hs_rv;
710 }
711
712 NCBI_UVNGHTTP2_TLS_TRACE(this << " handshake: " << hs_rv);
713
714 auto v_rv = mbedtls_ssl_get_verify_result(&m_Ssl);
715
716 if (v_rv) {
717 NCBI_UVNGHTTP2_TLS_TRACE(this << " verify: " << v_rv);
718 } else {
719 NCBI_UVNGHTTP2_TLS_TRACE(this << " verified");
720 }
721
722 m_State = eReady;
723 return 0;
724 }
725
Read(const char * & buf,ssize_t & nread)726 int SUvNgHttp2_TlsImpl::Read(const char*& buf, ssize_t& nread)
727 {
728 auto scope_guard = m_IncomingData(&buf, &nread);
729
730 if (auto rv = Init()) return rv;
731
732 auto rv = mbedtls_ssl_read(&m_Ssl, reinterpret_cast<unsigned char*>(m_ReadBuffer.data()), m_ReadBuffer.size());
733
734 if (rv < 0) {
735 NCBI_UVNGHTTP2_TLS_TRACE(this << " read: " << SUvNgHttp2_Error::MbedTlsStr(rv));
736 } else {
737 NCBI_UVNGHTTP2_TLS_TRACE(this << " read: " << rv);
738 }
739
740 return rv;
741 }
742
Write()743 int SUvNgHttp2_TlsImpl::Write()
744 {
745 if (auto rv = Init()) return rv;
746
747 auto buf = m_WriteBuffer.data();
748 auto size = m_WriteBuffer.size();
749
750 while (size > 0) {
751 auto rv = mbedtls_ssl_write(&m_Ssl, (unsigned char*)buf, size);
752
753 if (rv > 0) {
754 buf += rv;
755 size -= rv;
756
757 } else if (rv < 0) {
758 NCBI_UVNGHTTP2_TLS_TRACE(this << " write: " << SUvNgHttp2_Error::MbedTlsStr(rv));
759 return rv;
760 }
761 }
762
763 auto written = m_WriteBuffer.size() - size;
764 m_WriteBuffer.erase(m_WriteBuffer.begin(), m_WriteBuffer.begin() + written);
765 NCBI_UVNGHTTP2_TLS_TRACE(this << " write: " << written);
766 return static_cast<int>(written);
767 }
768
Close()769 int SUvNgHttp2_TlsImpl::Close()
770 {
771 NCBI_UVNGHTTP2_TLS_TRACE(this << " close");
772
773 switch (m_State)
774 {
775 case eInitialized:
776 case eClosed: return 0;
777 case eReady: break;
778 }
779
780 m_State = eClosed;
781 return mbedtls_ssl_close_notify(&m_Ssl);
782 }
783
OnRecv(unsigned char * buf,size_t len)784 int SUvNgHttp2_TlsImpl::OnRecv(unsigned char* buf, size_t len)
785 {
786 if (m_IncomingData.first && m_IncomingData.second) {
787 auto copied = min(len, static_cast<size_t>(*m_IncomingData.second));
788 NCBI_UVNGHTTP2_TLS_TRACE(this << " on receiving: " << copied);
789
790 if (copied) {
791 memcpy(buf, *m_IncomingData.first, copied);
792 *m_IncomingData.first += copied;
793 *m_IncomingData.second -= copied;
794 return static_cast<int>(copied);
795 }
796 } else {
797 NCBI_UVNGHTTP2_TLS_TRACE(this << " on receiving");
798 }
799
800 return MBEDTLS_ERR_SSL_WANT_READ;
801 }
802
OnSend(const unsigned char * buf,size_t len)803 int SUvNgHttp2_TlsImpl::OnSend(const unsigned char* buf, size_t len)
804 {
805 NCBI_UVNGHTTP2_TLS_TRACE(this << " on sending: " << len);
806 auto& write_buf = m_GetWriteBuf();
807 write_buf.insert(write_buf.end(), buf, buf + len);
808 return static_cast<int>(len);
809 }
810
Create(bool https,const SSocketAddress & address,size_t rd_buf_size,size_t wr_buf_size,TGetWriteBuf get_write_buf)811 SUvNgHttp2_Tls* SUvNgHttp2_Tls::Create(bool https, const SSocketAddress& address, size_t rd_buf_size, size_t wr_buf_size, TGetWriteBuf get_write_buf)
812 {
813 if (https) {
814 return new SUvNgHttp2_TlsImpl(address, rd_buf_size, wr_buf_size, get_write_buf);
815 }
816
817 return new SUvNgHttp2_TlsNoOp(get_write_buf);
818 }
819
Send()820 bool SUvNgHttp2_SessionBase::Send()
821 {
822 auto send_rv = m_Session.Send(m_Tls->GetWriteBuffer());
823
824 if (send_rv < 0) {
825 Reset(SUvNgHttp2_Error::FromNgHttp2(send_rv, "on send"));
826
827 } else if (send_rv == SNgHttp2_Session::eWantsClose) {
828 Reset("nghttp2 asked to drop connection");
829
830 } else {
831 auto tls_rv = m_Tls->Write();
832
833 if ((tls_rv < 0) && !s_WantReadOrWrite(tls_rv)) {
834 Reset(SUvNgHttp2_Error::FromMbedTls(tls_rv, "on write"));
835
836 } else if (auto tcp_rv = m_Tcp.Write()) {
837 Reset(SUvNgHttp2_Error::FromLibuv(tcp_rv, "on write"));
838
839 } else {
840 return true;
841 }
842 }
843
844 return false;
845 }
846
OnConnect(int status)847 void SUvNgHttp2_SessionBase::OnConnect(int status)
848 {
849 NCBI_UVNGHTTP2_SESSION_TRACE(this << " connected: " << status);
850
851 if (status < 0) {
852 Reset(SUvNgHttp2_Error::FromLibuv(status, "on connecting"));
853 } else {
854 Send();
855 }
856 }
857
OnWrite(int status)858 void SUvNgHttp2_SessionBase::OnWrite(int status)
859 {
860 NCBI_UVNGHTTP2_SESSION_TRACE(this << " wrote: " << status);
861
862 if (status < 0) {
863 Reset(SUvNgHttp2_Error::FromLibuv(status, "on writing"));
864 }
865 }
866
OnRead(const char * buf,ssize_t nread)867 void SUvNgHttp2_SessionBase::OnRead(const char* buf, ssize_t nread)
868 {
869 NCBI_UVNGHTTP2_SESSION_TRACE(this << " read: " << nread);
870
871 if (nread < 0) {
872 Reset(SUvNgHttp2_Error::FromLibuv(nread, "on reading"));
873 return;
874 }
875
876 while (nread > 0) {
877 auto read_rv = m_Tls->Read(buf, nread);
878
879 if (read_rv == 0) {
880 m_Session.Del();
881 m_Tls->Close();
882 m_Tcp.Close();
883
884 } else if (s_WantReadOrWrite(read_rv)) {
885 if (nread == 0) break;
886
887 Reset("Some encrypted data was ignored");
888
889 } else if (read_rv < 0) {
890 Reset(SUvNgHttp2_Error::FromMbedTls(read_rv, "on read"));
891
892 } else {
893 auto recv_rv = m_Session.Recv((const uint8_t*)m_Tls->GetReadBuffer(), (size_t)read_rv);
894
895 if (recv_rv < 0) {
896 Reset(SUvNgHttp2_Error::FromNgHttp2(recv_rv, "on receive"));
897
898 } else if (recv_rv != read_rv) {
899 Reset("Processed size does not equal to received");
900
901 } else {
902 continue;
903 }
904 }
905
906 return;
907 }
908
909 Send();
910 }
911
Reset(SUvNgHttp2_Error error)912 void SUvNgHttp2_SessionBase::Reset(SUvNgHttp2_Error error)
913 {
914 NCBI_UVNGHTTP2_SESSION_TRACE(this << " resetting with " << error);
915 m_Session.Del();
916 m_Tls->Close();
917 m_Tcp.Close();
918 OnReset(move(error));
919 }
920
921 END_NCBI_SCOPE
922