def test_parse_frame_header_unknown_type_strict(self): with pytest.raises(UnknownFrameError) as excinfo: Frame.parse_frame_header(b'\x00\x00\x59\xFF\x00\x00\x00\x00\x01', strict=True) exception = excinfo.value assert exception.frame_type == 0xFF assert exception.length == 0x59 assert str(exception) == ( "UnknownFrameError: Unknown frame type 0xFF received, " "length 89 bytes")
def test_parse_frame_header_unknown_type(self): with pytest.raises(UnknownFrameError) as excinfo: Frame.parse_frame_header(b'\x00\x00\x59\xFF\x00\x00\x00\x00\x01') exception = excinfo.value assert exception.frame_type == 0xFF assert exception.length == 0x59 assert str(exception) == ( "UnknownFrameError: Unknown frame type 0xFF received, " "length 89 bytes" )
def next(self): # Python 2 if len(self.data) < 9: raise StopIteration() try: f, length = Frame.parse_frame_header(self.data[:9]) except UnknownFrameError as e: # Here we do something a bit odd. We want to consume the frame data # as consistently as possible, but we also don't ever want to yield # None. Instead, we make sure that, if there is no frame, we # recurse into ourselves. length = e.length f = None if len(self.data) < length + 9: raise StopIteration() # Don't try to parse the body if we didn't get a frame we know about: # there's nothing we can do with it anyway. if f is not None: f.parse_body(memoryview(self.data[9:9+length])) self.data = self.data[9+length:] # If we got a frame we didn't understand, rather than return None it'd # be better if we just tried to get the next frame in the sequence # instead. Recurse back into ourselves to do that. # This is safe because the amount of work we have to do here is # strictly bounded by the length of the buffer. return f if f is not None else self.next()
def test_parse_frame_header_unknown_type(self): frame, length = Frame.parse_frame_header( b'\x00\x00\x59\xFF\x00\x00\x00\x00\x01') assert frame.type == 0xFF assert length == 0x59 assert isinstance(frame, ExtensionFrame) assert frame.stream_id == 1
def next(self): # Python 2 if len(self.data) < 9: raise StopIteration() try: f, length = Frame.parse_frame_header(self.data[:9]) except UnknownFrameError as e: # Here we do something a bit odd. We want to consume the frame data # as consistently as possible, but we also don't ever want to yield # None. Instead, we make sure that, if there is no frame, we # recurse into ourselves. length = e.length f = None if len(self.data) < length + 9: raise StopIteration() # Don't try to parse the body if we didn't get a frame we know about: # there's nothing we can do with it anyway. if f is not None: f.parse_body(memoryview(self.data[9:9 + length])) self.data = self.data[9 + length:] # If we got a frame we didn't understand, rather than return None it'd # be better if we just tried to get the next frame in the sequence # instead. Recurse back into ourselves to do that. # This is safe because the amount of work we have to do here is # strictly bounded by the length of the buffer. return f if f is not None else self.next()
def test_flags_are_persisted(self): f, l = Frame.parse_frame_header( b'\x00\x00\x59\xFF\x09\x00\x00\x00\x01' ) assert f.type == 0xFF assert l == 0x59 assert f.flag_byte == 0x09
def _consume_single_frame(self): try: header = self._sock.recv(9) except Exception as e: self.logger.debug("%s _consume_single_frame:%r, inactive time:%d", self.ip, e, time.time() - self.last_recv_time) self.close("ConnectionReset:%r" % e) return self.last_recv_time = time.time() # Parse the header. We can use the returned memoryview directly here. frame, length = Frame.parse_frame_header(header) if length > FRAME_MAX_ALLOWED_LEN: self.logger.error( "%s Frame size exceeded on stream %d (received: %d, max: %d)", self.ip, frame.stream_id, length, FRAME_MAX_LEN) # self._send_rst_frame(frame.stream_id, 6) # 6 = FRAME_SIZE_ERROR try: data = self._recv_payload(length) except Exception as e: self.close("ConnectionReset:%r" % e) return self._consume_frame_payload(frame, data)
def test_parse_frame_header_unknown_type(self): f, l = Frame.parse_frame_header( b'\x00\x00\x59\xFF\x00\x00\x00\x00\x01' ) assert f.type == 0xFF assert l == 0x59 assert isinstance(f, ExtensionFrame) assert f.stream_id == 1
def _parse_frame_header(self, data): """ Parses the frame header from the data. Either returns a tuple of (frame, length), or throws an exception. The returned frame may be None if the frame is of unknown type. """ try: frame, length = Frame.parse_frame_header(data[:9]) except ValueError as e: # The frame header is invalid. This is a ProtocolError raise ProtocolError("Invalid frame header received: %s" % str(e)) return frame, length
def test(self, tc_filepath): with open(os.path.join(root, tc_filepath)) as f: tc = json.load(f) data = bytes.fromhex(tc["wire"]) if tc["error"] is None and tc["frame"]: check_valid_frame(tc, data) elif tc["error"] and tc["frame"] is None: with pytest.raises(Exception): new_frame, length = Frame.parse_frame_header( data[:9], strict=True ) new_frame.parse_body(memoryview(data[9:9 + length])) assert length == new_frame.body_len else: pytest.fail("unexpected test case: {} {}".format(tc_filepath, tc))
def check_valid_frame(tc, data): # noqa: C901 new_frame, length = Frame.parse_frame_header(data[:9], strict=True) new_frame.parse_body(memoryview(data[9:9 + length])) assert tc["frame"]["length"] == length assert tc["frame"]["stream_identifier"] == new_frame.stream_id assert tc["frame"]["type"] == new_frame.type flags = 0 for flag, flag_bit in new_frame.defined_flags: if flag in new_frame.flags: flags |= flag_bit assert tc["frame"]["flags"] == flags p = tc["frame"]["frame_payload"] if "header_block_fragment" in p: assert p["header_block_fragment"] == new_frame.data.decode() if "data" in p: assert p["data"] == new_frame.data.decode() if "padding" in p: # the padding data itself is not retained by hyperframe after parsing pass if "padding_length" in p and p["padding_length"]: assert p["padding_length"] == new_frame.pad_length if "error_code" in p: assert p["error_code"] == new_frame.error_code if "additional_debug_data" in p: assert p["additional_debug_data"].encode() == new_frame.additional_data if "last_stream_id" in p: assert p["last_stream_id"] == new_frame.last_stream_id if "stream_dependency" in p: assert p["stream_dependency"] or 0 == new_frame.depends_on if "weight" in p and p["weight"]: assert p["weight"] - 1 == new_frame.stream_weight if "exclusive" in p: assert (p["exclusive"] or False) == new_frame.exclusive if "opaque_data" in p: assert p["opaque_data"].encode() == new_frame.opaque_data if "promised_stream_id" in p: assert p["promised_stream_id"] == new_frame.promised_stream_id if "settings" in p: assert dict(p["settings"]) == new_frame.settings if "window_size_increment" in p: assert p["window_size_increment"] == new_frame.window_increment
def __next__(self): # First, check that we have enough data to successfully parse the # next frame header. If not, bail. Otherwise, parse it. if len(self.data) < 9: raise StopIteration() try: f, length = Frame.parse_frame_header(self.data[:9]) except (InvalidDataError, InvalidFrameError) as e: # pragma: no cover raise ProtocolError( "Received frame with invalid header: %s" % str(e) ) # Next, check that we have enough length to parse the frame body. If # not, bail, leaving the frame header data in the buffer for next time. if len(self.data) < length + 9: raise StopIteration() # Confirm the frame has an appropriate length. self._validate_frame_length(length) # Try to parse the frame body try: f.parse_body(memoryview(self.data[9:9+length])) except InvalidDataError: raise ProtocolError("Received frame with non-compliant data") except InvalidFrameError: raise FrameDataMissingError("Frame data missing or invalid") # At this point, as we know we'll use or discard the entire frame, we # can update the data. self.data = self.data[9+length:] # Pass the frame through the header buffer. f = self._update_header_buffer(f) # If we got a frame we didn't understand or shouldn't yield, rather # than return None it'd be better if we just tried to get the next # frame in the sequence instead. Recurse back into ourselves to do # that. This is safe because the amount of work we have to do here is # strictly bounded by the length of the buffer. return f if f is not None else self.__next__()
def _parse_frame_header(self, data): """ Parses the frame header from the data. Either returns a tuple of (frame, length), or throws an exception. The returned frame may be None if the frame is of unknown type. """ try: frame, length = Frame.parse_frame_header(data[:9]) except UnknownFrameError as e: # Here we do something a bit odd. We want to consume the frame data # as consistently as possible, but we also don't ever want to yield # None. Instead, we make sure that, if there is no frame, we # recurse into ourselves. length = e.length frame = None except ValueError as e: # The frame header is invalid. This is a ProtocolError raise ProtocolError("Invalid frame header received: %s" % str(e)) return frame, length
def read_frame(rfile, parse=True): """ Reads a full HTTP/2 frame from a file-like object. Returns a parsed frame and the consumed bytes. """ header = rfile.safe_read(9) length = int(codecs.encode(header[:3], 'hex_codec'), 16) if length == 4740180: raise exceptions.HttpException( "Length field looks more like HTTP/1.1:\n{}".format( rfile.read(-1))) body = rfile.safe_read(length) if parse: frame, _ = Frame.parse_frame_header(header) frame.parse_body(memoryview(body)) else: frame = None return frame, b''.join([header, body])
def decode_frame(frame_data): f, length = Frame.parse_frame_header(frame_data[:9]) f.parse_body(memoryview(frame_data[9:9 + length])) assert 9 + length == len(frame_data) return f
def test_cannot_parse_invalid_frame_header(self): with pytest.raises(InvalidFrameError): Frame.parse_frame_header(b'\x00\x00\x08\x00\x01\x00\x00\x00')
def test_parse_frame_header_ignore_first_bit_of_stream_id(self): s = b'\x00\x00\x00\x06\x01\x80\x00\x00\x00' f, _ = Frame.parse_frame_header(s) assert f.stream_id == 0