Skip to content

Commit aaf5adf

Browse files
committed
protocol5: SUBACK, UNSUBACK, DISCONNECT parsed packets: reason_strings() method added
1 parent 12b0ecd commit aaf5adf

File tree

2 files changed

+187
-9
lines changed

2 files changed

+187
-9
lines changed

mqtt/protocol5.lua

+123-5
Original file line numberDiff line numberDiff line change
@@ -1145,6 +1145,43 @@ local function parse_packet_subscribe(ptype, flags, input)
11451145
return packet
11461146
end
11471147

1148+
-- SUBACK return codes/reason code strings
1149+
-- DOC: Table 3‑8 - Subscribe Reason Codes
1150+
local suback_rc = {
1151+
[0x00] = "Granted QoS 0",
1152+
[0x01] = "Granted QoS 1",
1153+
[0x02] = "Granted QoS 2",
1154+
[0x80] = "Unspecified error",
1155+
[0x83] = "Implementation specific error",
1156+
[0x87] = "Not authorized",
1157+
[0x8F] = "Topic Filter invalid",
1158+
[0x91] = "Packet Identifier in use",
1159+
[0x97] = "Quota exceeded",
1160+
[0x9E] = "Shared Subscriptions not supported",
1161+
[0xA1] = "Subscription Identifiers not supported",
1162+
[0xA2] = "Wildcard Subscriptions not supported",
1163+
}
1164+
protocol5.suback_rc = suback_rc
1165+
1166+
--- Parsed SUBACK packet metatable
1167+
local suback_packet_mt = {
1168+
__tostring = protocol.packet_tostring, -- packet-to-human-readable-string conversion metamethod using protocol.packet_tostring()
1169+
reason_strings = function(self) -- Returns reason strings for the SUBACK packet according to its rc field
1170+
local human_readable = {}
1171+
for i, rc in ipairs(self.rc) do
1172+
local reason_string = suback_rc[rc]
1173+
if reason_string then
1174+
human_readable[i] = reason_string
1175+
else
1176+
human_readable[i] = "Unknown: "..tostring(rc)
1177+
end
1178+
end
1179+
return human_readable
1180+
end,
1181+
}
1182+
suback_packet_mt.__index = suback_packet_mt
1183+
protocol5.suback_packet_mt = suback_packet_mt
1184+
11481185
-- Parse SUBACK packet, DOC: 3.9 SUBACK – Subscribe acknowledgement
11491186
local function parse_packet_suback(ptype, flags, input)
11501187
-- DOC: 3.9.1 SUBACK Fixed Header
@@ -1158,7 +1195,7 @@ local function parse_packet_suback(ptype, flags, input)
11581195
return false, packet_type[ptype]..": failed to parse packet_id: "..err
11591196
end
11601197
-- DOC: 3.9.2.1 SUBACK Properties
1161-
local packet = setmetatable({type=ptype, packet_id=packet_id}, packet_mt)
1198+
local packet = setmetatable({type=ptype, packet_id=packet_id}, suback_packet_mt)
11621199
local ok
11631200
ok, err = parse_properties(ptype, read_data, input, packet)
11641201
if not ok then
@@ -1177,7 +1214,7 @@ local function parse_packet_suback(ptype, flags, input)
11771214
if not next(rcs) then
11781215
return false, packet_type[ptype]..": expecting at least one reason code"
11791216
end
1180-
packet.rc = rcs -- TODO: reason codes table somewhere should be placed?
1217+
packet.rc = rcs
11811218
return packet
11821219
end
11831220

@@ -1218,6 +1255,38 @@ local function parse_packet_unsubscribe(ptype, flags, input)
12181255
return packet
12191256
end
12201257

