Skip to content

Commit 79daeea

Browse files
authored
willFlag; keepAliveMonitor; handle ping response; test keep alive (#65)
1 parent ea9476a commit 79daeea

4 files changed

Lines changed: 79 additions & 34 deletions

File tree

‎nmqtt.nim‎

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ type
4949
pubCallbacks: Table[string, TopicCb]
5050
inWork: bool
5151
hasNewWorks: bool
52-
keepAlive: uint16
52+
keepAlive: Duration
53+
keepAliveTimeout: Duration
54+
keepAliveMonitorRunned: bool
5355
pingRunned: bool
5456
lastSend: MonoTime
5557
awaitingPingResp: bool
@@ -67,7 +69,7 @@ type
6769
version: uint8
6870
connFlags: string
6971
subscribed: Table[string, QoS] # Topic, QoS
70-
lastAction: float # Check keepAlive
72+
lastAction: MonoTime # Check keepAlive
7173

7274
#when defined(broker):
7375
ConnAckFlag = enum
@@ -324,7 +326,7 @@ proc splitTopic(topic: string): seq[string] =
324326

325327
when defined(broker):
326328
proc retainMsg(topic: string, message: string, qos: QoS, clientid: string) =
327-
mqttbroker.retained[topic] = RetainedMsg(msg: message, qos: qos, time: epochTime(), clientid: clientid)
329+
mqttbroker.retained[topic] = RetainedMsg(msg: message, qos: qos, clientid: clientid)
328330

329331
when defined(broker):
330332
proc addSubscriber*(ctx: MqttCtx, topic: string) {.async.} =
@@ -519,7 +521,7 @@ proc sendConnect(ctx: MqttCtx): Future[bool] =
519521
pkt.put("MQTT", true)
520522
pkt.put(4.uint8)
521523
pkt.put(flags)
522-
pkt.put(ctx.keepAlive)
524+
pkt.put(uint16(ctx.keepAlive.inSeconds))
523525
pkt.put(ctx.clientId, true)
524526

525527
if ctx.willFlag:
@@ -715,14 +717,19 @@ proc work(ctx: MqttCtx) {.async.} =
715717
when defined(broker):
716718
proc sendWill(ctx: MqttCtx) {.async.} =
717719
## Send the will
718-
if ctx.willTopic != "":
719-
if ctx.willRetain:
720-
retainMsg(ctx.willTopic, ctx.willMsg, ctx.willQoS, ctx.clientid)
721-
for c in mqttbroker.subscribers[ctx.willTopic].ctxs:
722-
let msgId = c.nextMsgId()
723-
let qos = qosAlign(ctx.willQoS, c.subscribed[ctx.willTopic])
724-
c.workQueue[msgId] = Work(wk: PubWork, msgId: msgId, topic: ctx.willTopic, qos: qos, retain: ctx.willRetain, message: ctx.willMsg, typ: Publish)
725-
await c.work()
720+
if not ctx.willFlag:
721+
return
722+
if ctx.willRetain:
723+
retainMsg(ctx.willTopic, ctx.willMsg, ctx.willQoS, ctx.clientid)
724+
725+
if not mqttbroker.subscribers.hasKey(ctx.willTopic):
726+
return
727+
for c in mqttbroker.subscribers[ctx.willTopic].ctxs:
728+
let msgId = c.nextMsgId()
729+
let qos = qosAlign(ctx.willQoS, c.subscribed[ctx.willTopic])
730+
c.workQueue[msgId] = Work(wk: PubWork, msgId: msgId, topic: ctx.willTopic, qos: qos,
731+
retain: ctx.willRetain, message: ctx.willMsg, typ: Publish)
732+
await c.work()
726733

727734
when defined(broker):
728735
proc publishToSubscribers(seqctx: seq[MqttCtx], pkt: Pkt, matchedTopic, topic, message: string, qos: QoS, retain: bool, senderId: string) {.async.} =
@@ -756,19 +763,24 @@ proc onConnect(ctx: MqttCtx, pkt: Pkt) {.async.} =
756763
var
757764
offset: int
758765
nextLen: uint16
766+
keepAlive: uint16
759767

760768
# Main data
761769
(ctx.proto, offset) = pkt.getstring(0, true)
762770
(ctx.version, offset) = pkt.getu8(offset)
763771
(ctx.connFlags, offset) = getbin(pkt, offset)
764-
(ctx.keepAlive, offset) = pkt.getu16(offset)
772+
(keepAlive, offset) = pkt.getu16(offset)
773+
ctx.keepAlive = initDuration(seconds = int64(keepAlive))
774+
ctx.keepAliveTimeout = initDuration(milliseconds = int64(float(ctx.keepAlive.inMilliseconds) * 1.5))
765775
(nextLen, offset) = pkt.getu16(offset)
766776
(ctx.clientId, offset) = pkt.getstring(offset, parseInt($nextLen))
767777
if not mqttbroker.spacesInClientId:
768778
ctx.clientid = ctx.clientid.replace(" ", "")
769779

770780
# Will Topic
771-
if ctx.connFlags[5] == '1':
781+
ctx.willFlag = ctx.connFlags[5] == '1'
782+
if ctx.willFlag:
783+
ctx.willFlag = true
772784
(nextLen, offset) = pkt.getu16(offset)
773785
(ctx.willTopic, offset) = pkt.getstring(offset, parseInt($nextLen))
774786
(nextLen, offset) = pkt.getu16(offset)
@@ -1058,8 +1070,6 @@ proc onDisconnect(ctx: MqttCtx, pkt: Pkt) {.async.} =
10581070
when not defined(broker):
10591071
ctx.wrn("Packet type only supported for broker: " & $pkt.typ)
10601072
else:
1061-
#await removeSubscriber(ctx)
1062-
#await sendWill(ctx)
10631073
ctx.state = Disconnected
10641074
if mqttbroker.verbosity >= 1:
10651075
verbose("Connections >> " & ctx.clientid & " has disconnected")
@@ -1080,7 +1090,7 @@ proc onPingResp(ctx: MqttCtx, pkt: Pkt) {.async.} =
10801090

10811091
proc handle(ctx: MqttCtx, pkt: Pkt) {.async.} =
10821092
when defined(broker):
1083-
ctx.lastAction = epochTime()
1093+
ctx.lastAction = getMonoTime()
10841094

10851095
case pkt.typ
10861096
of ConnAck: await ctx.onConnAck(pkt)
@@ -1120,30 +1130,38 @@ proc runPing(ctx: MqttCtx) {.async.} =
11201130
return
11211131
ctx.pingRunned = true
11221132
while true:
1123-
await sleepAsync(1000)
1124-
11251133
if ctx.state notin {Connecting, Connected, Disconnecting}:
11261134
break
11271135
if ctx.state != Connected:
1136+
await sleepAsync(500)
11281137
continue
11291138

11301139
let now = getMonoTime()
11311140
if ctx.awaitingPingResp:
11321141
if now >= ctx.timeoutPingRespTime:
11331142
await ctx.close("ping response timeout")
11341143
break
1144+
await sleepAsync(500)
1145+
1146+
else:
1147+
let ms = (ctx.keepAlive - (now - ctx.lastSend)).inMilliseconds
1148+
if ms < 0:
1149+
discard await ctx.sendPingReq()
1150+
ctx.timeoutPingRespTime = now + ctx.pingRespTimeout
1151+
ctx.awaitingPingResp = true
11351152

1136-
elif (now - ctx.lastSend).inSeconds >= int64(ctx.keepAlive):
1137-
discard await ctx.sendPingReq()
1138-
ctx.timeoutPingRespTime = now + ctx.pingRespTimeout
1139-
ctx.awaitingPingResp = true
1153+
elif ms < 1000:
1154+
await sleepAsync(int(ms))
1155+
1156+
else:
1157+
await sleepAsync(1000)
11401158

11411159
ctx.pingRunned = false
11421160

11431161
proc connectBroker(ctx: MqttCtx) {.async.} =
11441162
## Connect to the broker.
1145-
if ctx.keepAlive == 0:
1146-
ctx.keepAlive = 60
1163+
if ctx.keepAlive == DurationZero:
1164+
ctx.keepAlive = initDuration(seconds = 60)
11471165

11481166
if ctx.verbosity >= 1:
11491167
ctx.dbg("Connecting to " & ctx.host & ":" & $ctx.port)
@@ -1206,12 +1224,12 @@ proc runConnect(ctx: MqttCtx) {.async.} =
12061224
proc newMqttCtx*(clientId: string): MqttCtx =
12071225
## Initiate a new MQTT client.
12081226
MqttCtx(clientId: clientId, state: Disconnected, maxInflightMessages: 20,
1209-
keepAlive: 60, pingRespTimeout: initDuration(seconds = 10))
1227+
keepAlive: initDuration(seconds = 60), pingRespTimeout: initDuration(seconds = 10))
12101228

