Module: Twilic::Core::V2

Defined in:
lib/twilic/core/v2.rb

Defined Under Namespace

Classes: DecodeState, EncodeState

Constant Summary collapse

NULL_TAG =
0xC0
FALSE_TAG =
0xC1
TRUE_TAG =
0xC2
F64_TAG =
0xC3
U8_TAG =
0xC4
U16_TAG =
0xC5
U32_TAG =
0xC6
U64_TAG =
0xC7
I8_TAG =
0xC8
I16_TAG =
0xC9
I32_TAG =
0xCA
I64_TAG =
0xCB
BIN8_TAG =
0xCC
BIN16_TAG =
0xCD
BIN32_TAG =
0xCE
STR8_TAG =
0xCF
STR16_TAG =
0xD0
STR32_TAG =
0xD1
ARRAY16_TAG =
0xD2
ARRAY32_TAG =
0xD3
MAP16_TAG =
0xD4
MAP32_TAG =
0xD5
SHAPE_DEF_TAG =
0xD6
KEY_REF_TAG =
0xD8
STR_REF_TAG =
0xD9

Class Method Summary collapse

Class Method Details

.decode_v2(bytes) ⇒ Object



68
69
70
71
72
73
74
75
# File 'lib/twilic/core/v2.rb', line 68

def decode_v2(bytes)
  reader = Wire::Reader.new(bytes)
  state = DecodeState.new
  value = decode_v2_value(reader, state)
  raise Errors.invalid_data("trailing bytes in v2 decode") unless reader.eof?

  value
end

.decode_v2_array_body(reader, state, length) ⇒ Object



327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/twilic/core/v2.rb', line 327

def decode_v2_array_body(reader, state, length)
  return Model.array_value([]) if length.zero?

  first_tag = reader.read_u8
  if first_tag == SHAPE_DEF_TAG
    shape_id = reader.read_varuint
    key_count = reader.read_varuint
    keys = Array.new(key_count) { decode_v2_key(reader, state) }
    while state.shapes.length <= shape_id
      state.shapes << nil
    end
    state.shapes[shape_id] = keys
    values = Array.new(length) do
      row = keys.map do |key|
        val = decode_v2_value(reader, state)
        Model.entry(key, val)
      end
      Model.map_value(row)
    end
    return Model.array_value(values)
  end
  values = Array.new(length)
  values[0] = decode_v2_value_from_tag(reader, state, first_tag)
  (1...length).each { |i| values[i] = decode_v2_value(reader, state) }
  Model.array_value(values)
end

.decode_v2_key(reader, state) ⇒ Object



363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/twilic/core/v2.rb', line 363

def decode_v2_key(reader, state)
  tag = reader.read_u8
  if tag == KEY_REF_TAG
    id = reader.read_varuint
    raise Errors.invalid_data("unknown key_ref id") if id >= state.keys.length

    return state.keys[id]
  end
  if (0x80..0x9F).cover?(tag)
    key = reader.read_exact(tag & 0x1F).force_encoding(Encoding::UTF_8)
    state.keys << key
    return key
  end
  if [STR8_TAG, STR16_TAG, STR32_TAG].include?(tag)
    v = decode_v2_value_from_tag(reader, state, tag)
    raise Errors.invalid_data("expected string key") unless v.kind == Model::ValueKind::STRING

    state.keys << v.str
    return v.str
  end
  raise Errors.invalid_data("map key must be key_ref or string")
end

.decode_v2_map_body(reader, state, length) ⇒ Object



354
355
356
357
358
359
360
361
# File 'lib/twilic/core/v2.rb', line 354

def decode_v2_map_body(reader, state, length)
  entries = Array.new(length) do
    key = decode_v2_key(reader, state)
    value = decode_v2_value(reader, state)
    Model.entry(key, value)
  end
  Model.map_value(entries)
end

.decode_v2_string_tag(reader, state, tag) ⇒ Object



315
316
317
318
319
320
321
322
323
324
325
# File 'lib/twilic/core/v2.rb', line 315

def decode_v2_string_tag(reader, state, tag)
  length = case tag
           when STR8_TAG then reader.read_u8
           when STR16_TAG then reader.read_exact(2).unpack1("v")
           when STR32_TAG then reader.read_exact(4).unpack1("V")
           else raise Errors.invalid_data("invalid string tag")
           end
  s = reader.read_exact(length).force_encoding(Encoding::UTF_8)
  state.strings << s
  Model.string_value(s)
end

.decode_v2_value(reader, state) ⇒ Object



245
246
247
248
# File 'lib/twilic/core/v2.rb', line 245

def decode_v2_value(reader, state)
  tag = reader.read_u8
  decode_v2_value_from_tag(reader, state, tag)
end

.decode_v2_value_from_tag(reader, state, tag) ⇒ Object



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/twilic/core/v2.rb', line 250