1258+
-- UNSUBACK Reason Codes
1259+
-- DOC[2]: Table 3‑9 - Unsubscribe Reason Codes
1260+
local unsuback_rc = {
1261+
[0x00] = "Success",
1262+
[0x11] = "No subscription existed",
1263+
[0x80] = "Unspecified error",
1264+
[0x83] = "Implementation specific error",
1265+
[0x87] = "Not authorized",
1266+
[0x8F] = "Topic Filter invalid",
1267+
[0x91] = "Packet Identifier in use",
1268+
}
1269+
protocol5.unsuback_rc = unsuback_rc
1270+
1271+
--- Parsed UNSUBACK packet metatable
1272+
local unsuback_packet_mt = {
1273+
__tostring = protocol.packet_tostring, -- packet-to-human-readable-string conversion metamethod using protocol.packet_tostring()
1274+
reason_strings = function(self) -- Returns reason strings for the UNSUBACK packet according to its rc field
1275+
local human_readable = {}
1276+
for i, rc in ipairs(self.rc) do
1277+
local reason_string = unsuback_rc[rc]
1278+
if reason_string then
1279+
human_readable[i] = reason_string
1280+
else
1281+
human_readable[i] = "Unknown: "..tostring(rc)
1282+
end
1283+
end
1284+
return human_readable
1285+
end,
1286+
}
1287+
unsuback_packet_mt.__index = unsuback_packet_mt
1288+
protocol5.unsuback_packet_mt = unsuback_packet_mt
1289+
12211290
-- Parse UNSUBACK packet, DOC: 3.11 UNSUBACK – Unsubscribe acknowledgement
12221291
local function parse_packet_unsuback(ptype, flags, input)
12231292
-- DOC: 3.11.1 UNSUBACK Fixed Header
@@ -1231,7 +1300,7 @@ local function parse_packet_unsuback(ptype, flags, input)
12311300
return false, packet_type[ptype]..": failed to parse packet_id: "..err
12321301
end
12331302
-- 3.11.2.1 UNSUBACK Properties
1234-
local packet = setmetatable({type=ptype, packet_id=packet_id}, packet_mt)
1303+
local packet = setmetatable({type=ptype, packet_id=packet_id}, unsuback_packet_mt)
12351304
local ok
12361305
ok, err = parse_properties(ptype, read_data, input, packet)
12371306
if not ok then
@@ -1272,18 +1341,67 @@ local function parse_packet_pingresp(ptype, flags, input_)
12721341
return setmetatable({type=ptype, properties={}, user_properties={}}, packet_mt)
12731342
end
12741343

