Class: MQTT::Packet
- Inherits:
-
Object
- Object
- MQTT::Packet
- Defined in:
- lib/mqtt/packet.rb
Overview
Class representing a MQTT Packet Performs binary encoding and decoding of headers
Direct Known Subclasses
Connack, Connect, Disconnect, Pingreq, Pingresp, Puback, Pubcomp, Publish, Pubrec, Pubrel, Suback, Subscribe, Unsuback, Unsubscribe
Defined Under Namespace
Classes: Connack, Connect, Disconnect, Pingreq, Pingresp, Puback, Pubcomp, Publish, Pubrec, Pubrel, Suback, Subscribe, Unsuback, Unsubscribe
Constant Summary collapse
- ATTR_DEFAULTS =
Default attribute values
{ version: '3.1.0', id: 0, body_length: nil }.freeze
Instance Attribute Summary collapse
-
#body_length ⇒ Object
The length of the parsed packet body.
-
#flags ⇒ Object
Array of 4 bits in the fixed header.
-
#id ⇒ Object
Identifier to link related control packets together.
-
#version ⇒ Object
The version number of the MQTT protocol to use (default 3.1.0).
Class Method Summary collapse
-
.create_from_header(byte) ⇒ Object
Create a new packet object from the first byte of a MQTT packet.
-
.parse(buffer) ⇒ Object
Parse buffer into new packet object.
-
.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.
-
.read(socket) ⇒ Object
Read in a packet from a socket.
-
.read_byte(socket) ⇒ Object
Read and unpack a single byte from a socket.
Instance Method Summary collapse
- #dup ⇒ Object
-
#encode_body ⇒ Object
Get serialisation of packet’s body (variable header and payload).
-
#initialize(args = {}) ⇒ Packet
constructor
Create a new empty packet.
-
#inspect ⇒ Object
Returns a human readable string.
-
#message_id ⇒ Object
deprecated
Deprecated.
Please use #id instead
-
#message_id=(args) ⇒ Object
deprecated
Deprecated.
Please use #id= instead
-
#parse_body(buffer) ⇒ Object
Parse the body (variable header and payload) of a packet.
-
#to_s ⇒ Object
Serialise the packet.
-
#type_id ⇒ Object
Get the identifer for this packet type.
-
#type_name ⇒ Object
Get the name of the packet type as a string in capitals (like the MQTT specification uses).
-
#update_attributes(attr = {}) ⇒ Object
Set packet attributes from a hash of attribute names and values.
-
#validate_flags ⇒ Object
Check that fixed header flags are valid for types that don’t use the flags.
Constructor Details
#initialize(args = {}) ⇒ Packet
Create a new empty packet
115 116 117 118 119 |
# File 'lib/mqtt/packet.rb', line 115 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_length ⇒ Object
The length of the parsed packet body
18 19 20 |
# File 'lib/mqtt/packet.rb', line 18 def body_length @body_length end |
#flags ⇒ Object
Array of 4 bits in the fixed header
15 16 17 |
# File 'lib/mqtt/packet.rb', line 15 def flags @flags end |
#id ⇒ Object
Identifier to link related control packets together
12 13 14 |
# File 'lib/mqtt/packet.rb', line 12 def id @id end |
#version ⇒ Object
The version number of the MQTT protocol to use (default 3.1.0)
9 10 11 |
# File 'lib/mqtt/packet.rb', line 9 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
101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/mqtt/packet.rb', line 101 def self.create_from_header(byte) # Work out the class type_id = ((byte & 0xF0) >> 4) packet_class = MQTT::PACKET_TYPES[type_id] raise ProtocolException, "Invalid packet type identifier: #{type_id}" if packet_class.nil? # 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 |
.parse(buffer) ⇒ Object
Parse buffer into new packet object
58 59 60 61 62 63 |
# File 'lib/mqtt/packet.rb', line 58 def self.parse(buffer) buffer = buffer.dup 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
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 |
# File 'lib/mqtt/packet.rb', line 67 def self.parse_header(buffer) # Check that the packet is a long as the minimum packet size raise ProtocolException, 'Invalid packet: less than 2 bytes long' if buffer.bytesize < 2 # 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 loop do raise ProtocolException, 'The packet length header is incomplete' if buffer.bytesize <= pos digit = bytes[pos] body_length += ((digit & 0x7F) * multiplier) multiplier *= 0x80 pos += 1 break if (digit & 0x80).zero? || pos > 4 end # 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
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 |
# File 'lib/mqtt/packet.rb', line 28 def self.read(socket) # Read in the packet header and create a new packet object packet = create_from_header( read_byte(socket) ) packet.validate_flags # Read in the packet length multiplier = 1 body_length = 0 pos = 1 loop do digit = read_byte(socket) body_length += ((digit & 0x7F) * multiplier) multiplier *= 0x80 pos += 1 break if (digit & 0x80).zero? || pos > 4 end # 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)) packet end |
.read_byte(socket) ⇒ Object
Read and unpack a single byte from a socket
224 225 226 227 228 229 |
# File 'lib/mqtt/packet.rb', line 224 def self.read_byte(socket) byte = socket.read(1) raise ProtocolException, 'Failed to read byte from socket' if byte.nil? byte.unpack1('C') end |
Instance Method Details
#dup ⇒ Object
121 122 123 124 125 |
# File 'lib/mqtt/packet.rb', line 121 def dup result = super result.instance_variable_set(:@flags, @flags.dup) result end |
#encode_body ⇒ Object
Get serialisation of packet’s body (variable header and payload)
174 175 176 |
# File 'lib/mqtt/packet.rb', line 174 def encode_body '' # No body by default end |
#inspect ⇒ Object
Returns a human readable string
219 220 221 |
# File 'lib/mqtt/packet.rb', line 219 def inspect "\#<#{self.class}>" end |
#message_id ⇒ Object
Please use #id instead
1026 1027 1028 |
# File 'lib/mqtt/packet.rb', line 1026 def id end |
#message_id=(args) ⇒ Object
Please use #id= instead
1031 1032 1033 |
# File 'lib/mqtt/packet.rb', line 1031 def (args) self.id = args end |
#parse_body(buffer) ⇒ Object
Parse the body (variable header and payload) of a packet
165 166 167 168 169 170 171 |
# File 'lib/mqtt/packet.rb', line 165 def parse_body(buffer) return if buffer.bytesize == body_length raise ProtocolException, "Failed to parse packet - input buffer (#{buffer.bytesize}) " \ "is not the same as the body length header (#{body_length})" end |
#to_s ⇒ Object
Serialise the packet
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 |
# File 'lib/mqtt/packet.rb', line 179 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 = encode_body # Check that that packet isn't too big body_length = body.bytesize raise 'Error serialising packet: body is more than 256MB' if body_length > 268_435_455 # Build up the body length field bytes loop do digit = (body_length % 128) body_length = body_length.div(128) # if there are more digits to encode, set the top bit of this digit digit |= 0x80 if body_length > 0 header.push(digit) break if body_length <= 0 end # Convert header to binary and add on body header.pack('C*') + body end |
#type_id ⇒ Object
Get the identifer for this packet type
139 140 141 142 143 144 |
# File 'lib/mqtt/packet.rb', line 139 def type_id index = MQTT::PACKET_TYPES.index(self.class) raise "Invalid packet type: #{self.class}" if index.nil? index end |
#type_name ⇒ Object
Get the name of the packet type as a string in capitals (like the MQTT specification uses)
Example: CONNACK
150 151 152 |
# File 'lib/mqtt/packet.rb', line 150 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
128 129 130 131 132 133 134 135 136 |
# File 'lib/mqtt/packet.rb', line 128 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_flags ⇒ Object
Check that fixed header flags are valid for types that don’t use the flags
212 213 214 215 216 |
# File 'lib/mqtt/packet.rb', line 212 def validate_flags return if flags == [false, false, false, false] raise ProtocolException, "Invalid flags in #{type_name} packet header" end |