twilic 3.0.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.
- checksums.yaml +7 -0
- data/.editorconfig +18 -0
- data/.gitattributes +1 -0
- data/.gitignore +9 -0
- data/.markdownlint.jsonc +22 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +53 -0
- data/LICENSE +21 -0
- data/README.md +119 -0
- data/Rakefile +12 -0
- data/docs/CHANGELOG.md +31 -0
- data/docs/CONTRIBUTING.md +51 -0
- data/docs/SPEC-TEST-TRACEABILITY.md +87 -0
- data/lib/twilic/core/api.rb +30 -0
- data/lib/twilic/core/codec.rb +766 -0
- data/lib/twilic/core/dictionary.rb +236 -0
- data/lib/twilic/core/errors.rb +87 -0
- data/lib/twilic/core/interop_fixtures.rb +340 -0
- data/lib/twilic/core/model.rb +506 -0
- data/lib/twilic/core/protocol.rb +2044 -0
- data/lib/twilic/core/protocol_helpers.rb +512 -0
- data/lib/twilic/core/session.rb +461 -0
- data/lib/twilic/core/v2.rb +387 -0
- data/lib/twilic/core/wire.rb +158 -0
- data/lib/twilic/version.rb +5 -0
- data/lib/twilic.rb +147 -0
- data/package.json +14 -0
- data/pnpm-lock.yaml +723 -0
- data/twilic.gemspec +32 -0
- metadata +118 -0
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "twilic/core/errors"
|
|
4
|
+
require "twilic/core/model"
|
|
5
|
+
require "twilic/core/wire"
|
|
6
|
+
|
|
7
|
+
module Twilic
|
|
8
|
+
module Core
|
|
9
|
+
module V2
|
|
10
|
+
NULL_TAG = 0xC0
|
|
11
|
+
FALSE_TAG = 0xC1
|
|
12
|
+
TRUE_TAG = 0xC2
|
|
13
|
+
F64_TAG = 0xC3
|
|
14
|
+
U8_TAG = 0xC4
|
|
15
|
+
U16_TAG = 0xC5
|
|
16
|
+
U32_TAG = 0xC6
|
|
17
|
+
U64_TAG = 0xC7
|
|
18
|
+
I8_TAG = 0xC8
|
|
19
|
+
I16_TAG = 0xC9
|
|
20
|
+
I32_TAG = 0xCA
|
|
21
|
+
I64_TAG = 0xCB
|
|
22
|
+
BIN8_TAG = 0xCC
|
|
23
|
+
BIN16_TAG = 0xCD
|
|
24
|
+
BIN32_TAG = 0xCE
|
|
25
|
+
STR8_TAG = 0xCF
|
|
26
|
+
STR16_TAG = 0xD0
|
|
27
|
+
STR32_TAG = 0xD1
|
|
28
|
+
ARRAY16_TAG = 0xD2
|
|
29
|
+
ARRAY32_TAG = 0xD3
|
|
30
|
+
MAP16_TAG = 0xD4
|
|
31
|
+
MAP32_TAG = 0xD5
|
|
32
|
+
SHAPE_DEF_TAG = 0xD6
|
|
33
|
+
KEY_REF_TAG = 0xD8
|
|
34
|
+
STR_REF_TAG = 0xD9
|
|
35
|
+
|
|
36
|
+
class EncodeState
|
|
37
|
+
attr_accessor :key_ids, :str_ids, :shape_ids, :next_key_id, :next_str_id, :next_shape_id
|
|
38
|
+
|
|
39
|
+
def initialize
|
|
40
|
+
@key_ids = {}
|
|
41
|
+
@str_ids = {}
|
|
42
|
+
@shape_ids = {}
|
|
43
|
+
@next_key_id = 0
|
|
44
|
+
@next_str_id = 0
|
|
45
|
+
@next_shape_id = 0
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class DecodeState
|
|
50
|
+
attr_accessor :keys, :strings, :shapes
|
|
51
|
+
|
|
52
|
+
def initialize
|
|
53
|
+
@keys = []
|
|
54
|
+
@strings = []
|
|
55
|
+
@shapes = []
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
module_function
|
|
60
|
+
|
|
61
|
+
def encode_v2(value)
|
|
62
|
+
out = +""
|
|
63
|
+
state = EncodeState.new
|
|
64
|
+
encode_v2_value(value, out, state)
|
|
65
|
+
out
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def decode_v2(bytes)
|
|
69
|
+
reader = Wire::Reader.new(bytes)
|
|
70
|
+
state = DecodeState.new
|
|
71
|
+
value = decode_v2_value(reader, state)
|
|
72
|
+
raise Errors.invalid_data("trailing bytes in v2 decode") unless reader.eof?
|
|
73
|
+
|
|
74
|
+
value
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def encode_v2_value(value, out, state)
|
|
78
|
+
case value.kind
|
|
79
|
+
when Model::ValueKind::NULL
|
|
80
|
+
out << NULL_TAG.chr
|
|
81
|
+
when Model::ValueKind::BOOL
|
|
82
|
+
out << (value.bool ? TRUE_TAG : FALSE_TAG).chr
|
|
83
|
+
when Model::ValueKind::I64
|
|
84
|
+
encode_v2_i64(value.i64, out)
|
|
85
|
+
when Model::ValueKind::U64
|
|
86
|
+
encode_v2_u64(value.u64, out)
|
|
87
|
+
when Model::ValueKind::F64
|
|
88
|
+
out << F64_TAG.chr
|
|
89
|
+
Wire.append_f64_le(out, value.f64)
|
|
90
|
+
when Model::ValueKind::STRING
|
|
91
|
+
if state.str_ids.key?(value.str)
|
|
92
|
+
out << STR_REF_TAG.chr
|
|
93
|
+
Wire.encode_varuint(state.str_ids[value.str], out)
|
|
94
|
+
else
|
|
95
|
+
encode_v2_string_literal(value.str, out)
|
|
96
|
+
state.str_ids[value.str] = state.next_str_id
|
|
97
|
+
state.next_str_id += 1
|
|
98
|
+
end
|
|
99
|
+
when Model::ValueKind::BINARY
|
|
100
|
+
encode_v2_binary(value.bin, out)
|
|
101
|
+
when Model::ValueKind::ARRAY
|
|
102
|
+
encode_v2_array(value.arr, out, state)
|
|
103
|
+
when Model::ValueKind::MAP
|
|
104
|
+
encode_v2_map(value.map, out, state)
|
|
105
|
+
else
|
|
106
|
+
raise Errors.invalid_data("unsupported value kind")
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def encode_v2_array(values, out, state)
|
|
111
|
+
shape_keys = detect_shape_keys(values)
|
|
112
|
+
if shape_keys
|
|
113
|
+
sk = shape_keys.join("\0")
|
|
114
|
+
shape_id = state.shape_ids[sk]
|
|
115
|
+
unless shape_id
|
|
116
|
+
shape_id = state.next_shape_id
|
|
117
|
+
state.next_shape_id += 1
|
|
118
|
+
state.shape_ids[sk] = shape_id
|
|
119
|
+
end
|
|
120
|
+
write_v2_array_header(values.length, out)
|
|
121
|
+
out << SHAPE_DEF_TAG.chr
|
|
122
|
+
Wire.encode_varuint(shape_id, out)
|
|
123
|
+
Wire.encode_varuint(shape_keys.length, out)
|
|
124
|
+
shape_keys.each { |key| encode_v2_key(key, out, state) }
|
|
125
|
+
values.each do |value|
|
|
126
|
+
raise Errors.invalid_data("shape array row must be map") unless value.kind == Model::ValueKind::MAP
|
|
127
|
+
|
|
128
|
+
value.map.each { |field| encode_v2_value(field.value, out, state) }
|
|
129
|
+
end
|
|
130
|
+
return
|
|
131
|
+
end
|
|
132
|
+
write_v2_array_header(values.length, out)
|
|
133
|
+
values.each { |value| encode_v2_value(value, out, state) }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def encode_v2_map(entries, out, state)
|
|
137
|
+
write_v2_map_header(entries.length, out)
|
|
138
|
+
entries.each do |entry|
|
|
139
|
+
encode_v2_key(entry.key, out, state)
|
|
140
|
+
encode_v2_value(entry.value, out, state)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def encode_v2_key(key, out, state)
|
|
145
|
+
if state.key_ids.key?(key)
|
|
146
|
+
out << KEY_REF_TAG.chr
|
|
147
|
+
Wire.encode_varuint(state.key_ids[key], out)
|
|
148
|
+
return
|
|
149
|
+
end
|
|
150
|
+
encode_v2_string_literal(key, out)
|
|
151
|
+
state.key_ids[key] = state.next_key_id
|
|
152
|
+
state.next_key_id += 1
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def encode_v2_string_literal(value, out)
|
|
156
|
+
bytes = value.b
|
|
157
|
+
if bytes.bytesize <= 31
|
|
158
|
+
out << (0x80 | bytes.bytesize).chr
|
|
159
|
+
elsif bytes.bytesize <= 0xFF
|
|
160
|
+
out << STR8_TAG.chr << bytes.bytesize.chr
|
|
161
|
+
elsif bytes.bytesize <= 0xFFFF
|
|
162
|
+
out << STR16_TAG.chr << [bytes.bytesize].pack("v")
|
|
163
|
+
else
|
|
164
|
+
out << STR32_TAG.chr << [bytes.bytesize].pack("V")
|
|
165
|
+
end
|
|
166
|
+
out << bytes
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def encode_v2_binary(value, out)
|
|
170
|
+
if value.bytesize <= 0xFF
|
|
171
|
+
out << BIN8_TAG.chr << value.bytesize.chr
|
|
172
|
+
elsif value.bytesize <= 0xFFFF
|
|
173
|
+
out << BIN16_TAG.chr << [value.bytesize].pack("v")
|
|
174
|
+
else
|
|
175
|
+
out << BIN32_TAG.chr << [value.bytesize].pack("V")
|
|
176
|
+
end
|
|
177
|
+
out << value
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def encode_v2_u64(value, out)
|
|
181
|
+
if value <= 127
|
|
182
|
+
out << value.chr
|
|
183
|
+
elsif value <= 0xFF
|
|
184
|
+
out << U8_TAG.chr << value.chr
|
|
185
|
+
elsif value <= 0xFFFF
|
|
186
|
+
out << U16_TAG.chr << [value].pack("v")
|
|
187
|
+
elsif value <= 0xFFFFFFFF
|
|
188
|
+
out << U32_TAG.chr << [value].pack("V")
|
|
189
|
+
else
|
|
190
|
+
out << U64_TAG.chr
|
|
191
|
+
Wire.append_u64_le(out, value)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def encode_v2_i64(value, out)
|
|
196
|
+
if value >= -32 && value <= -1
|
|
197
|
+
out << (value & 0xFF).chr
|
|
198
|
+
elsif value >= 0 && value <= 127
|
|
199
|
+
out << value.chr
|
|
200
|
+
elsif value >= -128 && value <= 127
|
|
201
|
+
out << I8_TAG.chr << [value].pack("c")
|
|
202
|
+
elsif value >= -32_768 && value <= 32_767
|
|
203
|
+
out << I16_TAG.chr << [value].pack("s<")
|
|
204
|
+
elsif value >= -2_147_483_648 && value <= 2_147_483_647
|
|
205
|
+
out << I32_TAG.chr << [value].pack("l<")
|
|
206
|
+
else
|
|
207
|
+
out << I64_TAG.chr
|
|
208
|
+
Wire.append_u64_le(out, value & 0xFFFFFFFFFFFFFFFF)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def write_v2_array_header(length, out)
|
|
213
|
+
if length <= 15
|
|
214
|
+
out << (0xA0 | length).chr
|
|
215
|
+
elsif length <= 0xFFFF
|
|
216
|
+
out << ARRAY16_TAG.chr << [length].pack("v")
|
|
217
|
+
else
|
|
218
|
+
out << ARRAY32_TAG.chr << [length].pack("V")
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def write_v2_map_header(length, out)
|
|
223
|
+
if length <= 15
|
|
224
|
+
out << (0xB0 | length).chr
|
|
225
|
+
elsif length <= 0xFFFF
|
|
226
|
+
out << MAP16_TAG.chr << [length].pack("v")
|
|
227
|
+
else
|
|
228
|
+
out << MAP32_TAG.chr << [length].pack("V")
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def detect_shape_keys(values)
|
|
233
|
+
return nil if values.length < 2
|
|
234
|
+
return nil unless values[0].kind == Model::ValueKind::MAP && !values[0].map.empty?
|
|
235
|
+
|
|
236
|
+
keys = values[0].map.map(&:key)
|
|
237
|
+
values[1..].each do |value|
|
|
238
|
+
return nil unless value.kind == Model::ValueKind::MAP && value.map.length == keys.length
|
|
239
|
+
|
|
240
|
+
value.map.each_with_index { |e, i| return nil unless e.key == keys[i] }
|
|
241
|
+
end
|
|
242
|
+
keys
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def decode_v2_value(reader, state)
|
|
246
|
+
tag = reader.read_u8
|
|
247
|
+
decode_v2_value_from_tag(reader, state, tag)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def decode_v2_value_from_tag(reader, state, tag)
|
|
251
|
+
case tag
|
|
252
|
+
when 0..0x7F
|
|
253
|
+
Model.u64_value(tag)
|
|
254
|
+
when 0x80..0x9F
|
|
255
|
+
length = tag & 0x1F
|
|
256
|
+
s = reader.read_exact(length).force_encoding(Encoding::UTF_8)
|
|
257
|
+
state.strings << s
|
|
258
|
+
Model.string_value(s)
|
|
259
|
+
when 0xA0..0xAF
|
|
260
|
+
decode_v2_array_body(reader, state, tag & 0x0F)
|
|
261
|
+
when 0xB0..0xBF
|
|
262
|
+
decode_v2_map_body(reader, state, tag & 0x0F)
|
|
263
|
+
when 0xE0..0xFF
|
|
264
|
+
Model.i64_value(tag - 256)
|
|
265
|
+
when NULL_TAG
|
|
266
|
+
Model.null_value
|
|
267
|
+
when FALSE_TAG
|
|
268
|
+
Model.bool_value(false)
|
|
269
|
+
when TRUE_TAG
|
|
270
|
+
Model.bool_value(true)
|
|
271
|
+
when F64_TAG
|
|
272
|
+
Model.f64_value(reader.read_f64_le)
|
|
273
|
+
when U8_TAG
|
|
274
|
+
Model.u64_value(reader.read_u8)
|
|
275
|
+
when U16_TAG
|
|
276
|
+
Model.u64_value(reader.read_exact(2).unpack1("v"))
|
|
277
|
+
when U32_TAG
|
|
278
|
+
Model.u64_value(reader.read_exact(4).unpack1("V"))
|
|
279
|
+
when U64_TAG
|
|
280
|
+
Model.u64_value(reader.read_u64_le)
|
|
281
|
+
when I8_TAG
|
|
282
|
+
Model.i64_value(reader.read_exact(1).unpack1("c"))
|
|
283
|
+
when I16_TAG
|
|
284
|
+
Model.i64_value(reader.read_exact(2).unpack1("s<"))
|
|
285
|
+
when I32_TAG
|
|
286
|
+
Model.i64_value(reader.read_exact(4).unpack1("l<"))
|
|
287
|
+
when I64_TAG
|
|
288
|
+
Model.i64_value(reader.read_exact(8).unpack1("q<"))
|
|
289
|
+
when BIN8_TAG
|
|
290
|
+
Model.binary_value(reader.read_exact(reader.read_u8))
|
|
291
|
+
when BIN16_TAG
|
|
292
|
+
Model.binary_value(reader.read_exact(reader.read_exact(2).unpack1("v")))
|
|
293
|
+
when BIN32_TAG
|
|
294
|
+
Model.binary_value(reader.read_exact(reader.read_exact(4).unpack1("V")))
|
|
295
|
+
when STR8_TAG, STR16_TAG, STR32_TAG
|
|
296
|
+
decode_v2_string_tag(reader, state, tag)
|
|
297
|
+
when ARRAY16_TAG
|
|
298
|
+
decode_v2_array_body(reader, state, reader.read_exact(2).unpack1("v"))
|
|
299
|
+
when ARRAY32_TAG
|
|
300
|
+
decode_v2_array_body(reader, state, reader.read_exact(4).unpack1("V"))
|
|
301
|
+
when MAP16_TAG
|
|
302
|
+
decode_v2_map_body(reader, state, reader.read_exact(2).unpack1("v"))
|
|
303
|
+
when MAP32_TAG
|
|
304
|
+
decode_v2_map_body(reader, state, reader.read_exact(4).unpack1("V"))
|
|
305
|
+
when STR_REF_TAG
|
|
306
|
+
id = reader.read_varuint
|
|
307
|
+
raise Errors.invalid_data("unknown str_ref id") if id >= state.strings.length
|
|
308
|
+
|
|
309
|
+
Model.string_value(state.strings[id])
|
|
310
|
+
else
|
|
311
|
+
raise Errors.invalid_tag(tag)
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def decode_v2_string_tag(reader, state, tag)
|
|
316
|
+
length = case tag
|
|
317
|
+
when STR8_TAG then reader.read_u8
|
|
318
|
+
when STR16_TAG then reader.read_exact(2).unpack1("v")
|
|
319
|
+
when STR32_TAG then reader.read_exact(4).unpack1("V")
|
|
320
|
+
else raise Errors.invalid_data("invalid string tag")
|
|
321
|
+
end
|
|
322
|
+
s = reader.read_exact(length).force_encoding(Encoding::UTF_8)
|
|
323
|
+
state.strings << s
|
|
324
|
+
Model.string_value(s)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def decode_v2_array_body(reader, state, length)
|
|
328
|
+
return Model.array_value([]) if length.zero?
|
|
329
|
+
|
|
330
|
+
first_tag = reader.read_u8
|
|
331
|
+
if first_tag == SHAPE_DEF_TAG
|
|
332
|
+
shape_id = reader.read_varuint
|
|
333
|
+
key_count = reader.read_varuint
|
|
334
|
+
keys = Array.new(key_count) { decode_v2_key(reader, state) }
|
|
335
|
+
while state.shapes.length <= shape_id
|
|
336
|
+
state.shapes << nil
|
|
337
|
+
end
|
|
338
|
+
state.shapes[shape_id] = keys
|
|
339
|
+
values = Array.new(length) do
|
|
340
|
+
row = keys.map do |key|
|
|
341
|
+
val = decode_v2_value(reader, state)
|
|
342
|
+
Model.entry(key, val)
|
|
343
|
+
end
|
|
344
|
+
Model.map_value(row)
|
|
345
|
+
end
|
|
346
|
+
return Model.array_value(values)
|
|
347
|
+
end
|
|
348
|
+
values = Array.new(length)
|
|
349
|
+
values[0] = decode_v2_value_from_tag(reader, state, first_tag)
|
|
350
|
+
(1...length).each { |i| values[i] = decode_v2_value(reader, state) }
|
|
351
|
+
Model.array_value(values)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def decode_v2_map_body(reader, state, length)
|
|
355
|
+
entries = Array.new(length) do
|
|
356
|
+
key = decode_v2_key(reader, state)
|
|
357
|
+
value = decode_v2_value(reader, state)
|
|
358
|
+
Model.entry(key, value)
|
|
359
|
+
end
|
|
360
|
+
Model.map_value(entries)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def decode_v2_key(reader, state)
|
|
364
|
+
tag = reader.read_u8
|
|
365
|
+
if tag == KEY_REF_TAG
|
|
366
|
+
id = reader.read_varuint
|
|
367
|
+
raise Errors.invalid_data("unknown key_ref id") if id >= state.keys.length
|
|
368
|
+
|
|
369
|
+
return state.keys[id]
|
|
370
|
+
end
|
|
371
|
+
if (0x80..0x9F).cover?(tag)
|
|
372
|
+
key = reader.read_exact(tag & 0x1F).force_encoding(Encoding::UTF_8)
|
|
373
|
+
state.keys << key
|
|
374
|
+
return key
|
|
375
|
+
end
|
|
376
|
+
if [STR8_TAG, STR16_TAG, STR32_TAG].include?(tag)
|
|
377
|
+
v = decode_v2_value_from_tag(reader, state, tag)
|
|
378
|
+
raise Errors.invalid_data("expected string key") unless v.kind == Model::ValueKind::STRING
|
|
379
|
+
|
|
380
|
+
state.keys << v.str
|
|
381
|
+
return v.str
|
|
382
|
+
end
|
|
383
|
+
raise Errors.invalid_data("map key must be key_ref or string")
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "twilic/core/errors"
|
|
4
|
+
|
|
5
|
+
module Twilic
|
|
6
|
+
module Core
|
|
7
|
+
module Wire
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def encode_varuint(value, out)
|
|
11
|
+
if value < 0x80
|
|
12
|
+
out << value.chr
|
|
13
|
+
return
|
|
14
|
+
end
|
|
15
|
+
loop do
|
|
16
|
+
b = value & 0x7F
|
|
17
|
+
value >>= 7
|
|
18
|
+
b |= 0x80 unless value.zero?
|
|
19
|
+
out << b.chr
|
|
20
|
+
break if value.zero?
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def encode_zigzag(value)
|
|
25
|
+
((value << 1) ^ (value >> 63)) & 0xFFFFFFFFFFFFFFFF
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def decode_zigzag(value)
|
|
29
|
+
((value >> 1) ^ (-(value & 1))) & 0xFFFFFFFFFFFFFFFF
|
|
30
|
+
v = (value >> 1) ^ (-(value & 1))
|
|
31
|
+
v >= 0x8000000000000000 ? v - 0x10000000000000000 : v
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def encode_bytes(bytes, out)
|
|
35
|
+
encode_varuint(bytes.bytesize, out)
|
|
36
|
+
out << bytes
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def encode_string(value, out)
|
|
40
|
+
encode_bytes(value.b, out)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def encode_bitmap(bits, out)
|
|
44
|
+
encode_varuint(bits.length, out)
|
|
45
|
+
current = 0
|
|
46
|
+
bits.each_with_index do |bit, i|
|
|
47
|
+
current |= (1 << (i % 8)) if bit
|
|
48
|
+
if (i % 8) == 7
|
|
49
|
+
out << current.chr
|
|
50
|
+
current = 0
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
out << current.chr unless bits.empty? || (bits.length % 8).zero?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class Reader
|
|
57
|
+
attr_reader :offset
|
|
58
|
+
|
|
59
|
+
def initialize(input)
|
|
60
|
+
@input = input.b
|
|
61
|
+
@offset = 0
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def position
|
|
65
|
+
@offset
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def eof?
|
|
69
|
+
@offset >= @input.bytesize
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def read_u8
|
|
73
|
+
raise Errors.unexpected_eof if @offset >= @input.bytesize
|
|
74
|
+
|
|
75
|
+
b = @input.getbyte(@offset)
|
|
76
|
+
@offset += 1
|
|
77
|
+
b
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def read_exact(n)
|
|
81
|
+
raise Errors.unexpected_eof if @offset + n > @input.bytesize
|
|
82
|
+
|
|
83
|
+
slice = @input.byteslice(@offset, n)
|
|
84
|
+
@offset += n
|
|
85
|
+
slice
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def read_varuint
|
|
89
|
+
shift = 0
|
|
90
|
+
result = 0
|
|
91
|
+
loop do
|
|
92
|
+
raise Errors.invalid_data("varuint too large") if shift >= 64
|
|
93
|
+
|
|
94
|
+
b = read_u8
|
|
95
|
+
result |= (b & 0x7F) << shift
|
|
96
|
+
return result if (b & 0x80).zero?
|
|
97
|
+
|
|
98
|
+
shift += 7
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def read_i64_zigzag
|
|
103
|
+
encoded = read_varuint
|
|
104
|
+
Wire.decode_zigzag(encoded)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def read_bytes
|
|
108
|
+
n = read_varuint
|
|
109
|
+
read_exact(n)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def read_string
|
|
113
|
+
n = read_varuint
|
|
114
|
+
bytes = read_exact(n)
|
|
115
|
+
raise Errors.utf8_error unless bytes.valid_encoding? && bytes.force_encoding(Encoding::UTF_8).valid_encoding?
|
|
116
|
+
|
|
117
|
+
bytes.force_encoding(Encoding::UTF_8)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def read_bitmap
|
|
121
|
+
bit_count = read_varuint
|
|
122
|
+
byte_count = (bit_count + 7) / 8
|
|
123
|
+
bytes = read_exact(byte_count)
|
|
124
|
+
bits = Array.new(bit_count)
|
|
125
|
+
bit_count.times do |i|
|
|
126
|
+
bits[i] = ((bytes.getbyte(i / 8) >> (i % 8)) & 1) == 1
|
|
127
|
+
end
|
|
128
|
+
bits
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def read_u64_le
|
|
132
|
+
b = read_exact(8)
|
|
133
|
+
b.unpack1("Q<")
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def read_f64_le
|
|
137
|
+
[read_u64_le].pack("Q<").unpack1("E")
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def read_u64_le(reader)
|
|
142
|
+
reader.read_u64_le
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def read_f64_le(reader)
|
|
146
|
+
reader.read_f64_le
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def append_u64_le(out, v)
|
|
150
|
+
out << [v].pack("Q<")
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def append_f64_le(out, v)
|
|
154
|
+
append_u64_le(out, [v].pack("E").unpack1("Q<"))
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
data/lib/twilic.rb
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "twilic/version"
|
|
4
|
+
require "twilic/core/errors"
|
|
5
|
+
require "twilic/core/model"
|
|
6
|
+
require "twilic/core/wire"
|
|
7
|
+
require "twilic/core/codec"
|
|
8
|
+
require "twilic/core/session"
|
|
9
|
+
require "twilic/core/dictionary"
|
|
10
|
+
require "twilic/core/v2"
|
|
11
|
+
require "twilic/core/protocol"
|
|
12
|
+
require "twilic/core/api"
|
|
13
|
+
|
|
14
|
+
module Twilic
|
|
15
|
+
# Types
|
|
16
|
+
MessageKind = Core::Model::MessageKind
|
|
17
|
+
ValueKind = Core::Model::ValueKind
|
|
18
|
+
Value = Core::Model::Value
|
|
19
|
+
MapEntry = Core::Model::MapEntry
|
|
20
|
+
MessageMapEntry = Core::Model::MessageMapEntry
|
|
21
|
+
KeyRef = Core::Model::KeyRef
|
|
22
|
+
StringMode = Core::Model::StringMode
|
|
23
|
+
StringValue = Core::Model::StringValue
|
|
24
|
+
ElementType = Core::Model::ElementType
|
|
25
|
+
VectorCodec = Core::Model::VectorCodec
|
|
26
|
+
TypedVectorData = Core::Model::TypedVectorData
|
|
27
|
+
TypedVector = Core::Model::TypedVector
|
|
28
|
+
SchemaField = Core::Model::SchemaField
|
|
29
|
+
Schema = Core::Model::Schema
|
|
30
|
+
NullStrategy = Core::Model::NullStrategy
|
|
31
|
+
Column = Core::Model::Column
|
|
32
|
+
ControlOpcode = Core::Model::ControlOpcode
|
|
33
|
+
ControlMessage = Core::Model::ControlMessage
|
|
34
|
+
RegisterShapeControl = Core::Model::RegisterShapeControl
|
|
35
|
+
PromoteEnumControl = Core::Model::PromoteEnumControl
|
|
36
|
+
PatchOpcode = Core::Model::PatchOpcode
|
|
37
|
+
BaseRef = Core::Model::BaseRef
|
|
38
|
+
PatchOperation = Core::Model::PatchOperation
|
|
39
|
+
ControlStreamCodec = Core::Model::ControlStreamCodec
|
|
40
|
+
Message = Core::Model::Message
|
|
41
|
+
ShapedObjectMessage = Core::Model::ShapedObjectMessage
|
|
42
|
+
SchemaObjectMessage = Core::Model::SchemaObjectMessage
|
|
43
|
+
RowBatchMessage = Core::Model::RowBatchMessage
|
|
44
|
+
ColumnBatchMessage = Core::Model::ColumnBatchMessage
|
|
45
|
+
ExtMessage = Core::Model::ExtMessage
|
|
46
|
+
StatePatchMessage = Core::Model::StatePatchMessage
|
|
47
|
+
TemplateBatchMessage = Core::Model::TemplateBatchMessage
|
|
48
|
+
ControlStreamMessage = Core::Model::ControlStreamMessage
|
|
49
|
+
BaseSnapshotMessage = Core::Model::BaseSnapshotMessage
|
|
50
|
+
TemplateDescriptor = Core::Model::TemplateDescriptor
|
|
51
|
+
TwilicError = Core::Errors::TwilicError
|
|
52
|
+
UnknownReferencePolicy = Core::Session::UnknownReferencePolicy
|
|
53
|
+
DictionaryFallback = Core::Session::DictionaryFallback
|
|
54
|
+
DictionaryProfile = Core::Session::DictionaryProfile
|
|
55
|
+
SessionOptions = Core::Session::SessionOptions
|
|
56
|
+
SessionState = Core::Session::MutableSessionState
|
|
57
|
+
TwilicCodec = Core::Protocol::TwilicCodec
|
|
58
|
+
SessionEncoder = Core::Protocol::SessionEncoder
|
|
59
|
+
|
|
60
|
+
# Error constants
|
|
61
|
+
ERR_UNEXPECTED_EOF = Core::Errors::UNEXPECTED_EOF
|
|
62
|
+
ERR_INVALID_KIND = Core::Errors::INVALID_KIND
|
|
63
|
+
ERR_INVALID_TAG = Core::Errors::INVALID_TAG
|
|
64
|
+
ERR_INVALID_DATA = Core::Errors::INVALID_DATA
|
|
65
|
+
ERR_UTF8 = Core::Errors::UTF8
|
|
66
|
+
ERR_UNKNOWN_REFERENCE = Core::Errors::UNKNOWN_REFERENCE
|
|
67
|
+
ERR_STATELESS_RETRY_REQUIRED = Core::Errors::STATELESS_RETRY_REQUIRED
|
|
68
|
+
|
|
69
|
+
class << self
|
|
70
|
+
def encode(value)
|
|
71
|
+
Core::API.encode(value)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def decode(bytes)
|
|
75
|
+
Core::API.decode(bytes)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def encode_with_schema(schema, value)
|
|
79
|
+
Core::API.encode_with_schema(schema, value)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def encode_batch(values)
|
|
83
|
+
Core::API.encode_batch(values)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def null
|
|
87
|
+
Core::Model.null_value
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def bool(b)
|
|
91
|
+
Core::Model.bool_value(b)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def i64(n)
|
|
95
|
+
Core::Model.i64_value(n)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def u64(n)
|
|
99
|
+
Core::Model.u64_value(n)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def f64(n)
|
|
103
|
+
Core::Model.f64_value(n)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def string(s)
|
|
107
|
+
Core::Model.string_value(s)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def binary(b)
|
|
111
|
+
Core::Model.binary_value(b)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def array(items)
|
|
115
|
+
Core::Model.array_value(items)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def map(**kwargs)
|
|
119
|
+
Core::Model.map_value(kwargs)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def equal(a, b)
|
|
123
|
+
Core::Model.equal(a, b)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def default_session_options
|
|
127
|
+
Core::Session::SessionOptions.default
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def new_twilic_codec
|
|
131
|
+
TwilicCodec.new
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def twilic_codec_with_options(options)
|
|
135
|
+
TwilicCodec.new(options)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def new_session_encoder(options = default_session_options)
|
|
139
|
+
SessionEncoder.new(options)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def reset_encode_shape_observation(codec, keys)
|
|
143
|
+
key = codec.shape_key(keys)
|
|
144
|
+
codec.state.encode_shape_observations.delete(key)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
data/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "twilic-ruby",
|
|
3
|
+
"private": true,
|
|
4
|
+
"packageManager": "pnpm@10.33.2",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"format": "prettier --write \"{README.md,docs/**/*.md,.github/**/*.md}\"",
|
|
7
|
+
"format:check": "prettier --check \"{README.md,docs/**/*.md,.github/**/*.md}\"",
|
|
8
|
+
"lint": "markdownlint-cli2 \"{README.md,docs/**/*.md}\""
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"markdownlint-cli2": "^0.22.1",
|
|
12
|
+
"prettier": "^3.8.3"
|
|
13
|
+
}
|
|
14
|
+
}
|