def decode_v2_value_from_tag(reader, state, tag)
  case tag
  when 0..0x7F
    Model.u64_value(tag)
  when 0x80..0x9F
    length = tag & 0x1F
    s = reader.read_exact(length).force_encoding(Encoding::UTF_8)
    state.strings << s
    Model.string_value(s)
  when 0xA0..0xAF
    decode_v2_array_body(reader, state, tag & 0x0F)
  when 0xB0..0xBF
    decode_v2_map_body(reader, state, tag & 0x0F)
  when 0xE0..0xFF
    Model.i64_value(tag - 256)
  when NULL_TAG
    Model.null_value
  when FALSE_TAG
    Model.bool_value(false)
  when TRUE_TAG
    Model.bool_value(true)
  when F64_TAG
    Model.f64_value(reader.read_f64_le)
  when U8_TAG
    Model.u64_value(reader.read_u8)
  when U16_TAG
    Model.u64_value(reader.read_exact(2).unpack1("v"))
  when U32_TAG
    Model.u64_value(reader.read_exact(4).unpack1("V"))
  when U64_TAG
    Model.u64_value(reader.read_u64_le)
  when I8_TAG
    Model.i64_value(reader.read_exact(1).unpack1("c"))
  when I16_TAG
    Model.i64_value(reader.read_exact(2).unpack1("s<"))
  when I32_TAG
    Model.i64_value(reader.read_exact(4).unpack1("l<"))
  when I64_TAG
    Model.i64_value(reader.read_exact(8).unpack1("q<"))
  when BIN8_TAG
    Model.binary_value(reader.read_exact(reader.read_u8))
  when BIN16_TAG
    Model.binary_value(reader.read_exact(reader.read_exact(2).unpack1("v")))
  when BIN32_TAG
    Model.binary_value(reader.read_exact(reader.read_exact(4).unpack1("V")))
  when STR8_TAG, STR16_TAG, STR32_TAG
    decode_v2_string_tag(reader, state, tag)
  when ARRAY16_TAG
    decode_v2_array_body(reader, state, reader.read_exact(2).unpack1("v"))
  when ARRAY32_TAG
    decode_v2_array_body(reader, state, reader.read_exact(4).unpack1("V"))
  when MAP16_TAG
    decode_v2_map_body(reader, state, reader.read_exact(2).unpack1("v"))
  when MAP32_TAG
    decode_v2_map_body(reader, state, reader.read_exact(4).unpack1("V"))
  when STR_REF_TAG
    id = reader.read_varuint
    raise Errors.invalid_data("unknown str_ref id") if id >= state.strings.length

    Model.string_value(state.strings[id])
  else
    raise Errors.invalid_tag(tag)
  end
end

.detect_shape_keys(values) ⇒ Object



232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/twilic/core/v2.rb', line 232

def detect_shape_keys(values)
  return nil if values.length < 2
  return nil unless values[0].kind == Model::ValueKind::MAP && !values[0].map.empty?

  keys = values[0].map.map(&:key)
  values[1..].each do |value|
    return nil unless value.kind == Model::ValueKind::MAP && value.map.length == keys.length

    value.map.each_with_index { |e, i| return nil unless e.key == keys[i] }
  end
  keys
end

.encode_v2(value) ⇒ Object



61
62
63
64
65
66
# File 'lib/twilic/core/v2.rb', line 61

def encode_v2(value)
  out = +""
  state = EncodeState.new
  encode_v2_value(value, out, state)
  out
end

.encode_v2_array(values, out, state) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/twilic/core/v2.rb', line 110

def encode_v2_array(values, out, state)
  shape_keys = detect_shape_keys(values)
  if shape_keys
    sk = shape_keys.join("\0")
    shape_id = state.shape_ids[sk]
    unless shape_id
      shape_id = state.next_shape_id
      state.next_shape_id += 1
      state.shape_ids[sk] = shape_id
    end
    write_v2_array_header(values.length, out)
    out << SHAPE_DEF_TAG.chr
    Wire.encode_varuint(shape_id, out)
    Wire.encode_varuint(shape_keys.length, out)
    shape_keys.each { |key| encode_v2_key(key, out, state) }
    values.each do |value|
      raise Errors.invalid_data("shape array row must be map") unless value.kind == Model::ValueKind::MAP

      value.map.each { |field| encode_v2_value(field.value, out, state) }
    end
    return
  end
  write_v2_array_header(values.length, out)
  values.each { |value| encode_v2_value(value, out, state) }
end

.encode_v2_binary(value, out) ⇒ Object



169
170
171
172
173
174
175
176
177
178
# File 'lib/twilic/core/v2.rb', line 169

def encode_v2_binary(value, out)
  if value.bytesize <= 0xFF
    out << BIN8_TAG.chr << value.bytesize.chr
  elsif value.bytesize <= 0xFFFF
    out << BIN16_TAG.chr << [value.bytesize].pack("v")
  else
    out << BIN32_TAG.chr << [value.bytesize].pack("V")
  end
  out << value
end

