arf 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -0
  3. data/LICENSE.txt +21 -0
  4. data/Rakefile +12 -0
  5. data/lib/arf/configuration.rb +67 -0
  6. data/lib/arf/context.rb +170 -0
  7. data/lib/arf/errors.rb +10 -0
  8. data/lib/arf/io/buffer.rb +25 -0
  9. data/lib/arf/io/compression.rb +44 -0
  10. data/lib/arf/io/limit_reader.rb +30 -0
  11. data/lib/arf/observer.rb +29 -0
  12. data/lib/arf/proto/array.rb +31 -0
  13. data/lib/arf/proto/boolean.rb +15 -0
  14. data/lib/arf/proto/bytes.rb +35 -0
  15. data/lib/arf/proto/decoder.rb +31 -0
  16. data/lib/arf/proto/encoder.rb +71 -0
  17. data/lib/arf/proto/float.rb +48 -0
  18. data/lib/arf/proto/map.rb +49 -0
  19. data/lib/arf/proto/registry.rb +27 -0
  20. data/lib/arf/proto/scalar.rb +55 -0
  21. data/lib/arf/proto/string.rb +36 -0
  22. data/lib/arf/proto/struct.rb +84 -0
  23. data/lib/arf/proto/types.rb +67 -0
  24. data/lib/arf/proto/union.rb +25 -0
  25. data/lib/arf/proto.rb +17 -0
  26. data/lib/arf/reactor.rb +270 -0
  27. data/lib/arf/rpc/base_message.rb +119 -0
  28. data/lib/arf/rpc/client_base.rb +110 -0
  29. data/lib/arf/rpc/end_stream.rb +9 -0
  30. data/lib/arf/rpc/enum.rb +51 -0
  31. data/lib/arf/rpc/message_kind.rb +27 -0
  32. data/lib/arf/rpc/metadata.rb +120 -0
  33. data/lib/arf/rpc/method_meta.rb +42 -0
  34. data/lib/arf/rpc/request.rb +39 -0
  35. data/lib/arf/rpc/responder.rb +186 -0
  36. data/lib/arf/rpc/response.rb +46 -0
  37. data/lib/arf/rpc/service_base.rb +137 -0
  38. data/lib/arf/rpc/start_stream.rb +9 -0
  39. data/lib/arf/rpc/stream_error.rb +23 -0
  40. data/lib/arf/rpc/stream_item.rb +16 -0
  41. data/lib/arf/rpc/stream_metadata.rb +16 -0
  42. data/lib/arf/rpc/struct.rb +255 -0
  43. data/lib/arf/rpc.rb +19 -0
  44. data/lib/arf/server.rb +123 -0
  45. data/lib/arf/status.rb +75 -0
  46. data/lib/arf/types/array_type.rb +24 -0
  47. data/lib/arf/types/base_type.rb +14 -0
  48. data/lib/arf/types/coercion.rb +36 -0
  49. data/lib/arf/types/in_out_stream.rb +28 -0
  50. data/lib/arf/types/input_stream.rb +8 -0
  51. data/lib/arf/types/map_type.rb +32 -0
  52. data/lib/arf/types/mixin.rb +29 -0
  53. data/lib/arf/types/output_stream.rb +8 -0
  54. data/lib/arf/types/streamer.rb +21 -0
  55. data/lib/arf/types.rb +69 -0
  56. data/lib/arf/version.rb +5 -0
  57. data/lib/arf/wire/base_connection.rb +177 -0
  58. data/lib/arf/wire/client.rb +101 -0
  59. data/lib/arf/wire/encoding.rb +49 -0
  60. data/lib/arf/wire/error_code.rb +35 -0
  61. data/lib/arf/wire/errors.rb +88 -0
  62. data/lib/arf/wire/frame.rb +111 -0
  63. data/lib/arf/wire/frame_kind.rb +23 -0
  64. data/lib/arf/wire/frame_reader.rb +104 -0
  65. data/lib/arf/wire/frames/base_frame.rb +108 -0
  66. data/lib/arf/wire/frames/configuration_frame.rb +33 -0
  67. data/lib/arf/wire/frames/data_frame.rb +21 -0
  68. data/lib/arf/wire/frames/go_away_frame.rb +29 -0
  69. data/lib/arf/wire/frames/make_stream_frame.rb +15 -0
  70. data/lib/arf/wire/frames/ping_frame.rb +18 -0
  71. data/lib/arf/wire/frames/reset_stream_frame.rb +19 -0
  72. data/lib/arf/wire/frames.rb +9 -0
  73. data/lib/arf/wire/server/peer.rb +85 -0
  74. data/lib/arf/wire/server.rb +63 -0
  75. data/lib/arf/wire/stream/state.rb +69 -0
  76. data/lib/arf/wire/stream.rb +128 -0
  77. data/lib/arf/wire/wait_signal.rb +24 -0
  78. data/lib/arf/wire.rb +14 -0
  79. data/lib/arf.rb +46 -0
  80. metadata +195 -0