12111229
proc setPingInterval*(ctx: MqttCtx, txInterval: int = 60) =
12121230
## Set the clients ping interval in seconds. Default is 60 seconds.
12131231
if txInterval > 0 and txInterval < 65535:
1214-
ctx.keepAlive = txInterval.uint16
1232+
ctx.keepAlive = initDuration(seconds = int64(txInterval))
12151233

12161234
proc setHost*(ctx: MqttCtx, host: string, port: int = 1883, sslOn = false) =
12171235
## Set the MQTT host.

‎nmqtt.nimble‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Package
2-
version = "1.1.1"
2+
version = "1.1.2"
33
author = "zevv & ThomasTJdev & python36"
44
description = "Native MQTT library and binaries for publishing, subscribing and broker"
55
license = "MIT"

‎nmqtt/nmqtt.nim‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,21 @@
1313
include "../nmqtt.nim"
1414

1515
proc keepAliveMonitor(ctx: MqttCtx) {.async.} =
16-
ctx.lastAction = epochTime()
1716
# The keep alive time is one and a half times the Keep Alive periode [MQTT-3.1.2-24]
18-
let keepAlive = toFloat(ctx.keepAlive.int) * 1.5 * 1000
19-
while ctx.state in [Connecting, Connected]:
20-
let saveLastAction = ctx.lastAction
21-
await sleepAsync(keepAlive)
22-
if ctx.lastAction <= saveLastAction:
17+
if ctx.keepAliveMonitorRunned:
18+
return
19+
ctx.keepAliveMonitorRunned = true
20+
while true:
21+
await sleepAsync(1000)
22+
if ctx.state notin {Connecting, Connected}:
23+
break
24+
if getMonoTime() - ctx.lastAction > ctx.keepAliveTimeout:
2325
ctx.state = Error
2426
ctx.s.close()
2527
if mqttbroker.verbosity >= 1:
2628
verbose("Connections >> " & ctx.clientid & " was disconnected. Keep alive time overdue.")
2729
break
30+
ctx.keepAliveMonitorRunned = false
2831

2932
proc processClient(s: AsyncSocket) {.async.} =
3033
## Create new client

‎tests/ping.nim‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,27 @@ suite "test suite for ping":
6464
check(pingResp == 0)
6565

6666
waitFor conn()
67+
68+
test "keepalive timeout":
69+
let
70+
ctxMain = newCtx()
71+
72+
proc conn() {.async.} =
73+
await sleepAsync(500)
74+
await ctxMain.disconnect()
75+
await sleepAsync(500)
76+
77+
ctxMain.setPingInterval(1)
78+
await ctxMain.connect()
79+
80+
await sleepAsync(500)
81+
ctxMain.setPingInterval(30)
82+
83+
var disconnected = not ctxMain.isConnected()
84+
for i in 1 .. 15:
85+
await sleepAsync(1000)
86+
if not ctxMain.isConnected():
87+
break
88+
check(ctxMain.isConnected() == false)
89+
90+
waitFor conn()

0 commit comments

Comments
 (0)