.encode_v2_i64(value, out) ⇒ Object



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/twilic/core/v2.rb', line 195

def encode_v2_i64(value, out)
  if value >= -32 && value <= -1
    out << (value & 0xFF).chr
  elsif value >= 0 && value <= 127
    out << value.chr
  elsif value >= -128 && value <= 127
    out << I8_TAG.chr << [value].pack("c")
  elsif value >= -32_768 && value <= 32_767
    out << I16_TAG.chr << [value].pack("s<")
  elsif value >= -2_147_483_648 && value <= 2_147_483_647
    out << I32_TAG.chr << [value].pack("l<")
  else
    out << I64_TAG.chr
    Wire.append_u64_le(out, value & 0xFFFFFFFFFFFFFFFF)
  end
end

.encode_v2_key(key, out, state) ⇒ Object



144
145
146
147
148
149
150
151
152
153
# File 'lib/twilic/core/v2.rb', line 144

def encode_v2_key(key, out, state)
  if state.key_ids.key?(key)
    out << KEY_REF_TAG.chr
    Wire.encode_varuint(state.key_ids[key], out)
    return
  end
  encode_v2_string_literal(key, out)
  state.key_ids[key] = state.next_key_id
  state.next_key_id += 1
end

.encode_v2_map(entries, out, state) ⇒ Object



136
137
138
139
140
141
142
# File 'lib/twilic/core/v2.rb', line 136

def encode_v2_map(entries, out, state)
  write_v2_map_header(entries.length, out)
  entries.each do |entry|
    encode_v2_key(entry.key, out, state)
    encode_v2_value(entry.value, out, state)
  end
end

.encode_v2_string_literal(value, out) ⇒ Object



155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/twilic/core/v2.rb', line 155

def encode_v2_string_literal(value, out)
  bytes = value.b
  if bytes.bytesize <= 31
    out << (0x80 | bytes.bytesize).chr
  elsif bytes.bytesize <= 0xFF
    out << STR8_TAG.chr << bytes.bytesize.chr
  elsif bytes.bytesize <= 0xFFFF
    out << STR16_TAG.chr << [bytes.bytesize].pack("v")
  else
    out << STR32_TAG.chr << [bytes.bytesize].pack("V")
  end
  out << bytes
end

.encode_v2_u64(value, out) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/twilic/core/v2.rb', line 180

def encode_v2_u64(value, out)
  if value <= 127
    out << value.chr
  elsif value <= 0xFF
    out << U8_TAG.chr << value.chr
  elsif value <= 0xFFFF
    out << U16_TAG.chr << [value].pack("v")
  elsif value <= 0xFFFFFFFF
    out << U32_TAG.chr << [value].pack("V")
  else
    out << U64_TAG.chr
    Wire.append_u64_le(out, value)
  end
end

.encode_v2_value(value, out, state) ⇒ Object



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
# File 'lib/twilic/core/v2.rb', line 77

def encode_v2_value(value, out, state)
  case value.kind
  when Model::ValueKind::NULL
    out << NULL_TAG.chr
  when Model::ValueKind::BOOL
    out << (value.bool ? TRUE_TAG : FALSE_TAG).chr
  when Model::ValueKind::I64
    encode_v2_i64(value.i64, out)
  when Model::ValueKind::U64
    encode_v2_u64(value.u64, out)
  when Model::ValueKind::F64
    out << F64_TAG.chr
    Wire.append_f64_le(out, value.f64)
  when Model::ValueKind::STRING
    if state.str_ids.key?(value.str)
      out << STR_REF_TAG.chr
      Wire.encode_varuint(state.str_ids[value.str], out)
    else
      encode_v2_string_literal(value.str, out)
      state.str_ids[value.str] = state.next_str_id
      state.next_str_id += 1
    end
  when Model::ValueKind::BINARY
    encode_v2_binary(value.bin, out)
  when Model::ValueKind::ARRAY
    encode_v2_array(value.arr, out, state)
  when Model::ValueKind::MAP
    encode_v2_map(value.map, out, state)
  else
    raise Errors.invalid_data("unsupported value kind")
  end
end

.write_v2_array_header(length, out) ⇒ Object



212
213
214
215
216
217
218
219
220
# File 'lib/twilic/core/v2.rb', line 212

def write_v2_array_header(length, out)
  if length <= 15
    out << (0xA0 | length).chr
  elsif length <= 0xFFFF
    out << ARRAY16_TAG.chr << [length].pack("v")
  else
    out << ARRAY32_TAG.chr << [length].pack("V")
  end
end

.write_v2_map_header(length, out) ⇒ Object



222
223
224
225
226
227
228
229
230
# File 'lib/twilic/core/v2.rb', line 222

def write_v2_map_header(length, out)
  if length <= 15
    out << (0xB0 | length).chr
  elsif length <= 0xFFFF
    out << MAP16_TAG.chr << [length].pack("v")
  else
    out << MAP32_TAG.chr << [length].pack("V")
  end
end