Commit Graph

6 Commits (41c87d87afa3a1a9165fd530739aa3e9cd3992f4)

Author SHA1 Message Date
Dustin 41c87d87af mqtt: Add MqttClient::connect method
Separating the `connect` call out of the `MqttClient::new` function
makes is such that we do not have to create a new object for each
iteration of the initial connection loop.  Instead, we just create one
object and repeatedly call its `connect` method until it succeeds
2022-12-30 14:39:10 -06:00
Dustin cef128d1ef main: Add wait_signal function
Moving the signal handler setup and wait to a separate function will
allow us to eventually create platform-specific implementations.
Windows doesn't have SIGINT/SIGTERM, so we will need different logic if
we ever want to support that OS.
2022-12-30 14:09:24 -06:00
Dustin ee8ed0c644 Add basic MQTT client functionality
Naturally, we need a way to configure the MQTT connection parameters
(host, port, username, etc.).  For that, we'll use a TOML configuration
file, which is read at startup and deserialized into a structure owned
by the Session.

The Session object now has a `run` method, which establishes the MQTT
connection and then repeatedly waits for messages from the broker.  It
will continuously attempt to connect to the broker until it succeeds.
This way, if the broker is unavailable when the application starts, it
will eventually connect when it becomes available.  Once the initial
connection is established, the client will automatically reconnect if it
gets disconnected later.

Since the `run` method loops forever and never returns, we need to use a
separate Tokio task to manage it.  We keep the task handle so we can
cancel the task when the application shuts down.
2022-12-30 13:49:01 -06:00
Dustin ce2d77a32c Implement Display and Error for all errors
These standard traits should be implemented for all error types so they
can match `dyn Error`, etc.
2022-12-30 10:05:57 -06:00
Dustin c8386f9dee marionette: Handle concurrent communication
The Marionette protocol is designed to facilitate concurrent,
asynchronous messages.  Each request message includes a message
ID, and the corresponding response includes the same message ID.  This
allows several requests to be in flight at once.  In order for this to
be useful, the client needs to maintain a record of each request it has
sent so that it knows how to handle responses, even if they arrive out
of order.

To implement this functionality in *mqttmarionette*, the
`MarionetteConnection` structure spawns a Tokio task to handle all
incoming messages from the server.  When a message arrives, its ID is
looked up in a registry that maps message IDs to Tokio "oneshot"
channels.  If a channel is found in the map, the response is sent back
to the caller through the channel.

In order to handle incoming messages in a separate task, the TCP stream
has to be split into its read and write parts.  The receiver task cannot
be spawned, though, until after the first unsolicited message is read
from the socket, since a) there is no caller to send the message back to
and b) it does not follow the same encoding scheme as the rest of the
Marionette messages.  As such, I've refactored the
`MarionetteConnection` structure to handle the initial message in the
`connect` function and dropped the `handshake` method.  A new
`start_session` method is responsible for initiating the Marionette
session.
2022-12-30 10:02:15 -06:00
Dustin f3815e2b12 Initial commit 2022-12-30 09:10:05 -06:00