1344+
-- DISCONNECT reason codes
1345+
-- DOC: Table 3‑10 – Disconnect Reason Code values
1346+
local disconnect_rc = {
1347+
[0x00] = "Normal disconnection",
1348+
[0x04] = "Disconnect with Will Message",
1349+
[0x80] = "Unspecified error",
1350+
[0x81] = "Malformed Packet",
1351+
[0x82] = "Protocol Error",
1352+
[0x83] = "Implementation specific error",
1353+
[0x87] = "Not authorized",
1354+
[0x89] = "Server busy",
1355+
[0x8B] = "Server shutting down",
1356+
[0x8D] = "Keep Alive timeout",
1357+
[0x8E] = "Session taken over",
1358+
[0x8F] = "Topic Filter invalid",
1359+
[0x90] = "Topic Name invalid",
1360+
[0x93] = "Receive Maximum exceeded",
1361+
[0x94] = "Topic Alias invalid",
1362+
[0x95] = "Packet too large",
1363+
[0x96] = "Message rate too high",
1364+
[0x97] = "Quota exceeded",
1365+
[0x98] = "Administrative action",
1366+
[0x99] = "Payload format invalid",
1367+
[0x9A] = "Retain not supported",
1368+
[0x9B] = "QoS not supported",
1369+
[0x9C] = "Use another server",
1370+
[0x9D] = "Server moved",
1371+
[0x9E] = "Shared Subscriptions not supported",
1372+
[0x9F] = "Connection rate exceeded",
1373+
[0xA0] = "Maximum connect time",
1374+
[0xA1] = "Subscription Identifiers not supported",
1375+
[0xA2] = "Wildcard Subscriptions not supported",
1376+
}
1377+
protocol5.disconnect_rc = disconnect_rc
1378+
1379+
--- Parsed DISCONNECT packet metatable
1380+
local disconnect_packet_mt = {
1381+
__tostring = protocol.packet_tostring, -- packet-to-human-readable-string conversion metamethod using protocol.packet_tostring()
1382+
reason_string = function(self) -- Returns reason string for the DISCONNECT packet according to its rc field
1383+
local reason_string = disconnect_rc[self.rc]
1384+
if not reason_string then
1385+
reason_string = "Unknown: "..self.rc
1386+
end
1387+
return reason_string
1388+
end,
1389+
}
1390+
disconnect_packet_mt.__index = disconnect_packet_mt
1391+
protocol5.disconnect_packet_mt = disconnect_packet_mt
1392+
12751393
-- Parse DISCONNECT packet, DOC: 3.14 DISCONNECT – Disconnect notification
12761394
local function parse_packet_disconnect(ptype, flags, input)
12771395
-- DOC: 3.14.1 DISCONNECT Fixed Header
12781396
if flags ~= 0 then -- Reserved
12791397
return false, packet_type[ptype]..": unexpected flags value: "..flags
12801398
end
12811399
local read_data = input.read_func
1282-
local packet = setmetatable({type=ptype, rc=0, properties={}, user_properties={}}, packet_mt)
1400+
local packet = setmetatable({type=ptype, rc=0, properties={}, user_properties={}}, disconnect_packet_mt)
12831401
if input.available > 0 then
12841402
-- DOC: 3.14.2 DISCONNECT Variable Header
12851403
-- DOC: 3.14.2.1 Disconnect Reason Code
1286-
local rc, err = parse_uint8(read_data) -- TODO: reason codes table?
1404+
local rc, err = parse_uint8(read_data)
12871405
if not rc then
12881406
return false, packet_type[ptype]..": failed to parse rc: "..err
12891407
end

tests/spec/protocol5-parse.lua

+64-4
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,26 @@ describe("MQTT v5.0 protocol: parsing packets: CONNACK[2]", function()
305305
)
306306
end)
307307

