RESTinio
Loading...
Searching...
No Matches
ws_protocol_validator.hpp
Go to the documentation of this file.
1/*!
2 Protocol header validator.
3*/
4
5#pragma once
6
7#include <restinio/exception.hpp>
8#include <restinio/websocket/impl/utf8.hpp>
9#include <restinio/websocket/impl/ws_parser.hpp>
10
11namespace restinio
12{
13
14namespace websocket
15{
16
17namespace basic
18{
19
20namespace impl
21{
22
23//
24// validation_state_t
25//
26
27//! States of validated frame.
48
49//
50// validation_state_str
51//
52
53//! Helper function for logging validation states.
54inline const char *
56{
57 static constexpr const char* table[] =
58 {
59 "initial_state",
60 "frame_header_is_valid",
61 "payload_part_is_valid",
62 "frame_is_valid",
63 "invalid_opcode",
64 "empty_mask_from_client_side",
65 "non_final_control_frame",
66 "non_zero_rsv_flags",
67 "payload_len_is_too_big",
68 "continuation_frame_without_data_frame",
69 "new_data_frame_without_finishing_previous",
70 "invalid_close_code",
71 "incorrect_utf8_data"
72 };
73
74 return table[static_cast<unsigned int>(state)];
75}
76
77//
78// is_control_frame
79//
80
81//! Check frame is control frame.
82/*!
83 \return true if frame is control frame.
84 \return false otherwise.
85*/
86inline bool
88{
89 return opcode == opcode_t::connection_close_frame ||
90 opcode == opcode_t::ping_frame ||
91 opcode == opcode_t::pong_frame;
92}
93
94//
95// is_data_frame
96//
97
98//! Check frame is data frame.
99/*!
100 \return true if frame is data frame.
101 \return false otherwise.
102*/
103inline bool
105{
106 return opcode == opcode_t::text_frame ||
107 opcode == opcode_t::binary_frame;
108}
109
110//
111// unmasker_t
112//
113
114/*!
115 This class is need to unmask byte sequences.
116
117 Mask is 32 bit key.
118*/
120{
121 unmasker_t() = default;
122
123 unmasker_t( uint32_t masking_key )
124 : m_mask{
132 masking_key)} }
133 {
134 }
135
136 //! Do unmask operation.
137 /*!
138 \return unmasked value.
139 */
140 uint8_t
141 unmask_byte( uint8_t masked_byte )
142 {
143 return masked_byte ^ m_mask[ (m_processed_bytes_count++) % 4 ];
144 }
145
146 //! Reset to initial state.
147 void
148 reset( uint32_t masking_key )
149 {
150 m_processed_bytes_count = 0;
151
152 m_mask = mask_array_t{
153 {::restinio::utils::impl::bitops::n_bits_from< std::uint8_t, 24 >(
154 masking_key),
155 ::restinio::utils::impl::bitops::n_bits_from< std::uint8_t, 16 >(
156 masking_key),
157 ::restinio::utils::impl::bitops::n_bits_from< std::uint8_t, 8 >(
158 masking_key),
159 ::restinio::utils::impl::bitops::n_bits_from< std::uint8_t, 0 >(
160 masking_key)} };
161
162 }
163
165
166 //! Bytes array with masking key.
168
169 //! Processed bytes counter.
170 /*!
171 It needs for taking remainder after division on 4. Result of this operation
172 is index of value in mask array for next unmask operation.
173 */
175};
176
177//
178// ws_protocol_validator_t
179//
180
181//! Class for websocket protocol validations.
182/*!
183 This class checks:
184
185 text frame and close frame are with valid uff-8 payload;
186 close frame has a valid close code;
187 continuation chunks of text frame have valid utf-8 text;
188 there is invalid situation when continuation frame came without any data frame.
189 continuation frame or control frame is wating after data frame with fin flag set in 0.
190 opcode has a valid code
191 control frame can't be fragmented.
192
193*/
195{
196 public:
197
199
200 ws_protocol_validator_t( bool do_unmask )
201 : m_unmask_flag{ do_unmask }
202 {
203 }
204
205 //! Start work with new frame.
206 /*!
207 \attention methods finish_frame() or reset() should be called before
208 processing a new frame.
209 */
212 {
214 throw exception_t( "another frame is processing now" );
215
216 if( validate_frame_header( frame ) &&
218 {
219 switch( frame.m_opcode )
220 {
221 case opcode_t::text_frame:
222 if( !frame.m_final_flag )
224 break;
225
226 case opcode_t::binary_frame:
227 if( !frame.m_final_flag )
229 break;
230
231 case opcode_t::connection_close_frame:
232 m_expected_close_code.reset(2);
233 break;
234
235 default:
236 break;
237 }
238
239 m_current_frame = frame;
241
242 if( m_unmask_flag )
243 {
244 m_unmasker.reset( frame.m_masking_key );
245 }
246 }
247
248 return m_validation_state;
249 }
250
251 //! Validate next part of current frame.
253 process_next_payload_part( const char * data, size_t size )
254 {
256 throw exception_t( "current state is empty" );
257
261 else
262 return m_validation_state;
263
264 for( size_t i = 0; i < size; ++i )
265 {
267 static_cast<std::uint8_t>(data[i]) );
268
270 break;
271 }
272
273 return m_validation_state;
274 }
275
276 //! Validate next part of current frame and reset source part to unmasked data.
278 process_and_unmask_next_payload_part( char * data, size_t size )
279 {
281 throw exception_t( "current state is empty" );
282
286 else
287 return m_validation_state;
288
289 for( size_t i = 0; i < size; ++i )
290 {
291 data[i] = static_cast<char>(process_payload_byte(
292 static_cast<std::uint8_t>(data[i]) ));
293
295 break;
296 }
297
298 return m_validation_state;
299 }
300
301 //! Make final checks of payload if it is necessary and reset state.
304 {
307
309 {
313
315 }
316
318
319 // If continued data frame is present and current processed frame is
320 // continuation frame with final bit set in 1 then reset current continued
321 // data frame type.
325 {
328 }
329
330 // Remember current frame vaidation state and return this value.
331 auto this_frame_validation_state = m_validation_state;
332
333 // Reset validation state for next frame.
335
336 return this_frame_validation_state;
337 }
338
339 //! Reset to initial state.
340 void
350
351 private:
352
353 //! Validate frame header.
354 /*!
355 \return true if current validation state is 'frame_header_is_valid' after
356 all validation operations.
357
358 \return false otherwise.
359 */
360 bool
397
398 //! Process payload byte.
399 /*!
400 Do all necessary validations with payload byte.
401
402 \return unmasked byte if unmask flag is set.
403 \return copy of original byte if unmask flag isn't set.
404 */
405 std::uint8_t
406 process_payload_byte( std::uint8_t byte )
407 {
408 byte = m_unmask_flag?
409 m_unmasker.unmask_byte( byte ): byte;
410
411 if( m_current_frame.m_opcode == opcode_t::text_frame ||
412 (m_current_frame.m_opcode == opcode_t::continuation_frame &&
414 {
416 {
419 }
420 }
421 else if( m_current_frame.m_opcode == opcode_t::connection_close_frame )
422 {
423 if( !m_expected_close_code.all_bytes_loaded() )
424 {
425 if( m_expected_close_code.add_byte_and_check_size(byte) )
426 {
427 uint16_t status_code{0};
428
429 read_number_from_big_endian_bytes(
430 status_code,m_expected_close_code.m_loaded_data );
431
432 validate_close_code( status_code );
433 }
434 }
435 else
436 {
440 }
441 }
442
443 return byte;
444 }
445
446 //! Check previous frame type.
447 /*!
448 Need for following cases:
449
450 1) check current frame is not continuation frame withot
451 any data frame before.
452 2) check current frame is not new data frame with unfinished
453 other data frame before.
454
455 \return true if previous and current frames are without any conflicts.
456
457 \return false otherwise.
458 */
459 bool
461 {
463 opcode == opcode_t::continuation_frame )
464 {
467
468 return false;
469 }
470 else if( m_previous_data_frame !=
472 is_data_frame( opcode ) )
473 {
476
477 return false;
478 }
479
480 return true;
481 }
482
483 //! Validate close code.
484 void
485 validate_close_code( uint16_t close_code )
486 {
487 if( close_code < 1000 ||
488 (close_code > 1011 && close_code < 3000) ||
489 close_code > 4999 )
490 {
493
494 return;
495 }
496
497 if( close_code == 1004 ||
498 close_code == 1005 ||
499 close_code == 1006 )
500 {
503
504 return;
505 }
506 }
507
508 //! Check validation state is still valid.
509 bool
517
518 //! Try to set validation state.
519 /*!
520 Set validation state with new value.
521 */
522 void
527
528 //! Current validation state.
529 /*!
530 Normal case is sequence of states:
531 frame_header_is_valid -> payload_part_is_valid -> frame_is_valid.
532
533 After set in invalid state validator will save this state until frame
534 will be finished or validator will be reset.
535 */
538
539 //! Validator's orking states.
541 {
542 //! Waiting for new frame.
544 //! Frame is processing now.
546 };
547
548 //! Previous unfinished data frame type.
549 /*!
550 It needs for understanding what kind of data is present in
551 current continued frames.
552 */
559
560 //! Working state.
562
563 //! Previous unfinished data frame.
566
567 //! Current frame details.
569
570 //! Buffer for accumulating 2 bytes of close code.
572
573 //! UTF-8 checker for text frames and close frames.
575
576 //! This flag set if it's need to unmask payload parts.
577 bool m_unmask_flag{ false };
578
579 //! Unmask payload coming from client side.
581};
582
583} /* namespace impl */
584
585} /* namespace basic */
586
587} /* namespace websocket */
588
589} /* namespace restinio */
Exception class for all exceptions thrown by RESTinio.
Definition exception.hpp:26
exception_t(const char *err)
Definition exception.hpp:29
Helper class for checking UTF-8 byte sequence during parsing URI or incoming byte stream.
bool process_byte(std::uint8_t byte) noexcept
bool finalized() const noexcept
Websocket message class with more detailed protocol information.
Definition ws_parser.hpp:63
std::uint64_t payload_len() const
Get payload len.
Definition ws_parser.hpp:92
validation_state_t process_and_unmask_next_payload_part(char *data, size_t size)
Validate next part of current frame and reset source part to unmasked data.
bool validate_frame_header(const message_details_t &frame)
Validate frame header.
validation_state_t m_validation_state
Current validation state.
previous_data_frame_t m_previous_data_frame
Previous unfinished data frame.
validation_state_t process_new_frame(const message_details_t &frame)
Start work with new frame.
bool is_state_still_valid() const
Check validation state is still valid.
unmasker_t m_unmasker
Unmask payload coming from client side.
std::uint8_t process_payload_byte(std::uint8_t byte)
Process payload byte.
validation_state_t process_next_payload_part(const char *data, size_t size)
Validate next part of current frame.
expected_data_t m_expected_close_code
Buffer for accumulating 2 bytes of close code.
void validate_close_code(uint16_t close_code)
Validate close code.
bool check_previous_frame_type(opcode_t opcode)
Check previous frame type.
void set_validation_state(validation_state_t state)
Try to set validation state.
validation_state_t finish_frame()
Make final checks of payload if it is necessary and reset state.
restinio::utils::utf8_checker_t m_utf8_checker
UTF-8 checker for text frames and close frames.
bool m_unmask_flag
This flag set if it's need to unmask payload parts.
bool is_data_frame(opcode_t opcode)
Check frame is data frame.
bool is_control_frame(opcode_t opcode)
Check frame is control frame.
const char * validation_state_str(validation_state_t state)
Helper function for logging validation states.
validation_state_t
States of validated frame.
bool is_valid_opcode(opcode_t opcode)
Definition message.hpp:64
void reset(uint32_t masking_key)
Reset to initial state.
mask_array_t m_mask
Bytes array with masking key.
uint8_t unmask_byte(uint8_t masked_byte)
Do unmask operation.