arf 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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