reSIProcate
SIP stack, Dialog Usage Manager and related libraries
Loading...
Searching...
No Matches
WsFrameExtractor.cxx
Go to the documentation of this file.
1
2#include "rutil/Logger.hxx"
5
6using namespace resip;
7
8#define RESIPROCATE_SUBSYSTEM Subsystem::TRANSPORT
9
10
11const int WsFrameExtractor::mMaxHeaderLen = 14;
12const size_t WsFrameExtractor::mMaxFrames = 1024;
13
20
21// RFC 6455 section 5.5: a control frame carries at most 125 bytes
22static const uint64_t MaxControlPayload = 125;
23
25 : mMaxMessage(maxMessage),
26 mMessageSize(0),
27 mHaveHeader(false),
28 mHeaderLen(0),
29 mClosed(false),
30 mFinalFrame(false),
31 mMasked(false),
32 mOpcode(OpContinuation),
33 mPayloadLength(0),
34 mPayload(0),
35 mPayloadPos(0)
36{
37 memset(mWsMaskKey, 0, sizeof(mWsMaskKey));
38
39 // we re-use this for multiple messages throughout
40 // the lifetime of this parser object
41 mWsHeader = new uint8_t[mMaxHeaderLen];
42}
43
45{
46 // FIXME - delete any objects left in the queues
47 delete [] mWsHeader;
48
49 // the buffer of a frame that was only partially received
50 delete [] (char*)mPayload;
51
52 while(!mFrames.empty())
53 {
54 delete [] mFrames.front()->data();
55 delete mFrames.front();
56 mFrames.pop();
57 }
58
59 while(!mMessages.empty())
60 {
61 delete [] mMessages.front()->data();
62 delete mMessages.front();
63 mMessages.pop();
64 }
65
66}
67
68std::unique_ptr<Data>
69WsFrameExtractor::processBytes(uint8_t *input, Data::size_type len, bool& dropConnection)
70{
71 std::unique_ptr<Data> ret;
72 dropConnection = false;
73 Data::size_type pos = 0;
74 if(mClosed && input != 0 && len > 0)
75 {
76 // RFC 6455 section 5.5.1: the peer must not send anything after a Close
77 StackLog(<<"discarding " << len << " bytes received after a WebSocket Close");
78 pos = len;
79 }
80 while(input != 0 && pos < len)
81 {
82 while(!mHaveHeader)
83 {
84 StackLog(<<"Need a header, parsing bytes...");
85 // Append bytes to the header buffer. parseHeader() only returns
86 // zero once it has a complete header, so keep re-parsing after
87 // every batch of bytes rather than waiting for more input to
88 // arrive: an oversized frame has to be rejected as soon as its
89 // header is known, not when its payload starts turning up.
90 int needed = parseHeader();
91 if(needed == 0)
92 {
93 break;
94 }
95 // only a header that is still incomplete once mMaxHeaderLen bytes
96 // have been consumed is too long: a full header (64 bit length and
97 // a mask key) occupies exactly mMaxHeaderLen bytes
98 if((mHeaderLen + needed) > mMaxHeaderLen)
99 {
100 WarningLog(<<"WS Frame header too long");
101 dropConnection = true;
102 return ret;
103 }
104 for( ; needed > 0 && pos < len; needed-- )
105 {
106 mWsHeader[mHeaderLen++] = input[pos++];
107 }
108 if(needed > 0)
109 {
110 StackLog(<<"Not enough bytes available to form a full header");
111 return ret;
112 }
113 }
114 if(mHaveHeader)
115 {
116 StackLog(<<"have header, parsing payload data...");
117 const bool control = isControlFrame();
118 if(mOpcode != OpContinuation && mOpcode != OpText && mOpcode != OpBinary &&
119 mOpcode != OpClose && mOpcode != OpPing && mOpcode != OpPong)
120 {
121 WarningLog(<<"WS frame with reserved opcode " << (int)mOpcode << ", dropping connection");
122 dropConnection = true;
123 return ret;
124 }
125 if(control)
126 {
127 // RFC 6455 section 5.5: control frames are never fragmented and
128 // carry at most 125 bytes, so they cannot be used to get round the
129 // message size limit below
130 if(!mFinalFrame || mPayloadLength > MaxControlPayload)
131 {
132 WarningLog(<<"WS control frame (opcode " << (int)mOpcode << ") is fragmented or longer than "
133 << MaxControlPayload << " bytes, dropping connection");
134 dropConnection = true;
135 return ret;
136 }
137 }
138 // Process input bytes to output buffer, unmasking if necessary.
139 // mPayloadLength is an attacker controlled 64 bit value, so the
140 // check has to be written to avoid wrapping: comparing
141 // mMessageSize + mPayloadLength against mMaxMessage would let a
142 // peer bypass it with a length close to 2^64. Control frames are
143 // not part of a message and were bounded above.
144 else if(mPayloadLength > mMaxMessage ||
145 mMessageSize > mMaxMessage - mPayloadLength)
146 {
147 WarningLog(<<"WS frame header describes a payload size bigger than messageSizeMax, max = " << mMaxMessage
148 << ", dropping connection");
149 dropConnection = true;
150 return ret;
151 }
152
153 // safe to narrow now that it has been bounded by mMaxMessage
154 const Data::size_type payloadLength = (Data::size_type)mPayloadLength;
155
156 if(mPayload == 0)
157 {
158 StackLog(<<"starting new frame buffer");
159 // Include an extra byte at the end for null terminator
160 mPayload = (uint8_t*)new char[payloadLength + 1];
161 mPayloadPos = 0;
162 }
163
164 Data::size_type takeBytes = len - pos;
165 if(takeBytes > payloadLength - mPayloadPos)
166 {
167 takeBytes = payloadLength - mPayloadPos;
168 }
169
170 if(mMasked)
171 {
172 Data::size_type endOffset = mPayloadPos + takeBytes;
173 for( ; mPayloadPos < endOffset; mPayloadPos++)
174 {
175 mPayload[mPayloadPos] = (input[pos++] ^ mWsMaskKey[(mPayloadPos & 3)]);
176 }
177 }
178 else
179 {
180 memmove(&mPayload[mPayloadPos], &input[pos], takeBytes);
181 pos += takeBytes;
182 mPayloadPos += takeBytes;
183 }
184
185 if(mPayloadPos == payloadLength && control)
186 {
187 StackLog(<<"Got a whole control frame, opcode " << (int)mOpcode);
188 ControlFrame frame;
189 frame.opcode = mOpcode;
190 frame.payload = Data((const char*)mPayload, payloadLength);
191 mControlFrames.push(frame);
192 delete [] (char*)mPayload;
193 mPayload = 0;
194 mHaveHeader = false;
195 mHeaderLen = 0;
196 if(mOpcode == OpClose)
197 {
198 mClosed = true;
199 if(pos < len)
200 {
201 StackLog(<<"discarding " << (len - pos) << " bytes received after a WebSocket Close");
202 }
203 pos = len;
204 }
205 }
206 else if(mPayloadPos == payloadLength)
207 {
208 StackLog(<<"Got a whole frame, queueing it");
209 mMessageSize += payloadLength;
210 Data *mFrame = new Data(Data::Borrow, (char *)mPayload, payloadLength, payloadLength + 1);
211 mFrames.push(mFrame);
212 mHaveHeader = false;
213 mHeaderLen = 0;
214 mPayload = 0;
215 if(mFinalFrame)
216 {
217 joinFrames();
218 }
219 else if(mFrames.size() >= mMaxFrames)
220 {
221 // empty and tiny continuation frames don't advance
222 // mMessageSize fast enough (or at all) for the size check
223 // above to ever terminate this, so bound the frame count too
224 WarningLog(<<"WS message fragmented into more than " << mMaxFrames
225 << " frames, dropping connection");
226 dropConnection = true;
227 return ret;
228 }
229 }
230 }
231 }
232 if(mMessages.empty())
233 {
234 StackLog(<<"no full messages available in queue");
235 return ret;
236 }
237 ret = std::unique_ptr<Data>(mMessages.front());
238 mMessages.pop();
239 StackLog(<<"returning a message, size = " << ret->size());
240 return ret;
241}
242
243int
244WsFrameExtractor::parseHeader()
245{
246 if(mHeaderLen < 2)
247 {
248 StackLog(<< "Too short to contain ws data [0]");
249 return (2 - mHeaderLen);
250 }
251
252 uint64_t hdrPos = 2;
253
254 mFinalFrame = (mWsHeader[0] >> 7) != 0;
255 mOpcode = mWsHeader[0] & 0x0F;
256 mMasked = (mWsHeader[1] >> 7) != 0;
257
258 if(mWsHeader[0] & 0x40 || mWsHeader[0] & 0x20 || mWsHeader[0] & 0x10)
259 {
260 WarningLog(<< "Unknown extension: " << ((mWsHeader[0] >> 4) & 0x07));
261 // do not exit
262 }
263
264 mPayloadLength = mWsHeader[1] & 0x7F;
265 if(mPayloadLength == 126)
266 {
267 if(mHeaderLen < 4)
268 {
269 StackLog(<< "Too short to contain ws data [1]");
270 return (4 - mHeaderLen) + (mMasked ? 4 : 0);
271 }
272 mPayloadLength = (mWsHeader[hdrPos] << 8 | mWsHeader[hdrPos + 1]);
273 hdrPos += 2;
274 }
275 else if(mPayloadLength == 127)
276 {
277 // the 64 bit length occupies mWsHeader[2] through mWsHeader[9], so
278 // 10 bytes must have arrived before any of it can be read
279 if(mHeaderLen < 10)
280 {
281 StackLog(<< "Too short to contain ws data [2]");
282 return (10 - mHeaderLen) + (mMasked ? 4 : 0);
283 }
284 mPayloadLength = (((uint64_t)mWsHeader[hdrPos]) << 56 | ((uint64_t)mWsHeader[hdrPos + 1]) << 48 | ((uint64_t)mWsHeader[hdrPos + 2]) << 40 | ((uint64_t)mWsHeader[hdrPos + 3]) << 32 | ((uint64_t)mWsHeader[hdrPos + 4]) << 24 | ((uint64_t)mWsHeader[hdrPos + 5]) << 16 | ((uint64_t)mWsHeader[hdrPos + 6]) << 8 | ((uint64_t)mWsHeader[hdrPos + 7]));
285 hdrPos += 8;
286 }
287
288 if(mMasked)
289 {
290 if((mHeaderLen - hdrPos) < 4)
291 {
292 StackLog(<< "Too short to contain ws data [3]");
293 return (int)((hdrPos + 4) - mHeaderLen);
294 }
295 mWsMaskKey[0] = mWsHeader[hdrPos];
296 mWsMaskKey[1] = mWsHeader[hdrPos + 1];
297 mWsMaskKey[2] = mWsHeader[hdrPos + 2];
298 mWsMaskKey[3] = mWsHeader[hdrPos + 3];
299 hdrPos += 4;
300 }
301
302 StackLog(<< "successfully processed a WebSocket frame header, opcode = " << (int)mOpcode
303 << ", payload length = " << mPayloadLength
304 << ", masked = "<< mMasked << ", final frame = "<< mFinalFrame);
305
306 mHaveHeader = true;
307 mPayload = 0;
308 return 0;
309}
310
311bool
312WsFrameExtractor::isControlFrame() const
313{
314 // RFC 6455 section 5.5: opcodes with the high bit set are control frames
315 return (mOpcode & 0x08) != 0;
316}
317
318bool
320{
321 if(mControlFrames.empty())
322 {
323 return false;
324 }
325 frame = mControlFrames.front();
326 mControlFrames.pop();
327 return true;
328}
329
330void
331WsFrameExtractor::joinFrames()
332{
333 StackLog(<<"trying to join frames");
334 if(mFrames.empty())
335 {
336 ErrLog(<<"No frames to join!");
337 return;
338 }
339
340 Data *msg = mFrames.front();
341 mFrames.pop();
342 if(!mFrames.empty())
343 {
344 // must expand buffer because there are multiple frames
345 // can't use Data::reserve() to increase the buffer, because the
346 // ShareEnum will change to Take when expanded
347 char *_msg = (char *)msg->data();
348 Data::size_type frameSize = msg->size();
349
350 // allow extra byte for null terminator
351 char *newBuf = new char [mMessageSize + 1];
352 memcpy(newBuf, _msg, frameSize);
353 delete msg;
354 // the Data only borrowed this buffer, so it must be freed separately
355 delete [] _msg;
356
357 msg = new Data(Data::Borrow, newBuf, frameSize, mMessageSize + 1);
358 }
359 while(!mFrames.empty())
360 {
361 Data *mFrame = mFrames.front();
362 mFrames.pop();
363 msg->append(mFrame->data(), mFrame->size());
364 delete [] mFrame->data();
365 delete mFrame;
366 }
367
368 // It is safe to cast because we used Borrow:
369 char *_msg = (char *)msg->data();
370 // MsgHeaderScanner expects space for an extra byte at the end:
371 _msg[mMessageSize] = 0;
372
373 mMessages.push(msg);
374
375 // Ready to start examinging first frame of next message...
376 mMessageSize = 0;
377}
378
379/* ====================================================================
380 *
381 * Copyright (c) 2026 SIP Spectrum, Inc. https://www.sipspectrum.com
382 * Copyright 2013 Daniel Pocock. All rights reserved.
383 *
384 * Redistribution and use in source and binary forms, with or without
385 * modification, are permitted provided that the following conditions
386 * are met:
387 *
388 * 1. Redistributions of source code must retain the above copyright
389 * notice, this list of conditions and the following disclaimer.
390 *
391 * 2. Redistributions in binary form must reproduce the above copyright
392 * notice, this list of conditions and the following disclaimer in
393 * the documentation and/or other materials provided with the
394 * distribution.
395 *
396 * 3. Neither the name of the author(s) nor the names of any contributors
397 * may be used to endorse or promote products derived from this software
398 * without specific prior written permission.
399 *
400 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND
401 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
402 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
403 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE
404 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
405 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
406 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
407 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
408 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
409 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
410 * SUCH DAMAGE.
411 *
412 * ====================================================================
413 *
414 *
415 */
416
#define StackLog(args_)
Definition Logger.hxx:125
#define WarningLog(args_)
Definition Logger.hxx:134
#define ErrLog(args_)
Definition Logger.hxx:137
static const uint64_t MaxControlPayload
An alternative to std::string, encapsulates an arbitrary buffer of bytes.
Definition Data.hxx:90
@ Borrow
The Data instance is borrowing the memory from the passed in buffer.
Definition Data.hxx:260
Data & append(const char *str, size_type len)
Appends the specified number of bytes to the end of this Data.
Definition Data.cxx:1058
size_t size_type
Definition Data.hxx:94
size_type size() const noexcept
Returns the number of bytes in this Data.
Definition Data.hxx:599
const char * data() const noexcept
Returns a pointer to the contents of this Data.
Definition Data.hxx:608
WsFrameExtractor(Data::size_type maxMessage)
std::unique_ptr< Data > processBytes(uint8_t *input, Data::size_type len, bool &dropConnection)
static const uint8_t OpClose
static const uint8_t OpText
static const uint8_t OpBinary
static const uint8_t OpPing
bool popControlFrame(ControlFrame &frame)
static const uint8_t OpPong
static const uint8_t OpContinuation
dcm! – add UnusedChecking(_enum) below;
unsigned long long uint64_t
Definition Stun.hxx:39
unsigned char uint8_t
Definition Stun.hxx:29
unsigned char uint8_t
Definition stdint.h:83
unsigned __int64 uint64_t
Definition stdint.h:95