1# -*- coding: utf-8 -*- 2""" 3hyperframe/frame 4~~~~~~~~~~~~~~~~ 5 6Defines framing logic for HTTP/2. Provides both classes to represent framed 7data and logic for aiding the connection when it comes to reading from the 8socket. 9""" 10import collections 11import struct 12 13from .flags import Flag, Flags 14 15# The maximum initial length of a frame. Some frames have shorter maximum lengths. 16FRAME_MAX_LEN = (2 ** 14) 17 18# The maximum allowed length of a frame. 19FRAME_MAX_ALLOWED_LEN = (2 ** 24) - 1 20 21 22class Frame(object): 23 """ 24 The base class for all HTTP/2 frames. 25 """ 26 # The flags defined on this type of frame. 27 defined_flags = [] 28 29 # The type of the frame. 30 type = None 31 32 # If 'has-stream', the frame's stream_id must be non-zero. If 'no-stream', 33 # it must be zero. If 'either', it's not checked. 34 stream_association = None 35 36 def __init__(self, stream_id, flags=()): 37 self.stream_id = stream_id 38 self.flags = Flags(self.defined_flags) 39 self.body_len = 0 40 41 for flag in flags: 42 self.flags.add(flag) 43 44 if self.stream_association == 'has-stream' and not self.stream_id: 45 raise ValueError('Stream ID must be non-zero') 46 if self.stream_association == 'no-stream' and self.stream_id: 47 raise ValueError('Stream ID must be zero') 48 49 def __repr__(self): 50 flags = ", ".join(self.flags) or "None" 51 body = self.serialize_body() 52 if len(body) > 100: 53 body = str(body[:100]) + "..." 54 return ( 55 "{type}(Stream: {stream}; Flags: {flags}): {body}" 56 ).format(type=type(self).__name__, stream=self.stream_id, flags=flags, body=body) 57 58 @staticmethod 59 def parse_frame_header(header): 60 """ 61 Takes a 9-byte frame header and returns a tuple of the appropriate 62 Frame object and the length that needs to be read from the socket. 63 """ 64 fields = struct.unpack("!HBBBL", header) 65 # First 24 bits are frame length. 66 length = (fields[0] << 8) + fields[1] 67 type = fields[2] 68 flags = fields[3] 69 stream_id = fields[4] 70 71 if type not in FRAMES: 72 raise ValueError("Unknown frame type %d" % type) 73 74 frame = FRAMES[type](stream_id) 75 frame.parse_flags(flags) 76 return (frame, length) 77 78 def parse_flags(self, flag_byte): 79 for flag, flag_bit in self.defined_flags: 80 if flag_byte & flag_bit: 81 self.flags.add(flag) 82 83 return self.flags 84 85 def serialize(self): 86 body = self.serialize_body() 87 self.body_len = len(body) 88 89 # Build the common frame header. 90 # First, get the flags. 91 flags = 0 92 93 for flag, flag_bit in self.defined_flags: 94 if flag in self.flags: 95 flags |= flag_bit 96 97 header = struct.pack( 98 "!HBBBL", 99 (self.body_len & 0xFFFF00) >> 8, # Length is spread over top 24 bits 100 self.body_len & 0x0000FF, 101 self.type, 102 flags, 103 self.stream_id & 0x7FFFFFFF # Stream ID is 32 bits. 104 ) 105 106 return header + body 107 108 def serialize_body(self): 109 raise NotImplementedError() 110 111 def parse_body(self, data): 112 raise NotImplementedError() 113 114 115class Padding(object): 116 """ 117 Mixin for frames that contain padding. 118 """ 119 def __init__(self, stream_id, pad_length=0, **kwargs): 120 super(Padding, self).__init__(stream_id, **kwargs) 121 122 self.pad_length = pad_length 123 124 125 def serialize_padding_data(self): 126 if 'PADDED' in self.flags: 127 return struct.pack('!B', self.pad_length) 128 return b'' 129 130 def parse_padding_data(self, data): 131 if 'PADDED' in self.flags: 132 self.pad_length = struct.unpack('!B', data[:1])[0] 133 return 1 134 return 0 135 136 @property 137 def total_padding(self): 138 """Return the total length of the padding, if any.""" 139 return self.pad_length 140 141 142class Priority(object): 143 """ 144 Mixin for frames that contain priority data. 145 """ 146 def __init__(self, stream_id, depends_on=None, stream_weight=None, exclusive=None, **kwargs): 147 super(Priority, self).__init__(stream_id, **kwargs) 148 149 # The stream ID of the stream on which this stream depends. 150 self.depends_on = depends_on 151 152 # The weight of the stream. This is an integer between 0 and 256. 153 self.stream_weight = stream_weight 154 155 # Whether the exclusive bit was set. 156 self.exclusive = exclusive 157 158 def serialize_priority_data(self): 159 return struct.pack( 160 "!LB", 161 self.depends_on | (int(self.exclusive) << 31), 162 self.stream_weight 163 ) 164 165 def parse_priority_data(self, data): 166 MASK = 0x80000000 167 self.depends_on, self.stream_weight = struct.unpack( 168 "!LB", data[:5] 169 ) 170 self.exclusive = bool(self.depends_on & MASK) 171 self.depends_on &= ~MASK 172 return 5 173 174 175class DataFrame(Padding, Frame): 176 """ 177 DATA frames convey arbitrary, variable-length sequences of octets 178 associated with a stream. One or more DATA frames are used, for instance, 179 to carry HTTP request or response payloads. 180 """ 181 defined_flags = [ 182 Flag('END_STREAM', 0x01), 183 Flag('PADDED', 0x08), 184 ] 185 186 type = 0x0 187 188 stream_association = 'has-stream' 189 190 def __init__(self, stream_id, data=b'', **kwargs): 191 super(DataFrame, self).__init__(stream_id, **kwargs) 192 193 self.data = data 194 195 def serialize_body(self): 196 padding_data = self.serialize_padding_data() 197 padding = b'\0' * self.total_padding 198 return b''.join([padding_data, self.data, padding]) 199 200 def parse_body(self, data): 201 padding_data_length = self.parse_padding_data(data) 202 self.data = data[padding_data_length:len(data)-self.total_padding].tobytes() 203 self.body_len = len(data) 204 205 @property 206 def flow_controlled_length(self): 207 """ 208 If the frame is padded we need to include the padding length byte in 209 the flow control used. 210 """ 211 padding_len = self.total_padding + 1 if self.total_padding else 0 212 return len(self.data) + padding_len 213 214 215class PriorityFrame(Priority, Frame): 216 """ 217 The PRIORITY frame specifies the sender-advised priority of a stream. It 218 can be sent at any time for an existing stream. This enables 219 reprioritisation of existing streams. 220 """ 221 defined_flags = [] 222 223 type = 0x02 224 225 stream_association = 'has-stream' 226 227 def serialize_body(self): 228 return self.serialize_priority_data() 229 230 def parse_body(self, data): 231 self.parse_priority_data(data) 232 self.body_len = len(data) 233 234 235class RstStreamFrame(Frame): 236 """ 237 The RST_STREAM frame allows for abnormal termination of a stream. When sent 238 by the initiator of a stream, it indicates that they wish to cancel the 239 stream or that an error condition has occurred. When sent by the receiver 240 of a stream, it indicates that either the receiver is rejecting the stream, 241 requesting that the stream be cancelled or that an error condition has 242 occurred. 243 """ 244 defined_flags = [] 245 246 type = 0x03 247 248 stream_association = 'has-stream' 249 250 def __init__(self, stream_id, error_code=0, **kwargs): 251 super(RstStreamFrame, self).__init__(stream_id, **kwargs) 252 253 self.error_code = error_code 254 255 def serialize_body(self): 256 return struct.pack("!L", self.error_code) 257 258 def parse_body(self, data): 259 if len(data) != 4: 260 raise ValueError() 261 262 self.error_code = struct.unpack("!L", data)[0] 263 self.body_len = len(data) 264 265 266class SettingsFrame(Frame): 267 """ 268 The SETTINGS frame conveys configuration parameters that affect how 269 endpoints communicate. The parameters are either constraints on peer 270 behavior or preferences. 271 272 Settings are not negotiated. Settings describe characteristics of the 273 sending peer, which are used by the receiving peer. Different values for 274 the same setting can be advertised by each peer. For example, a client 275 might set a high initial flow control window, whereas a server might set a 276 lower value to conserve resources. 277 """ 278 defined_flags = [Flag('ACK', 0x01)] 279 280 type = 0x04 281 282 stream_association = 'no-stream' 283 284 # We need to define the known settings, they may as well be class 285 # attributes. 286 HEADER_TABLE_SIZE = 0x01 287 ENABLE_PUSH = 0x02 288 MAX_CONCURRENT_STREAMS = 0x03 289 INITIAL_WINDOW_SIZE = 0x04 290 SETTINGS_MAX_FRAME_SIZE = 0x05 291 SETTINGS_MAX_HEADER_LIST_SIZE = 0x06 292 293 def __init__(self, stream_id=0, settings=None, **kwargs): 294 super(SettingsFrame, self).__init__(stream_id, **kwargs) 295 296 if settings and "ACK" in kwargs.get("flags", ()): 297 raise ValueError("Settings must be empty if ACK flag is set.") 298 299 # A dictionary of the setting type byte to the value. 300 self.settings = settings or {} 301 302 def serialize_body(self): 303 settings = [struct.pack("!HL", setting & 0xFF, value) 304 for setting, value in self.settings.items()] 305 return b''.join(settings) 306 307 def parse_body(self, data): 308 for i in range(0, len(data), 6): 309 name, value = struct.unpack("!HL", data[i:i+6]) 310 self.settings[name] = value 311 312 self.body_len = len(data) 313 314 315class PushPromiseFrame(Padding, Frame): 316 """ 317 The PUSH_PROMISE frame is used to notify the peer endpoint in advance of 318 streams the sender intends to initiate. 319 """ 320 defined_flags = [ 321 Flag('END_HEADERS', 0x04), 322 Flag('PADDED', 0x08) 323 ] 324 325 type = 0x05 326 327 stream_association = 'has-stream' 328 329 def __init__(self, stream_id, promised_stream_id=0, data=b'', **kwargs): 330 super(PushPromiseFrame, self).__init__(stream_id, **kwargs) 331 332 self.promised_stream_id = promised_stream_id 333 self.data = data 334 335 def serialize_body(self): 336 padding_data = self.serialize_padding_data() 337 padding = b'\0' * self.total_padding 338 data = struct.pack("!L", self.promised_stream_id) 339 return b''.join([padding_data, data, self.data, padding]) 340 341 def parse_body(self, data): 342 padding_data_length = self.parse_padding_data(data) 343 self.promised_stream_id = struct.unpack("!L", data[padding_data_length:padding_data_length + 4])[0] 344 self.data = data[padding_data_length + 4:].tobytes() 345 self.body_len = len(data) 346 347 348class PingFrame(Frame): 349 """ 350 The PING frame is a mechanism for measuring a minimal round-trip time from 351 the sender, as well as determining whether an idle connection is still 352 functional. PING frames can be sent from any endpoint. 353 """ 354 defined_flags = [Flag('ACK', 0x01)] 355 356 type = 0x06 357 358 stream_association = 'no-stream' 359 360 def __init__(self, stream_id=0, opaque_data=b'', **kwargs): 361 super(PingFrame, self).__init__(stream_id, **kwargs) 362 363 self.opaque_data = opaque_data 364 365 def serialize_body(self): 366 if len(self.opaque_data) > 8: 367 raise ValueError() 368 369 data = self.opaque_data 370 data += b'\x00' * (8 - len(self.opaque_data)) 371 return data 372 373 def parse_body(self, data): 374 if len(data) > 8: 375 raise ValueError() 376 377 self.opaque_data = data.tobytes() 378 self.body_len = len(data) 379 380 381class GoAwayFrame(Frame): 382 """ 383 The GOAWAY frame informs the remote peer to stop creating streams on this 384 connection. It can be sent from the client or the server. Once sent, the 385 sender will ignore frames sent on new streams for the remainder of the 386 connection. 387 """ 388 type = 0x07 389 390 stream_association = 'no-stream' 391 392 def __init__(self, stream_id=0, last_stream_id=0, error_code=0, additional_data=b'', **kwargs): 393 super(GoAwayFrame, self).__init__(stream_id, **kwargs) 394 395 self.last_stream_id = last_stream_id 396 self.error_code = error_code 397 self.additional_data = additional_data 398 399 def serialize_body(self): 400 data = struct.pack( 401 "!LL", 402 self.last_stream_id & 0x7FFFFFFF, 403 self.error_code 404 ) 405 data += self.additional_data 406 407 return data 408 409 def parse_body(self, data): 410 self.last_stream_id, self.error_code = struct.unpack("!LL", data[:8]) 411 self.body_len = len(data) 412 413 if len(data) > 8: 414 self.additional_data = data[8:].tobytes() 415 416 417class WindowUpdateFrame(Frame): 418 """ 419 The WINDOW_UPDATE frame is used to implement flow control. 420 421 Flow control operates at two levels: on each individual stream and on the 422 entire connection. 423 424 Both types of flow control are hop by hop; that is, only between the two 425 endpoints. Intermediaries do not forward WINDOW_UPDATE frames between 426 dependent connections. However, throttling of data transfer by any receiver 427 can indirectly cause the propagation of flow control information toward the 428 original sender. 429 """ 430 type = 0x08 431 432 stream_association = 'either' 433 434 def __init__(self, stream_id, window_increment=0, **kwargs): 435 super(WindowUpdateFrame, self).__init__(stream_id, **kwargs) 436 437 self.window_increment = window_increment 438 439 def serialize_body(self): 440 return struct.pack("!L", self.window_increment & 0x7FFFFFFF) 441 442 def parse_body(self, data): 443 self.window_increment = struct.unpack("!L", data)[0] 444 self.body_len = len(data) 445 446 447class HeadersFrame(Padding, Priority, Frame): 448 """ 449 The HEADERS frame carries name-value pairs. It is used to open a stream. 450 HEADERS frames can be sent on a stream in the "open" or "half closed 451 (remote)" states. 452 453 The HeadersFrame class is actually basically a data frame in this 454 implementation, because of the requirement to control the sizes of frames. 455 A header block fragment that doesn't fit in an entire HEADERS frame needs 456 to be followed with CONTINUATION frames. From the perspective of the frame 457 building code the header block is an opaque data segment. 458 """ 459 type = 0x01 460 461 stream_association = 'has-stream' 462 463 defined_flags = [ 464 Flag('END_STREAM', 0x01), 465 Flag('END_HEADERS', 0x04), 466 Flag('PADDED', 0x08), 467 Flag('PRIORITY', 0x20), 468 ] 469 470 def __init__(self, stream_id, data=b'', **kwargs): 471 super(HeadersFrame, self).__init__(stream_id, **kwargs) 472 473 self.data = data 474 475 def serialize_body(self): 476 padding_data = self.serialize_padding_data() 477 padding = b'\0' * self.total_padding 478 479 if 'PRIORITY' in self.flags: 480 priority_data = self.serialize_priority_data() 481 else: 482 priority_data = b'' 483 484 return b''.join([padding_data, priority_data, self.data, padding]) 485 486 def parse_body(self, data): 487 padding_data_length = self.parse_padding_data(data) 488 data = data[padding_data_length:] 489 490 if 'PRIORITY' in self.flags: 491 priority_data_length = self.parse_priority_data(data) 492 else: 493 priority_data_length = 0 494 495 self.body_len = len(data) 496 self.data = data[priority_data_length:len(data)-self.total_padding].tobytes() 497 498 499class ContinuationFrame(Frame): 500 """ 501 The CONTINUATION frame is used to continue a sequence of header block 502 fragments. Any number of CONTINUATION frames can be sent on an existing 503 stream, as long as the preceding frame on the same stream is one of 504 HEADERS, PUSH_PROMISE or CONTINUATION without the END_HEADERS flag set. 505 506 Much like the HEADERS frame, hyper treats this as an opaque data frame with 507 different flags and a different type. 508 """ 509 type = 0x09 510 511 stream_association = 'has-stream' 512 513 defined_flags = [Flag('END_HEADERS', 0x04),] 514 515 def __init__(self, stream_id, data=b'', **kwargs): 516 super(ContinuationFrame, self).__init__(stream_id, **kwargs) 517 518 self.data = data 519 520 def serialize_body(self): 521 return self.data 522 523 def parse_body(self, data): 524 self.data = data.tobytes() 525 self.body_len = len(data) 526 527 528Origin = collections.namedtuple('Origin', ['scheme', 'host', 'port']) 529 530 531class AltSvcFrame(Frame): 532 """ 533 The ALTSVC frame is used to advertise alternate services that the current 534 host, or a different one, can understand. 535 """ 536 type = 0xA 537 538 stream_association = 'no-stream' 539 540 def __init__(self, stream_id=0, host=b'', port=0, protocol_id=b'', max_age=0, origin=None, **kwargs): 541 super(AltSvcFrame, self).__init__(stream_id, **kwargs) 542 543 self.host = host 544 self.port = port 545 self.protocol_id = protocol_id 546 self.max_age = max_age 547 self.origin = origin 548 549 def serialize_origin(self): 550 if self.origin is not None: 551 if self.origin.port is None: 552 hostport = self.origin.host 553 else: 554 hostport = self.origin.host + b':' + str(self.origin.port).encode('ascii') 555 return self.origin.scheme + b'://' + hostport 556 return b'' 557 558 def parse_origin(self, data): 559 if len(data) > 0: 560 data = data.tobytes() 561 scheme, hostport = data.split(b'://') 562 host, _, port = hostport.partition(b':') 563 self.origin = Origin(scheme=scheme, host=host, 564 port=int(port) if len(port) > 0 else None) 565 566 def serialize_body(self): 567 first = struct.pack("!LHxB", self.max_age, self.port, len(self.protocol_id)) 568 host_length = struct.pack("!B", len(self.host)) 569 return b''.join([first, self.protocol_id, host_length, self.host, 570 self.serialize_origin()]) 571 572 def parse_body(self, data): 573 self.body_len = len(data) 574 self.max_age, self.port, protocol_id_length = struct.unpack("!LHxB", data[:8]) 575 pos = 8 576 self.protocol_id = data[pos:pos+protocol_id_length].tobytes() 577 pos += protocol_id_length 578 host_length = struct.unpack("!B", data[pos:pos+1])[0] 579 pos += 1 580 self.host = data[pos:pos+host_length].tobytes() 581 pos += host_length 582 self.parse_origin(data[pos:]) 583 584 585class BlockedFrame(Frame): 586 """ 587 The BLOCKED frame indicates that the sender is unable to send data due to a 588 closed flow control window. 589 590 The BLOCKED frame is used to provide feedback about the performance of flow 591 control for the purposes of performance tuning and debugging. The BLOCKED 592 frame can be sent by a peer when flow controlled data cannot be sent due to 593 the connection- or stream-level flow control. This frame MUST NOT be sent 594 if there are other reasons preventing data from being sent, either a lack 595 of available data, or the underlying transport being blocked. 596 """ 597 type = 0x0B 598 599 stream_association = 'both' 600 601 defined_flags = [] 602 603 def serialize_body(self): 604 return b'' 605 606 def parse_body(self, data): 607 pass 608 609 610# A map of type byte to frame class. 611_FRAME_CLASSES = [ 612 DataFrame, 613 HeadersFrame, 614 PriorityFrame, 615 RstStreamFrame, 616 SettingsFrame, 617 PushPromiseFrame, 618 PingFrame, 619 GoAwayFrame, 620 WindowUpdateFrame, 621 ContinuationFrame, 622 AltSvcFrame, 623 BlockedFrame 624] 625FRAMES = {cls.type: cls for cls in _FRAME_CLASSES} 626