Erl message

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 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
109
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
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
314
315
316
317
318
319
320
321
322
323
324
325
326
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
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
use "lib:otp_pony_node_c"

use @opn_ei_message_new[Pointer[None]]()
use @opn_ei_message_destroy[None](message: Pointer[None] tag)
use @opn_ei_message_length[I32](message: Pointer[None] tag)
use @opn_ei_message_index[I32](message: Pointer[None] tag)
use @opn_ei_message_truncate[I32](message: Pointer[None] tag, index: I32)
use @opn_ei_message_beginning[I32](message: Pointer[None] tag)
use @opn_ei_message_type_at[I32](message: Pointer[None] tag, position: I32,
  type_tag: Pointer[I32] tag, size: Pointer[I32] tag)
use @opn_ei_message_skip_term[I32](message: Pointer[None] tag, position: I32,
  next: Pointer[I32] tag)
use @opn_ei_message_copy[I32](message: Pointer[None] tag, from: I32, to: I32,
  buffer: Pointer[U8] tag, capacity: USize)
use @opn_ei_message_append_raw[I32](message: Pointer[None] tag,
  bytes: Pointer[U8] tag, length: I32)

use @opn_ei_message_decode_atom[I32](message: Pointer[None] tag,
  index: Pointer[I32] tag, buffer: Pointer[U8] tag, capacity: USize)
use @opn_ei_message_decode_int[I32](message: Pointer[None] tag,
  index: Pointer[I32] tag, value: Pointer[I64] tag)
use @opn_ei_message_decode_double[I32](message: Pointer[None] tag,
  index: Pointer[I32] tag, value: Pointer[F64] tag)
use @opn_ei_message_decode_binary[I32](message: Pointer[None] tag,
  index: Pointer[I32] tag, buffer: Pointer[U8] tag, capacity: USize,
  length: Pointer[I64] tag)
use @opn_ei_message_decode_bitstring[I32](message: Pointer[None] tag,
  index: Pointer[I32] tag, buffer: Pointer[U8] tag, capacity: USize,
  length: Pointer[I64] tag, used_bits: Pointer[I32] tag)
use @opn_ei_message_decode_string[I32](message: Pointer[None] tag,
  index: Pointer[I32] tag, buffer: Pointer[U8] tag, capacity: USize)
use @opn_ei_message_decode_list_header[I32](message: Pointer[None] tag,
  index: Pointer[I32] tag, arity: Pointer[I32] tag)
use @opn_ei_message_decode_tuple_header[I32](message: Pointer[None] tag,
  index: Pointer[I32] tag, arity: Pointer[I32] tag)
use @opn_ei_message_decode_map_header[I32](message: Pointer[None] tag,
  index: Pointer[I32] tag, arity: Pointer[I32] tag)
use @opn_ei_message_decode_pid[I32](message: Pointer[None] tag,
  index: Pointer[I32] tag, node: Pointer[U8] tag, node_capacity: USize,
  num: Pointer[U32] tag, serial: Pointer[U32] tag, creation: Pointer[U32] tag)
use @opn_ei_message_decode_port[I32](message: Pointer[None] tag,
  index: Pointer[I32] tag, node: Pointer[U8] tag, node_capacity: USize,
  id: Pointer[U64] tag, creation: Pointer[U32] tag)
use @opn_ei_message_decode_ref[I32](message: Pointer[None] tag,
  index: Pointer[I32] tag, node: Pointer[U8] tag, node_capacity: USize,
  ids: Pointer[U32] tag, ids_capacity: I32, id_count: Pointer[I32] tag,
  creation: Pointer[U32] tag)

use @opn_ei_message_encode_atom[I32](message: Pointer[None] tag,
  what: Pointer[U8] tag)
use @opn_ei_message_encode_int[I32](message: Pointer[None] tag, value: I64)
use @opn_ei_message_encode_double[I32](message: Pointer[None] tag, value: F64)
use @opn_ei_message_encode_binary[I32](message: Pointer[None] tag,
  what: Pointer[U8] tag, length: I32)
use @opn_ei_message_encode_bitstring[I32](message: Pointer[None] tag,
  what: Pointer[U8] tag, length: I32, used_bits_in_last_byte: I32)
use @opn_ei_message_encode_string[I32](message: Pointer[None] tag,
  what: Pointer[U8] tag, length: I32)
use @opn_ei_message_encode_list_header[I32](message: Pointer[None] tag,
  arity: I32)
use @opn_ei_message_encode_empty_list[I32](message: Pointer[None] tag)
use @opn_ei_message_encode_tuple_header[I32](message: Pointer[None] tag,
  arity: I32)
