Module: E3DCMqtt::RSCP::Frame

Defined in:
lib/e3dc_mqtt/rscp/frame.rb

Overview

Frames a list of messages into an (unencrypted) RSCP frame, and parses frames back into messages. Encryption/decryption is handled one layer up.

Class Method Summary collapse

Class Method Details

.decode(bytes) ⇒ Object

Parse an already-decrypted frame. Returns Array<Message>.

Raises:



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
# File 'lib/e3dc_mqtt/rscp/frame.rb', line 30

def decode(bytes)
  raise InvalidFrameLength, "frame shorter than header" if bytes.bytesize < FRAME_HEADER_SIZE

  magic = bytes.byteslice(0, 2).unpack1("S<")
  raise InvalidMagic, "expected 0x%04x, got 0x%04x" % [MAGIC, magic] if magic != MAGIC

  ctrl = bytes.byteslice(2, 2).unpack1("S<")
  raise InvalidControl, "reserved bits set in control field 0x%04x" % ctrl if (ctrl | CTRL_ALLOWED_MASK) != CTRL_ALLOWED_MASK

  version = (ctrl & CTRL_VERSION_MASK) >> CTRL_VERSION_SHIFT
  raise ProtocolVersionMismatch, "expected version %d, got %d" % [VERSION_1_0, version] if version != VERSION_1_0

  crc_flag = (ctrl & CTRL_CRC_MASK) != 0
  data_size = bytes.byteslice(FRAME_HEADER_SIZE - FRAME_LENGTH_SIZE, FRAME_LENGTH_SIZE).unpack1("S<")
  frame_size = FRAME_HEADER_SIZE + data_size + (crc_flag ? FRAME_CRC_SIZE : 0)

  trimmed = trim_padding(bytes, frame_size)
  raise InvalidFrameLength, "unexpected data after frame" if trimmed.bytesize > frame_size

  if crc_flag
    expected = Zlib.crc32(trimmed.byteslice(0, frame_size - FRAME_CRC_SIZE))
    actual = trimmed.byteslice(frame_size - FRAME_CRC_SIZE, FRAME_CRC_SIZE).unpack1("L<")
    raise InvalidCrc, "expected 0x%08x, got 0x%08x" % [expected, actual] if expected != actual
  end

  MessageCodec.decode_all(trimmed, FRAME_HEADER_SIZE, data_size)
end

.encode(messages, use_checksum: true, time: Time.now.utc) ⇒ Object



14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/e3dc_mqtt/rscp/frame.rb', line 14

def encode(messages, use_checksum: true, time: Time.now.utc)
  body = MessageCodec.encode_all(messages)
  if body.bytesize > FRAME_MAX_DATA_SIZE
    raise DataLimitExceeded, "data size %d exceeds max %d" % [body.bytesize, FRAME_MAX_DATA_SIZE]
  end

  ctrl = (VERSION_1_0 << CTRL_VERSION_SHIFT) & CTRL_VERSION_MASK
  ctrl |= (1 << CTRL_CRC_SHIFT) & CTRL_CRC_MASK if use_checksum

  header = [MAGIC].pack("S<") + [ctrl].pack("S<") + encode_time(time) + [body.bytesize].pack("S<")
  frame = header + body
  frame += [Zlib.crc32(frame)].pack("L<") if use_checksum
  frame
end

.encode_time(time) ⇒ Object



58
59
60
61
# File 'lib/e3dc_mqtt/rscp/frame.rb', line 58

def encode_time(time)
  t = time.utc
  [t.to_i].pack("q<") + [t.nsec].pack("l<")
end

.pad_for_crypto(bytes) ⇒ Object



71
72
73
74
75
76
# File 'lib/e3dc_mqtt/rscp/frame.rb', line 71

def pad_for_crypto(bytes)
  rem = bytes.bytesize % CRYPT_BLOCK_SIZE
  return bytes if rem == 0

  bytes + "\x00".b * (CRYPT_BLOCK_SIZE - rem)
end

.trim_padding(bytes, frame_size) ⇒ Object

Drop trailing zero padding that may follow the frame (introduced by the crypto layer so that total size is a multiple of the block size).



65
66
67
68
69
# File 'lib/e3dc_mqtt/rscp/frame.rb', line 65

def trim_padding(bytes, frame_size)
  i = bytes.bytesize
  i -= 1 while i > frame_size && bytes.getbyte(i - 1) == CRYPT_BLOCK_PAD
  bytes.byteslice(0, i)
end