data/lib/arf/status.rb ADDED
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module Status
5
+ OK = 0
6
+ CANCELLED = 1
7
+ UNKNOWN = 2
8
+ INVALID_ARGUMENT = 3
9
+ DEADLINE_EXCEEDED = 4
10
+ NOT_FOUND = 5
11
+ ALREADY_EXISTS = 6
12
+ PERMISSION_DENIED = 7
13
+ RESOURCE_EXHAUSTED = 8
14
+ FAILED_PRECONDITION = 9
15
+ ABORTED = 10
16
+ OUT_OF_RANGE = 11
17
+ UNIMPLEMENTED = 12
18
+ INTERNAL_ERROR = 13
19
+ UNAVAILABLE = 14
20
+ DATA_LOSS = 15
21
+ UNAUTHENTICATED = 16
22
+
23
+ TO_SYMBOL = {
24
+ OK => :ok,
25
+ CANCELLED => :cancelled,
26
+ UNKNOWN => :unknown,
27
+ INVALID_ARGUMENT => :invalid_argument,
28
+ DEADLINE_EXCEEDED => :deadline_exceeded,
29
+ NOT_FOUND => :not_found,
30
+ ALREADY_EXISTS => :already_exists,
31
+ PERMISSION_DENIED => :permission_denied,
32
+ RESOURCE_EXHAUSTED => :resource_exhausted,
33
+ FAILED_PRECONDITION => :failed_precondition,
34
+ ABORTED => :aborted,
35
+ OUT_OF_RANGE => :out_of_range,
36
+ UNIMPLEMENTED => :unimplemented,
37
+ INTERNAL_ERROR => :internal_error,
38
+ UNAVAILABLE => :unavailable,
39
+ DATA_LOSS => :data_loss,
40
+ UNAUTHENTICATED => :unauthenticated
41
+ }.freeze
42
+
43
+ FROM_SYMBOL = TO_SYMBOL.to_a.to_h(&:reverse)
44
+
45
+ STATUS_TEXT = {
46
+ ok: "OK",
47
+ cancelled: "Cancelled",
48
+ unknown: "Unknown",
49
+ invalid_argument: "Invalid Argument",
50
+ deadline_exceeded: "Deadline Exceeded",
51
+ not_found: "Not Found",
52
+ already_exists: "Already Exists",
53
+ permission_denied: "Permission Denied",
54
+ resource_exhausted: "Resource Exhausted",
55
+ failed_precondition: "Failed Precondition",
56
+ aborted: "Aborted",
57
+ out_of_range: "Out of Range",
58
+ unimplemented: "Unimplemented",
59
+ internal_error: "Internal Error",
60
+ unavailable: "Unavailable",
61
+ data_loss: "Data Loss",
62
+ unauthenticated: "Unauthenticated"
63
+ }.freeze
64
+
65
+ class BadStatus < Arf::Error
66
+ attr_reader :code
67
+
68
+ def initialize(code, msg = nil)
69
+ @code = code.is_a?(Symbol) ? code : TO_SYMBOL[code]
70
+ @msg = msg || STATUS_TEXT[@code] || "Unknown status"
71
+ super(@msg)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module Types
5
+ class ArrayType < BaseType
6
+ attr_reader :type
7
+
8
+ def initialize(v)
9
+ super()
10
+ @type = v
11
+ end
12
+
13
+ def self.[](v) = new(v)
14
+
15
+ def resolved_type
16
+ @resolved_type ||= resolve_type(@type)
17
+ end
18
+
19
+ def bind(to) = tap { @bind = to }
20
+
21
+ def coerce(val) = val.map { coerce_value(_1, resolved_type) }
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module Types
5
+ class BaseType
6
+ def bind(to) = tap { @bind = to }
7
+ def coerce_value(*) = Arf::Types.coerce_value(*)
8
+
9
+ def resolve_type(type)
10
+ type.is_a?(String) ? @bind.find_type(type) : type
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module Types
5
+ def self.coerce_value(value, type)
6
+ if Types::INTEGERS.include?(type)
7
+ value.to_i
8
+ elsif Types::FLOATS.include?(type)
9
+ value.to_f
10
+ elsif type == :string
11
+ value.to_s
12
+ elsif type == :bytes
13
+ case value
14
+ when StringIO then value.string
15
+ when String then value
16
+ when Array then value.pack("C*")
17
+ else
18
+ raise ArgumentError, "Invalid type for bytes: #{i.class.name}"
19
+ end
20
+ elsif type == :bool
21
+ !!value
22
+ elsif type.is_a?(Class)
23
+ case value
24
+ when Hash then type.new(**value)
25
+ when type then value
26
+ else
27
+ raise ArgumentError,
28
+ "Cannot initialize #{type} with #{value.inspect} (#{value.class}). " \
29
+ "You may want to use an instance of #{type}, or a Hash"
30
+ end
31
+ elsif type.is_a?(Arf::Types::ArrayType) || type.is_a?(Arf::Types::MapType)
32
+ type.coerce(value)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module Types
5
+ class InOutStream
6
+ def self.[](input, output) = new(input, output)
7
+
8
+ attr_accessor :input, :output
9
+
10
+ def initialize(input, output)
11
+ @input = input
12
+ @output = output
13
+ end
14
+
15
+ def resolved_input(resolver)
16
+ return input if input.is_a? Symbol
17
+
18
+ @resolved_input ||= resolver.find_type(input)
19
+ end
20
+
21
+ def resolved_output(resolver)
22
+ return output if output.is_a? Symbol
23
+
24
+ @resolved_output ||= resolver.find_type(output)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module Types
5
+ class InputStream < Streamer
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module Types
5
+ class MapType < BaseType
6
+ attr_reader :key, :value
7
+
8
+ def initialize(k, v)
9
+ super()
10
+ @key = k
11
+ @value = v
12
+ end
13
+
14
+ def self.[](k, v) = new(k, v)
15
+
16
+ def resolved_types
17
+ return @resolved_types if @resolved_types
18
+
19
+ key = resolve_type(@key)
20
+ value = resolve_type(@value)
21
+ @resolved_types = [key, value]
22
+ end
23
+
24
+ def coerce(val)
25
+ key_type, value_type = resolved_types
26
+ val
27
+ .transform_keys { coerce_value(_1, key_type) }
28
+ .transform_values { coerce_value(_1, value_type) }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module Types
5
+ module Mixin
6
+ MapType = ::Arf::Types::MapType
7
+ ArrayType = ::Arf::Types::ArrayType
8
+ InputStream = ::Arf::Types::InputStream
9
+ OutputStream = ::Arf::Types::OutputStream
10
+ InOutStream = ::Arf::Types::InOutStream
11
+ Streamer = ::Arf::Types::Streamer
12
+
13
+ module ClassMethods
14
+ def find_type(named)
15
+ components = named.split("::")
16
+ if components.first.empty?
17
+ # Look from the root, starting at Object
18
+ ::Arf::Types.lookup_type(Object, components[1...], direction: :down)
19
+ else
20
+ # Look from local scope upwards
21
+ ::Arf::Types.lookup_type(self, components, direction: :up)
22
+ end
23
+ end
24
+ end
25
+
26
+ def self.included(base) = base.extend(ClassMethods)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module Types
5
+ class OutputStream < Streamer
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module Types
5
+ class Streamer
6
+ def self.[](type) = new(type)
7
+
8
+ attr_accessor :type
9
+
10
+ def initialize(type)
11
+ @type = type
12
+ end
13
+
14
+ def resolved_type(resolver)
15
+ return type if type.is_a? Symbol
16
+
17
+ @resolved_type ||= resolver.find_type(type)
18
+ end
19
+ end
20
+ end
21
+ end
data/lib/arf/types.rb ADDED
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "types/coercion"
4
+ require_relative "types/base_type"
5
+ require_relative "types/array_type"
6
+ require_relative "types/map_type"
7
+ require_relative "types/streamer"
8
+ require_relative "types/in_out_stream"
9
+ require_relative "types/input_stream"
10
+ require_relative "types/output_stream"
11
+ require_relative "types/mixin"
12
+
13
+ module Arf
14
+ module Types
15
+ INTEGERS = %i[uint8 uint16 uint32 uint64 int8 int16 int32 int64].freeze
16
+ FLOATS = %i[float32 float64].freeze
17
+ OTHERS = %i[bool string bytes].freeze
18
+
19
+ def self.try_const_get(mod, name)
20
+ mod.const_get(name)
21
+ rescue NameError
22
+ nil
23
+ end
24
+
25
+ def self.lookup_type(mod, path, direction:)
26
+ key = [mod, path.join("::"), direction]
27
+ @lookup_cache ||= {}
28
+ return @lookup_cache[key] if @lookup_cache.key? key
29
+
30
+ v = if direction == :up
31
+ lookup_type_up(mod, path)
32
+ else
33
+ lookup_type_down(mod, path)
34
+ end
35
+
36
+ @lookup_cache[key] = v
37
+ end
38
+
39
+ def self.lookup_type_up(mod, path)
40
+ return mod if path.empty?
41
+
42
+ key = path.first
43
+ v = try_const_get(mod, key)
44
+ if v
45
+ lookup_type_down(v, path.tap(&:shift))
46
+ elsif mod == Object
47
+ nil
48
+ else
49
+ parent = mod.name.split("::").tap(&:pop).last
50
+ return nil if parent.nil?
51
+
52
+ v = try_const_get(mod, parent)
53
+ return nil if v.nil?
54
+
55
+ lookup_type_down(v, path)
56
+ end
57
+ end
58
+
59
+ def self.lookup_type_down(mod, path)
60
+ return mod if path.empty?
61
+
62
+ key = path.first
63
+ v = try_const_get(mod, key)
64
+ return nil unless v
65
+
66
+ lookup_type_down(v, path.tap(&:shift))
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module Wire
5
+ class BaseConnection
6
+ def initialize
7
+ @_write_lock = Monitor.new
8
+ @_write_buffer = Queue.new
9
+ @_write_head = nil
10
+ @compression = :none
11
+ @last_stream_id = 0
12
+ @reader = FrameReader.new
13
+ @configured = false
14
+ @pong_signal = WaitSignal.new
15
+
16
+ @streams = {}
17
+ @streams_monitor = Monitor.new
18
+
19
+ @configuration_ready_lock = Mutex.new
20
+ @configuration_ready_signal = Thread::ConditionVariable.new
21
+ @registered = false
22
+ end
23
+
24
+ def registered? = @registered
25
+
26
+ def registered!
27
+ @registered = true
28
+ @_write_lock.synchronize do
29
+ Arf::Reactor.client_writes_pending(self) unless @_write_buffer.empty?
30
+ end
31
+ end
32
+
33
+ def wait_configuration
34
+ return if @configured
35
+
36
+ loop do
37
+ @configuration_ready_lock.synchronize do
38
+ @configuration_ready_signal.wait(@configuration_ready_lock)
39
+ return if @configured
40
+ end
41
+ end
42
+ end
43
+
44
+ def recv(data)
45
+ data.each_byte do |b|
46
+ v = @reader.feed(b)
47
+ next unless v
48
+
49
+ handle_frame(v)
50
+ end
51
+ end
52
+
53
+ def handle_frame(raw_frame)
54
+ frame = raw_frame.specialize(@compression)
55
+ case frame
56
+ when ConfigurationFrame
57
+ handle_configuration(frame)
58
+ when PingFrame
59
+ handle_ping(frame)
60
+ when GoAwayFrame
61
+ handle_go_away(frame)
62
+ when MakeStreamFrame
63
+ handle_make_stream(frame)
64
+ when ResetStreamFrame
65
+ handle_reset_stream(frame)
66
+ when DataFrame
67
+ handle_data(frame)
68
+ else
69
+ protocol_error!
70
+ end
71
+ rescue UnknownFrameKindError, InvalidFrameLengthError
72
+ protocol_error!
73
+ end
74
+
75
+ def cancel_streams(code)
76
+ @streams_monitor.synchronize do
77
+ @streams.each_value do |v|
78
+ v.reset(code)
79
+ rescue ClosedStreamError, StreamResetError
80
+ next
81
+ end
82
+ end
83
+ end
84
+
85
+ def ping
86
+ wait_configuration
87
+ @streams_monitor.synchronize do
88
+ dispatch_frame(PingFrame) do |p|
89
+ p.payload = SecureRandom.bytes(8)
90
+ end
91
+ end
92
+ end
93
+
94
+ def wait_pong = @pong_signal.wait
95
+
96
+ def fetch_stream(id) = @streams_monitor.synchronize { @streams[id] }
97
+
98
+ def arf_close_after_writing?
99
+ @arf_close_after_writing || false
100
+ end
101
+
102
+ def close_connection = Reactor.detach_client(self)
103
+
104
+ def close
105
+ close_connection
106
+ end
107
+
108
+ def close_connection_after_writing
109
+ @arf_close_after_writing = true
110
+ end
111
+
112
+ def send_data(data)
113
+ @_write_lock.synchronize do
114
+ @_write_buffer << data
115
+ end
116
+ Reactor.client_writes_pending(self) if registered?
117
+ data.bytesize
118
+ end
119
+
120
+ def handle_ping(fr)
121
+ return protocol_error! unless @configured
122
+
123
+ if fr.ack?
124
+ @pong_signal.broadcast
125
+ return
126
+ end
127
+
128
+ dispatch_frame(PingFrame) do |p|
129
+ p.ack!
130
+ p.payload = fr.payload
131
+ end
132
+ end
133
+
134
+ def dispatch_frame(type, terminate: false, &)
135
+ dispatch(type.new(&))
136
+ close_connection_after_writing if terminate
137
+ end
138
+
139
+ def dispatch(frame)
140
+ send_data(frame.to_frame.bytes(@compression))
141
+ end
142
+
143
+ def protocol_error! = go_away!(ERROR_CODE_PROTOCOL_ERROR, terminate: true)
144
+
145
+ def go_away!(code, extra_data: nil, terminate: false)
146
+ dispatch_frame(GoAwayFrame, terminate:) do |g|
147
+ g.last_stream_id = @last_stream_id
148
+ g.error_code = code
149
+ g.additional_data = extra_data if extra_data
150
+ end
151
+ end
152
+
153
+ def flush_write_buffer(io)
154
+ @_write_lock.synchronize do
155
+ loop do
156
+ if @_write_head.nil?
157
+ return true if @_write_buffer.empty?
158
+
159
+ @_write_head = @_write_buffer.pop
160
+ end
161
+
162
+ written = io.write_nonblock(@_write_head, exception: false)
163
+ case written
164
+ when :wait_writable
165
+ return false
166
+ when @_write_head.bytesize
167
+ @_write_head = nil
168
+ else
169
+ @_write_head = @_write_head.byteslice(written, @_write_head.bytesize)
170
+ return false
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module Wire
5
+ class Client < BaseConnection
6
+ def post_init
7
+ dispatch_frame(ConfigurationFrame) do |fr|
8
+ compression = Arf.config.client_compression
9
+ case compression
10
+ when :brotli
11
+ fr.compression_brotli!
12
+ when :gzip
13
+ fr.compression_gzip!
14
+ else
15
+ Arf.config.client_compression = :none
16
+ end
17
+
18
+ @compression = Arf.config.client_compression
19
+ end
20
+ end
21
+
22
+ def handle_ping(fr)
23
+ return protocol_error! unless @configured
24
+
25
+ if fr.ack?
26
+ @pong_signal.broadcast
27
+ return
28
+ end
29
+
30
+ dispatch_frame(PingFrame) do |p|
31
+ p.ack!
32
+ p.payload = fr.payload
33
+ end
34
+ end
35
+
36
+ def handle_go_away(fr)
37
+ return protocol_error! unless @configured
38
+
39
+ # server intends to disconnect
40
+ cancel_streams(fr.error_code)
41
+ close_connection_after_writing
42
+ end
43
+
44
+ def handle_reset_stream(fr)
45
+ return protocol_error! unless @configured
46
+
47
+ str = fetch_stream(fr.stream_id)
48
+ return protocol_error! unless str
49
+
50
+ str.handle_reset_stream(fr)
51
+ end
52
+
53
+ def handle_configuration(fr)
54
+ return protocol_error! if !fr.ack? || @configured
55
+
56
+ @configured = true
57
+ @configuration_ready_lock.synchronize do
58
+ @configuration_ready_signal.broadcast
59
+ end
60
+ end
61
+
62
+ def handle_data(fr)
63
+ return protocol_error! unless @configured
64
+
65
+ str = fetch_stream(fr.stream_id)
66
+ return reset_stream(fr.stream_id, ERROR_CODE_PROTOCOL_ERROR) unless str
67
+
68
+ str.handle_data(fr)
69
+ end
70
+
71
+ def new_stream
72
+ wait_configuration
73
+ id = nil
74
+ @streams_monitor.synchronize do
75
+ @last_stream_id += 1
76
+ id = @last_stream_id
77
+ @streams[id] = Stream.new(id, self)
78
+ dispatch_frame(MakeStreamFrame) do |fr|
79
+ fr.stream_id = id
80
+ end
81
+ end
82
+ @streams[id]
83
+ end
84
+
85
+ def terminate(reason)
86
+ dispatch_frame(GoAwayFrame) do |g|
87
+ g.error_code = reason
88
+ g.last_stream_id = @last_stream_id
89
+ end
90
+ close_connection_after_writing
91
+ end
92
+
93
+ def close
94
+ wait_configuration
95
+ @streams_monitor.synchronize do
96
+ close_connection
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module Wire
5
+ MAX_PAYLOAD = 65_535
6
+
7
+ def self.encode_uint16(v) = [v].pack("S>")
8
+ def self.encode_uint32(v) = [v].pack("L>")
9
+ def self.decode_uint16(io) = io.read(2).unpack1("S>")
10
+ def self.decode_uint32(io) = io.read(4).unpack1("L>")
11
+
12
+ def self.data_frames_from_buffer(id, buf, end_stream: false)
13
+ buf = case buf
14
+ when StringIO then buf.string
15
+ when String then buf
16
+ else buf.to_s
17
+ end
18
+
19
+ len = buf.length
20
+ if len <= MAX_PAYLOAD
21
+ return [
22
+ DataFrame.new do |fr|
23
+ fr.stream_id = id
24
+ fr.end_data!
25
+ fr.end_stream! if end_stream
26
+ fr.payload = buf
27
+ end
28
+ ]
29
+ end
30
+
31
+ frames = []
32
+ written = 0
33
+ loop do
34
+ to_write = [len - written, MAX_PAYLOAD].min
35
+ end_data = (len - written - to_write).zero?
36
+ frames << DataFrame.new do |fr|
37
+ fr.stream_id = id
38
+ fr.end_data! if end_data
39
+ fr.end_stream! if end_stream
40
+ fr.payload = buf[written...written + to_write]
41
+ end
42
+ written += to_write
43
+ break if end_data
44
+ end
45
+
46
+ frames
47
+ end
48
+ end
49
+ end