aboutsummaryrefslogtreecommitdiffstats
path: root/lib/plum/frame.rb
blob: cbddbe803cfe26e9fab0a9b0c50ddcf093641492 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
module Plum
  class Frame
    FRAME_TYPES = {
      data:           0x00,
      headers:        0x01,
      priority:       0x02,
      rst_stream:     0x03,
      settings:       0x04,
      push_promise:   0x05,
      ping:           0x06,
      goaway:         0x07,
      window_update:  0x08,
      continuation:   0x09
    }

    FRAME_FLAGS = {
      data: {
        end_stream:   0x01,
        padded:       0x08
      },
      headers: {
        end_stream:   0x01,
        end_headers:  0x04,
        padded:       0x08,
        priority:     0x20
      },
      priority: {},
      rst_stream: {},
      settings: {
        ack:          0x01
      },
      push_promise: {
        end_headers:  0x04,
        padded:       0x08
      },
      ping: {
        ack:          0x01
      },
      goaway: {},
      window_update: {},
      continuation: {
        end_headers:  0x04
      }
    }

    # RFC7540: 4.1 Frame format
    # +-----------------------------------------------+
    # |                 Length (24)                   |
    # +---------------+---------------+---------------+
    # |   Type (8)    |   Flags (8)   |
    # +-+-------------+---------------+-------------------------------+
    # |R|                 Stream Identifier (31)                      |
    # +=+=============================================================+
    # |                   Frame Payload (0...)                      ...
    # +---------------------------------------------------------------+

    # [Integer] The length of payload. unsigned 24-bit integer
    attr_reader :length
    # [Integer] Frame type. 8-bit
    attr_reader :type_value
    # [Integer] Flags. 8-bit
    attr_reader :flags_value
    # [Integer] Stream Identifier. unsigned 31-bit integer
    attr_reader :stream_id
    # [String] The payload.
    attr_reader :payload

    def initialize(length: nil, type: nil, type_value: nil, flags: nil, flags_value: nil, stream_id: nil, payload: nil)
      @payload = payload.to_s
      @length = length || @payload.bytesize
      @type_value = type_value || FRAME_TYPES[type] or raise ArgumentError.new("type_value or type is necessary")
      @flags_value = flags_value || (flags && flags.map {|flag| FRAME_FLAGS[type][flag] }.inject(:|)) || 0
      @stream_id = stream_id or raise ArgumentError.new("stream_id is necessary")
    end

    def type
      FRAME_TYPES.key(type_value)
    end

    def flags
      FRAME_FLAGS[type].select {|name, value| value & flags_value > 0 }.map {|name, value| name }
    end

    def assemble
      bytes = ""
      bytes << [length].pack("N")[1, 3] # last 3*8 bits
      bytes << [type_value].pack("C")
      bytes << [flags_value].pack("C")
      bytes << [stream_id & ~(1 << 31)].pack("N") # first bit is reserved (MUST be 0)
      bytes << payload
      bytes
    end

    def inspect
      "#<Plum::Frame:0x#{__id__.to_s(16)} length=#{length.inspect}, type=#{type.inspect}, flags=#{flags.inspect}, stream_id=0x#{stream_id.to_s(16)}, payload=#{payload.inspect}>"
    end

    def self.parse!(buffer)
      return nil if buffer.size < 9 # header: 9 bytes
      bhead = buffer[0, 9]
      length = ("\x00" + bhead[0, 3]).unpack("N")[0]
      return nil if buffer.size < 9 + length

      payload = buffer.slice!(0...(9 + length))[9, length]
      type = bhead[3, 1].unpack("C")[0]
      flags = bhead[4, 1].unpack("C")[0]
      r_sid = bhead[5, 4].unpack("N")[0]
      r = r_sid >> 31
      stream_id = r_sid & ~(1 << 31)

      self.new(length: length, type_value: type, flags_value: flags, stream_id: stream_id, payload: payload)
    end
  end
end