use @opn_ei_message_encode_map_header[I32](message: Pointer[None] tag,
  arity: I32)
use @opn_ei_message_encode_pid[I32](message: Pointer[None] tag,
  node: Pointer[U8] tag, num: U32, serial: U32, creation: U32)
use @opn_ei_message_encode_port[I32](message: Pointer[None] tag,
  node: Pointer[U8] tag, id: U64, creation: U32)
use @opn_ei_message_encode_ref[I32](message: Pointer[None] tag,
  node: Pointer[U8] tag, ids: Pointer[U32] tag, id_count: I32, creation: U32)

class EMessage
  """
  An encoded Erlang message: the bytes of one term in the external format.

  `EMessage.begin()` starts an empty message to encode into, `encode(term)`
  writes a whole term into it, and the `decode_*` functions read terms out at a
  position.  For the usual "bytes in, term out" direction see
  `ErlangTermDecoder`.
  """

  var _message: Pointer[None]
  let beginning: I32

  new begin() =>
    _message = @opn_ei_message_new()
    beginning = _beginning_of(_message)

  new from_cpointer(message': Pointer[None]) =>
    """Takes ownership of a message received from the C layer."""
    _message = message'
    beginning = _beginning_of(_message)

  fun ref valid(): Bool =>
    not _message.is_null()

  fun ref length(): USize =>
    if valid() then @opn_ei_message_length(_message).usize() else 0 end

  fun ref write_position(): I32 =>
    """Where the next term would start."""
    if valid() then @opn_ei_message_index(_message) else 0 end

  fun ref truncate(position: I32): Bool =>
    """Forgets everything written after `position`."""
    if not valid() then return false end
    @opn_ei_message_truncate(_message, position) == 0

  fun ref cpointer(): Pointer[None] =>
    _message

  fun ref type_at(position: I32): (I32, I32) =>
    """The type tag and payload size of the term at `position`."""
    if not valid() then return (TermType.none(), 0) end

    var type_tag: I32 = 0
    var size: I32 = 0
    let result = @opn_ei_message_type_at(_message, position, addressof type_tag,
      addressof size)
    if result != 0 then return (TermType.none(), 0) end

    (type_tag, size)

  fun ref skip_term(position: I32): I32 =>
    """The position just past the term at `position`, -1 for an invalid term."""
    if not valid() then return -1 end

    var next: I32 = 0
    let result = @opn_ei_message_skip_term(_message, position, addressof next)
    if result != 0 then return -1 end

    next

  fun ref copy_bytes(from: I32, to: I32): (String | None) =>
    """The encoded bytes of a term, which is how unmodelled terms are kept."""
    if not valid() or (to <= from) then return None end

    let size = to - from
    let buffer = Strings.buffer(size.usize())
    let copied = @opn_ei_message_copy(_message, from, to, buffer.cpointer(),
      buffer.size())
    if copied != size then return None end

    Strings.from_bytes(buffer, size.usize())

  fun ref append_raw(encoded: String): I32 =>
    """Appends bytes that are already in the external format."""
    if not valid() then return -1 end

    @opn_ei_message_append_raw(_message, encoded.cpointer(),
      encoded.size().i32())

  fun ref decode_atom(position: I32): (String | None, I32) =>
    (let type_tag, let size) = type_at(position)
    if not TermType.is_atom(type_tag) then return (None, position) end
    if (size < 0) or (size.usize() > length()) then return (None, position) end

    let buffer = _buffer(size + 1)
    var index: I32 = position
    let result = @opn_ei_message_decode_atom(_message, addressof index,
      buffer.cpointer(), buffer.size())
    if result != 0 then return (None, position) end

    (Strings.null_trimmed(buffer), index)

  fun ref decode_int(position: I32): (I64 | None, I32) =>
    (let type_tag, _) = type_at(position)
    if not ((type_tag == TermType.small_integer())
      or (type_tag == TermType.integer())
      or (type_tag == TermType.small_big())
      or (type_tag == TermType.large_big()))
    then
      return (None, position)
    end

    var value: I64 = 0
    var index: I32 = position
    let result = @opn_ei_message_decode_int(_message, addressof index,
      addressof value)
    if result != 0 then return (None, position) end

    (value, index)

  fun ref decode_double(position: I32): (F64 | None, I32) =>
    (let type_tag, _) = type_at(position)
    if (type_tag != TermType.float())
      and (type_tag != TermType.new_float())
    then
      return (None, position)
    end

    var value: F64 = 0
    var index: I32 = position
    let result = @opn_ei_message_decode_double(_message, addressof index,
      addressof value)
    if result != 0 then return (None, position) end

    (value, index)

  fun ref decode_binary(position: I32): (String | None, I32) =>
    (let type_tag, let size) = type_at(position)
    if type_tag != TermType.binary() then return (None, position) end
    if (size < 0) or (size.usize() > length()) then return (None, position) end

    let buffer = _buffer(size + 1)
    var byte_count: I64 = 0
    var index: I32 = position
    let result = @opn_ei_message_decode_binary(_message, addressof index,
      buffer.cpointer(), buffer.size(), addressof byte_count)
    if result != 0 then return (None, position) end

    (Strings.from_bytes(buffer, byte_count.usize()), index)

  fun ref decode_bitstring(position: I32): (ErlangBitstring | None, I32) =>
    (let type_tag, let size) = type_at(position)
    if type_tag != TermType.bit_binary() then return (None, position) end
    if (size < 0) or (size.usize() > length()) then return (None, position) end

    let buffer = _buffer(size + 1)
    var byte_count: I64 = 0
    var used_bits: I32 = 0
    var index: I32 = position
    let result = @opn_ei_message_decode_bitstring(_message, addressof index,
      buffer.cpointer(), buffer.size(), addressof byte_count,
      addressof used_bits)
    if result != 0 then return (None, position) end

    (ErlangBitstring(Strings.from_bytes(buffer, byte_count.usize()),
      used_bits.u8()), index)

  fun ref decode_string(position: I32): (String | None, I32) =>
    (let type_tag, let size) = type_at(position)
    if type_tag != TermType.string() then return (None, position) end
    if (size < 0) or (size.usize() > length()) then return (None, position) end

    let buffer = _buffer(size + 1)
    var index: I32 = position
    let result = @opn_ei_message_decode_string(_message, addressof index,
      buffer.cpointer(), buffer.size())
    if result != 0 then return (None, position) end

    (Strings.null_trimmed(buffer), index)

  fun ref decode_list_header(position: I32): (I32, I32) =>
    (let type_tag, _) = type_at(position)
    if (type_tag != TermType.list()) and (type_tag != TermType.nil()) then
      return (-1, position)
    end

    var arity: I32 = 0
    var index: I32 = position
    let result = @opn_ei_message_decode_list_header(_message, addressof index,
      addressof arity)
    if result != 0 then return (-1, position) end

    (arity, index)

  fun ref decode_tuple_header(position: I32): (I32, I32) =>
    (let type_tag, _) = type_at(position)
    if (type_tag != TermType.small_tuple())
      and (type_tag != TermType.large_tuple())
    then
      return (-1, position)
    end

    var arity: I32 = 0
    var index: I32 = position
    let result = @opn_ei_message_decode_tuple_header(_message, addressof index,
      addressof arity)
    if result != 0 then return (-1, position) end

    (arity, index)

  fun ref decode_map_header(position: I32): (I32, I32) =>
    (let type_tag, _) = type_at(position)
    if type_tag != TermType.map() then return (-1, position) end

    var arity: I32 = 0
    var index: I32 = position
    let result = @opn_ei_message_decode_map_header(_message, addressof index,
      addressof arity)
    if result != 0 then return (-1, position) end

    (arity, index)

  fun ref decode_pid(position: I32): (ErlangPid | None, I32) =>
    (let type_tag, _) = type_at(position)
    if (type_tag != TermType.pid()) and (type_tag != TermType.new_pid()) then
      return (None, position)
    end

    let node = _buffer(_node_capacity())
    var num: U32 = 0
    var serial: U32 = 0
    var creation: U32 = 0
    var index: I32 = position
    let result = @opn_ei_message_decode_pid(_message, addressof index,
      node.cpointer(), node.size(), addressof num, addressof serial,
      addressof creation)
    if result != 0 then return (None, position) end

    (ErlangPid(Strings.null_trimmed(node), num, serial, creation), index)

  fun ref decode_port(position: I32): (ErlangPort | None, I32) =>
    (let type_tag, _) = type_at(position)
    if not ((type_tag == TermType.port()) or (type_tag == TermType.new_port())
      or (type_tag == TermType.v4_port()))
    then
      return (None, position)
    end

    let node = _buffer(_node_capacity())
    var id: U64 = 0
    var creation: U32 = 0
    var index: I32 = position
    let result = @opn_ei_message_decode_port(_message, addressof index,
      node.cpointer(), node.size(), addressof id, addressof creation)
    if result != 0 then return (None, position) end

    (ErlangPort(Strings.null_trimmed(node), id, creation), index)

  fun ref decode_reference(position: I32): (ErlangReference | None, I32) =>
    (let type_tag, _) = type_at(position)
    if not ((type_tag == TermType.reference())
      or (type_tag == TermType.new_reference())
      or (type_tag == TermType.newer_reference()))
    then
      return (None, position)
    end

    let node = _buffer(_node_capacity())
    let ids: Array[U32] val =
      recover Array[U32].init(0, _max_reference_words()) end
    var id_count: I32 = 0
    var creation: U32 = 0
    var index: I32 = position
    let result = @opn_ei_message_decode_ref(_message, addressof index,
      node.cpointer(), node.size(), ids.cpointer(),
      _max_reference_words().i32(), addressof id_count,
      addressof creation)
    if result != 0 then return (None, position) end

    let used = recover ids.slice(0, id_count.usize()) end
    (ErlangReference(Strings.null_trimmed(node), consume used, creation), index)

  fun ref encode(term: ErlangTerm) ? =>
    """
    Writes a whole term into the message.

    A term that cannot be written leaves the message as it was, so a caller
    that handles the error does not send a truncated term.
    """
    let start = write_position()
    try
      ErlangTermEncoder.encode(this, term)?
    else
      truncate(start)
      error
    end

  fun ref encode_atom(name: String): I32 =>
    if not valid() then return -1 end
    @opn_ei_message_encode_atom(_message, name.cstring())

  fun ref encode_int(value: I64): I32 =>
    if not valid() then return -1 end
    @opn_ei_message_encode_int(_message, value)

  fun ref encode_double(value: F64): I32 =>
    if not valid() then return -1 end
    @opn_ei_message_encode_double(_message, value)

  fun ref encode_binary(value: String): I32 =>
    if not valid() then return -1 end
    @opn_ei_message_encode_binary(_message, value.cpointer(),
      value.size().i32())

  fun ref encode_bitstring(value: String, used_bits_in_last_byte: U8): I32 =>
    if not valid() then return -1 end
    @opn_ei_message_encode_bitstring(_message, value.cpointer(),
      value.size().i32(), used_bits_in_last_byte.i32())

  fun ref encode_string(value: String): I32 =>
    if not valid() then return -1 end
    @opn_ei_message_encode_string(_message, value.cpointer(),
      value.size().i32())

  fun ref encode_list_header(arity: USize): I32 =>
    if not valid() then return -1 end
    @opn_ei_message_encode_list_header(_message, arity.i32())

  fun ref encode_empty_list(): I32 =>
    if not valid() then return -1 end
    @opn_ei_message_encode_empty_list(_message)

  fun ref encode_tuple_header(arity: USize): I32 =>
    if not valid() then return -1 end
    @opn_ei_message_encode_tuple_header(_message, arity.i32())

  fun ref encode_map_header(arity: USize): I32 =>
    if not valid() then return -1 end
    @opn_ei_message_encode_map_header(_message, arity.i32())

  fun ref encode_pid(node: String, num: U32, serial: U32, creation: U32): I32 =>
    if not valid() then return -1 end
    @opn_ei_message_encode_pid(_message, node.cstring(), num, serial, creation)

  fun ref encode_port(node: String, id: U64, creation: U32): I32 =>
    if not valid() then return -1 end
    @opn_ei_message_encode_port(_message, node.cstring(), id, creation)

  fun ref encode_reference(node: String, ids: Array[U32] val,
    creation: U32)
    : I32
  =>
    if not valid() then return -1 end
    @opn_ei_message_encode_ref(_message, node.cstring(), ids.cpointer(),
      ids.size().i32(), creation)

  fun ref _buffer(size: I32): Array[U8] val =>
    let requested: I32 = if size < 1 then 1 else size end
    Strings.buffer(requested.usize())

  fun tag _beginning_of(message: Pointer[None]): I32 =>
    if message.is_null() then 0 else @opn_ei_message_beginning(message) end

  fun _node_capacity(): I32 =>
    """`MAXATOMLEN_UTF8` from `ei.h`: a node name, NUL included."""
    (255 * 4) + 1

  fun _max_reference_words(): USize =>
    """A reference carries at most five words in the external format."""
    5

  fun ref dispose() =>
    """Frees the message now instead of waiting for the collector."""
    if not _message.is_null() then
      @opn_ei_message_destroy(_message)
      _message = Pointer[None]
    end

  fun _final() =>
    if not _message.is_null() then
      @opn_ei_message_destroy(_message)
    end