Mercurial > prosody-modules
comparison mod_pubsub_mqtt/mqtt.lib.lua @ 1240:e0d97eb52ab8
mod_pubsub_mqtt: MQTT (a lightweight binary pubsub protocol) interface for mod_pubsub
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Sun, 01 Dec 2013 19:12:08 +0000 |
| parents | |
| children | 7dbde05b48a9 |
comparison
equal
deleted
inserted
replaced
| 1239:cc5cbeeb9fc7 | 1240:e0d97eb52ab8 |
|---|---|
| 1 local bit = require "bit"; | |
| 2 | |
| 3 local stream_mt = {}; | |
| 4 stream_mt.__index = stream_mt; | |
| 5 | |
| 6 function stream_mt:read_bytes(n_bytes) | |
| 7 module:log("debug", "Reading %d bytes... (buffer: %d)", n_bytes, #self.buffer); | |
| 8 local data = self.buffer; | |
| 9 if not data then | |
| 10 module:log("debug", "No data, pausing."); | |
| 11 data = coroutine.yield(); | |
| 12 module:log("debug", "Have %d bytes of data now (want %d)", #data, n_bytes); | |
| 13 end | |
| 14 if #data >= n_bytes then | |
| 15 data, self.buffer = data:sub(1, n_bytes), data:sub(n_bytes+1); | |
| 16 elseif #data < n_bytes then | |
| 17 module:log("debug", "Not enough data (only %d bytes out of %d), pausing.", #data, n_bytes); | |
| 18 self.buffer = data..coroutine.yield(); | |
| 19 module:log("debug", "Now we have %d bytes, reading...", #data); | |
| 20 return self:read_bytes(n_bytes); | |
| 21 end | |
| 22 module:log("debug", "Returning %d bytes (buffer: %d)", #data, #self.buffer); | |
| 23 return data; | |
| 24 end | |
| 25 | |
| 26 function stream_mt:read_string() | |
| 27 local len1, len2 = self:read_bytes(2):byte(1,2); | |
| 28 local len = bit.lshift(len1, 8) + len2; | |
| 29 return self:read_bytes(len), len+2; | |
| 30 end | |
| 31 | |
| 32 local packet_type_codes = { | |
| 33 "connect", "connack", | |
| 34 "publish", "puback", "pubrec", "pubrel", "pubcomp", | |
| 35 "subscribe", "subak", "unsubscribe", "unsuback", | |
| 36 "pingreq", "pingresp", | |
| 37 "disconnect" | |
| 38 }; | |
| 39 | |
| 40 function stream_mt:read_packet() | |
| 41 local packet = {}; | |
| 42 local header = self:read_bytes(1):byte(); | |
| 43 packet.type = packet_type_codes[bit.rshift(bit.band(header, 0xf0), 4)]; | |
| 44 packet.dup = bit.band(header, 0x08) == 0x08; | |
| 45 packet.qos = bit.rshift(bit.band(header, 0x06), 1); | |
| 46 packet.retain = bit.band(header, 0x01) == 0x01; | |
| 47 | |
| 48 -- Get length | |
| 49 local length, multiplier = 0, 1; | |
| 50 repeat | |
| 51 local digit = self:read_bytes(1):byte(); | |
| 52 length = length + bit.band(digit, 0x7f)*multiplier; | |
| 53 multiplier = multiplier*128; | |
| 54 until bit.band(digit, 0x80) == 0; | |
| 55 packet.length = length; | |
| 56 if packet.type == "connect" then | |
| 57 if self:read_string() ~= "MQIsdp" then | |
| 58 module:log("warn", "Unexpected packet signature!"); | |
| 59 packet.type = nil; -- Invalid packet | |
| 60 else | |
| 61 packet.version = self:read_bytes(1):byte(); | |
| 62 packet.connect_flags = self:read_bytes(1):byte(); | |
| 63 packet.keepalive_timer = self:read_bytes(1):byte(); | |
| 64 length = length - 11; | |
| 65 end | |
| 66 elseif packet.type == "publish" then | |
| 67 packet.topic = self:read_string(); | |
| 68 length = length - (#packet.topic+2); | |
| 69 if packet.qos == 1 or packet.qos == 2 then | |
| 70 packet.id = self:read_bytes(2); | |
| 71 length = length - 2; | |
| 72 end | |
| 73 elseif packet.type == "subscribe" then | |
| 74 if packet.qos == 1 or packet.qos == 2 then | |
| 75 packet.id = self:read_bytes(2); | |
| 76 length = length - 2; | |
| 77 end | |
| 78 local topics = {}; | |
| 79 while length > 0 do | |
| 80 local topic, len = self:read_string(); | |
| 81 table.insert(topics, topic); | |
| 82 self:read_bytes(1); -- QoS not used | |
| 83 length = length - (len+1); | |
| 84 end | |
| 85 packet.topics = topics; | |
| 86 end | |
| 87 if length > 0 then | |
| 88 packet.data = self:read_bytes(length); | |
| 89 end | |
| 90 return packet; | |
| 91 end | |
| 92 | |
| 93 local function new_parser(self) | |
| 94 return coroutine.wrap(function (data) | |
| 95 self.buffer = data; | |
| 96 while true do | |
| 97 data = coroutine.yield(self:read_packet()); | |
| 98 module:log("debug", "Parser: %d new bytes", #data); | |
| 99 self.buffer = (self.buffer or "")..data; | |
| 100 end | |
| 101 end); | |
| 102 end | |
| 103 | |
| 104 function stream_mt:feed(data) | |
| 105 module:log("debug", "Feeding %d bytes", #data); | |
| 106 local packets = {}; | |
| 107 local packet = self.parser(data); | |
| 108 while packet do | |
| 109 module:log("debug", "Received packet"); | |
| 110 table.insert(packets, packet); | |
| 111 packet = self.parser(""); | |
| 112 end | |
| 113 module:log("debug", "Returning %d packets", #packets); | |
| 114 return packets; | |
| 115 end | |
| 116 | |
| 117 local function new_stream() | |
| 118 local stream = setmetatable({}, stream_mt); | |
| 119 stream.parser = new_parser(stream); | |
| 120 return stream; | |
| 121 end | |
| 122 | |
| 123 local function serialize_packet(packet) | |
| 124 local type_num = 0; | |
| 125 for i, v in ipairs(packet_type_codes) do -- FIXME: I'm so tired right now. | |
| 126 if v == packet.type then | |
| 127 type_num = i; | |
| 128 break; | |
| 129 end | |
| 130 end | |
| 131 local header = string.char(bit.lshift(type_num, 4)); | |
| 132 | |
| 133 if packet.type == "publish" then | |
| 134 local topic = packet.topic or ""; | |
| 135 packet.data = string.char(bit.band(#topic, 0xff00), bit.band(#topic, 0x00ff))..topic..packet.data; | |
| 136 elseif packet.type == "suback" then | |
| 137 local t = {}; | |
| 138 for _, topic in ipairs(packet.topics) do | |
| 139 table.insert(t, string.char(bit.band(#topic, 0xff00), bit.band(#topic, 0x00ff))..topic.."\000"); | |
| 140 end | |
| 141 packet.data = table.concat(t); | |
| 142 end | |
| 143 | |
| 144 -- Get length | |
| 145 local length = #(packet.data or ""); | |
| 146 repeat | |
| 147 local digit = length%128; | |
| 148 length = math.floor(length/128); | |
| 149 if length > 0 then | |
| 150 digit = bit.bor(digit, 0x80); | |
| 151 end | |
| 152 header = header..string.char(digit); -- FIXME: ... | |
| 153 until length <= 0; | |
| 154 | |
| 155 return header..(packet.data or ""); | |
| 156 end | |
| 157 | |
| 158 return { | |
| 159 new_stream = new_stream; | |
| 160 serialize_packet = serialize_packet; | |
| 161 }; |
