class MQTT::MQTT::MQTT::Packet
Class representing a MQTT Packet Performs binary encoding and decoding of headers
Constants
- ATTR_DEFAULTS
Default attribute values
Attributes
The length of the parsed packet body
Array of 4 bits in the fixed header
Identifier to link related control packets together
The version number of the MQTT protocol to use (default 3.1.0)
Public Class Methods
Create a new packet object from the first byte of a MQTT packet
# File lib/mqttbridge/packet.rb, line 102 def self.create_from_header(byte) # Work out the class type_id = ((byte & 0xF0) >> 4) packet_class = MQTT::PACKET_TYPES[type_id] if packet_class.nil? raise ProtocolException.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
Create a new empty packet
# File lib/mqttbridge/packet.rb, line 118 def initialize(args={}) # We must set flags before the other values @flags = [false, false, false, false] update_attributes(ATTR_DEFAULTS.merge(args)) end
Parse buffer into new packet object
# File lib/mqttbridge/packet.rb, line 59 def self.parse(buffer) packet = parse_header(buffer) packet.parse_body(buffer) return packet end
Parse the header and create a new packet object of the correct type The header is removed from the buffer passed into this function
# File lib/mqttbridge/packet.rb, line 67 def self.parse_header(buffer) # Check that the packet is a long as the minimum packet size if buffer.bytesize < 2 raise ProtocolException.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 ProtocolException.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) and 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) return packet end
Read in a packet from a socket
# File lib/mqttbridge/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 begin digit = read_byte(socket) body_length += ((digit & 0x7F) * multiplier) multiplier *= 0x80 pos += 1 end while ((digit & 0x80) != 0x00) and pos <= 4 # Store the expected body length in the packet packet.instance_variable_set('@body_length', body_length) # Read in the packet body $subackret=packet.parse_body(socket.read(body_length)) #~ puts $subackret # puts @return_codes #~ puts $subackret return packet end
Public Instance Methods
Set the length of the packet body
# File lib/mqttbridge/packet.rb, line 158 def body_length=(arg) @body_length = arg.to_i end
Get serialisation of packet's body (variable header and payload)
# File lib/mqttbridge/packet.rb, line 172 def encode_body '' # No body by default end
Returns a human readable string
# File lib/mqttbridge/packet.rb, line 219 def inspect "\#<#{self.class}>" end
@deprecated Please use {#id} instead
# File lib/mqttbridge/packet.rb, line 1081 def message_id id end
@deprecated Please use {#id=} instead
# File lib/mqttbridge/packet.rb, line 1086 def message_id=(args) self.id = args end
Parse the body (variable header and payload) of a packet
# File lib/mqttbridge/packet.rb, line 163 def parse_body(buffer) if buffer.bytesize != body_length raise ProtocolException.new( "Failed to parse packet - input buffer (#{buffer.bytesize}) is not the same as the body length header (#{body_length})" ) end end
Serialise the packet
# File lib/mqttbridge/packet.rb, line 178 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 "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.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) end while (body_length > 0) # Convert header to binary and add on body header.pack('C*') + body end
Get the identifer for this packet type
# File lib/mqttbridge/packet.rb, line 136 def type_id index = MQTT::PACKET_TYPES.index(self.class) if index.nil? raise "Invalid packet type: #{self.class}" end return index end
Get the name of the packet type as a string in capitals (like the MQTT specification uses)
Example: CONNACK
# File lib/mqttbridge/packet.rb, line 148 def type_name self.class.name.split('::').last.upcase end
Set packet attributes from a hash of attribute names and values
# File lib/mqttbridge/packet.rb, line 125 def update_attributes(attr={}) attr.each_pair do |k,v| if v.is_a?(Array) or v.is_a?(Hash) send("#{k}=", v.dup) else send("#{k}=", v) end end end
Check that fixed header flags are valid for types that don't use the flags @private
# File lib/mqttbridge/packet.rb, line 212 def validate_flags if flags != [false, false, false, false] raise ProtocolException.new("Invalid flags in #{type_name} packet header") end end
Set the protocol version number
# File lib/mqttbridge/packet.rb, line 153 def version=(arg) @version = arg.to_s end
Protected Instance Methods
Encode an array of bits and return them
# File lib/mqttbridge/packet.rb, line 231 def encode_bits(bits) [bits.map{|b| b ? '1' : '0'}.join].pack('b*') end
Encode an array of bytes and return them
# File lib/mqttbridge/packet.rb, line 226 def encode_bytes(*bytes) bytes.pack('C*') end
Encode a 16-bit unsigned integer and return it
# File lib/mqttbridge/packet.rb, line 236 def encode_short(val) [val.to_i].pack('n') end
Encode a UTF-8 string and return it (preceded by the length of the string)
# File lib/mqttbridge/packet.rb, line 242 def encode_string(str) str = str.to_s.encode('UTF-8') # Force to binary, when assembling the packet str.force_encoding('ASCII-8BIT') encode_short(str.bytesize) + str end
Remove 8 bits from the front of buffer
# File lib/mqttbridge/packet.rb, line 262 def shift_bits(buffer) buffer.slice!(0...1).unpack('b8').first.split('').map {|b| b == '1'} end
Remove one byte from the front of the string
# File lib/mqttbridge/packet.rb, line 257 def shift_byte(buffer) buffer.slice!(0...1).unpack('C').first end
Remove n bytes from the front of buffer
# File lib/mqttbridge/packet.rb, line 267 def shift_data(buffer,bytes) buffer.slice!(0...bytes) end
Remove a 16-bit unsigned integer from the front of buffer
# File lib/mqttbridge/packet.rb, line 251 def shift_short(buffer) bytes = buffer.slice!(0..1) bytes.unpack('n').first end
Remove string from the front of buffer
# File lib/mqttbridge/packet.rb, line 272 def shift_string(buffer) len = shift_short(buffer) str = shift_data(buffer,len) # Strings in MQTT v3.1 are all UTF-8 str.force_encoding('UTF-8') end