308+
it("CONNACK with unknown reason code, minimal params and without properties", function()
309+
local packet, err = protocol5.parse_packet(make_read_func_hex(
310+
extract_hex[[
311+
20 -- packet type == 2 (CONNACK), flags == 0
312+
03 -- variable length == 3 bytes
313+
00 -- 0-th bit is sp (session present) -- DOC: 3.2.2.1 Connect Acknowledge Flags
314+
20 -- connect reason code
315+
00 -- properties length
316+
]]
317+
))
318+
assert.is_nil(err)
319+
assert.are.same(
320+
{
321+
type=pt.CONNACK, sp=false, rc=32, properties={}, user_properties={},
322+
},
323+
packet
324+
)
325+
assert.are.same("Unknown: 32", packet:reason_string())
326+
end)
327+
308328
it("CONNACK with full params and without properties", function()
309329
local packet, err = protocol5.parse_packet(make_read_func_hex(
310330
extract_hex[[
@@ -1045,15 +1065,15 @@ describe("MQTT v5.0 protocol: parsing packets: SUBACK[9]", function()
10451065
26 0005 68656C6C6F 0005 776F726C64 -- property 0x26 (user) == ("hello", "world") -- DOC: 3.4.2.2.3 User Property
10461066
10471067
00 -- Subscribe Reason Codes, 0x00 == Granted QoS 0
1048-
01 -- Subscribe Reason Codes, 0x01 == Granted QoS 1
1068+
09 -- Subscribe Reason Codes, 0x09 == Unknown
10491069
80 -- Subscribe Reason Codes, 0x80 == Unspecified error
10501070
97 -- Subscribe Reason Codes, 0x97 == Quota exceeded
10511071
]]
10521072
))
10531073
assert.is_nil(err)
10541074
assert.are.same(
10551075
{
1056-
type=pt.SUBACK, packet_id=0x0101, rc={0, 1, 0x80, 0x97},
1076+
type=pt.SUBACK, packet_id=0x0101, rc={0, 9, 0x80, 0x97},
10571077
properties={
10581078
reason_string = "it's okay",
10591079
},
@@ -1063,6 +1083,15 @@ describe("MQTT v5.0 protocol: parsing packets: SUBACK[9]", function()
10631083
},
10641084
packet
10651085
)
1086+
assert.are.same(
1087+
{
1088+
"Granted QoS 0",
1089+
"Unknown: 9",
1090+
"Unspecified error",
1091+
"Quota exceeded",
1092+
},
1093+
packet:reason_strings()
1094+
)
10661095
end)
10671096
end)
10681097

@@ -1219,13 +1248,13 @@ describe("MQTT v5.0 protocol: parsing packets: UNSUBACK[11]", function()
12191248
00 -- Unsubscribe Reason Codes, 0x00 == Success
12201249
11 -- Unsubscribe Reason Codes, 0x11 == No subscription existed
12211250
80 -- Unsubscribe Reason Codes, 0x80 == Unspecified error
1222-
87 -- Unsubscribe Reason Codes, 0x87 == Not authorized
1251+
05 -- Unsubscribe Reason Codes, 0x05 == Unknown
12231252
]]
12241253
))
12251254
assert.is_nil(err)
12261255
assert.are.same(
12271256
{
1228-
type=pt.UNSUBACK, packet_id=0x3434, rc={0, 0x11, 0x80, 0x87},
1257+
type=pt.UNSUBACK, packet_id=0x3434, rc={0, 0x11, 0x80, 0x5},
12291258
properties={
12301259
reason_string = "it's okay",
12311260
},
@@ -1235,6 +1264,15 @@ describe("MQTT v5.0 protocol: parsing packets: UNSUBACK[11]", function()
12351264
},
12361265
packet
12371266
)
1267+
assert.are.same(
1268+
{
1269+
"Success",
1270+
"No subscription existed",
1271+
"Unspecified error",
1272+
"Unknown: 5",
1273+
},
1274+
packet:reason_strings()
1275+
)
12381276
end)
12391277
end)
12401278

@@ -1339,6 +1377,27 @@ describe("MQTT v5.0 protocol: parsing packets: DISCONNECT[14]", function()
13391377
)
13401378
end)
13411379

1380+
it("with unknown reason code, without properties", function()
1381+
local packet, err = protocol5.parse_packet(make_read_func_hex(
1382+
extract_hex[[
1383+
E0 -- packet type == 14 (DISCONNECT), flags == 0
1384+
02 -- variable length == 2 bytes
1385+
1386+
20 -- reason code == 0x20 (Unknown), DOC: 3.14.2.1 Disconnect Reason Code
1387+
00 -- properties length == 0
1388+
1389+
]]
1390+
))
1391+
assert.is_nil(err)
1392+
assert.are.same(
1393+
{
1394+
type=pt.DISCONNECT, rc=32, properties={}, user_properties={},
1395+
},
1396+
packet
1397+
)
1398+
assert.are.same("Unknown: 32", packet:reason_string())
1399+
end)
1400+
13421401
it("with non-zero reason code, without properties", function()
13431402
local packet, err = protocol5.parse_packet(make_read_func_hex(
13441403
extract_hex[[
@@ -1388,6 +1447,7 @@ describe("MQTT v5.0 protocol: parsing packets: DISCONNECT[14]", function()
13881447
},
13891448
packet
13901449
)
1450+
assert.are.same("Malformed Packet", packet:reason_string())
13911451
end)
13921452
end)
13931453

0 commit comments

Comments
 (0)