Class: MqttRails::Packet::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/mqtt_rails/packet/base.rb

Overview

Class representing a MQTT Packet Performs binary encoding and decoding of headers

Constant Summary collapse

ATTR_DEFAULTS =

Default attribute values

{
  :version     => '3.1.0',
  :id          => 0,
  :body_length => nil
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args = {}) ⇒ Base

Create a new empty packet



139
140
141
142
143
# File 'lib/mqtt_rails/packet/base.rb', line 139

def initialize(args={})
  # We must set flags before the other values
  @flags = [false, false, false, false]
  update_attributes(ATTR_DEFAULTS.merge(args))
end

Instance Attribute Details

#body_lengthObject

The length of the parsed packet body



35
36
37
# File 'lib/mqtt_rails/packet/base.rb', line 35

def body_length
  @body_length
end

#flagsObject

Array of 4 bits in the fixed header



32
33
34
# File 'lib/mqtt_rails/packet/base.rb', line 32

def flags
  @flags
end

#idObject

Identifier to link related control packets together



29
30
31
# File 'lib/mqtt_rails/packet/base.rb', line 29

def id
  @id
end

#versionObject

The version number of the MQTT protocol to use (default 3.1.0)



26
27
28
# File 'lib/mqtt_rails/packet/base.rb', line 26

def version
  @version
end

Class Method Details

.create_from_header(byte) ⇒ Object

Create a new packet object from the first byte of a MQTT packet



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/mqtt_rails/packet/base.rb', line 120

def self.create_from_header(byte)
  unless byte.nil?
    # Work out the class
    type_id = ((byte & 0xF0) >> 4)
    packet_class = MqttRails::PACKET_TYPES[type_id]
    if packet_class.nil?
      raise MqttRails::PacketFormatException.new(
              "Invalid packet type identifier: #{type_id}")
    end

    # Convert the last 4 bits of byte into array of true/false
    flags = (0..3).map { |i| byte & (2 ** i) != 0 }

    # Create a new packet object
    packet_class.new(:flags => flags)
  end
end

.parse(buffer) ⇒ Object

Parse buffer into new packet object



75
76
77
78
79
# File 'lib/mqtt_rails/packet/base.rb', line 75

def self.parse(buffer)
  packet = parse_header(buffer)
  packet.parse_body(buffer)
  packet
end

.parse_header(buffer) ⇒ Object

Parse the header and create a new packet object of the correct type The header is removed from the buffer passed into this function



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
# File 'lib/mqtt_rails/packet/base.rb', line 83

def self.parse_header(buffer)
  # Check that the packet is a long as the minimum packet size
  if buffer.bytesize < 2
    raise MqttRails::PacketFormatException.new(
            "Invalid packet: less than 2 bytes long")
  end

  # Create a new packet object
  bytes = buffer.unpack("C5")
  packet = create_from_header(bytes.first)
  packet.validate_flags

  # Parse the packet length
  body_length = 0
  multiplier = 1
  pos = 1
  begin
    if buffer.bytesize <= pos
      raise MqttRails::PacketFormatException.new(
              "The packet length header is incomplete")
    end
    digit = bytes[pos]
    body_length += ((digit & 0x7F) * multiplier)
    multiplier *= 0x80
    pos += 1
  end while ((digit & 0x80) != 0x00) && pos <= 4

  # Store the expected body length in the packet
  packet.instance_variable_set('@body_length', body_length)

  # Delete the fixed header from the raw packet passed in
  buffer.slice!(0...pos)

  packet
end

.read(socket) ⇒ Object

Read in a packet from a socket



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
# File 'lib/mqtt_rails/packet/base.rb', line 45

def self.read(socket)
  # Read in the packet header and create a new packet object
  packet = create_from_header(
    read_byte(socket)
  )

  unless packet.nil?
    packet.validate_flags

    # Read in the packet length
    multiplier = 1
    body_length = 0
    pos = 1
    begin
      digit = read_byte(socket)
      body_length += ((digit & 0x7F) * multiplier)
      multiplier *= 0x80
      pos += 1
    end while ((digit & 0x80) != 0x00) && pos <= 4

    # Store the expected body length in the packet
    packet.instance_variable_set('@body_length', body_length)

    # Read in the packet body
    packet.parse_body(socket.read(body_length))
  end
  packet
end

Instance Method Details

#encode_bodyObject

Get serialisation of packet’s body (variable header and payload)



193
194
195
# File 'lib/mqtt_rails/packet/base.rb', line 193

def encode_body
  '' # No body by default
end

#inspectObject

Returns a human readable string



241
242
243
# File 'lib/mqtt_rails/packet/base.rb', line 241

def inspect
  "\#<#{self.class}>"
end

#parse_body(buffer) ⇒ Object

Parse the body (variable header and payload) of a packet



185
186
187
188
189
190
# File 'lib/mqtt_rails/packet/base.rb', line 185

def parse_body(buffer)
  if buffer.bytesize != body_length
    raise MqttRails::PacketFormatException.new(
            "Failed to parse packet - input buffer (#{buffer.bytesize}) is not the same as the body length header (#{body_length})")
  end
end

#to_sObject

Serialise the packet



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
# File 'lib/mqtt_rails/packet/base.rb', line 198

def to_s
  # Encode the fixed header
  header = [
    ((type_id.to_i & 0x0F) << 4) |
    (flags[3] ? 0x8 : 0x0) |
    (flags[2] ? 0x4 : 0x0) |
    (flags[1] ? 0x2 : 0x0) |
    (flags[0] ? 0x1 : 0x0)
  ]

  # Get the packet's variable header and payload
  body = self.encode_body

  # Check that that packet isn't too big
  body_length = body.bytesize
  if body_length > 268435455
    raise MqttRails::PacketFormatException.new(
    "Error serialising packet: body is more than 256MB")
  end

  # Build up the body length field bytes
  begin
    digit = (body_length % 128)
    body_length = (body_length / 128)
    # if there are more digits to encode, set the top bit of this digit
    digit |= 0x80 if (body_length > 0)
    header.push(digit)
  end while (body_length > 0)

  # Convert header to binary and add on body
  header.pack('C*') + body
end

#type_idObject

Get the identifer for this packet type



157
158
159
160
161
162
163
164
# File 'lib/mqtt_rails/packet/base.rb', line 157

def type_id
  index = MqttRails::PACKET_TYPES.index(self.class)
  if index.nil?
    raise MqttRails::PacketFormatException.new(
            "Invalid packet type: #{self.class}")
  end
  index
end

#type_nameObject

Get the name of the packet type as a string in capitals (like the MQTT specification uses)

Example: CONNACK



170
171
172
# File 'lib/mqtt_rails/packet/base.rb', line 170

def type_name
  self.class.name.split('::').last.upcase
end

#update_attributes(attr = {}) ⇒ Object

Set packet attributes from a hash of attribute names and values



146
147
148
149
150
151
152
153
154
# File 'lib/mqtt_rails/packet/base.rb', line 146

def update_attributes(attr={})
  attr.each_pair do |k,v|
    if v.is_a?(Array) || v.is_a?(Hash)
      send("#{k}=", v.dup)
    else
      send("#{k}=", v)
    end
  end
end

#validate_flagsObject

Check that fixed header flags are valid for types that don’t use the flags



233
234
235
236
237
238
# File 'lib/mqtt_rails/packet/base.rb', line 233

def validate_flags
  if flags != [false, false, false, false]
    raise MqttRails::PacketFormatException.new(
    "Invalid flags in #{type_name} packet header")
  end
end