@@ -23,6 +23,7 @@ use tedge_actors::futures::channel::mpsc;
23
23
use tedge_actors:: Actor ;
24
24
use tedge_actors:: Builder ;
25
25
use tedge_actors:: DynSender ;
26
+ use tedge_actors:: NullSender ;
26
27
use tedge_actors:: RuntimeError ;
27
28
use tedge_actors:: RuntimeRequest ;
28
29
use tedge_actors:: RuntimeRequestSink ;
@@ -40,9 +41,7 @@ pub use mqtt_channel::Topic;
40
41
pub use mqtt_channel:: TopicFilter ;
41
42
use tedge_config:: TEdgeConfig ;
42
43
43
- pub struct MqttBridgeActorBuilder {
44
- signal_sender : mpsc:: Sender < RuntimeRequest > ,
45
- }
44
+ pub struct MqttBridgeActorBuilder { }
46
45
47
46
impl MqttBridgeActorBuilder {
48
47
pub async fn new (
@@ -56,7 +55,6 @@ impl MqttBridgeActorBuilder {
56
55
tedge_config. device . cert_path . clone ( ) . into ( ) ,
57
56
)
58
57
. unwrap ( ) ;
59
- let ( signal_sender, _signal_receiver) = mpsc:: channel ( 10 ) ;
60
58
61
59
let prefix = tedge_config. c8y . bridge . topic_prefix . clone ( ) ;
62
60
let mut local_config = MqttOptions :: new (
@@ -67,7 +65,6 @@ impl MqttBridgeActorBuilder {
67
65
let health_topic = main_device_health_topic ( & service_name) ;
68
66
// TODO cope with secured mosquitto
69
67
local_config. set_manual_acks ( true ) ;
70
- // TODO const for payload
71
68
local_config. set_last_will ( LastWill :: new (
72
69
& health_topic,
73
70
C8Y_BRIDGE_DOWN_PAYLOAD ,
@@ -110,16 +107,18 @@ impl MqttBridgeActorBuilder {
110
107
move |topic| topic. strip_prefix ( & topic_prefix) . unwrap ( ) . into ( ) ,
111
108
msgs_local,
112
109
None ,
110
+ "local" ,
113
111
) ) ;
114
112
tokio:: spawn ( half_bridge (
115
113
cloud_event_loop,
116
114
local_client,
117
115
move |topic| format ! ( "{prefix}/{topic}" ) . into ( ) ,
118
116
msgs_cloud,
119
117
Some ( health_topic) ,
118
+ "cloud" ,
120
119
) ) ;
121
120
122
- Self { signal_sender }
121
+ Self { }
123
122
}
124
123
125
124
pub ( crate ) fn build_actor ( self ) -> MqttBridgeActor {
@@ -157,23 +156,53 @@ impl<'a, T> BidirectionalChannelHalf<T> {
157
156
}
158
157
}
159
158
159
+ /// Forward messages received from `recv_event_loop` to `target`
160
+ ///
161
+ /// The result of running this function constitutes half the MQTT bridge, hence the name.
162
+ /// Each half has two main responsibilities, one is to take messages received on the event
163
+ /// loop and forward them to the target client, the other is to communicate with the corresponding
164
+ /// half of the bridge to ensure published messages get acknowledged only when they have been
165
+ /// fully processed.
166
+ ///
167
+ /// # Message flow
168
+ /// Messages in the bridge go through a few states
169
+ /// 1. Received from the sending broker
170
+ /// 2. Forwarded to the receiving broker
171
+ /// 3. The receiving broker sends an acknowledgement
172
+ /// 4. The original forwarded message is acknowledged now we know it is fully processed
173
+ ///
174
+ /// Since the function is processing one [EventLoop], messages can be sent to the receiving broker,
175
+ /// but we cannot receive acknowledgements from that broker, therefore a communication link must
176
+ /// be established between the bridge halves. This link is the argument `corresponding_bridge_half`,
177
+ /// which can both send and receive messages from the other bridge loop.
178
+ ///
179
+ /// When a message is forwarded, the [Publish] is forwarded from this loop to the corresponding loop.
180
+ /// This allows the loop to store the message along with its packet ID when the forwarded message is
181
+ /// published. When an acknowledgement is received for the forwarded message, the packet id is used
182
+ /// to retrieve the original [Publish], which is then passed to [AsyncClient::ack] to complete the
183
+ /// final step of the message flow.
184
+ ///
185
+ /// The channel sends [Option<Publish>] rather than [Publish] to allow the bridge to send entirely
186
+ /// novel messages, and not just forwarded ones, as attaching packet IDs relies on pairing every
187
+ /// [Outgoing] publish notification with a message sent by the relevant client. This means the
188
+ ///
189
+ /// # Health topics
160
190
async fn half_bridge < F : for < ' a > Fn ( & ' a str ) -> Cow < ' a , str > > (
161
191
mut recv_event_loop : EventLoop ,
162
192
target : AsyncClient ,
163
193
transform_topic : F ,
164
194
mut corresponding_bridge_half : BidirectionalChannelHalf < Option < Publish > > ,
165
195
health_topic : Option < String > ,
196
+ name : & ' static str ,
166
197
) {
167
198
let mut forward_pkid_to_received_msg = HashMap :: new ( ) ;
168
199
let mut last_err = Some ( "dummy error" . into ( ) ) ;
169
200
170
201
loop {
171
202
let notification = match recv_event_loop. poll ( ) . await {
172
- // TODO notify if this is us recovering from an error
173
203
Ok ( notification) => {
174
204
if last_err. as_ref ( ) . is_some ( ) {
175
- // TODO clarify whether this is cloud/local
176
- info ! ( "MQTT bridge connected" ) ;
205
+ info ! ( "MQTT bridge connected to {name} broker" ) ;
177
206
last_err = None ;
178
207
if let Some ( health_topic) = & health_topic {
179
208
target
@@ -188,8 +217,7 @@ async fn half_bridge<F: for<'a> Fn(&'a str) -> Cow<'a, str>>(
188
217
Err ( err) => {
189
218
let err = err. to_string ( ) ;
190
219
if last_err. as_ref ( ) != Some ( & err) {
191
- // TODO clarify whether this is cloud/local
192
- error ! ( "MQTT bridge connection error: {err}" ) ;
220
+ error ! ( "MQTT bridge failed to connect to {name} broker: {err}" ) ;
193
221
last_err = Some ( err) ;
194
222
if let Some ( health_topic) = & health_topic {
195
223
target
@@ -219,22 +247,24 @@ async fn half_bridge<F: for<'a> Fn(&'a str) -> Cow<'a, str>>(
219
247
)
220
248
. await
221
249
. unwrap ( ) ;
222
- corresponding_bridge_half. send ( Some ( publish) ) . await . unwrap ( ) ;
250
+ let publish = ( publish. qos > QoS :: AtMostOnce ) . then_some ( publish) ;
251
+ corresponding_bridge_half. send ( publish) . await . unwrap ( ) ;
223
252
}
224
253
// Forwarding acks from event loop to target
225
254
Event :: Incoming (
226
255
Incoming :: PubAck ( PubAck { pkid : ack_pkid } )
227
256
| Incoming :: PubRec ( PubRec { pkid : ack_pkid } ) ,
228
257
) => {
229
- if let Some ( msg) = forward_pkid_to_received_msg. remove ( & ack_pkid) . unwrap ( ) {
258
+ if let Some ( msg) = forward_pkid_to_received_msg. remove ( & ack_pkid) {
230
259
target. ack ( & msg) . await . unwrap ( ) ;
231
260
}
232
261
}
233
262
Event :: Outgoing ( Outgoing :: Publish ( pkid) ) => {
234
263
match corresponding_bridge_half. recv ( ) . await {
235
- Some ( optional_msg ) => {
236
- forward_pkid_to_received_msg. insert ( pkid, optional_msg ) ;
264
+ Some ( Some ( msg ) ) => {
265
+ forward_pkid_to_received_msg. insert ( pkid, msg ) ;
237
266
}
267
+ Some ( None ) => { }
238
268
None => break ,
239
269
}
240
270
}
@@ -257,7 +287,7 @@ impl Builder<MqttBridgeActor> for MqttBridgeActorBuilder {
257
287
258
288
impl RuntimeRequestSink for MqttBridgeActorBuilder {
259
289
fn get_signal_sender ( & self ) -> DynSender < RuntimeRequest > {
260
- Box :: new ( self . signal_sender . clone ( ) )
290
+ NullSender . into ( )
261
291
}
262
292
}
263
293
0 commit comments