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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8268851fe0bf2d0252c65f9a42b58a5e1335f5d5a5bdff839ff3961a1e530e69
4
+ data.tar.gz: 1ba718db7a6a8372c1cb813437c1bd17e6925456eca5c3709d15b48f8a7567d0
5
+ SHA512:
6
+ metadata.gz: fd37152c07e7c8f78c57e5f4dcc7530f18e2d61fc615978992f0fae6c98e2ddd65ac544974da2c153394f724e774f78c3367ca0046b3a387b01e9d2134ec2d73
7
+ data.tar.gz: ff4bc35c94c938c3c0a9b904ec33fbabad4428bd418d922d6f7e12876f8ce1262b5070e9bbde81a1140a9185aceda39dbe1569fed154398120e2a5d55d5cbac4
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.3.5
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Vito Sartori
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ class Configuration
5
+ TLS_OPTS = {
6
+ private_key_file: :tls_private_key_file,
7
+ private_key: :tls_private_key,
8
+ private_key_pass: :tls_private_key_pass,
9
+ cert_chain_file: :tls_cert_chain_file,
10
+ cert: :tls_cert,
11
+ verify_peer: :tls_verify_peer,
12
+ sni_hostname: :tls_sni_hostname,
13
+ cipher_list: :tls_cipher_list,
14
+ ssl_version: :tls_ssl_version,
15
+ ecdh_curve: :tls_ecdh_curve,
16
+ dhparam: :tls_dhparam,
17
+ fail_if_no_peer_cert: :tls_fail_if_no_peer_cert
18
+ }.freeze
19
+
20
+ attr_accessor :bind_address, :bind_port,
21
+ :enable_tls, :tls_private_key_file, :tls_private_key,
22
+ :tls_private_key_pass, :tls_cert_chain_file, :tls_cert,
23
+ :tls_verify_peer, :tls_sni_hostname, :tls_cipher_list,
24
+ :tls_ssl_version, :tls_ecdh_curve, :tls_dhparam,
25
+ :tls_fail_if_no_peer_cert,
26
+ :client_compression
27
+ attr_writer :logger
28
+
29
+ # Determines the log verbosity level. Valid options are:
30
+ # - :debug
31
+ # - :info (default)
32
+ # - :warn
33
+ # - :fatal
34
+ # - :error
35
+ attr_reader :log_level
36
+
37
+ def initialize
38
+ @bind_address = "127.0.0.1"
39
+ @bind_port = 2730
40
+ @log_level = :info
41
+ end
42
+
43
+ def tls_configuration
44
+ return @tls_configuration unless @tls_configuration.nil?
45
+
46
+ @tls_configuration = TLS_OPTS
47
+ .map { |k, v| [k, send(v)] }
48
+ .compact
49
+ .to_h
50
+ end
51
+
52
+ def enable_tls? = enable_tls
53
+
54
+ def logger
55
+ @logger ||= Logrb.new($stderr, level: @log_level)
56
+ end
57
+
58
+ def log_level=(level)
59
+ @log_level = level
60
+ logger.level = level
61
+ end
62
+
63
+ def self.instance
64
+ @instance ||= new
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ class Context
5
+ attr_accessor :request_id, :log, :stream, :request,
6
+ :has_recv_stream, :recv_stream_error, :recv_stream_started,
7
+ :has_send_stream, :send_stream_error, :send_stream_started, :send_stream_type, :send_stream_finished,
8
+ :has_sent_response, :response, :error
9
+
10
+ def initialize(req_id, log, stream)
11
+ @request_id = req_id
12
+ @log = log
13
+ @stream = stream
14
+
15
+ @request = nil
16
+ @error = nil
17
+
18
+ @has_recv_stream = false
19
+ @recv_stream_error = nil
20
+ @recv_stream_started_lock = Monitor.new
21
+ @recv_stream_started = false
22
+
23
+ @has_send_stream = false
24
+ @send_stream_error = nil
25
+ @send_stream_started = false
26
+ @send_stream_started_lock = Monitor.new
27
+ @send_stream_finished = false
28
+ @send_stream_type = nil
29
+
30
+ @has_sent_response = false
31
+ @response = Arf::RPC::Response.new
32
+ end
33
+
34
+ def prepare
35
+ @has_recv_stream = @request.streaming
36
+ @response.metadata.set("arf-request-id", @request_id)
37
+ end
38
+
39
+ def recv
40
+ raise RPC::NoStreamError, true unless has_recv_stream
41
+ raise @error if @error
42
+ raise @recv_stream_error if @recv_stream_error
43
+
44
+ loop do
45
+ v = _read_stream_item
46
+ return v if v
47
+ end
48
+ end
49
+
50
+ def stream_send(val)
51
+ raise RPC::NoStreamError, false unless @has_send_stream
52
+ raise @error if @error
53
+ raise @send_stream_error if @send_stream_error
54
+
55
+ unless has_sent_response
56
+ # Arf::RPC::Service makes sure this method can be called now. If it
57
+ # allowed calling without a response being sent, it is acceptable to
58
+ # push a response as-is, as the client does not expect parameters as
59
+ # response other than the stream being sent.
60
+ @log.debug("Pushing synthetic response for method without response values")
61
+ @response.status = :ok
62
+ @response.streaming = true
63
+ @response.params = []
64
+ send_response
65
+ end
66
+
67
+ begin
68
+ unless @send_stream_started
69
+ @send_stream_started_lock.synchronize do
70
+ next if @send_stream_started
71
+
72
+ @log.debug("Pushing StartStream frame")
73
+ @stream.write_data(RPC::BaseMessage.encode(RPC::StartStream.new))
74
+ @send_stream_started = true
75
+ end
76
+ end
77
+
78
+ @log.debug("Pushing StreamItem frame")
79
+ @stream.write_data(RPC::BaseMessage.encode(RPC::StreamItem.new(value: val)))
80
+ rescue StandardError => e
81
+ @send_stream_error = e
82
+ raise
83
+ end
84
+ end
85
+
86
+ def send_stream_metadata
87
+ @stream.write_data(RPC::BaseMessage.encode(RPC::StreamMetadata.new(metadata: @response.metadata)))
88
+ end
89
+
90
+ def end_send
91
+ return if @send_stream_finished
92
+ raise RPC::NoStreamError, false unless @has_send_stream
93
+ raise @error if @error
94
+ raise @send_stream_error if @send_stream_error
95
+
96
+ @send_stream_finished = true
97
+
98
+ begin
99
+ @stream.write_data(RPC::BaseMessage.encode(RPC::EndStream.new), end_stream: true)
100
+ rescue StandardError => e
101
+ @log.error("Failed running #end_send", e)
102
+ @error = e
103
+ raise
104
+ end
105
+ end
106
+
107
+ def send_response(end_stream: false)
108
+ @log.debug("Sending response", response: @response)
109
+
110
+ @has_sent_response = true
111
+ @send_stream_started = false
112
+
113
+ @response.status ||= :ok
114
+ @log.debug("Start stream write data")
115
+ @stream.write_data(RPC::BaseMessage.encode(@response), end_stream:)
116
+ @log.debug("Send response done")
117
+ rescue Exception => e
118
+ @log.error("Failed sending response", e)
119
+ @error = e
120
+ raise
121
+ end
122
+
123
+ private
124
+
125
+ def _read_stream_item
126
+ begin
127
+ unless @recv_stream_started
128
+ @recv_stream_started_lock.synchronize do
129
+ next if @recv_stream_started
130
+
131
+ msg = RPC::BaseMessage.initialize_from(@stream.read_blocking)
132
+ if msg.is_a?(RPC::StartStream)
133
+ @recv_stream_started = true
134
+ else
135
+ @error = RPC::StreamFailureError.new("Received unexpected message kind waiting for StartStream")
136
+ raise @error
137
+ end
138
+ end
139
+ end
140
+
141
+ msg = RPC::BaseMessage.initialize_from(@stream.read_blocking)
142
+ rescue StandardError => e
143
+ @log.error("Failed starting recv_stream", e)
144
+ @recv_stream_error = e
145
+ raise
146
+ end
147
+
148
+ case msg
149
+ when RPC::StreamItem
150
+ msg.value
151
+
152
+ when RPC::EndStream
153
+ @recv_stream_error = RPC::StreamEndError.new
154
+ raise @recv_stream_error
155
+
156
+ when RPC::StreamError
157
+ @recv_stream_error = msg
158
+ raise @recv_stream_error
159
+
160
+ when RPC::StreamMetadata
161
+ @request.metadata.replace!(msg.metadata)
162
+ nil
163
+
164
+ else
165
+ @error = RPC::StreamFailureError.new("Received unexpected message kind during stream")
166
+ raise @error
167
+ end
168
+ end
169
+ end
170
+ end
data/lib/arf/errors.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ class Error < StandardError; end
5
+ class UnknownTypeError < Error; end
6
+ class InvalidEncodingTypeError < Error; end
7
+ class UnsupportedNestedUnionError < Error; end
8
+ class DecodeFailedError < Error; end
9
+ class UnknownMeessageError < Error; end
10
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module IO
5
+ class Buffer
6
+ def initialize
7
+ @buf = StringIO.new
8
+ end
9
+
10
+ def write(v) = tap { write_raw([v].pack("C*")) }
11
+ def write_raw(v) = tap { @buf.write(v) }
12
+ def reset = @buf.tap(&:rewind).truncate(0)
13
+ def length = @buf.length
14
+ def rewind = @buf.rewind
15
+ def read(*) = @buf.read(*)
16
+ def string = @buf.tap(&:rewind).string
17
+
18
+ def extract
19
+ buf = @buf
20
+ @buf = StringIO.new
21
+ buf
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module IO
5
+ class BaseCompressor
6
+ def self.compress(value)
7
+ return nil if value.nil?
8
+
9
+ value = value.string if value.is_a? StringIO
10
+ value = do_compress(value)
11
+ value.is_a?(StringIO) ? value : StringIO.new(value)
12
+ end
13
+
14
+ def self.decompress(value)
15
+ return nil if value.nil?
16
+
17
+ value = value.string if value.is_a? StringIO
18
+ value = do_decompress(value)
19
+ value.is_a?(StringIO) ? value : StringIO.new(value)
20
+ end
21
+ end
22
+
23
+ class NoneCompressor
24
+ def self.compress(value) = value
25
+ def self.decompress(value) = value
26
+ end
27
+
28
+ class GzipCompressor < BaseCompressor
29
+ def self.do_compress(value) = Zlib::Deflate.deflate(value)
30
+ def self.do_decompress(value) = Zlib::Inflate.inflate(value)
31
+ end
32
+
33
+ class BrotliCompressor < BaseCompressor
34
+ def self.do_compress(value) = Brotli.deflate(value)
35
+ def self.do_decompress(value) = Brotli.inflate(value)
36
+ end
37
+
38
+ COMPRESSOR = {
39
+ none: NoneCompressor,
40
+ gzip: GzipCompressor,
41
+ brotli: BrotliCompressor
42
+ }.freeze
43
+ end
44
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module IO
5
+ class LimitReader
6
+ def initialize(io, size)
7
+ @io = io
8
+ @left = size
9
+ end
10
+
11
+ def readpartial(size)
12
+ size = normalize_size(size)
13
+ @io.readpartial(size).tap { @left -= _1.length }
14
+ end
15
+
16
+ def read(size)
17
+ size = normalize_size(size)
18
+ @io.read(size).tap { @left -= _1.length }
19
+ end
20
+
21
+ private
22
+
23
+ def normalize_size(size)
24
+ raise EOFError if @left.zero?
25
+
26
+ [@left, size].min
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ class Observer
5
+ def initialize
6
+ @handler = nil
7
+ @primed = false
8
+ @mutex = Monitor.new
9
+ end
10
+
11
+ def attach_handler(handler)
12
+ @handler = handler
13
+ end
14
+
15
+ def prime
16
+ @primed = true
17
+ end
18
+
19
+ def modify
20
+ @mutex.synchronize do
21
+ yield
22
+ if @primed
23
+ @primed = false
24
+ Arf::Reactor.post { @handler.observer_changed(self) }
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module Proto
5
+ ARRAY_EMPTY_MASK = 0x01 << 4
6
+
7
+ def self.encode_array(v)
8
+ b = IO::Buffer.new
9
+ t = TYPE_ARRAY
10
+ if v.empty?
11
+ t |= ARRAY_EMPTY_MASK
12
+ return b.write(t).string if v.empty?
13
+ end
14
+
15
+ b.write(t).write_raw(encode_uint64(v.length))
16
+ v.each { b.write_raw(encode(_1)) }
17
+ b.string
18
+ end
19
+
20
+ def self.decode_array(header, io)
21
+ return [] if header.anybits?(ARRAY_EMPTY_MASK)
22
+
23
+ len = decode_uint64(io)
24
+ arr = []
25
+ len.times do
26
+ arr << decode(io)
27
+ end
28
+ arr
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module Proto
5
+ BOOL_FLAG_MASK = 0x01 << 4
6
+
7
+ def self.encode_boolean(b)
8
+ v = TYPE_BOOLEAN
9
+ v |= BOOL_FLAG_MASK if b
10
+ [v].pack("C*")
11
+ end
12
+
13
+ def self.decode_boolean(header, _io) = header.allbits?(BOOL_FLAG_MASK)
14
+ end
15
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module Proto
5
+ BYTES_EMPTY_MASK = 0x01 << 4
6
+
7
+ def self.encode_bytes(b)
8
+ v = TYPE_BYTES
9
+
10
+ if b.empty?
11
+ v |= BYTES_EMPTY_MASK
12
+ return [v].pack("C*")
13
+ end
14
+
15
+ IO::Buffer.new
16
+ .write(v)
17
+ .write_raw(encode_uint64(b.length))
18
+ .write_raw(b)
19
+ .string
20
+ end
21
+
22
+ def self.decode_bytes(header, io)
23
+ return "" if header.anybits?(BYTES_EMPTY_MASK)
24
+
25
+ size = decode_uint64(io)
26
+ data = StringIO.new
27
+ until size.zero?
28
+ read = io.readpartial(size)
29
+ data.write(read)
30
+ size -= read.length
31
+ end
32
+ data.string
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module Proto
5
+ def self.decode(io)
6
+ type, header = read_type(io)
7
+ case type
8
+ when TYPE_VOID
9
+ nil
10
+ when TYPE_SCALAR
11
+ decode_scalar(header, io)
12
+ when TYPE_BOOLEAN
13
+ decode_boolean(header, io)
14
+ when TYPE_FLOAT
15
+ decode_float(header, io)
16
+ when TYPE_STRING
17
+ decode_string(header, io)
18
+ when TYPE_BYTES
19
+ decode_bytes(header, io)
20
+ when TYPE_ARRAY
21
+ decode_array(header, io)
22
+ when TYPE_MAP
23
+ decode_map(header, io)
24
+ when TYPE_STRUCT
25
+ decode_struct(header, io)
26
+ when TYPE_UNION
27
+ decode_union(header, io)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module Proto
5
+ def self.encode(value)
6
+ case value
7
+ when NilClass
8
+ [TYPE_VOID].pack("C*")
9
+ when String, Symbol
10
+ encode_string(value.to_s)
11
+ when TrueClass, FalseClass
12
+ encode_boolean(value)
13
+ when Float
14
+ if value.between?(FLOAT32_MIN_VALUE, FLOAT32_MAX_VALUE)
15
+ encode_float32(value)
16
+ else
17
+ encode_float64(value)
18
+ end
19
+ when Integer
20
+ encode_scalar(value, signed: value.negative?)
21
+ when Array
22
+ encode_array(value)
23
+ when Hash
24
+ encode_map(value)
25
+ else
26
+ unless value.class.ancestors.include? Arf::RPC::Struct
27
+ raise InvalidEncodingTypeError, "Unable to encode value of type #{value.class.name}"
28
+ end
29
+
30
+ encode_struct(value)
31
+
32
+ end
33
+ end
34
+
35
+ def self.encode_as(value, type)
36
+ return [TYPE_VOID].pack("C*") if value.nil?
37
+
38
+ case type
39
+ when :uint8, :uint16, :uint32, :uint64
40
+ encode_scalar(value, signed: false)
41
+ when :int8, :int16, :int32, :int64
42
+ encode_scalar(value, signed: true)
43
+ when :float32
44
+ encode_float32(value)
45
+ when :float64
46
+ encode_float64(value)
47
+ when :bool
48
+ encode_boolean(value)
49
+ when :string
50
+ encode_string(value)
51
+ when :bytes
52
+ encode_bytes(value)
53
+ else
54
+ if type.is_a?(Arf::Types::MapType)
55
+ encode_map(value)
56
+ elsif type.is_a?(Arf::Types::ArrayType)
57
+ encode_array(value)
58
+ elsif type.is_a?(String) && value.class.ancestors.include?(Arf::RPC::Struct)
59
+ encode_struct(value)
60
+ elsif type.is_a?(Class) && type.ancestors.include?(Arf::RPC::Struct)
61
+ encode_struct(value)
62
+ elsif type.is_a?(Class) && type.ancestors.include?(Arf::RPC::Enum)
63
+ encode_scalar(type.to_i(value), signed: false)
64
+ else
65
+ raise InvalidEncodingTypeError,
66
+ "Unable to encode value of type #{value.class.name} (reported type is #{type.inspect})"
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arf
4
+ module Proto
5
+ FLOAT64_MASK = 0x01 << 4
6
+ FLOAT_EMPTY_MASK = 0x01 << 5
7
+ FLOAT32_MIN_VALUE = -3.4028235e38
8
+ FLOAT32_MAX_VALUE = 3.4028235e38
9
+
10
+ def self.encode_float32(value)
11
+ t = TYPE_FLOAT
12
+ if value.zero?
13
+ t |= FLOAT_EMPTY_MASK
14
+ return [t].pack("C*")
15
+ end
16
+
17
+ [
18
+ [t].pack("C*"),
19
+ [value].pack("g").unpack("N").pack("L>")
20
+ ].join
21
+ end
22
+
23
+ def self.encode_float64(value)
24
+ t = TYPE_FLOAT | FLOAT64_MASK
25
+ if value.zero?
26
+ t |= FLOAT_EMPTY_MASK
27
+ return [t].pack("C*")
28
+ end
29
+
30
+ [
31
+ [t].pack("C*"),
32
+ [value].pack("G").unpack("Q>").pack("Q>")
33
+ ].join
34
+ end
35
+
36
+ def self.decode_float(header, io)
37
+ return 0.0 if header.anybits?(FLOAT_EMPTY_MASK)
38
+
39
+ bits = header.nobits?(FLOAT64_MASK) ? 32 : 64
40
+ data = io.read(bits / 8)
41
+ if bits == 32
42
+ data.unpack("L>").pack("L>").unpack1("g")
43
+ else
44
+ data.unpack("Q>").pack("Q>").unpack1("G")
45
+ end
46
+ end
47
+ end
48
+ end