1571 lines
59 KiB
Lua
1571 lines
59 KiB
Lua
--[[
|
||
|
||
Here is a MQTT v5.0 protocol implementation
|
||
|
||
MQTT v5.0 documentation (DOC):
|
||
http://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html
|
||
|
||
]]
|
||
|
||
-- module table
|
||
local protocol5 = {}
|
||
|
||
-- load required stuff
|
||
local type = type
|
||
local error = error
|
||
local assert = assert
|
||
local ipairs = ipairs
|
||
local require = require
|
||
local tostring = tostring
|
||
local setmetatable = setmetatable
|
||
|
||
local table = require("table")
|
||
local unpack = table.unpack or unpack
|
||
local tbl_sort = table.sort
|
||
|
||
local string = require("string")
|
||
local str_char = string.char
|
||
local fmt = string.format
|
||
|
||
local const = require("mqtt.const")
|
||
local const_v50 = const.v50
|
||
|
||
local tools = require("mqtt.tools")
|
||
local sortedpairs = tools.sortedpairs
|
||
|
||
local bit = require("mqtt.bitwrap")
|
||
local bor = bit.bor
|
||
local band = bit.band
|
||
local lshift = bit.lshift
|
||
local rshift = bit.rshift
|
||
|
||
local protocol = require("mqtt.protocol")
|
||
local make_uint8 = protocol.make_uint8
|
||
local make_uint16 = protocol.make_uint16
|
||
local make_uint32 = protocol.make_uint32
|
||
local make_string = protocol.make_string
|
||
local make_var_length = protocol.make_var_length
|
||
local parse_var_length = protocol.parse_var_length
|
||
local make_uint8_0_or_1 = protocol.make_uint8_0_or_1
|
||
local make_uint16_nonzero = protocol.make_uint16_nonzero
|
||
local make_var_length_nonzero = protocol.make_var_length_nonzero
|
||
local parse_string = protocol.parse_string
|
||
local parse_uint8 = protocol.parse_uint8
|
||
local parse_uint8_0_or_1 = protocol.parse_uint8_0_or_1
|
||
local parse_uint16 = protocol.parse_uint16
|
||
local parse_uint16_nonzero = protocol.parse_uint16_nonzero
|
||
local parse_uint32 = protocol.parse_uint32
|
||
local parse_var_length_nonzero = protocol.parse_var_length_nonzero
|
||
local make_header = protocol.make_header
|
||
local check_qos = protocol.check_qos
|
||
local check_packet_id = protocol.check_packet_id
|
||
local combine = protocol.combine
|
||
local packet_type = protocol.packet_type
|
||
local packet_mt = protocol.packet_mt
|
||
local connack_packet_mt = protocol.connack_packet_mt
|
||
local start_parse_packet = protocol.start_parse_packet
|
||
local parse_packet_connect_input = protocol.parse_packet_connect_input
|
||
|
||
-- Returns true if given value is a valid Retain Handling option, DOC: 3.8.3.1 Subscription Options
|
||
local function check_retain_handling(val)
|
||
return (val == 0) or (val == 1) or (val == 2)
|
||
end
|
||
|
||
-- Create Connect Flags data, DOC: 3.1.2.3 Connect Flags
|
||
local function make_connect_flags(args)
|
||
local byte = 0 -- bit 0 should be zero
|
||
-- DOC: 3.1.2.4 Clean Start
|
||
if args.clean ~= nil then
|
||
assert(type(args.clean) == "boolean", "expecting .clean to be a boolean")
|
||
if args.clean then
|
||
byte = bor(byte, lshift(1, 1))
|
||
end
|
||
end
|
||
-- DOC: 3.1.2.5 Will Flag
|
||
if args.will ~= nil then
|
||
-- check required args are presented
|
||
assert(type(args.will) == "table", "expecting .will to be a table")
|
||
assert(type(args.will.payload) == "string", "expecting .will.payload to be a string")
|
||
assert(type(args.will.topic) == "string", "expecting .will.topic to be a string")
|
||
assert(type(args.will.qos) == "number", "expecting .will.qos to be a number")
|
||
assert(check_qos(args.will.qos), "expecting .will.qos to be a valid QoS value")
|
||
assert(type(args.will.retain) == "boolean", "expecting .will.retain to be a boolean")
|
||
if args.will.properties ~= nil then
|
||
assert(type(args.will.properties) == "table", "expecting .will.properties to be a table")
|
||
end
|
||
if args.will.user_properties ~= nil then
|
||
assert(type(args.will.user_properties) == "table", "expecting .will.user_properties to be a table")
|
||
end
|
||
-- will flag should be set to 1
|
||
byte = bor(byte, lshift(1, 2))
|
||
-- DOC: 3.1.2.6 Will QoS
|
||
byte = bor(byte, lshift(args.will.qos, 3))
|
||
-- DOC: 3.1.2.7 Will Retain
|
||
if args.will.retain then
|
||
byte = bor(byte, lshift(1, 5))
|
||
end
|
||
end
|
||
-- DOC: 3.1.2.8 User Name Flag
|
||
if args.username ~= nil then
|
||
assert(type(args.username) == "string", "expecting .username to be a string")
|
||
byte = bor(byte, lshift(1, 7))
|
||
end
|
||
-- DOC: 3.1.2.9 Password Flag
|
||
if args.password ~= nil then
|
||
assert(type(args.password) == "string", "expecting .password to be a string")
|
||
assert(args.username, "the .username is required to set .password")
|
||
byte = bor(byte, lshift(1, 6))
|
||
end
|
||
return make_uint8(byte)
|
||
end
|
||
|
||
-- Known property names and its identifiers, DOC: 2.2.2.2 Property
|
||
local property_pairs = {
|
||
{ 0x01, "payload_format_indicator",
|
||
make = make_uint8_0_or_1,
|
||
parse = parse_uint8_0_or_1, },
|
||
{ 0x02, "message_expiry_interval",
|
||
make = make_uint32,
|
||
parse = parse_uint32, },
|
||
{ 0x03, "content_type",
|
||
make = make_string,
|
||
parse = parse_string, },
|
||
{ 0x08, "response_topic",
|
||
make = make_string,
|
||
parse = parse_string, },
|
||
{ 0x09, "correlation_data",
|
||
make = make_string,
|
||
parse = parse_string, },
|
||
{ 0x0B, "subscription_identifiers",
|
||
make = function(value) return str_char(make_var_length_nonzero(value)) end,
|
||
parse = parse_var_length_nonzero,
|
||
multiple = true, },
|
||
{ 0x11, "session_expiry_interval",
|
||
make = make_uint32,
|
||
parse = parse_uint32, },
|
||
{ 0x12, "assigned_client_identifier",
|
||
make = make_string,
|
||
parse = parse_string, },
|
||
{ 0x13, "server_keep_alive",
|
||
make = make_uint16,
|
||
parse = parse_uint16, },
|
||
{ 0x15, "authentication_method",
|
||
make = make_string,
|
||
parse = parse_string, },
|
||
{ 0x16, "authentication_data",
|
||
make = make_string,
|
||
parse = parse_string, },
|
||
{ 0x17, "request_problem_information",
|
||
make = make_uint8_0_or_1,
|
||
parse = parse_uint8_0_or_1, },
|
||
{ 0x18, "will_delay_interval",
|
||
make = make_uint32,
|
||
parse = parse_uint32, },
|
||
{ 0x19, "request_response_information",
|
||
make = make_uint8_0_or_1,
|
||
parse = parse_uint8_0_or_1, },
|
||
{ 0x1A, "response_information",
|
||
make = make_string,
|
||
parse = parse_string, },
|
||
{ 0x1C, "server_reference",
|
||
make = make_string,
|
||
parse = parse_string, },
|
||
{ 0x1F, "reason_string",
|
||
make = make_string,
|
||
parse = parse_string, },
|
||
{ 0x21, "receive_maximum",
|
||
make = make_uint16,
|
||
parse = parse_uint16, },
|
||
{ 0x22, "topic_alias_maximum",
|
||
make = make_uint16,
|
||
parse = parse_uint16, },
|
||
{ 0x23, "topic_alias",
|
||
make = make_uint16_nonzero,
|
||
parse = parse_uint16_nonzero, },
|
||
{ 0x24, "maximum_qos",
|
||
make = make_uint8_0_or_1,
|
||
parse = parse_uint8_0_or_1, },
|
||
{ 0x25, "retain_available",
|
||
make = make_uint8_0_or_1,
|
||
parse = parse_uint8_0_or_1, },
|
||
{ 0x26, "user_property", -- NOTE: not implemented intentionally
|
||
make = function(value_) error("not implemented") end, -- luacheck: ignore
|
||
parse = function(read_func_) error("not implemented") end, }, -- luacheck: ignore
|
||
{ 0x27, "maximum_packet_size",
|
||
make = make_uint32,
|
||
parse = parse_uint32, },
|
||
{ 0x28, "wildcard_subscription_available",
|
||
make = make_uint8_0_or_1,
|
||
parse = parse_uint8_0_or_1, },
|
||
{ 0x29, "subscription_identifiers_available",
|
||
make = make_uint8_0_or_1,
|
||
parse = parse_uint8_0_or_1, },
|
||
{ 0x2A, "shared_subscription_available",
|
||
make = make_uint8_0_or_1,
|
||
parse = parse_uint8_0_or_1, },
|
||
}
|
||
|
||
-- properties table with keys in two directions: from name to identifier and back
|
||
local properties = {}
|
||
-- table with property value make functions
|
||
local property_make = {}
|
||
-- table with property value parse function
|
||
local property_parse = {}
|
||
-- table with property multiple flag
|
||
local property_multiple = {}
|
||
-- fill the properties and property_make tables
|
||
for _, prop in ipairs(property_pairs) do
|
||
properties[prop[2]] = prop[1] -- name ==> identifier
|
||
properties[prop[1]] = prop[2] -- identifier ==> name
|
||
property_make[prop[1]] = prop.make -- identifier ==> make function
|
||
property_parse[prop[1]] = prop.parse -- identifier ==> make function
|
||
property_multiple[prop[1]] = prop.multiple -- identifier ==> multiple flag
|
||
end
|
||
|
||
-- Allowed properties per packet type
|
||
local allowed_properties = {
|
||
[packet_type.CONNECT] = {
|
||
[0x11] = true, -- DOC: 3.1.2.11.2 Session Expiry Interval
|
||
[0x21] = true, -- DOC: 3.1.2.11.3 Receive Maximum
|
||
[0x27] = true, -- DOC: 3.1.2.11.4 Maximum Packet Size
|
||
[0x22] = true, -- DOC: 3.1.2.11.5 Topic Alias Maximum
|
||
[0x19] = true, -- DOC: 3.1.2.11.6 Request Response Information
|
||
[0x17] = true, -- DOC: 3.1.2.11.7 Request Problem Information
|
||
[0x26] = true, -- DOC: 3.1.2.11.8 User Property
|
||
[0x15] = true, -- DOC: 3.1.2.11.9 Authentication Method
|
||
[0x16] = true, -- DOC: 3.1.2.11.10 Authentication Data
|
||
},
|
||
[packet_type.CONNACK] = {
|
||
[0x11] = true, -- DOC: 3.2.2.3.2 Session Expiry Interval
|
||
[0x21] = true, -- DOC: 3.2.2.3.3 Receive Maximum
|
||
[0x24] = true, -- DOC: 3.2.2.3.4 Maximum QoS
|
||
[0x25] = true, -- DOC: 3.2.2.3.5 Retain Available
|
||
[0x27] = true, -- DOC: 3.2.2.3.6 Maximum Packet Size
|
||
[0x12] = true, -- DOC: 3.2.2.3.7 Assigned Client Identifier
|
||
[0x22] = true, -- DOC: 3.2.2.3.8 Topic Alias Maximum
|
||
[0x1F] = true, -- DOC: 3.2.2.3.9 Reason String
|
||
[0x26] = true, -- DOC: 3.2.2.3.10 User Property
|
||
[0x28] = true, -- DOC: 3.2.2.3.11 Wildcard Subscription Available
|
||
[0x29] = true, -- DOC: 3.2.2.3.12 Subscription Identifiers Available
|
||
[0x2A] = true, -- DOC: 3.2.2.3.13 Shared Subscription Available
|
||
[0x13] = true, -- DOC: 3.2.2.3.14 Server Keep Alive
|
||
[0x1A] = true, -- DOC: 3.2.2.3.15 Response Information
|
||
[0x1C] = true, -- DOC: 3.2.2.3.16 Server Reference
|
||
[0x15] = true, -- DOC: 3.2.2.3.17 Authentication Method
|
||
[0x16] = true, -- DOC: 3.2.2.3.18 Authentication Data
|
||
},
|
||
[packet_type.PUBLISH] = {
|
||
[0x01] = true, -- DOC: 3.3.2.3.2 Payload Format Indicator
|
||
[0x02] = true, -- DOC: 3.3.2.3.3 Message Expiry Interval
|
||
[0x23] = true, -- DOC: 3.3.2.3.4 Topic Alias
|
||
[0x08] = true, -- DOC: 3.3.2.3.5 Response Topic
|
||
[0x09] = true, -- DOC: 3.3.2.3.6 Correlation Data
|
||
[0x26] = true, -- DOC: 3.3.2.3.7 User Property
|
||
[0x0B] = true, -- DOC: 3.3.2.3.8 Subscription Identifier
|
||
[0x03] = true, -- DOC: 3.3.2.3.9 Content Type
|
||
},
|
||
will = {
|
||
[0x18] = true, -- DOC: 3.1.3.2.2 Will Delay Interval
|
||
[0x01] = true, -- DOC: 3.1.3.2.3 Payload Format Indicator
|
||
[0x02] = true, -- DOC: 3.1.3.2.4 Message Expiry Interval
|
||
[0x03] = true, -- DOC: 3.1.3.2.5 Content Type
|
||
[0x08] = true, -- DOC: 3.1.3.2.6 Response Topic
|
||
[0x09] = true, -- DOC: 3.1.3.2.7 Correlation Data
|
||
[0x26] = true, -- DOC: 3.1.3.2.8 User Property
|
||
},
|
||
[packet_type.PUBACK] = {
|
||
[0x1F] = true, -- DOC: 3.4.2.2.2 Reason String
|
||
[0x26] = true, -- DOC: 3.4.2.2.3 User Property
|
||
},
|
||
[packet_type.PUBREC] = {
|
||
[0x1F] = true, -- DOC: 3.5.2.2.2 Reason String
|
||
[0x26] = true, -- DOC: 3.5.2.2.3 User Property
|
||
},
|
||
[packet_type.PUBREL] = {
|
||
[0x1F] = true, -- DOC: 3.6.2.2.2 Reason String
|
||
[0x26] = true, -- DOC: 3.6.2.2.3 User Property
|
||
},
|
||
[packet_type.PUBCOMP] = {
|
||
[0x1F] = true, -- DOC: 3.7.2.2.2 Reason String
|
||
[0x26] = true, -- DOC: 3.7.2.2.3 User Property
|
||
},
|
||
[packet_type.SUBSCRIBE] = {
|
||
[0x0B] = { multiple=false }, -- DOC: 3.8.2.1.2 Subscription Identifier -- DOC: It is a Protocol Error to include the Subscription Identifier more than once.
|
||
[0x26] = true, -- DOC: 3.8.2.1.3 User Property
|
||
},
|
||
[packet_type.SUBACK] = {
|
||
[0x1F] = true, -- DOC: 3.9.2.1.2 Reason String
|
||
[0x26] = true, -- DOC: 3.9.2.1.3 User Property
|
||
},
|
||
[packet_type.UNSUBSCRIBE] = {
|
||
[0x26] = true, -- DOC: 3.10.2.1.2 User Property
|
||
},
|
||
[packet_type.UNSUBACK] = {
|
||
[0x1F] = true, -- DOC: 3.11.2.1.2 Reason String
|
||
[0x26] = true, -- DOC: 3.11.2.1.3 User Property
|
||
},
|
||
-- NOTE: PINGREQ (3.12), PINGRESP (3.13) has no properties
|
||
[packet_type.DISCONNECT] = {
|
||
[0x11] = true, -- DOC: 3.14.2.2.2 Session Expiry Interval
|
||
[0x1F] = true, -- DOC: 3.14.2.2.3 Reason String
|
||
[0x26] = true, -- DOC: 3.14.2.2.4 User Property
|
||
[0x1C] = true, -- DOC: 3.14.2.2.5 Server Reference
|
||
},
|
||
[packet_type.AUTH] = {
|
||
[0x15] = true, -- DOC: 3.15.2.2.2 Authentication Method
|
||
[0x16] = true, -- DOC: 3.15.2.2.3 Authentication Data
|
||
[0x1F] = true, -- DOC: 3.15.2.2.4 Reason String
|
||
[0x26] = true, -- DOC: 3.15.2.2.5 User Property
|
||
},
|
||
}
|
||
|
||
-- Create properties field for various control packets, DOC: 2.2.2 Properties
|
||
local function make_properties(ptype, args)
|
||
local allowed = assert(allowed_properties[ptype], "invalid packet type to detect allowed properties")
|
||
local props = ""
|
||
local uprop_id = properties.user_property
|
||
-- writing known properties
|
||
if args.properties ~= nil then
|
||
assert(type(args.properties) == "table", "expecting .properties to be a table")
|
||
-- validate all properties and append them to order list
|
||
local order = {}
|
||
for name, value in sortedpairs(args.properties) do
|
||
assert(type(name) == "string", "expecting property name to be a string: "..tostring(name))
|
||
-- detect property identifier and check it's allowed for that packet type
|
||
local prop_id = assert(properties[name], "unknown property: "..tostring(name))
|
||
assert(prop_id ~= uprop_id, "user properties should be passed in .user_properties table")
|
||
if not allowed[prop_id] then
|
||
error("property "..name.." is not allowed for packet type "..packet_type[ptype])
|
||
end
|
||
order[#order + 1] = { prop_id, name, value }
|
||
end
|
||
-- sort props in the identifier ascending order
|
||
tbl_sort(order, function(a, b) return a[1] < b[1] end)
|
||
for _, item in ipairs(order) do
|
||
local prop_id, name, value = unpack(item)
|
||
if property_multiple[prop_id] then
|
||
assert(type(value) == "table", "expecting list-table for property with multiple value")
|
||
assert(#value == 1, "only one value for multiple-property supported")
|
||
value = value[1]
|
||
end
|
||
-- make property data
|
||
local ok, val = pcall(property_make[prop_id], value)
|
||
if not ok then
|
||
error("invalid property value: "..name.." = "..tostring(value)..": "..tostring(val))
|
||
end
|
||
local prop = combine(
|
||
str_char(make_var_length(prop_id)),
|
||
val
|
||
)
|
||
-- and append it to props
|
||
if type(props) == "string" then
|
||
props = combine(prop)
|
||
else
|
||
props:append(prop)
|
||
end
|
||
end
|
||
end
|
||
-- writing userproperties
|
||
if args.user_properties ~= nil then
|
||
assert(type(args.user_properties) == "table", "expecting .user_properties to be a table")
|
||
assert(allowed[uprop_id], "user_property is not allowed for packet type "..ptype)
|
||
local order = {}
|
||
local dups = {}
|
||
if args.user_properties[1] then
|
||
-- at first use array items as they given as {name, value} pairs with stable order
|
||
for i, pair in ipairs(args.user_properties) do
|
||
-- validate types for name and value
|
||
if type(pair) ~= "table" then
|
||
error(fmt("user property at position %d should be {name, value} table", i))
|
||
end
|
||
if type(pair[1]) ~= "string" then
|
||
error(fmt("user property name at position %d should be a string", i))
|
||
end
|
||
if type(pair[2]) ~= "string" then
|
||
error(fmt("user property '%s' value at position %d should be a string", pair[1], i))
|
||
end
|
||
order[i] = pair
|
||
dups[pair[1]] = pair[2]
|
||
end
|
||
end
|
||
-- now add the rest of user properties given as string table keys
|
||
for name, val in sortedpairs(args.user_properties) do
|
||
if type(name) ~= "number" then -- skipping number keys as they already added above
|
||
-- validate types for name and value
|
||
if type(name) ~= "string" then
|
||
error(fmt("user property name '%s' should be a string", name))
|
||
end
|
||
if type(val) ~= "string" then
|
||
error(fmt("user property '%s' value '%s' should be a string", name, val))
|
||
end
|
||
-- check that name+value key already added
|
||
if dups[name] ~= val then
|
||
order[#order + 1] = {name, val}
|
||
end
|
||
end
|
||
end
|
||
for _, pair in ipairs(order) do
|
||
local name = pair[1]
|
||
local value = pair[2]
|
||
-- make user property data
|
||
local prop = combine(
|
||
str_char(make_var_length(uprop_id)),
|
||
make_string(name),
|
||
make_string(value)
|
||
)
|
||
-- and append it to props
|
||
if type(props) == "string" then
|
||
props = combine(prop)
|
||
else
|
||
props:append(prop)
|
||
end
|
||
end
|
||
end
|
||
-- and combine properties with its length field
|
||
return combine(
|
||
str_char(make_var_length(props:len())), -- DOC: 2.2.2.1 Property Length
|
||
props -- DOC: 2.2.2.2 Property
|
||
)
|
||
end
|
||
|
||
-- Create CONNECT packet, DOC: 3.1 CONNECT – Connection Request
|
||
local function make_packet_connect(args)
|
||
-- check args
|
||
assert(type(args.id) == "string", "expecting .id to be a string with MQTT client id")
|
||
-- DOC: 3.1.2.10 Keep Alive
|
||
local keep_alive_ival = 0
|
||
if args.keep_alive then
|
||
assert(type(args.keep_alive) == "number")
|
||
keep_alive_ival = args.keep_alive
|
||
end
|
||
-- DOC: 3.1.2.11 CONNECT Properties
|
||
local props = make_properties(packet_type.CONNECT, args)
|
||
-- DOC: 3.1.2 CONNECT Variable Header
|
||
local variable_header = combine(
|
||
make_string("MQTT"), -- DOC: 3.1.2.1 Protocol Name
|
||
make_uint8(5), -- DOC: 3.1.2.2 Protocol Version (5 is for MQTT v5.0)
|
||
make_connect_flags(args), -- DOC: 3.1.2.3 Connect Flags
|
||
make_uint16(keep_alive_ival), -- DOC: 3.1.2.10 Keep Alive
|
||
props -- DOC: 3.1.2.11 CONNECT Properties
|
||
)
|
||
-- DOC: 3.1.3 CONNECT Payload
|
||
-- DOC: 3.1.3.1 Client Identifier (ClientID)
|
||
local payload = combine(
|
||
make_string(args.id)
|
||
)
|
||
if args.will then
|
||
-- DOC: 3.1.3.2 Will Properties
|
||
payload:append(make_properties("will", args.will))
|
||
-- DOC: 3.1.3.3 Will Topic
|
||
assert(type(args.will.topic) == "string", "expecting will.topic to be a string")
|
||
payload:append(make_string(args.will.topic))
|
||
-- DOC: 3.1.3.4 Will Payload
|
||
assert(args.will.payload == nil or type(args.will.payload) == "string", "expecting will.payload to be a string or nil")
|
||
payload:append(make_string(args.will.payload))
|
||
end
|
||
if args.username then
|
||
-- DOC: 3.1.3.5 User Name
|
||
payload:append(make_string(args.username))
|
||
if args.password then
|
||
-- DOC: 3.1.3.6 Password
|
||
payload:append(make_string(args.password))
|
||
end
|
||
end
|
||
-- DOC: 3.1.1 Fixed header
|
||
local header = make_header(packet_type.CONNECT, 0, variable_header:len() + payload:len())
|
||
return combine(header, variable_header, payload)
|
||
end
|
||
|
||
-- Create CONNACK packet, DOC: 3.2 CONNACK – Connect acknowledgement
|
||
local function make_packet_connack(args)
|
||
-- check args
|
||
assert(type(args.sp) == "boolean", "expecting .sp to be a boolean with Session Present flag")
|
||
assert(type(args.rc) == "number", "expecting .rc to be a number with Connect Reason Code")
|
||
-- DOC: 3.2.2 CONNACK Variable Header
|
||
local props = make_properties(packet_type.CONNACK, args)
|
||
local variable_header = combine(
|
||
make_uint8(args.sp and 1 or 0), -- DOC: 3.2.2.1.1 Session Present
|
||
make_uint8(args.rc), -- DOC: 3.2.2.2 Connect Reason Code
|
||
props -- DOC: 3.2.2.3 CONNACK Properties
|
||
)
|
||
-- DOC: 3.2.3 CONNACK Payload
|
||
-- DOC: The CONNACK packet has no Payload.
|
||
-- DOC: 3.2.1 CONNACK Fixed Header
|
||
local header = make_header(packet_type.CONNACK, 0, variable_header:len())
|
||
return combine(header, variable_header)
|
||
end
|
||
|
||
-- Create PUBLISH packet, DOC: 3.3 PUBLISH – Publish message
|
||
local function make_packet_publish(args)
|
||
-- check args
|
||
assert(type(args.topic) == "string", "expecting .topic to be a string")
|
||
if args.payload ~= nil then
|
||
assert(type(args.payload) == "string", "expecting .payload to be a string")
|
||
end
|
||
if args.qos ~= nil then
|
||
assert(type(args.qos) == "number", "expecting .qos to be a number")
|
||
assert(check_qos(args.qos), "expecting .qos to be a valid QoS value")
|
||
end
|
||
if args.retain ~= nil then
|
||
assert(type(args.retain) == "boolean", "expecting .retain to be a boolean")
|
||
end
|
||
if args.dup ~= nil then
|
||
assert(type(args.dup) == "boolean", "expecting .dup to be a boolean")
|
||
end
|
||
|
||
-- DOC: 3.3.1 PUBLISH Fixed Header
|
||
local flags = 0
|
||
-- 3.3.1.3 RETAIN
|
||
if args.retain then
|
||
flags = bor(flags, 0x1)
|
||
end
|
||
-- DOC: 3.3.1.2 QoS
|
||
flags = bor(flags, lshift(args.qos or 0, 1))
|
||
-- DOC: 3.3.1.1 DUP
|
||
if args.dup then
|
||
flags = bor(flags, lshift(1, 3))
|
||
end
|
||
-- DOC: 3.3.2 PUBLISH Variable Header
|
||
local variable_header = combine(
|
||
make_string(args.topic)
|
||
)
|
||
-- DOC: 3.3.2.2 Packet Identifier
|
||
if args.qos and args.qos > 0 then
|
||
assert(type(args.packet_id) == "number", "expecting .packet_id to be a number")
|
||
assert(check_packet_id(args.packet_id), "expecting .packet_id to be a valid Packet Identifier")
|
||
variable_header:append(make_uint16(args.packet_id))
|
||
end
|
||
-- DOC: 3.3.2.3 PUBLISH Properties
|
||
variable_header:append(make_properties(packet_type.PUBLISH, args))
|
||
-- DOC: 3.3.3 PUBLISH Payload
|
||
local payload
|
||
if args.payload then
|
||
payload = args.payload
|
||
else
|
||
payload = ""
|
||
end
|
||
-- DOC: 3.3.1 Fixed header
|
||
local header = make_header(packet_type.PUBLISH, flags, variable_header:len() + payload:len())
|
||
return combine(header, variable_header, payload)
|
||
end
|
||
|
||
-- Create PUBACK packet, DOC: 3.4 PUBACK – Publish acknowledgement
|
||
local function make_packet_puback(args)
|
||
-- check args
|
||
assert(type(args.packet_id) == "number", "expecting .packet_id to be a number")
|
||
assert(check_packet_id(args.packet_id), "expecting .packet_id to be a valid Packet Identifier")
|
||
assert(type(args.rc) == "number", "expecting .rc to be a number")
|
||
-- DOC: 3.4.2 PUBACK Variable Header
|
||
local variable_header = combine(make_uint16(args.packet_id))
|
||
local props = make_properties(packet_type.PUBACK, args) -- DOC: 3.4.2.2 PUBACK Properties
|
||
-- DOC: The Reason Code and Property Length can be omitted if the Reason Code is 0x00 (Success) and there are no Properties. In this case the PUBACK has a Remaining Length of 2.
|
||
if props:len() > 1 or args.rc ~= 0 then
|
||
variable_header:append(make_uint8(args.rc)) -- DOC: 3.4.2.1 PUBACK Reason Code
|
||
variable_header:append(props) -- DOC: 3.4.2.2 PUBACK Properties
|
||
end
|
||
-- DOC: 3.4.1 PUBACK Fixed Header
|
||
local header = make_header(packet_type.PUBACK, 0, variable_header:len())
|
||
return combine(header, variable_header)
|
||
end
|
||
|
||
-- Create PUBREC packet, DOC: 3.5 PUBREC – Publish received (QoS 2 delivery part 1)
|
||
local function make_packet_pubrec(args)
|
||
-- check args
|
||
assert(type(args.packet_id) == "number", "expecting .packet_id to be a number")
|
||
assert(check_packet_id(args.packet_id), "expecting .packet_id to be a valid Packet Identifier")
|
||
assert(type(args.rc) == "number", "expecting .rc to be a number")
|
||
-- DOC: 3.5.2 PUBREC Variable Header
|
||
local variable_header = combine(make_uint16(args.packet_id))
|
||
local props = make_properties(packet_type.PUBREC, args) -- DOC: 3.5.2.2 PUBREC Properties
|
||
-- DOC: The Reason Code and Property Length can be omitted if the Reason Code is 0x00 (Success) and there are no Properties. In this case the PUBREC has a Remaining Length of 2.
|
||
if props:len() > 1 or args.rc ~= 0 then
|
||
variable_header:append(make_uint8(args.rc)) -- DOC: 3.5.2.1 PUBREC Reason Code
|
||
variable_header:append(props) -- DOC: 3.5.2.2 PUBREC Properties
|
||
end
|
||
-- DOC: 3.5.1 PUBREC Fixed Header
|
||
local header = make_header(packet_type.PUBREC, 0, variable_header:len())
|
||
return combine(header, variable_header)
|
||
end
|
||
|
||
-- Create PUBREL packet, DOC: 3.6 PUBREL – Publish release (QoS 2 delivery part 2)
|
||
local function make_packet_pubrel(args)
|
||
-- check args
|
||
assert(type(args.packet_id) == "number", "expecting .packet_id to be a number")
|
||
assert(check_packet_id(args.packet_id), "expecting .packet_id to be a valid Packet Identifier")
|
||
assert(type(args.rc) == "number", "expecting .rc to be a number")
|
||
-- DOC: 3.6.2 PUBREL Variable Header
|
||
local variable_header = combine(make_uint16(args.packet_id))
|
||
local props = make_properties(packet_type.PUBREL, args) -- DOC: 3.6.2.2 PUBREL Properties
|
||
-- DOC: The Reason Code and Property Length can be omitted if the Reason Code is 0x00 (Success) and there are no Properties. In this case the PUBREL has a Remaining Length of 2.
|
||
if props:len() > 1 or args.rc ~= 0 then
|
||
variable_header:append(make_uint8(args.rc)) -- DOC: 3.6.2.1 PUBREL Reason Code
|
||
variable_header:append(props) -- DOC: 3.6.2.2 PUBREL Properties
|
||
end
|
||
-- DOC: 3.6.1 PUBREL Fixed Header
|
||
local header = make_header(packet_type.PUBREL, 2, variable_header:len()) -- flags: fixed 0010 bits, DOC: Figure 3‑14 – PUBREL packet Fixed Header
|
||
return combine(header, variable_header)
|
||
end
|
||
|
||
-- Create PUBCOMP packet, DOC: 3.7 PUBCOMP – Publish complete (QoS 2 delivery part 3)
|
||
local function make_packet_pubcomp(args)
|
||
-- check args
|
||
assert(type(args.packet_id) == "number", "expecting .packet_id to be a number")
|
||
assert(check_packet_id(args.packet_id), "expecting .packet_id to be a valid Packet Identifier")
|
||
assert(type(args.rc) == "number", "expecting .rc to be a number")
|
||
-- DOC: 3.7.2 PUBCOMP Variable Header
|
||
local variable_header = combine(make_uint16(args.packet_id))
|
||
local props = make_properties(packet_type.PUBCOMP, args) -- DOC: 3.7.2.2 PUBCOMP Properties
|
||
-- DOC: The Reason Code and Property Length can be omitted if the Reason Code is 0x00 (Success) and there are no Properties. In this case the PUBCOMP has a Remaining Length of 2.
|
||
if props:len() > 1 or args.rc ~= 0 then
|
||
variable_header:append(make_uint8(args.rc)) -- DOC: 3.7.2.1 PUBCOMP Reason Code
|
||
variable_header:append(props) -- DOC: 3.7.2.2 PUBCOMP Properties
|
||
end
|
||
-- DOC: 3.7.1 PUBCOMP Fixed Header
|
||
local header = make_header(packet_type.PUBCOMP, 0, variable_header:len())
|
||
return combine(header, variable_header)
|
||
end
|
||
|
||
-- Create SUBSCRIBE packet, DOC: 3.8 SUBSCRIBE - Subscribe request
|
||
local function make_packet_subscribe(args)
|
||
-- check args
|
||
assert(type(args.packet_id) == "number", "expecting .packet_id to be a number")
|
||
assert(check_packet_id(args.packet_id), "expecting .packet_id to be a valid Packet Identifier")
|
||
assert(type(args.subscriptions) == "table", "expecting .subscriptions to be a table")
|
||
assert(#args.subscriptions > 0, "expecting .subscriptions to be a non-empty array")
|
||
-- DOC: 3.8.2 SUBSCRIBE Variable Header
|
||
local variable_header = combine(
|
||
make_uint16(args.packet_id),
|
||
make_properties(packet_type.SUBSCRIBE, args) -- DOC: 3.8.2.1 SUBSCRIBE Properties
|
||
)
|
||
-- DOC: 3.8.3 SUBSCRIBE Payload
|
||
local payload = combine()
|
||
for i, subscription in ipairs(args.subscriptions) do
|
||
assert(type(subscription) == "table", "expecting .subscriptions["..i.."] to be a table")
|
||
assert(type(subscription.topic) == "string", "expecting .subscriptions["..i.."].topic to be a string")
|
||
if subscription.qos ~= nil then
|
||
assert(type(subscription.qos) == "number", "expecting .subscriptions["..i.."].qos to be a number")
|
||
assert(check_qos(subscription.qos), "expecting .subscriptions["..i.."].qos to be a valid QoS value")
|
||
end
|
||
if subscription.retain_as_published ~= nil then
|
||
assert(type(subscription.retain_as_published) == "boolean", "expecting .subscriptions["..i.."].retain_as_published to be a boolean")
|
||
end
|
||
if subscription.retain_handling ~= nil then
|
||
assert(type(subscription.retain_handling) == "number", "expecting .subscriptions["..i.."].retain_handling to be a number")
|
||
assert(check_retain_handling(subscription.retain_handling), "expecting .subscriptions["..i.."].retain_handling to be a valid Retain Handling option")
|
||
end
|
||
-- DOC: 3.8.3.1 Subscription Options
|
||
local so = subscription.qos or 0
|
||
if subscription.no_local then
|
||
so = bor(so, 4) -- set Bit 2
|
||
end
|
||
if subscription.retain_as_published then
|
||
so = bor(so, 8) -- set Bit 3
|
||
end
|
||
if subscription.retain_handling then
|
||
so = bor(so, lshift(subscription.retain_handling, 4)) -- set Bit 4 and 5
|
||
end
|
||
payload:append(make_string(subscription.topic))
|
||
payload:append(make_uint8(so))
|
||
end
|
||
-- DOC: 3.8.1 SUBSCRIBE Fixed Header
|
||
local header = make_header(packet_type.SUBSCRIBE, 2, variable_header:len() + payload:len()) -- flags: fixed 0010 bits, DOC: Figure 3‑18 SUBSCRIBE packet Fixed Header
|
||
return combine(header, variable_header, payload)
|
||
end
|
||
|
||
-- Create SUBACK packet, DOC: 3.9 SUBACK – Subscribe acknowledgement
|
||
local function make_packet_suback(args)
|
||
-- check args
|
||
assert(type(args.packet_id) == "number", "expecting .packet_id to be a number")
|
||
assert(check_packet_id(args.packet_id), "expecting .packet_id to be a valid Packet Identifier")
|
||
assert(type(args.rc) == "table", "expecting .rc to be a table")
|
||
assert(#args.rc > 0, "expecting .rc to be a non-empty array")
|
||
-- DOC: 3.9.2 SUBACK Variable Header
|
||
local variable_header = combine(
|
||
make_uint16(args.packet_id),
|
||
make_properties(packet_type.SUBACK, args) -- DOC: 3.9.2.1 SUBACK Properties
|
||
)
|
||
-- DOC: 3.9.3 SUBACK Payload
|
||
local payload = combine()
|
||
for i, rc in ipairs(args.rc) do
|
||
assert(type(rc) == "number", "expecting .rc["..i.."] to be a number")
|
||
assert(rc >= 0 and rc <= 255, "expecting .rc["..i.."] to be in range [0, 255]")
|
||
payload:append(make_uint8(rc))
|
||
end
|
||
-- DOC: 3.9.1 SUBACK Fixed Header
|
||
local header = make_header(packet_type.SUBACK, 0, variable_header:len() + payload:len()) -- NOTE: fixed flags value 0x0
|
||
return combine(header, variable_header, payload)
|
||
end
|
||
|
||
-- Create UNSUBSCRIBE packet, DOC: 3.10 UNSUBSCRIBE – Unsubscribe request
|
||
local function make_packet_unsubscribe(args)
|
||
-- check args
|
||
assert(type(args.packet_id) == "number", "expecting .packet_id to be a number")
|
||
assert(check_packet_id(args.packet_id), "expecting .packet_id to be a valid Packet Identifier")
|
||
assert(type(args.subscriptions) == "table", "expecting .subscriptions to be a table")
|
||
assert(#args.subscriptions > 0, "expecting .subscriptions to be a non-empty array")
|
||
-- DOC: 3.10.2 UNSUBSCRIBE Variable Header
|
||
local variable_header = combine(
|
||
make_uint16(args.packet_id),
|
||
make_properties(packet_type.UNSUBSCRIBE, args) -- DOC: 3.10.2.1 UNSUBSCRIBE Properties
|
||
)
|
||
-- DOC: 3.10.3 UNSUBSCRIBE Payload
|
||
local payload = combine()
|
||
for i, subscription in ipairs(args.subscriptions) do
|
||
assert(type(subscription) == "string", "expecting .subscriptions["..i.."] to be a string")
|
||
payload:append(make_string(subscription))
|
||
end
|
||
-- DOC: 3.10.1 UNSUBSCRIBE Fixed Header
|
||
local header = make_header(packet_type.UNSUBSCRIBE, 2, variable_header:len() + payload:len()) -- flags: fixed 0010 bits, DOC: Figure 3.28 – UNSUBSCRIBE packet Fixed Header
|
||
return combine(header, variable_header, payload)
|
||
end
|
||
|
||
-- Create UNSUBACK packet, DOC: 3.11 UNSUBACK – Unsubscribe acknowledgement
|
||
local function make_packet_unsuback(args)
|
||
-- check args
|
||
assert(type(args.packet_id) == "number", "expecting .packet_id to be a number")
|
||
assert(check_packet_id(args.packet_id), "expecting .packet_id to be a valid Packet Identifier")
|
||
assert(type(args.rc) == "table", "expecting .rc to be a table")
|
||
assert(#args.rc > 0, "expecting .rc to be a non-empty array")
|
||
-- DOC: 3.11.2 UNSUBACK Variable Header
|
||
local variable_header = combine(
|
||
make_uint16(args.packet_id),
|
||
make_properties(packet_type.UNSUBACK, args) -- DOC: 3.11.2.1 UNSUBACK Properties
|
||
)
|
||
-- DOC: 3.11.3 UNSUBACK Payload
|
||
local payload = combine()
|
||
for i, rc in ipairs(args.rc) do
|
||
assert(type(rc) == "number", "expecting .rc["..i.."] to be a number")
|
||
assert(rc >= 0 and rc <= 255, "expecting .rc["..i.."] to be in range [0, 255]")
|
||
payload:append(make_uint8(rc))
|
||
end
|
||
-- DOC: 3.11.1 UNSUBACK Fixed Header
|
||
local header = make_header(packet_type.UNSUBACK, 0, variable_header:len() + payload:len()) -- NOTE: fixed flags value 0x0
|
||
return combine(header, variable_header, payload)
|
||
end
|
||
|
||
-- Create DISCONNECT packet, DOC: 3.14 DISCONNECT – Disconnect notification
|
||
local function make_packet_disconnect(args)
|
||
-- check args
|
||
assert(type(args.rc) == "number", "expecting .rc to be a number")
|
||
-- DOC: 3.14.2 DISCONNECT Variable Header
|
||
local variable_header = combine()
|
||
local props = make_properties(packet_type.DISCONNECT, args) -- DOC: 3.14.2.2 DISCONNECT Properties
|
||
-- DOC: The Reason Code and Property Length can be omitted if the Reason Code is 0x00 (Normal disconnecton) and there are no Properties. In this case the DISCONNECT has a Remaining Length of 0.
|
||
if props:len() > 1 or args.rc ~= 0 then
|
||
variable_header:append(make_uint8(args.rc)) -- DOC: 3.14.2.1 Disconnect Reason Code
|
||
variable_header:append(props) -- DOC: 3.14.2.2 DISCONNECT Properties
|
||
end
|
||
-- DOC: 3.14.1 DISCONNECT Fixed Header
|
||
local header = make_header(packet_type.DISCONNECT, 0, variable_header:len()) -- flags: 0
|
||
return combine(header, variable_header)
|
||
end
|
||
|
||
-- Create AUTH packet, DOC: 3.15 AUTH – Authentication exchange
|
||
local function make_packet_auth(args)
|
||
-- check args
|
||
assert(type(args.rc) == "number", "expecting .rc to be a number")
|
||
-- DOC: 3.15.2 AUTH Variable Header
|
||
local variable_header = combine()
|
||
-- DOC: The Reason Code and Property Length can be omitted if the Reason Code is 0x00 (Success) and there are no Properties. In this case the AUTH has a Remaining Length of 0.
|
||
local props = make_properties(packet_type.AUTH, args) -- DOC: 3.15.2.2 AUTH Properties
|
||
if props:len() > 1 or args.rc ~= 0 then
|
||
variable_header:append(make_uint8(args.rc)) -- DOC: 3.15.2.1 Authenticate Reason Code
|
||
variable_header:append(props) -- DOC: 3.15.2.2 AUTH Properties
|
||
end
|
||
-- DOC: 3.15.1 AUTH Fixed Header
|
||
local header = make_header(packet_type.AUTH, 0, variable_header:len())
|
||
return combine(header, variable_header)
|
||
end
|
||
|
||
-- Create packet of given {type: number} in args
|
||
function protocol5.make_packet(args)
|
||
assert(type(args) == "table", "expecting args to be a table")
|
||
assert(type(args.type) == "number", "expecting .type number in args")
|
||
local ptype = args.type
|
||
if ptype == packet_type.CONNECT then -- 1
|
||
return make_packet_connect(args)
|
||
elseif ptype == packet_type.CONNACK then -- 3
|
||
return make_packet_connack(args)
|
||
elseif ptype == packet_type.PUBLISH then -- 3
|
||
return make_packet_publish(args)
|
||
elseif ptype == packet_type.PUBACK then -- 4
|
||
return make_packet_puback(args)
|
||
elseif ptype == packet_type.PUBREC then -- 5
|
||
return make_packet_pubrec(args)
|
||
elseif ptype == packet_type.PUBREL then -- 6
|
||
return make_packet_pubrel(args)
|
||
elseif ptype == packet_type.PUBCOMP then -- 7
|
||
return make_packet_pubcomp(args)
|
||
elseif ptype == packet_type.SUBSCRIBE then -- 8
|
||
return make_packet_subscribe(args)
|
||
elseif ptype == packet_type.SUBACK then -- 9
|
||
return make_packet_suback(args)
|
||
elseif ptype == packet_type.UNSUBSCRIBE then -- 10
|
||
return make_packet_unsubscribe(args)
|
||
elseif ptype == packet_type.UNSUBACK then -- 11
|
||
return make_packet_unsuback(args)
|
||
elseif ptype == packet_type.PINGREQ then -- 12
|
||
-- DOC: 3.12 PINGREQ – PING request
|
||
return combine("\192\000") -- 192 == 0xC0, type == 12, flags == 0
|
||
elseif ptype == packet_type.PINGRESP then -- 12
|
||
-- DOC: 3.13 PINGRESP – PING response
|
||
return combine("\208\000") -- 208 == 0xD0, type == 13, flags == 0
|
||
elseif ptype == packet_type.DISCONNECT then -- 14
|
||
return make_packet_disconnect(args)
|
||
elseif ptype == packet_type.AUTH then -- 15
|
||
return make_packet_auth(args)
|
||
else
|
||
error("unexpected packet type to make: "..ptype)
|
||
end
|
||
end
|
||
|
||
-- Parse properties using given read_data function for specified packet type
|
||
-- Result will be stored in packet.properties and packet.user_properties
|
||
-- Returns false plus string with error message on failure
|
||
-- Returns true on success
|
||
local function parse_properties(ptype, read_data, input, packet)
|
||
assert(type(read_data) == "function", "expecting read_data to be a function")
|
||
-- DOC: 2.2.2 Properties
|
||
-- parse Property Length
|
||
-- create read_func for parse_var_length and other parse functions, reading from data string instead of network connector
|
||
local len, err = parse_var_length(read_data)
|
||
if not len then
|
||
return false, "failed to parse properties length: "..err
|
||
end
|
||
-- check data contains enough bytes for reading properties
|
||
if input.available < len then
|
||
return false, "not enough data to parse properties of length "..len
|
||
end
|
||
-- ensure properties and user_properties are presented in packet
|
||
if not packet.properties then
|
||
packet.properties = {}
|
||
end
|
||
if not packet.user_properties then
|
||
packet.user_properties = {}
|
||
end
|
||
local uprops = packet.user_properties
|
||
-- parse allowed properties
|
||
local uprop_id = properties.user_property
|
||
local allowed = assert(allowed_properties[ptype], "no allowed properties for specified packet type: "..tostring(ptype))
|
||
local props_end = input[1] + len
|
||
while input[1] < props_end do
|
||
-- property id, DOC: 2.2.2.2 Property
|
||
local prop_id
|
||
prop_id, err = parse_var_length(read_data)
|
||
if not prop_id then
|
||
return false, "failed to parse property length: "..err
|
||
end
|
||
if not allowed[prop_id] then
|
||
if not allowed[prop_id] then
|
||
return false, "property "..tostring(properties[prop_id]).." ("..prop_id..") is not allowed for that packet type"
|
||
end
|
||
end
|
||
if prop_id == uprop_id then
|
||
-- parse name=value string pair
|
||
local name, value
|
||
name, err = parse_string(read_data)
|
||
if not name then
|
||
return false, "failed to parse user property name: "..err
|
||
end
|
||
value, err = parse_string(read_data)
|
||
if not value then
|
||
return false, "failed to parse user property value: "..err
|
||
end
|
||
local old_val = uprops[name]
|
||
if old_val ~= nil then
|
||
-- ensure uprops contains pairs with name = <old value>
|
||
local found = false
|
||
for _, pair in ipairs(uprops) do
|
||
if pair[1] == name and pair[2] == old_val then
|
||
found = true
|
||
break
|
||
end
|
||
end
|
||
if not found then
|
||
uprops[#uprops + 1] = {name, old_val}
|
||
end
|
||
uprops[#uprops + 1] = {name, value}
|
||
end
|
||
uprops[name] = value
|
||
else
|
||
-- parse property value according its identifier
|
||
local value
|
||
value, err = property_parse[prop_id](read_data)
|
||
if err then
|
||
return false, "failed to parse property "..prop_id.." value: "..err
|
||
end
|
||
if allowed[prop_id] ~= true then
|
||
if packet.properties[properties[prop_id]] ~= nil then
|
||
return false, "it is a Protocol Error to include the "..properties[prop_id].." ("..prop_id..") property more than once"
|
||
end
|
||
end
|
||
-- make an array of property values, if it's allowed to send multiple such properties
|
||
if property_multiple[prop_id] then
|
||
local curr = packet.properties[properties[prop_id]] or {}
|
||
curr[#curr + 1] = value
|
||
packet.properties[properties[prop_id]] = curr
|
||
else
|
||
packet.properties[properties[prop_id]] = value
|
||
end
|
||
end
|
||
end
|
||
return true
|
||
end
|
||
|
||
-- Parse CONNACK packet, DOC: 3.2 CONNACK – Connect acknowledgement
|
||
local function parse_packet_connack(ptype, flags, input)
|
||
-- DOC: 3.2.1 CONNACK Fixed Header
|
||
if flags ~= 0 then -- Reserved
|
||
return false, packet_type[ptype]..": unexpected flags value: "..flags
|
||
end
|
||
if input.available < 3 then
|
||
return false, packet_type[ptype]..": expecting data of length 3 bytes or more"
|
||
end
|
||
local read_data = input.read_func
|
||
-- DOC: 3.2.2 CONNACK Variable Header
|
||
-- DOC: 3.2.2.1.1 Session Present
|
||
-- DOC: 3.2.2.2 Connect Reason Code
|
||
local byte1, byte2 = parse_uint8(read_data), parse_uint8(read_data)
|
||
local sp = (band(byte1, 0x1) ~= 0)
|
||
local packet = setmetatable({type=ptype, sp=sp, rc=byte2}, connack_packet_mt)
|
||
-- DOC: 3.2.2.3 CONNACK Properties
|
||
local ok, err = parse_properties(ptype, read_data, input, packet)
|
||
if not ok then
|
||
return false, packet_type[ptype]..": failed to parse packet properties: "..err
|
||
end
|
||
return packet
|
||
end
|
||
|
||
-- Parse PUBLISH packet, DOC: 3.3 PUBLISH – Publish message
|
||
local function parse_packet_publish(ptype, flags, input)
|
||
-- DOC: 3.3.1 PUBLISH Fixed Header
|
||
-- DOC: 3.3.1.1 DUP
|
||
local dup = (band(flags, 0x8) ~= 0)
|
||
-- DOC: 3.3.1.2 QoS
|
||
local qos = band(rshift(flags, 1), 0x3)
|
||
-- DOC: 3.3.1.3 RETAIN
|
||
local retain = (band(flags, 0x1) ~= 0)
|
||
-- DOC: 3.3.2 PUBLISH Variable Header
|
||
-- DOC: 3.3.2.1 Topic Name
|
||
local read_data = input.read_func
|
||
local topic, err = parse_string(read_data)
|
||
if not topic then
|
||
return false, packet_type[ptype]..": failed to parse topic: "..err
|
||
end
|
||
-- DOC: 3.3.2.2 Packet Identifier
|
||
local packet_id, ok
|
||
if qos > 0 then
|
||
packet_id, err = parse_uint16(read_data)
|
||
if not packet_id then
|
||
return false, packet_type[ptype]..": failed to parse packet_id: "..err
|
||
end
|
||
end
|
||
-- DOC: 3.3.2.3 PUBLISH Properties
|
||
local packet = setmetatable({type=ptype, dup=dup, qos=qos, retain=retain, packet_id=packet_id, topic=topic}, packet_mt)
|
||
ok, err = parse_properties(ptype, read_data, input, packet)
|
||
if not ok then
|
||
return false, packet_type[ptype]..": failed to parse packet properties: "..err
|
||
end
|
||
if input.available > 0 then
|
||
-- DOC: 3.3.3 PUBLISH Payload
|
||
packet.payload = read_data(input.available)
|
||
end
|
||
return packet
|
||
end
|
||
|
||
-- Parse PUBACK packet, DOC: 3.4 PUBACK – Publish acknowledgement
|
||
local function parse_packet_puback(ptype, flags, input)
|
||
-- DOC: 3.4.1 PUBACK Fixed Header
|
||
if flags ~= 0 then -- Reserved
|
||
return false, packet_type[ptype]..": unexpected flags value: "..flags
|
||
end
|
||
local read_data = input.read_func
|
||
-- DOC: 3.4.2 PUBACK Variable Header
|
||
local packet_id, err = parse_uint16(read_data)
|
||
if not packet_id then
|
||
return false, packet_type[ptype]..": failed to parse packet_id: "..err
|
||
end
|
||
local packet = setmetatable({type=ptype, packet_id=packet_id, rc=0, properties={}, user_properties={}}, packet_mt)
|
||
if input.available > 0 then
|
||
-- DOC: 3.4.2.1 PUBACK Reason Code
|
||
local rc, ok
|
||
rc, err = parse_uint8(read_data)
|
||
if not rc then
|
||
return false, packet_type[ptype]..": failed to parse rc: "..err
|
||
end
|
||
packet.rc = rc
|
||
-- DOC: 3.4.2.2 PUBACK Properties
|
||
ok, err = parse_properties(ptype, read_data, input, packet)
|
||
if not ok then
|
||
return false, packet_type[ptype]..": failed to parse packet properties: "..err
|
||
end
|
||
end
|
||
return packet
|
||
end
|
||
|
||
-- Parse PUBREC packet, DOC: 3.5 PUBREC – Publish received (QoS 2 delivery part 1)
|
||
local function parse_packet_pubrec(ptype, flags, input)
|
||
-- DOC: 3.5.1 PUBREC Fixed Header
|
||
if flags ~= 0 then -- Reserved
|
||
return false, packet_type[ptype]..": unexpected flags value: "..flags
|
||
end
|
||
local read_data = input.read_func
|
||
-- DOC: 3.5.2 PUBREC Variable Header
|
||
local packet_id, err = parse_uint16(read_data)
|
||
if not packet_id then
|
||
return false, packet_type[ptype]..": failed to parse packet_id: "..err
|
||
end
|
||
local packet = setmetatable({type=ptype, packet_id=packet_id, rc=0, properties={}, user_properties={}}, packet_mt)
|
||
if input.available > 0 then
|
||
-- DOC: 3.5.2.1 PUBREC Reason Code
|
||
local rc
|
||
rc, err = parse_uint8(read_data)
|
||
if not rc then
|
||
return false, packet_type[ptype]..": failed to parse rc: "..err
|
||
end
|
||
packet.rc = rc
|
||
-- DOC: 3.5.2.2 PUBREC Properties
|
||
local ok
|
||
ok, err = parse_properties(ptype, read_data, input, packet)
|
||
if not ok then
|
||
return false, packet_type[ptype]..": failed to parse packet properties: "..err
|
||
end
|
||
end
|
||
return packet
|
||
end
|
||
|
||
-- Parse PUBREL packet, DOC: 3.6 PUBREL – Publish release (QoS 2 delivery part 2)
|
||
local function parse_packet_pubrel(ptype, flags, input)
|
||
-- DOC: 3.6.1 PUBREL Fixed Header
|
||
if flags ~= 2 then -- Reserved
|
||
return false, packet_type[ptype]..": unexpected flags value: "..flags
|
||
end
|
||
local read_data = input.read_func
|
||
-- DOC: 3.6.2 PUBREL Variable Header
|
||
local packet_id, err = parse_uint16(read_data)
|
||
if not packet_id then
|
||
return false, packet_type[ptype]..": failed to parse packet_id: "..err
|
||
end
|
||
local packet = setmetatable({type=ptype, packet_id=packet_id, rc=0, properties={}, user_properties={}}, packet_mt)
|
||
if input.available > 0 then
|
||
-- DOC: 3.6.2.1 PUBREL Reason Code
|
||
local rc
|
||
rc, err = parse_uint8(read_data)
|
||
if not rc then
|
||
return false, packet_type[ptype]..": failed to parse rc: "..err
|
||
end
|
||
packet.rc = rc
|
||
-- DOC: 3.6.2.2 PUBREL Properties
|
||
local ok
|
||
ok, err = parse_properties(ptype, read_data, input, packet)
|
||
if not ok then
|
||
return false, packet_type[ptype]..": failed to parse packet properties: "..err
|
||
end
|
||
end
|
||
return packet
|
||
end
|
||
|
||
-- Parse PUBCOMP packet, DOC: 3.7 PUBCOMP – Publish complete (QoS 2 delivery part 3)
|
||
local function parse_packet_pubcomp(ptype, flags, input)
|
||
-- DOC: 3.7.1 PUBCOMP Fixed Header
|
||
if flags ~= 0 then -- Reserved
|
||
return false, packet_type[ptype]..": unexpected flags value: "..flags
|
||
end
|
||
local read_data = input.read_func
|
||
-- DOC: 3.7.2 PUBCOMP Variable Header
|
||
local packet_id, err = parse_uint16(read_data)
|
||
if not packet_id then
|
||
return false, packet_type[ptype]..": failed to parse packet_id: "..err
|
||
end
|
||
local packet = setmetatable({type=ptype, packet_id=packet_id, rc=0, properties={}, user_properties={}}, packet_mt)
|
||
if input.available > 0 then
|
||
-- DOC: 3.7.2.1 PUBCOMP Reason Code
|
||
local rc
|
||
rc, err = parse_uint8(read_data)
|
||
if not rc then
|
||
return false, packet_type[ptype]..": failed to parse rc: "..err
|
||
end
|
||
packet.rc = rc
|
||
-- DOC: 3.7.2.2 PUBCOMP Properties
|
||
local ok
|
||
ok, err = parse_properties(ptype, read_data, input, packet)
|
||
if not ok then
|
||
return false, packet_type[ptype]..": failed to parse packet properties: "..err
|
||
end
|
||
end
|
||
return packet
|
||
end
|
||
|
||
-- Parse SUBSCRIBE packet, DOC: 3.8 SUBSCRIBE - Subscribe request
|
||
local function parse_packet_subscribe(ptype, flags, input)
|
||
-- DOC: 3.8.1 SUBSCRIBE Fixed Header
|
||
if flags ~= 2 then -- Reserved
|
||
return false, packet_type[ptype]..": unexpected flags value: "..flags
|
||
end
|
||
local read_data = input.read_func
|
||
-- DOC: 3.8.2 SUBSCRIBE Variable Header
|
||
local packet_id, err = parse_uint16(read_data)
|
||
if not packet_id then
|
||
return false, packet_type[ptype]..": failed to parse packet_id: "..err
|
||
end
|
||
local packet = setmetatable({type=ptype, packet_id=packet_id, properties={}, user_properties={}}, packet_mt)
|
||
-- DOC: 3.8.2.1 SUBSCRIBE Properties
|
||
local ok
|
||
ok, err = parse_properties(ptype, read_data, input, packet)
|
||
if not ok then
|
||
return false, packet_type[ptype]..": failed to parse packet properties: "..err
|
||
end
|
||
-- DOC: 3.8.3 SUBSCRIBE Payload
|
||
if input.available == 0 then
|
||
-- DOC: A SUBSCRIBE packet with no Payload is a Protocol Error.
|
||
return false, packet_type[ptype]..": empty subscriptions list"
|
||
end
|
||
local subscriptions = {}
|
||
while input.available > 0 do
|
||
local topic_filter
|
||
topic_filter, err = parse_string(input.read_func)
|
||
if not topic_filter then
|
||
return false, packet_type[ptype]..": failed to parse SUBSCRIBE topic filter: "..err
|
||
end
|
||
-- DOC: 3.8.3.1 Subscription Options
|
||
local subscription_options
|
||
subscription_options, err = parse_uint8(input.read_func)
|
||
if not subscription_options then
|
||
return false, packet_type[ptype]..": failed to parse subscription_options: "..err
|
||
end
|
||
subscriptions[#subscriptions + 1] = {
|
||
topic = topic_filter,
|
||
qos = band(subscription_options, 0x3),
|
||
no_local = band(subscription_options, 0x4) ~= 0,
|
||
retain_as_published = band(subscription_options, 0x8) ~= 0, -- Retain As Published
|
||
retain_handling = band(rshift(subscription_options, 4), 0x3), -- Retain Handling
|
||
}
|
||
end
|
||
packet.subscriptions = subscriptions
|
||
return packet
|
||
end
|
||
|
||
-- SUBACK return codes/reason code strings
|
||
-- DOC: Table 3‑8 - Subscribe Reason Codes
|
||
local suback_rc = {
|
||
[0x00] = "Granted QoS 0",
|
||
[0x01] = "Granted QoS 1",
|
||
[0x02] = "Granted QoS 2",
|
||
[0x80] = "Unspecified error",
|
||
[0x83] = "Implementation specific error",
|
||
[0x87] = "Not authorized",
|
||
[0x8F] = "Topic Filter invalid",
|
||
[0x91] = "Packet Identifier in use",
|
||
[0x97] = "Quota exceeded",
|
||
[0x9E] = "Shared Subscriptions not supported",
|
||
[0xA1] = "Subscription Identifiers not supported",
|
||
[0xA2] = "Wildcard Subscriptions not supported",
|
||
}
|
||
protocol5.suback_rc = suback_rc
|
||
|
||
--- Parsed SUBACK packet metatable
|
||
local suback_packet_mt = {
|
||
__tostring = protocol.packet_tostring, -- packet-to-human-readable-string conversion metamethod using protocol.packet_tostring()
|
||
reason_strings = function(self) -- Returns reason strings for the SUBACK packet according to its rc field
|
||
local human_readable = {}
|
||
for i, rc in ipairs(self.rc) do
|
||
local reason_string = suback_rc[rc]
|
||
if reason_string then
|
||
human_readable[i] = reason_string
|
||
else
|
||
human_readable[i] = "Unknown: "..tostring(rc)
|
||
end
|
||
end
|
||
return human_readable
|
||
end,
|
||
}
|
||
suback_packet_mt.__index = suback_packet_mt
|
||
protocol5.suback_packet_mt = suback_packet_mt
|
||
|
||
-- Parse SUBACK packet, DOC: 3.9 SUBACK – Subscribe acknowledgement
|
||
local function parse_packet_suback(ptype, flags, input)
|
||
-- DOC: 3.9.1 SUBACK Fixed Header
|
||
if flags ~= 0 then -- Reserved
|
||
return false, packet_type[ptype]..": unexpected flags value: "..flags
|
||
end
|
||
local read_data = input.read_func
|
||
-- DOC: 3.9.2 SUBACK Variable Header
|
||
local packet_id, err = parse_uint16(read_data)
|
||
if not packet_id then
|
||
return false, packet_type[ptype]..": failed to parse packet_id: "..err
|
||
end
|
||
-- DOC: 3.9.2.1 SUBACK Properties
|
||
local packet = setmetatable({type=ptype, packet_id=packet_id}, suback_packet_mt)
|
||
local ok
|
||
ok, err = parse_properties(ptype, read_data, input, packet)
|
||
if not ok then
|
||
return false, packet_type[ptype]..": failed to parse packet properties: "..err
|
||
end
|
||
-- DOC: 3.9.3 SUBACK Payload
|
||
local rcs = {}
|
||
while input.available > 0 do
|
||
local rc
|
||
rc, err = parse_uint8(read_data)
|
||
if not rc then
|
||
return false, packet_type[ptype]..": failed to parse reason code: "..err
|
||
end
|
||
rcs[#rcs + 1] = rc
|
||
end
|
||
if not next(rcs) then
|
||
return false, packet_type[ptype]..": expecting at least one reason code"
|
||
end
|
||
packet.rc = rcs
|
||
return packet
|
||
end
|
||
|
||
-- Parse UNSUBSCRIBE packet, DOC: 3.10 UNSUBSCRIBE – Unsubscribe request
|
||
local function parse_packet_unsubscribe(ptype, flags, input)
|
||
-- DOC: 3.10.1 UNSUBSCRIBE Fixed Header
|
||
if flags ~= 2 then -- Reserved
|
||
return false, packet_type[ptype]..": unexpected flags value: "..flags
|
||
end
|
||
local read_data = input.read_func
|
||
-- DOC: 3.10.2 UNSUBSCRIBE Variable Header
|
||
local packet_id, err = parse_uint16(read_data)
|
||
if not packet_id then
|
||
return false, packet_type[ptype]..": failed to parse packet_id: "..err
|
||
end
|
||
local packet = setmetatable({type=ptype, packet_id=packet_id, properties={}, user_properties={}}, packet_mt)
|
||
-- DOC: 3.10.2.1 UNSUBSCRIBE Properties
|
||
local ok
|
||
ok, err = parse_properties(ptype, read_data, input, packet)
|
||
if not ok then
|
||
return false, packet_type[ptype]..": failed to parse packet properties: "..err
|
||
end
|
||
-- 3.10.3 UNSUBSCRIBE Payload
|
||
-- DOC: An UNSUBSCRIBE packet with no Payload is a Protocol Error.
|
||
if input.available == 0 then
|
||
return false, packet_type[ptype]..": empty subscriptions list"
|
||
end
|
||
local subscriptions = {}
|
||
while input.available > 0 do
|
||
local topic_filter
|
||
topic_filter, err = parse_string(input.read_func)
|
||
if not topic_filter then
|
||
return false, packet_type[ptype]..": failed to parse topic filter: "..err
|
||
end
|
||
subscriptions[#subscriptions + 1] = topic_filter
|
||
end
|
||
packet.subscriptions = subscriptions
|
||
return packet
|
||
end
|
||
|
||
-- UNSUBACK Reason Codes
|
||
-- DOC[2]: Table 3‑9 - Unsubscribe Reason Codes
|
||
local unsuback_rc = {
|
||
[0x00] = "Success",
|
||
[0x11] = "No subscription existed",
|
||
[0x80] = "Unspecified error",
|
||
[0x83] = "Implementation specific error",
|
||
[0x87] = "Not authorized",
|
||
[0x8F] = "Topic Filter invalid",
|
||
[0x91] = "Packet Identifier in use",
|
||
}
|
||
protocol5.unsuback_rc = unsuback_rc
|
||
|
||
--- Parsed UNSUBACK packet metatable
|
||
local unsuback_packet_mt = {
|
||
__tostring = protocol.packet_tostring, -- packet-to-human-readable-string conversion metamethod using protocol.packet_tostring()
|
||
reason_strings = function(self) -- Returns reason strings for the UNSUBACK packet according to its rc field
|
||
local human_readable = {}
|
||
for i, rc in ipairs(self.rc) do
|
||
local reason_string = unsuback_rc[rc]
|
||
if reason_string then
|
||
human_readable[i] = reason_string
|
||
else
|
||
human_readable[i] = "Unknown: "..tostring(rc)
|
||
end
|
||
end
|
||
return human_readable
|
||
end,
|
||
}
|
||
unsuback_packet_mt.__index = unsuback_packet_mt
|
||
protocol5.unsuback_packet_mt = unsuback_packet_mt
|
||
|
||
-- Parse UNSUBACK packet, DOC: 3.11 UNSUBACK – Unsubscribe acknowledgement
|
||
local function parse_packet_unsuback(ptype, flags, input)
|
||
-- DOC: 3.11.1 UNSUBACK Fixed Header
|
||
if flags ~= 0 then -- Reserved
|
||
return false, packet_type[ptype]..": unexpected flags value: "..flags
|
||
end
|
||
local read_data = input.read_func
|
||
-- DOC: 3.11.2 UNSUBACK Variable Header
|
||
local packet_id, err = parse_uint16(read_data)
|
||
if not packet_id then
|
||
return false, packet_type[ptype]..": failed to parse packet_id: "..err
|
||
end
|
||
-- 3.11.2.1 UNSUBACK Properties
|
||
local packet = setmetatable({type=ptype, packet_id=packet_id}, unsuback_packet_mt)
|
||
local ok
|
||
ok, err = parse_properties(ptype, read_data, input, packet)
|
||
if not ok then
|
||
return false, packet_type[ptype]..": failed to parse packet properties: "..err
|
||
end
|
||
-- 3.11.3 UNSUBACK Payload
|
||
local rcs = {}
|
||
while input.available > 0 do
|
||
local rc
|
||
rc, err = parse_uint8(read_data)
|
||
if not rc then
|
||
return false, packet_type[ptype]..": failed to parse reason code: "..err
|
||
end
|
||
rcs[#rcs + 1] = rc
|
||
end
|
||
if not next(rcs) then
|
||
return false, packet_type[ptype]..": expecting at least one reason code in"
|
||
end
|
||
packet.rc = rcs
|
||
return packet
|
||
end
|
||
|
||
-- Parse PINGREQ packet, DOC: 3.12 PINGREQ – PING request
|
||
local function parse_packet_pingreq(ptype, flags, _)
|
||
-- DOC: 3.12.1 PINGREQ Fixed Header
|
||
if flags ~= 0 then -- Reserved
|
||
return false, packet_type[ptype]..": unexpected flags value: "..flags
|
||
end
|
||
return setmetatable({type=ptype, properties={}, user_properties={}}, packet_mt)
|
||
end
|
||
|
||
-- Parse PINGRESP packet, DOC: 3.13 PINGRESP – PING response
|
||
local function parse_packet_pingresp(ptype, flags, _)
|
||
-- DOC: 3.13.1 PINGRESP Fixed Header
|
||
if flags ~= 0 then -- Reserved
|
||
return false, packet_type[ptype]..": unexpected flags value: "..flags
|
||
end
|
||
return setmetatable({type=ptype, properties={}, user_properties={}}, packet_mt)
|
||
end
|
||
|
||
-- DISCONNECT reason codes
|
||
-- DOC: Table 3‑10 – Disconnect Reason Code values
|
||
local disconnect_rc = {
|
||
[0x00] = "Normal disconnection",
|
||
[0x04] = "Disconnect with Will Message",
|
||
[0x80] = "Unspecified error",
|
||
[0x81] = "Malformed Packet",
|
||
[0x82] = "Protocol Error",
|
||
[0x83] = "Implementation specific error",
|
||
[0x87] = "Not authorized",
|
||
[0x89] = "Server busy",
|
||
[0x8B] = "Server shutting down",
|
||
[0x8D] = "Keep Alive timeout",
|
||
[0x8E] = "Session taken over",
|
||
[0x8F] = "Topic Filter invalid",
|
||
[0x90] = "Topic Name invalid",
|
||
[0x93] = "Receive Maximum exceeded",
|
||
[0x94] = "Topic Alias invalid",
|
||
[0x95] = "Packet too large",
|
||
[0x96] = "Message rate too high",
|
||
[0x97] = "Quota exceeded",
|
||
[0x98] = "Administrative action",
|
||
[0x99] = "Payload format invalid",
|
||
[0x9A] = "Retain not supported",
|
||
[0x9B] = "QoS not supported",
|
||
[0x9C] = "Use another server",
|
||
[0x9D] = "Server moved",
|
||
[0x9E] = "Shared Subscriptions not supported",
|
||
[0x9F] = "Connection rate exceeded",
|
||
[0xA0] = "Maximum connect time",
|
||
[0xA1] = "Subscription Identifiers not supported",
|
||
[0xA2] = "Wildcard Subscriptions not supported",
|
||
}
|
||
protocol5.disconnect_rc = disconnect_rc
|
||
|
||
--- Parsed DISCONNECT packet metatable
|
||
local disconnect_packet_mt = {
|
||
__tostring = protocol.packet_tostring, -- packet-to-human-readable-string conversion metamethod using protocol.packet_tostring()
|
||
reason_string = function(self) -- Returns reason string for the DISCONNECT packet according to its rc field
|
||
local reason_string = disconnect_rc[self.rc]
|
||
if not reason_string then
|
||
reason_string = "Unknown: "..self.rc
|
||
end
|
||
return reason_string
|
||
end,
|
||
}
|
||
disconnect_packet_mt.__index = disconnect_packet_mt
|
||
protocol5.disconnect_packet_mt = disconnect_packet_mt
|
||
|
||
-- Parse DISCONNECT packet, DOC: 3.14 DISCONNECT – Disconnect notification
|
||
local function parse_packet_disconnect(ptype, flags, input)
|
||
-- DOC: 3.14.1 DISCONNECT Fixed Header
|
||
if flags ~= 0 then -- Reserved
|
||
return false, packet_type[ptype]..": unexpected flags value: "..flags
|
||
end
|
||
local read_data = input.read_func
|
||
local packet = setmetatable({type=ptype, rc=0, properties={}, user_properties={}}, disconnect_packet_mt)
|
||
if input.available > 0 then
|
||
-- DOC: 3.14.2 DISCONNECT Variable Header
|
||
-- DOC: 3.14.2.1 Disconnect Reason Code
|
||
local rc, err = parse_uint8(read_data)
|
||
if not rc then
|
||
return false, packet_type[ptype]..": failed to parse rc: "..err
|
||
end
|
||
packet.rc = rc
|
||
-- DOC: 3.14.2.2 DISCONNECT Properties
|
||
local ok
|
||
ok, err = parse_properties(ptype, read_data, input, packet)
|
||
if not ok then
|
||
return false, packet_type[ptype]..": failed to parse packet properties: "..err
|
||
end
|
||
end
|
||
return packet
|
||
end
|
||
|
||
-- Parse AUTH packet, DOC: 3.15 AUTH – Authentication exchange
|
||
local function parse_packet_auth(ptype, flags, input)
|
||
-- DOC: 3.15.1 AUTH Fixed Header
|
||
if flags ~= 0 then -- Reserved
|
||
return false, packet_type[ptype]..": unexpected flags value: "..flags
|
||
end
|
||
local read_data = input.read_func
|
||
-- DOC: 3.15.2.1 Authenticate Reason Code
|
||
local packet = setmetatable({type=ptype, rc=0, properties={}, user_properties={}}, packet_mt)
|
||
if input.available > 1 then
|
||
-- DOC: 3.15.2 AUTH Variable Header
|
||
local rc, err = parse_uint8(read_data)
|
||
if not rc then
|
||
return false, packet_type[ptype]..": failed to parse Authenticate Reason Code: "..err
|
||
end
|
||
packet.rc = rc
|
||
-- DOC: 3.15.2.2 AUTH Properties
|
||
local ok
|
||
ok, err = parse_properties(ptype, read_data, input, packet)
|
||
if not ok then
|
||
return false, packet_type[ptype]..": failed to parse packet properties: "..err
|
||
end
|
||
end
|
||
return packet
|
||
end
|
||
|
||
-- Parse packet using given read_func
|
||
-- Returns packet on success or false and error message on failure
|
||
function protocol5.parse_packet(read_func)
|
||
local ptype, flags, input = start_parse_packet(read_func)
|
||
if not ptype then
|
||
return false, flags
|
||
end
|
||
local packet, err
|
||
|
||
-- parse read data according type in fixed header
|
||
if ptype == packet_type.CONNECT then -- 1
|
||
packet, err = parse_packet_connect_input(input, const_v50)
|
||
elseif ptype == packet_type.CONNACK then -- 2
|
||
packet, err = parse_packet_connack(ptype, flags, input)
|
||
elseif ptype == packet_type.PUBLISH then -- 3
|
||
packet, err = parse_packet_publish(ptype, flags, input)
|
||
elseif ptype == packet_type.PUBACK then -- 4
|
||
packet, err = parse_packet_puback(ptype, flags, input)
|
||
elseif ptype == packet_type.PUBREC then -- 5
|
||
packet, err = parse_packet_pubrec(ptype, flags, input)
|
||
elseif ptype == packet_type.PUBREL then -- 6
|
||
packet, err = parse_packet_pubrel(ptype, flags, input)
|
||
elseif ptype == packet_type.PUBCOMP then -- 7
|
||
packet, err = parse_packet_pubcomp(ptype, flags, input)
|
||
elseif ptype == packet_type.SUBSCRIBE then -- 8
|
||
packet, err = parse_packet_subscribe(ptype, flags, input)
|
||
elseif ptype == packet_type.SUBACK then -- 9
|
||
packet, err = parse_packet_suback(ptype, flags, input)
|
||
elseif ptype == packet_type.UNSUBSCRIBE then -- 10
|
||
packet, err = parse_packet_unsubscribe(ptype, flags, input)
|
||
elseif ptype == packet_type.UNSUBACK then -- 11
|
||
packet, err = parse_packet_unsuback(ptype, flags, input)
|
||
elseif ptype == packet_type.PINGREQ then -- 12
|
||
packet, err = parse_packet_pingreq(ptype, flags, input)
|
||
elseif ptype == packet_type.PINGRESP then -- 13
|
||
packet, err = parse_packet_pingresp(ptype, flags, input)
|
||
elseif ptype == packet_type.DISCONNECT then -- 14
|
||
packet, err = parse_packet_disconnect(ptype, flags, input)
|
||
elseif ptype == packet_type.AUTH then -- 15
|
||
packet, err = parse_packet_auth(ptype, flags, input)
|
||
else
|
||
return false, "unexpected packet type received: "..tostring(ptype)
|
||
end
|
||
if packet and input.available > 0 then
|
||
return false, packet_type[ptype]..": extra data in remaining length left after packet parsing"
|
||
end
|
||
return packet, err
|
||
end
|
||
|
||
-- Continue parsing of the MQTT v5.0 CONNECT packet
|
||
-- Internally called from the protocol.parse_packet_connect_input() function
|
||
-- Returns packet on success or false and error message on failure
|
||
function protocol5._parse_packet_connect_continue(input, packet)
|
||
local read_func = input.read_func
|
||
local ok, client_id, err
|
||
|
||
-- DOC: 3.1.2.11 CONNECT Properties
|
||
ok, err = parse_properties(packet_type.CONNECT, read_func, input, packet)
|
||
if not ok then
|
||
return false, "CONNECT: failed to parse packet properties: "..err
|
||
end
|
||
|
||
-- DOC: 3.1.3 CONNECT Payload
|
||
|
||
-- DOC: 3.1.3.1 Client Identifier (ClientID)
|
||
client_id, err = parse_string(read_func)
|
||
if not client_id then
|
||
return false, "CONNECT: failed to parse client_id: "..err
|
||
end
|
||
packet.id = client_id
|
||
|
||
local will = packet.will
|
||
if will then
|
||
-- DOC: 3.1.3.2 Will Properties
|
||
ok, err = parse_properties("will", read_func, input, will)
|
||
if not ok then
|
||
return false, "CONNECT: failed to parse will message properties: "..err
|
||
end
|
||
|
||
-- DOC: 3.1.3.3 Will Topic
|
||
local will_topic, will_payload
|
||
will_topic, err = parse_string(read_func)
|
||
if not will_topic then
|
||
return false, "CONNECT: failed to parse will_topic: "..err
|
||
end
|
||
will.topic = will_topic
|
||
|
||
-- DOC: 3.1.3.4 Will Payload
|
||
will_payload, err = parse_string(read_func)
|
||
if not will_payload then
|
||
return false, "CONNECT: failed to parse will_payload: "..err
|
||
end
|
||
will.payload = will_payload
|
||
end
|
||
|
||
-- DOC: 3.1.3.5 User Name
|
||
if packet.username then
|
||
local username
|
||
username, err = parse_string(read_func)
|
||
if not username then
|
||
return false, "CONNECT: failed to parse username: "..err
|
||
end
|
||
packet.username = username
|
||
else
|
||
packet.username = nil
|
||
end
|
||
|
||
-- DOC: 3.1.3.6 Password
|
||
if packet.password then
|
||
local password
|
||
password, err = parse_string(read_func)
|
||
if not password then
|
||
return false, "CONNECT: failed to parse password: "..err
|
||
end
|
||
packet.password = password
|
||
else
|
||
packet.password = nil
|
||
end
|
||
|
||
return setmetatable(packet, packet_mt)
|
||
end
|
||
|
||
-- export module table
|
||
return protocol5
|
||
|
||
-- vim: ts=4 sts=4 sw=4 noet ft=lua
|