Coverage Report

Created: 2026-11-12 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wt/src/Wt/WWebSocketConnection.C
Line
Count
Source
1
#include "Wt/WWebSocketConnection.h"
2
3
#include "Wt/AsioWrapper/namespace.hpp"
4
#include "Wt/AsioWrapper/system_error.hpp"
5
6
#include "Wt/WApplication.h"
7
#include "Wt/WLogger.h"
8
#include "Wt/WString.h"
9
#include "Wt/WWebSocketResource.h"
10
11
#include <chrono>
12
#include <system_error>
13
14
namespace Wt {
15
LOGGER("WWebSocketConnection");
16
17
namespace asio = AsioWrapper::asio;
18
19
std::string OpCodeToString(OpCode code)
20
0
{
21
0
  switch(code) {
22
0
    case OpCode::Continuation:
23
0
      return "Continuation";
24
0
    case OpCode::Text:
25
0
      return "Text";
26
0
    case OpCode::Binary:
27
0
      return "Binary";
28
0
    case OpCode::Close:
29
0
      return "Close";
30
0
    case OpCode::Ping:
31
0
      return "Ping";
32
0
    case OpCode::Pong:
33
0
      return "Pong";
34
0
  }
35
0
  return "";
36
0
}
37
38
struct WebSocketFrameHeader
39
{
40
public:
41
  bool isFinished;
42
  bool reserved1;
43
  bool reserved2;
44
  bool reserved3;
45
  OpCode opCode;
46
  bool isMasked;
47
48
  bool readFrameHeader(const char* begin, std::size_t maxLength);
49
  std::vector<char> generateFrameHeader(std::size_t dataSize, const std::vector<char>& mask = {}) const;
50
51
  int getHeaderSize() const;
52
53
  std::string toString() const;
54
55
0
  unsigned char lengthCode() const { return lengthCode_; }
56
0
  std::size_t length() const { return length_; }
57
0
  unsigned char* mask() { return mask_; }
58
59
private:
60
  unsigned char lengthCode_;
61
  uint64_t length_;
62
  unsigned char mask_[4];
63
};
64
65
bool WebSocketFrameHeader::readFrameHeader(const char* begin, std::size_t maxLength)
66
0
{
67
  /* FRAME:
68
  0                1                2
69
  0 1 2 3 4 5 6 7  0 1 2 3 4 5 6 7  0 1 2 3 4 5 6 7
70
  F R R R OPCODE   M PAYLOAD LENGTH EXTENDED LENGTH
71
  I S S S          A
72
  N V V V          S
73
    1 2 3          K
74
75
  3/5/11           ... 5/9/15
76
  0 1 2 3 4 5 6 7  ... 0 1 2 3 4 5 6 7
77
              MASKING KEY
78
  */
79
0
  if (maxLength < 2) {
80
0
    return false;
81
0
  }
82
83
  // First byte
84
0
  unsigned char codes = begin[0];
85
  // Second byte
86
0
  unsigned char maskAndLength = begin[1];
87
88
  // Byte 0 bit 0
89
0
  isFinished = codes & 0x80;
90
  // Byte 0 bit 1
91
0
  reserved1 = codes & 0x40;
92
  // Byte 0 bit 2
93
0
  reserved2 = codes & 0x20;
94
  // Byte 0 bit 3
95
0
  reserved3 = codes & 0x10;
96
  // Byte 0 bit 4-7
97
0
  opCode = static_cast<OpCode>(codes & 0xF);
98
  // Byte 1 bit 0
99
0
  isMasked = maskAndLength & 0x80;
100
  // Byte 7 bit 1-7
101
0
  lengthCode_ = maskAndLength & 0x7F;
102
103
0
  if (lengthCode_ == 126) {
104
0
    if (maxLength < 4) {
105
0
      return false;
106
0
    }
107
108
    // Read next 2 bytes
109
0
    unsigned char length1 = begin[2];
110
0
    unsigned char length2 = begin[3];
111
0
    length_ = (length1 << 8) | length2;
112
0
  } else if (lengthCode_ == 127) {
113
    // Read next 8 bytes (where most significant must be 0)
114
    // assert length & (1 << 64) == 0
115
0
    if (maxLength < 10) {
116
0
      return false;
117
0
    }
118
119
0
    unsigned char length1 = begin[2];
120
0
    unsigned char length2 = begin[3];
121
0
    unsigned char length3 = begin[4];
122
0
    unsigned char length4 = begin[5];
123
0
    unsigned char length5 = begin[6];
124
0
    unsigned char length6 = begin[7];
125
0
    unsigned char length7 = begin[8];
126
0
    unsigned char length8 = begin[9];
127
0
    length_ = (static_cast<uint64_t>(length1) << 56) | (static_cast<uint64_t>(length2) << 48)
128
0
             | (static_cast<uint64_t>(length3) << 40) | (static_cast<uint64_t>(length4) << 32)
129
0
             | (static_cast<uint64_t>(length5) << 24) | (static_cast<uint64_t>(length6) << 16)
130
0
             | (static_cast<uint64_t>(length7) << 8) | static_cast<uint64_t>(length8);
131
0
  } else {
132
0
    length_ = lengthCode_;
133
0
  }
134
135
  // Normally starts at offset 2
136
0
  unsigned int maskOffset = 2;
137
0
  if (lengthCode_ == 126) {
138
    // Additional 2 bytes for medium payload length
139
0
    maskOffset += 2;
140
0
  } else if (lengthCode_ == 127) {
141
    // Additional 8 bytes for long payload length
142
0
    maskOffset += 8;
143
0
  }
144
145
0
  if (maxLength < maskOffset + 4) {
146
0
    return false;
147
0
  }
148
149
0
  if (isMasked) {
150
    // Third to sixth byte if (length <= 125)
151
    // Fifth to eighth byte if (length == 126)
152
    // Eleventh to fourteenth byte if (length == 127)
153
0
    mask_[0] = begin[maskOffset];
154
0
    mask_[1] = begin[maskOffset + 1];
155
0
    mask_[2] = begin[maskOffset + 2];
156
0
    mask_[3] = begin[maskOffset + 3];
157
0
  }
158
159
0
  return true;
160
0
}
161
162
std::vector<char> WebSocketFrameHeader::generateFrameHeader(std::size_t dataLength, const std::vector<char>& mask) const
163
0
{
164
0
  char FIN = static_cast<char>(isFinished);
165
0
  char RSV1 = static_cast<char>(reserved1);
166
0
  char RSV2 = static_cast<char>(reserved2);
167
0
  char RSV3 = static_cast<char>(reserved3);
168
0
  char code = static_cast<char>(opCode);
169
170
0
  char frameByte = (FIN << 7) + (RSV1 << 6) + (RSV2 << 5) + (RSV3 << 4) + code;
171
172
0
  std::vector<char> frameHeader;
173
174
0
  frameHeader.push_back(frameByte);
175
176
  // Masking & length
177
  // MASKING must NOT be done by the server
178
0
  char MASK = isMasked;
179
0
  char length = 0;
180
181
0
  if (dataLength < 126) {
182
0
    length = dataLength;
183
0
  } else if (dataLength < 65536) {
184
0
    length = 126;
185
0
  } else {
186
0
    length = 127;
187
0
  }
188
189
0
  char maskAndLength = (MASK << 7) + length;
190
191
0
  frameHeader.push_back(maskAndLength);
192
193
0
  if (length == 126) {
194
    // Optional extended length (2 byte)
195
0
    unsigned int l = dataLength;
196
0
    char l1 = (l >> 8) & 0xFF;
197
0
    char l2 = l & 0xFF;
198
0
    frameHeader.push_back(l1);
199
0
    frameHeader.push_back(l2);
200
0
  } else if (length == 127) {
201
    // Optional extended length (8 byte)
202
0
    std::size_t l = dataLength;
203
0
    char l1 = (l >> 56) & 0xFF;
204
0
    char l2 = (l >> 48) & 0xFF;
205
0
    char l3 = (l >> 40) & 0xFF;
206
0
    char l4 = (l >> 32) & 0xFF;
207
0
    char l5 = (l >> 24) & 0xFF;
208
0
    char l6 = (l >> 16) & 0xFF;
209
0
    char l7 = (l >> 8) & 0xFF;
210
0
    char l8 = l & 0xFF;
211
0
    frameHeader.push_back(l1);
212
0
    frameHeader.push_back(l2);
213
0
    frameHeader.push_back(l3);
214
0
    frameHeader.push_back(l4);
215
0
    frameHeader.push_back(l5);
216
0
    frameHeader.push_back(l6);
217
0
    frameHeader.push_back(l7);
218
0
    frameHeader.push_back(l8);
219
0
  }
220
221
0
  if (isMasked && mask.size() == 4) {
222
    // LOG
223
0
    frameHeader.push_back(mask[0]);
224
0
    frameHeader.push_back(mask[1]);
225
0
    frameHeader.push_back(mask[2]);
226
0
    frameHeader.push_back(mask[3]);
227
0
  }
228
229
0
  return frameHeader;
230
0
}
231
232
int WebSocketFrameHeader::getHeaderSize() const
233
0
{
234
  // Minimum size is 2
235
0
  int size = 2;
236
237
  // Length is 2 bytes
238
0
  if (lengthCode_ == 126) {
239
0
    size += 2;
240
  // Length it 8 bytes
241
0
  } else if (lengthCode_ == 127) {
242
0
    size += 8;
243
0
  }
244
245
  // Masking is 4 bytes
246
0
  if (isMasked) {
247
0
    size += 4;
248
0
  }
249
250
0
  return size;
251
0
}
252
253
std::string WebSocketFrameHeader::toString() const
254
0
{
255
0
  std::string maskValues;
256
0
  if (isMasked) {
257
0
    maskValues = std::to_string(mask_[0]) + " " + std::to_string(mask_[1]) + " "
258
0
                 + std::to_string(mask_[2]) + " " + std::to_string(mask_[3]);
259
0
  }
260
0
  WString result = WString("WebSocket Header with\nFinished: {1}\n"
261
0
                                   "RSV1: {2}\nRSV2: {3}\nRSV3: {4}\n"
262
0
                                   "OpCode: {5} ({6})\nMasking: {7}\n"
263
0
                                   "Mask: {8}\nPayload Length: {9}")
264
0
                       .arg(isFinished)
265
0
                       .arg(reserved1)
266
0
                       .arg(reserved2)
267
0
                       .arg(reserved3)
268
0
                       .arg(static_cast<int>(opCode))
269
0
                       .arg(OpCodeToString(opCode))
270
0
                       .arg(isMasked)
271
0
                       .arg(maskValues)
272
0
                       .arg(length_);
273
0
  return result.toUTF8();
274
0
}
275
276
WebSocketConnection::WebSocketConnection(AsioWrapper::asio::io_service& ioService)
277
0
  : ioService_(ioService),
278
0
    strand_(ioService),
279
0
    readBuffer_(),
280
0
    readBufferPtr_(readBuffer_.begin()),
281
0
    skipDataSize_(0),
282
0
    readingState_(ReadingState::ReadingHeader),
283
0
    skipMessage_(false),
284
0
    header_(new WebSocketFrameHeader()),
285
0
    isContinuation_(false),
286
0
    continuationSize_(0),
287
0
    isWriting_(false),
288
0
    isWritingToSocket_(false),
289
0
    hasPendingDataWrite_(false)
290
0
{
291
0
}
292
293
WebSocketConnection::~WebSocketConnection()
294
0
{
295
0
}
296
297
void WebSocketConnection::startReading()
298
0
{
299
0
  doAsyncRead(&(*readBufferPtr_), readBuffer_.end() - readBufferPtr_);
300
0
}
301
302
void WebSocketConnection::doAsyncRead(char* buffer, size_t size)
303
0
{
304
0
  if (readingState_ == ReadingState::ClosingSocket) {
305
0
    LOG_WARN("doAsyncRead() was called after a closing signal has been sent or received.");
306
0
    return;
307
0
  }
308
309
0
  doSocketRead(buffer, size);
310
0
}
311
312
// Returns amount of bytes consumed; 0 if more data is needed before
313
// more can be consumed.
314
std::size_t WebSocketConnection::parseBuffer(const char* begin, const char* end)
315
0
{
316
0
  const char *current = begin;
317
318
0
  if (readingState_ == ReadingState::ReadingHeader) {
319
0
    auto isCompleteHeader = header_->readFrameHeader(begin, end - begin);
320
321
0
    if (!isCompleteHeader) {
322
      // By returning 0; indicate that we need more data
323
0
      return 0;
324
0
    } else {
325
0
      current += header_->getHeaderSize();
326
0
      readingState_ = ReadingState::ReadingData;
327
328
0
      if (header_->length() > frameSize_) {
329
0
        LOG_ERROR("parseBuffer: A frame was received that exceeded the limit (" << header_->length() << " > " << frameSize_ << " (bytes))");
330
0
        readingState_ = ReadingState::SkipData;
331
0
        if (!header_->isFinished) {
332
0
          LOG_ERROR("parseBuffer: The received frame will be followed by a continuation that should be skipped as well.");
333
0
          skipMessage_ = true;
334
0
        }
335
0
      } else if (skipMessage_) {
336
0
        readingState_ = ReadingState::SkipData;
337
0
        LOG_ERROR("parseBuffer: A continuation frame was received that is part of a skipped message.");
338
0
      } else if (isContinuation_ && header_->length() + continuationSize_ > messageSize_) {
339
0
        readingState_ = ReadingState::SkipData;
340
0
        LOG_ERROR("parseBuffer: A continuation frame was received that made the whole message exceed the limit (" << header_->length() + continuationSize_ << " > " << messageSize_ << " (bytes))");
341
0
        if (!header_->isFinished) {
342
0
          LOG_ERROR("parseBuffer: The received frame will be followed by a continuation that should be skipped as well.");
343
0
          skipMessage_ = true;
344
0
        }
345
0
      }
346
0
    }
347
0
  }
348
349
0
  if (readingState_ == ReadingState::ReadingData) {
350
0
    std::size_t bytes_to_add = std::min(header_->length() - dataBuffer_.length(), (size_t)(end - current));
351
0
    dataBuffer_.append(current, bytes_to_add);
352
0
    if (dataBuffer_.length() == header_->length()) {
353
      // received a full frame
354
0
      doEmitAndCleanBuffers();
355
0
    }
356
0
    current += bytes_to_add;
357
0
  }
358
359
  // Case where data is skipped due to size limitation.
360
0
  if (readingState_ == ReadingState::SkipData) {
361
0
    std::size_t bytes_to_add = std::min(header_->length() - skipDataSize_, (size_t)(end - current));
362
0
    skipDataSize_ += bytes_to_add;
363
0
    if (skipDataSize_ == header_->length()) {
364
      // received a full skipped frame
365
0
      doEmitAndCleanBuffers();
366
0
    }
367
368
0
    current += bytes_to_add;
369
0
  }
370
371
0
  return current - begin;
372
0
}
373
374
void WebSocketConnection::doEmitAndCleanBuffers()
375
0
{
376
  // Indicates that a message or frame falls within the defined limited size.
377
0
  if (readingState_ != ReadingState::SkipData) {
378
0
    hasDataReadCallback_();
379
0
  } else {
380
0
    LOG_INFO("doEmitAndCleanBuffers: not calling read callback due to frame or message size being exceeded.");
381
0
  }
382
383
0
  readingState_ = ReadingState::ReadingHeader;
384
385
  // If the header marks a complete message, no longer skip any frames, if before
386
  // they were skipped due to the message limit.
387
0
  if (header_->isFinished) {
388
0
    skipMessage_ = false;
389
0
    isContinuation_ = false;
390
0
    continuationSize_ = 0;
391
0
  } else {
392
0
    isContinuation_ = true;
393
0
    continuationSize_ += header_->length();
394
0
  }
395
396
  // If closing received, mark as wanting to close, to avoid further listening,
397
  // and cancel active async (read) operations.
398
0
  if (header_->opCode == OpCode::Close) {
399
0
    readingState_ = ReadingState::ClosingSocket;
400
0
  }
401
402
0
  dataBuffer_.clear();
403
0
  skipDataSize_ = 0;
404
0
}
405
406
void WebSocketConnection::handleAsyncRead(const AsioWrapper::error_code& e, std::size_t bytes_transferred)
407
0
{
408
0
  if (e) {
409
    // Normal shutdown
410
0
    if (e == AsioWrapper::asio::error::operation_aborted) {
411
0
      dataBuffer_.clear();
412
0
      return;
413
0
    }
414
415
0
    LOG_ERROR("handleAsyncRead: error encountered " << e.value());
416
417
0
    dataBuffer_.clear();
418
419
0
    doClose();
420
0
    return;
421
0
  }
422
423
0
  LOG_DEBUG("handleAsyncRead: incoming frame of size " << bytes_transferred << " bytes");
424
425
  // parse, starting at the beginning of the read buffer
426
0
  const char *current = &(*readBuffer_.begin());
427
  // when calculating end, start after bytes that were still in buffer
428
0
  const char *end = &(*readBufferPtr_) + bytes_transferred;
429
430
  // parse the input buffer until we don't make progress anymore
431
0
  std::size_t parsedBytes = parseBuffer(current, end);
432
0
  while (parsedBytes != 0) {
433
0
    current += parsedBytes;
434
0
    parsedBytes = parseBuffer(current, end);
435
0
  }
436
  // move remainder of rx buffer back at the start. Buffer should be empty,
437
  // unless a partial header was received
438
0
  std::size_t restSize = end - current;
439
0
  std::memmove(&(*readBuffer_.begin()), current, restSize);
440
0
  readBufferPtr_ = readBuffer_.begin() + restSize;
441
442
0
  if (readingState_ != ReadingState::ClosingSocket) {
443
0
    doAsyncRead(&(*readBufferPtr_), readBuffer_.end() - readBufferPtr_);
444
0
  }
445
0
}
446
447
bool WebSocketConnection::doAsyncWrite(OpCode type, const std::vector<char>& frameHeader, const std::vector<char>& data)
448
0
{
449
  // Check for the write-done loop, developer driven
450
0
  if (isWriting_) {
451
0
    LOG_WARN("The connection is already being used to send data over. Wait for the done() signal to fire.");
452
0
    return false;
453
0
  }
454
455
  // Concat header and data to stream
456
0
  writeBuffer_.append(frameHeader.data(), frameHeader.size());
457
0
  writeBuffer_.append(data.data(), data.size());
458
459
  // Put data into asio::buffer actual write buffer
460
0
  using namespace AsioWrapper::asio;
461
462
0
  std::vector<const_buffer> buffer;
463
0
  writeBuffer_.asioBuffers(buffer);
464
465
  // Check for functionality outside write-done loop (ping/pong), framework driven
466
0
  std::unique_lock<std::mutex> lock(writingMutex_);
467
0
  if (isWritingToSocket_) {
468
0
    LOG_DEBUG("doAsyncWrite: The connection is being written to (Ping/Pong), the frame will be delayed.");
469
0
    hasPendingDataWrite_ = true;
470
0
    pendingWriteDataType_ = type;
471
0
    pendingWriteDataBuffer_ = buffer;
472
0
    return true;
473
0
  }
474
475
0
  isWriting_ = true;
476
0
  isWritingToSocket_ = true;
477
478
0
  doSocketWrite(buffer, type);
479
480
0
  return true;
481
0
}
482
483
void WebSocketConnection::doControlFrameWrite(const std::vector<char>& frameHeader, OpCode opcode)
484
0
{
485
  // Put data into asio::buffer actual write buffer
486
0
  writeBuffer_.append(frameHeader.data(), frameHeader.size());
487
488
0
  using namespace AsioWrapper::asio;
489
490
0
  std::vector<const_buffer> buffer;
491
0
  writeBuffer_.asioBuffers(buffer);
492
493
  // Check for functionality outside write-done loop (ping/pong), framework driven
494
0
  std::unique_lock<std::mutex> lock(writingMutex_);
495
0
  if (isWritingToSocket_) {
496
0
    if (opcode == OpCode::Ping) {
497
0
      LOG_DEBUG("doControlFrameWrite: Delay the sending of the control (Ping) frame, since the socket is busy");
498
0
      hasPendingPingWrite_ = true;
499
0
      pendingWritePingBuffer_ = buffer;
500
0
    }
501
0
    if (opcode == OpCode::Pong) {
502
0
      LOG_DEBUG("doControlFrameWrite: Delay the sending of the control (Pong) frame, since the socket is busy");
503
0
      hasPendingPongWrite_ = true;
504
0
      pendingWritePongBuffer_ = buffer;
505
0
    }
506
0
    return;
507
0
  }
508
509
0
  isWritingToSocket_ = true;
510
511
0
  doSocketWrite(buffer, opcode);
512
0
}
513
514
void WebSocketConnection::handleAsyncWritten(OpCode type, const AsioWrapper::error_code& e, std::size_t bytes_transferred)
515
0
{
516
0
  if (e) {
517
0
    LOG_ERROR("handleAsyncWritten: error encountered " << e.value());
518
0
    return;
519
0
  }
520
521
  // Check for functionality outside write-done loop (ping/pong), framework driven
522
0
  std::unique_lock<std::mutex> lock(writingMutex_);
523
524
  // Only perform a singular write of a delayed frame per handler.
525
0
  if (hasPendingPingWrite_) {
526
    // A control write (ping) event took place while the socket was busy sending the data frame.
527
0
    LOG_DEBUG("handleAsyncWritten: Handling a delayed Ping frame");
528
0
    doSocketWrite(pendingWritePingBuffer_, OpCode::Ping);
529
0
    hasPendingPingWrite_ = false;
530
0
    pendingWritePingBuffer_.clear();
531
0
  } else if (hasPendingPongWrite_) {
532
    // A control write (pong) event took place while the socket was busy sending the data frame.
533
0
    LOG_DEBUG("handleAsyncWritten: Handling a delayed Pong frame");
534
0
    doSocketWrite(pendingWritePongBuffer_, OpCode::Pong);
535
0
    hasPendingPongWrite_ = false;
536
0
    pendingWritePongBuffer_.clear();
537
0
  }else if (hasPendingDataWrite_) {
538
0
    LOG_DEBUG("handleAsyncControl: Handling a delayed data frame after: " << OpCodeToString(type));
539
0
    doSocketWrite(pendingWriteDataBuffer_, pendingWriteDataType_);
540
0
    hasPendingDataWrite_ = false;
541
0
    pendingWriteDataBuffer_.clear();
542
0
  }
543
544
0
  isWriting_= false;
545
0
  isWritingToSocket_ = false;
546
0
  writeBuffer_.clear();
547
548
0
  LOG_DEBUG("handleAsyncWritten: Outgoing frame of size: " << bytes_transferred << " bytes has been written");
549
550
0
  if (type == OpCode::Binary || type == OpCode::Text || type == OpCode::Close) {
551
0
    hasDataWrittenCallback_(e, bytes_transferred);
552
0
  }
553
0
}
554
555
void WebSocketConnection::setDataReadCallback(const std::function<void()>& callback)
556
0
{
557
0
  hasDataReadCallback_ = callback;
558
0
}
559
560
void WebSocketConnection::setDataWrittenCallback(const std::function<void(const AsioWrapper::error_code&, std::size_t)>& callback)
561
0
{
562
0
  hasDataWrittenCallback_ = callback;
563
0
}
564
565
bool WebSocketConnection::isOpen()
566
0
{
567
0
  return socket().is_open();
568
0
}
569
570
void WebSocketConnection::setMaximumReceivedFrameSize(std::size_t frameSize)
571
0
{
572
0
  frameSize_ = frameSize;
573
0
}
574
575
void WebSocketConnection::setMaximumReceivedMessageSize(std::size_t messageSize)
576
0
{
577
0
  messageSize_ = messageSize;
578
0
}
579
580
WebSocketTcpConnection::WebSocketTcpConnection(AsioWrapper::asio::io_service& ioService, std::unique_ptr<Socket> socket)
581
0
  : WebSocketConnection(ioService),
582
0
    socket_(std::move(socket))
583
0
{
584
0
}
585
586
WebSocketTcpConnection::~WebSocketTcpConnection()
587
0
{
588
0
  if (isOpen()) {
589
0
    doClose();
590
0
  }
591
0
}
592
593
void WebSocketTcpConnection::doClose()
594
0
{
595
0
  try {
596
0
    AsioWrapper::error_code ignored_ec;
597
0
    auto handle = socket().native_handle();
598
0
    socket().cancel();
599
0
    socket().shutdown(AsioWrapper::asio::ip::tcp::socket::shutdown_both, ignored_ec);
600
0
    socket().close();
601
0
    LOG_INFO("Socket " << handle << " was shut down correctly.");
602
0
  } catch (AsioWrapper::system_error& e) {
603
0
    LOG_DEBUG("Socket " << socket().native_handle() << " encountered an error while shutting down: " << e.what());
604
0
  }
605
0
}
606
607
void WebSocketTcpConnection::doSocketRead(char* buffer, size_t size)
608
0
{
609
0
  auto ptr = std::static_pointer_cast<WebSocketTcpConnection>(shared_from_this());
610
611
0
  socket_->async_read_some(AsioWrapper::asio::buffer(buffer, size),
612
0
    [ptr](const AsioWrapper::error_code& e, std::size_t bytes_transferred) {
613
0
      asio::dispatch(ptr->strand_, std::bind(&WebSocketTcpConnection::handleAsyncRead, ptr, e, bytes_transferred));
614
0
    });
615
0
}
616
617
void WebSocketTcpConnection::doSocketWrite(const std::vector<AsioWrapper::asio::const_buffer>& buffer, OpCode type)
618
0
{
619
0
  auto ptr = std::static_pointer_cast<WebSocketTcpConnection>(shared_from_this());
620
0
  async_write(socket(), buffer,
621
0
    [ptr, type](const AsioWrapper::error_code& e, std::size_t bytes_transferred) {
622
0
      asio::dispatch(ptr->strand_, std::bind(&WebSocketTcpConnection::handleAsyncWritten, ptr, type, e, bytes_transferred));
623
0
    });
624
0
}
625
626
Socket& WebSocketTcpConnection::socket()
627
0
{
628
0
  return *socket_.get();
629
0
}
630
631
#ifdef WT_WITH_SSL
632
WebSocketSslConnection::WebSocketSslConnection(AsioWrapper::asio::io_service& ioService, std::unique_ptr<SSLSocket> socket)
633
  : WebSocketConnection(ioService),
634
    socket_(std::move(socket))
635
{
636
}
637
638
WebSocketSslConnection::~WebSocketSslConnection()
639
{
640
  if (isOpen()) {
641
    doClose();
642
  }
643
}
644
645
void WebSocketSslConnection::doClose()
646
{
647
  // Non-static pointer
648
  std::shared_ptr<WebSocketSslConnection> ptr = std::static_pointer_cast<WebSocketSslConnection>(shared_from_this());
649
650
  socket_->async_shutdown([ptr](const AsioWrapper::error_code& e) {
651
      asio::dispatch(ptr->strand_, std::bind(&WebSocketSslConnection::stopTcpSocket, ptr, e));
652
  });
653
}
654
655
void WebSocketSslConnection::stopTcpSocket(const AsioWrapper::error_code& e)
656
{
657
  if (e) {
658
    LOG_ERROR("doClose: SSL socket shutdown failed " << e.value());
659
  }
660
661
  try {
662
    AsioWrapper::error_code ignored_ec;
663
    auto handle = socket().native_handle();
664
    socket().cancel();
665
    socket().shutdown(AsioWrapper::asio::ip::tcp::socket::shutdown_both, ignored_ec);
666
    socket().close();
667
    LOG_INFO("Socket " << handle << " was shut down correctly.");
668
  } catch (AsioWrapper::system_error& e) {
669
    LOG_DEBUG("Socket " << socket().native_handle() << " encountered an error while shutting down: " << e.what());
670
  }
671
672
}
673
674
void WebSocketSslConnection::doSocketRead(char* buffer, size_t size)
675
{
676
  auto ptr = std::static_pointer_cast<WebSocketSslConnection>(shared_from_this());
677
  socket_->async_read_some(AsioWrapper::asio::buffer(buffer, size),
678
    [ptr](const AsioWrapper::error_code& e, std::size_t bytes_transferred) {
679
      asio::dispatch(ptr->strand_, std::bind(&WebSocketSslConnection::handleAsyncRead, ptr, e, bytes_transferred));
680
    });
681
}
682
683
void WebSocketSslConnection::doSocketWrite(const std::vector<AsioWrapper::asio::const_buffer>& buffer, OpCode type)
684
{
685
  auto ptr = std::static_pointer_cast<WebSocketSslConnection>(shared_from_this());
686
  async_write(*socket_, buffer,
687
    [ptr, type](const AsioWrapper::error_code& e, std::size_t bytes_transferred) {
688
      asio::dispatch(ptr->strand_, std::bind(&WebSocketSslConnection::handleAsyncWritten, ptr, type, e, bytes_transferred));
689
    });
690
}
691
692
Socket& WebSocketSslConnection::socket()
693
{
694
  return socket_->next_layer();
695
}
696
#endif
697
698
WWebSocketConnection::WWebSocketConnection(WWebSocketResource* resource, AsioWrapper::asio::io_service& ioService)
699
0
  : resource_(resource),
700
0
    wantsToClose_(false),
701
0
    frameSize_(0),
702
0
    messageSize_(0),
703
0
    takesUpdateLock_(true),
704
0
    pingInterval_(180),
705
0
    pingTimeout_(360),
706
0
    pingSignalTimer_(ioService),
707
0
    pongTimeoutTimer_(ioService)
708
0
{
709
0
  LOG_DEBUG("New connection to track on endpoint: " << resource->url());
710
0
}
711
712
WWebSocketConnection::~WWebSocketConnection()
713
0
{
714
0
  AsioWrapper::error_code ignored_ec;
715
0
  closeSocket(ignored_ec, "");
716
0
}
717
718
void WWebSocketConnection::setSocket(const std::shared_ptr<WebSocketConnection>& connection)
719
0
{
720
0
  socketConnection_ = connection;
721
0
  socketConnection_->setMaximumReceivedFrameSize(frameSize_);
722
0
  socketConnection_->setMaximumReceivedMessageSize(messageSize_);
723
724
0
  socketConnection_->startReading();
725
  // Set up listeners to socket changes (written & read)
726
0
  socketConnection_->setDataWrittenCallback(std::bind(&WWebSocketConnection::writeFrame, this, std::placeholders::_1, std::placeholders::_2));
727
0
  socketConnection_->setDataReadCallback(std::bind(&WWebSocketConnection::receiveFrame, this));
728
0
}
729
730
void WWebSocketConnection::handleMessage(const std::string& text)
731
0
{
732
0
  LOG_INFO("handleMessage(text): text of length: " << std::to_string(text.size()));
733
0
}
734
735
void WWebSocketConnection::handleMessage(const std::vector<char>& buffer)
736
0
{
737
0
  LOG_INFO("handleMessage(binary): array of length: " + std::to_string(buffer.size()));
738
0
}
739
740
bool WWebSocketConnection::sendMessage(const std::string& text)
741
0
{
742
0
  std::vector<char> buff;
743
0
  std::copy(text.begin(), text.end(), std::back_inserter(buff));
744
0
  return sendDataFrame(buff, OpCode::Text);
745
0
}
746
747
bool WWebSocketConnection::sendMessage(const std::vector<char>& buffer)
748
0
{
749
0
  return sendDataFrame(buffer, OpCode::Binary);
750
0
}
751
752
bool WWebSocketConnection::close(WT_MAYBE_UNUSED CloseCode code, const std::string& reason)
753
0
{
754
0
  wantsToClose_ = true;
755
756
0
  if (reason.empty()) {
757
0
    return sendEmptyFrame(OpCode::Close);
758
0
  }
759
760
0
  std::vector<char> buff;
761
0
  std::copy(reason.begin(), reason.end(), std::back_inserter(buff));
762
0
  return sendDataFrame(buff, OpCode::Close);
763
0
}
764
765
bool WWebSocketConnection::sendDataFrame(const std::vector<char>& data, OpCode opcode)
766
0
{
767
0
  WebSocketFrameHeader header;
768
0
  header.isFinished = true;
769
0
  header.reserved1 = false;
770
0
  header.reserved2 = false;
771
0
  header.reserved3 = false;
772
0
  header.opCode = opcode;
773
0
  header.isMasked = false;
774
775
0
  std::vector<char> frameHeader = header.generateFrameHeader(data.size());;
776
777
0
  LOG_DEBUG("sendDataFrame: writing data frame with a header of " << frameHeader.size() << " bytes and data of " << data.size() << " bytes");
778
779
0
  return socketConnection_->doAsyncWrite(opcode, frameHeader, data);
780
0
}
781
782
bool WWebSocketConnection::sendEmptyFrame(OpCode opcode)
783
0
{
784
0
  if (opcode == OpCode::Ping || opcode == OpCode::Pong) {
785
0
    LOG_WARN("sendEmptyFrame: sending a ping or pong frame inside the write/done loop. This will trigger the done() signal upon successfully writing.");
786
0
  }
787
788
0
  WebSocketFrameHeader header;
789
0
  header.isFinished = true;
790
0
  header.reserved1 = false;
791
0
  header.reserved2 = false;
792
0
  header.reserved3 = false;
793
0
  header.opCode = opcode;
794
0
  header.isMasked = false;
795
796
0
  std::vector<char> frameHeader = header.generateFrameHeader(0);
797
798
0
  LOG_DEBUG("sendEmptyFrame: writing a control frame with a header of " << frameHeader.size() << " bytes and OpCode: " << OpCodeToString(header.opCode));
799
800
0
  return socketConnection_->doAsyncWrite(opcode, frameHeader);
801
0
}
802
803
bool WWebSocketConnection::sendControlFrame(OpCode opcode)
804
0
{
805
0
  if (opcode == OpCode::Close) {
806
0
    LOG_WARN("sendControlFrame: sending a close frame outside the write/done loop. This will NOT trigger the done() or closed() signals upon successfully writing.");
807
0
  }
808
809
0
  WebSocketFrameHeader header;
810
0
  header.isFinished = true;
811
0
  header.reserved1 = false;
812
0
  header.reserved2 = false;
813
0
  header.reserved3 = false;
814
0
  header.opCode = opcode;
815
0
  header.isMasked = false;
816
817
0
  std::vector<char> frameHeader = header.generateFrameHeader(0);
818
819
0
  LOG_DEBUG("sendControlFrame: writing a control frame with a header of " << frameHeader.size() << " bytes and OpCode: " << OpCodeToString(header.opCode));
820
821
0
  socketConnection_->doControlFrameWrite(frameHeader, opcode);
822
0
  return true;
823
0
}
824
825
void WWebSocketConnection::writeFrame(const AsioWrapper::error_code& e, std::size_t bytes_transferred)
826
0
{
827
0
  LOG_DEBUG("writeFrame: written data of size: " << bytes_transferred << " bytes");
828
829
0
  std::unique_ptr<WApplication::UpdateLock> updateLock;
830
0
  if (takesUpdateLock_ && app()) {
831
0
    updateLock.reset(new Wt::WApplication::UpdateLock(app()));
832
0
    if (!*updateLock) {
833
0
      LOG_ERROR("writeFrame: Cannot take the update lock");
834
0
      return;
835
0
    }
836
0
  }
837
838
0
  done_.emit(e);
839
0
  updateLock.reset(nullptr);
840
0
}
841
842
void WWebSocketConnection::receiveFrame()
843
0
{
844
0
  WebSocketFrameHeader* header = socketConnection_->header();
845
846
0
  std::string data;
847
0
  std::string rawData = socketConnection_->dataBuffer().str();
848
0
  std::size_t dataSize = rawData.size();
849
0
  data.reserve(dataSize);
850
851
0
  if (header->isMasked) {
852
0
    for (unsigned int i = 0; i < dataSize; ++i) {
853
0
      data += (rawData[i] ^ header->mask()[(i) % 4]);
854
0
    }
855
0
  } else {
856
0
    data += rawData;
857
0
  }
858
859
0
  if (socketConnection_->skipMessage()) {
860
0
    LOG_DEBUG("receiveFrame: Received a finished frame or message, that exceeded the received frame limit (" << header->length() << " > " << frameSize_ << " (bytes))");
861
0
    return;
862
0
  }
863
864
  // Continuation frame case
865
0
  if (!header->isFinished) {
866
    // Remember current unmasked data for when frame finishes.
867
0
    continuationBuffer_.append(data.c_str(), data.size());
868
0
    if (header->opCode != OpCode::Continuation) {
869
0
      continuationOpCode_ = header->opCode;
870
0
    }
871
0
    LOG_DEBUG("Received a non-finished frame, and expect a continuation. Current OpCode: " << OpCodeToString(continuationOpCode_) << " and data size: " << data.size());
872
0
    return;
873
0
  } else if (header->isFinished && header->opCode == OpCode::Continuation) {
874
0
    header->opCode = continuationOpCode_;
875
0
    continuationBuffer_.append(data.c_str(), data.size());
876
877
0
    if (continuationBuffer_.length() > messageSize_) {
878
0
      LOG_ERROR("receiveFrame: A message was received that exceeded the limit (" << continuationBuffer_.length() << " > " << messageSize_ << " (bytes))");
879
0
      continuationBuffer_.clear();
880
0
      return;
881
0
    }
882
883
0
    data = continuationBuffer_.str();
884
0
    LOG_DEBUG("Received a finished frame, after a continuation. Current OpCode: " << OpCodeToString(continuationOpCode_) << " and data size: " << data.size());
885
0
    continuationBuffer_.clear();
886
0
  }
887
888
  // Handle all non-update locked functionality:
889
0
  if (header->opCode == OpCode::Ping) {
890
0
    acknowledgePing();
891
0
    return;
892
0
  } else if (header->opCode == OpCode::Pong) {
893
0
    handlePong();
894
0
    return;
895
0
  }
896
897
0
  std::unique_ptr<WApplication::UpdateLock> updateLock;
898
0
  if (takesUpdateLock_ && app()) {
899
0
    updateLock.reset(new Wt::WApplication::UpdateLock(app()));
900
0
    if (!*updateLock) {
901
0
      LOG_ERROR("receiveFrame: Cannot take the update lock");
902
0
      return;
903
0
    }
904
0
  }
905
906
0
  if (header->opCode == OpCode::Close) {
907
0
    if (wantsToClose_) {
908
0
      AsioWrapper::error_code ignored_ec;
909
0
      closeSocket(ignored_ec, data);
910
0
    } else {
911
0
      acknowledgeClose(data);
912
0
    }
913
0
  } else if (header->opCode == OpCode::Text) {
914
0
    handleMessage(data);
915
0
  } else if (header->opCode == OpCode::Binary) {
916
0
    std::vector<char> binaryData(data.begin(), data.end());
917
0
    handleMessage(binaryData);
918
0
  } else if (header->opCode == OpCode::Continuation) {
919
0
    handleError();
920
0
  }
921
922
0
  updateLock.reset(nullptr);
923
0
}
924
925
void WWebSocketConnection::acknowledgeClose(const std::string& reason)
926
0
{
927
0
  std::vector<char> buff;
928
0
  std::copy(reason.begin(), reason.end(), std::back_inserter(buff));
929
0
  sendDataFrame(buff, OpCode::Close);
930
0
  done_.connect(this, std::bind(&WWebSocketConnection::closeSocket, this, std::placeholders::_1, reason));
931
0
}
932
933
void WWebSocketConnection::doSendPing(const AsioWrapper::error_code& e)
934
0
{
935
0
  if (e) {
936
    // Normal shutdown
937
0
    if (e == AsioWrapper::asio::error::operation_aborted) {
938
0
      return;
939
0
    }
940
0
    LOG_ERROR("doSendPing: encountered an error " << e.value());
941
0
    return;
942
0
  }
943
944
0
  sendPing();
945
0
}
946
947
bool WWebSocketConnection::sendPing()
948
0
{
949
0
  if (pingTimeout_ > 0) {
950
0
    pongTimeoutTimer_.expires_after(std::chrono::seconds(pingTimeout_));
951
0
    pongTimeoutTimer_.async_wait(std::bind(&WWebSocketConnection::missingPong, this, std::placeholders::_1));
952
0
  }
953
954
0
  return sendControlFrame(OpCode::Ping);
955
0
}
956
957
void WWebSocketConnection::acknowledgePing()
958
0
{
959
0
  sendControlFrame(OpCode::Pong);
960
0
  startPingTimer();
961
0
}
962
963
void WWebSocketConnection::handleError()
964
0
{
965
0
  LOG_ERROR("handleError: A had request was caught, currently specifically used for a bad OpCode.");
966
0
}
967
968
void WWebSocketConnection::handlePong()
969
0
{
970
0
  LOG_DEBUG("handlePong: a Pong frame was received.");
971
0
  startPingTimer();
972
0
}
973
974
void WWebSocketConnection::missingPong(const AsioWrapper::error_code& e)
975
0
{
976
0
  if (e) {
977
    // Normal shutdown
978
0
    if (e == AsioWrapper::asio::error::operation_aborted) {
979
0
      return;
980
0
    }
981
0
    LOG_ERROR("missingPong: encountered an error " << e.value());
982
0
    return;
983
0
  }
984
985
0
  LOG_ERROR("missingPong: the socket has send a Ping request, but received no Pong after " << pingTimeout_ << " seconds.");
986
0
  AsioWrapper::error_code ignored_ec;
987
0
  closeSocket(ignored_ec, "Missing Pong");
988
0
}
989
990
void WWebSocketConnection::setMaximumReceivedSize(std::size_t frameSize, std::size_t messageSize)
991
0
{
992
0
  frameSize_ = frameSize;
993
0
  messageSize_ = messageSize;
994
995
0
  if (socketConnection_) {
996
0
    socketConnection_->setMaximumReceivedFrameSize(frameSize_);
997
0
    socketConnection_->setMaximumReceivedMessageSize(messageSize_);
998
0
  }
999
0
}
1000
1001
void WWebSocketConnection::setTakesUpdateLock(bool takesUpdateLock)
1002
0
{
1003
0
  takesUpdateLock_ = takesUpdateLock;
1004
0
}
1005
1006
void WWebSocketConnection::setPingTimeout(int pingInterval, int pingTimeout)
1007
0
{
1008
0
  pingInterval_ = pingInterval;
1009
0
  pingTimeout_ = pingTimeout;
1010
1011
0
  startPingTimer();
1012
0
}
1013
1014
void WWebSocketConnection::startPingTimer()
1015
0
{
1016
0
  pingSignalTimer_.cancel();
1017
0
  pongTimeoutTimer_.cancel();
1018
1019
  // Socket "keep-alive", performed through ping-pong mechanism.
1020
0
  if (pingInterval_ > 0) {
1021
0
    pingSignalTimer_.expires_after(std::chrono::seconds(pingInterval_));
1022
0
    pingSignalTimer_.async_wait(std::bind(&WWebSocketConnection::doSendPing, this, std::placeholders::_1));
1023
0
  }
1024
0
}
1025
1026
void WWebSocketConnection::closeSocket(const AsioWrapper::error_code& e, const std::string& reason)
1027
0
{
1028
0
  if (e) {
1029
0
    LOG_ERROR("closeSocket: encountered an error: " << e.value());
1030
0
    return;
1031
0
  }
1032
1033
0
  pingSignalTimer_.cancel();
1034
0
  pongTimeoutTimer_.cancel();
1035
1036
0
  if (socketConnection_->isOpen()) {
1037
0
    socketConnection_->doClose();
1038
0
  }
1039
0
  if (takesUpdateLock_ && app()) {
1040
0
    WApplication::UpdateLock lock(app());
1041
0
    if (lock) {
1042
0
      closed_.emit(e, reason);
1043
0
    }
1044
0
  } else {
1045
0
    closed_.emit(e, reason);
1046
0
  }
1047
0
}
1048
1049
WApplication* WWebSocketConnection::app()
1050
0
{
1051
0
  if (resource_) {
1052
0
    return resource_->app_;
1053
0
  }
1054
0
  return nullptr;
1055
0
}
1056
}
1057