[![Build Status](https://travis-ci.org/zotonic/mqtt_packet_map.svg?branch=master)](https://travis-ci.org/zotonic/mqtt_packet_map)
MQTT Packet Encoder and Decoder
===============================
Encoder and decoder for MQTT v5 and earlier.
Maps are used for the representation of all MQTT messages.
There are two functions:
1. `mqtt_packet_map:encode/1`
2. `mqtt_packet_map:decode/1`
Both have a variant where the MQTT version (protocol level) is passed.
This defaults to 5, valid values are 3, 4 (v3.1.1) and 5.
Example usage:
```erlang
% Decode an incoming binary, return the message
case mqtt_packet_map:decode(Bin) of
{ok, {Msg, RestBin}} ->
% Decoded a packet, RestBin contains the
% remaining data for the next packet(s).
...;
{error, incomplete_packet} -> ...;
% Packet is too short, fetch more data first
...;
{error, malformed_header} ->
% Illegal package, close the connection
...;
{error, unknown_protocol} ->
% Only for connect messages
...
end.
% Encode a message
{ok, Bin} = mqtt_packet_map:encode(Msg).
% Encode a message as MQTT v3.1.1 (protocol level 4)
{ok, Bin} = mqtt_packet_map:encode(4, Msg).
```
MQTT v5 Specification
=====================
This library follows the following specification:
http://docs.oasis-open.org/mqtt/mqtt/v5.0/cs01/mqtt-v5.0-cs01.html
Usage
=====
Include the mqtt_packet_map directly in your rebar.config:
```erlang
{deps, [
mqtt_packet_map
]}.
```
Tests
=====
Run tests with:
```bash
make test
```
Sample output:
```
./rebar3 ct --config rebar.test.config
===> Verifying dependencies...
===> Compiling mqtt_packet_map
===> Running Common Test suites...
%%% mqtt_packet_map_SUITE ==> variable_byte_integer: OK
%%% mqtt_packet_map_SUITE ==> partial_packet: OK
%%% mqtt_packet_map_SUITE ==> connect_v5: OK
%%% mqtt_packet_map_SUITE ==> connect_v5_full: OK
%%% mqtt_packet_map_SUITE ==> connack_v5: OK
%%% mqtt_packet_map_SUITE ==> publish_v5: OK
%%% mqtt_packet_map_SUITE ==> puback_et_al_v5: OK
%%% mqtt_packet_map_SUITE ==> subscribe_v5: OK
%%% mqtt_packet_map_SUITE ==> suback_v5: OK
%%% mqtt_packet_map_SUITE ==> unsubscribe_v5: OK
%%% mqtt_packet_map_SUITE ==> unsuback_v5: OK
%%% mqtt_packet_map_SUITE ==> pingreq: OK
%%% mqtt_packet_map_SUITE ==> pingresp: OK
%%% mqtt_packet_map_SUITE ==> disconnect_v5: OK
%%% mqtt_packet_map_SUITE ==> auth_v5: OK
All 15 tests passed.
```
Packet Types
============
Below is the list of packet types and their fields.
Fields that are omitted are set to their defaults.
For example, `reason_code`, `qos`, and `packet_id` will
all default to `0`.
Some fields, like the topic for publish, are obligatory.
The encoder will crash if you leave out oblibatory fields.
Topics
------
Topics are parsed as lists (i.e. they are split on the `/` separator).
When encoding, both a binary and a list are accepted.
So the following are acceptable topics for the encoder:
* `<<"foo/bar">>`
* `[ <<"foo">> | <<"bar">> ]`
Which are both decoded as:
* `[ <<"foo">> | <<"bar">> ]`
Properties
----------
The (optional) properties of a package are represented as a map.
Known properties have an atom as key, user properties a binary.
Below is an example map with all properties and one user (`<<"myuserprop">>`) property. The values are random and
have no bearing in reality.
```Erlang
#{
payload_format_indicator => true,
message_expiry_interval => 1,
content_type => <<"text/plain">>,
response_topic => [ <<"response">>, <<"topic">> ],
correlation_data => <<"corrdata">>,
subscription_identifier => 2,
session_expiry_interval => 3,
assigned_client_identifier => <<"assclientid">>,
server_keep_alive => 4,
authentication_method => <<"authmethod">>,
authentication_data => <<"authdata">>,
request_problem_information => true,
will_delay_interval => 5,
request_response_information => false,
response_information => <<"respinfo">>,
server_reference => <<"servref">>,
reason_string => <<"reason">>,
receive_maximum => 12345,
topic_alias_maximum => 6,
topic_alias => 7,
maximum_qos => 2,
retain_available => true,
<<"myuserprop">> => <<"foobar">>,
maximum_packet_size => 1234567,
wildcard_subscription_available => true,
subscription_identifier_available => false,
shared_subscription_available => true
}.
```
CONNECT
-------
Minimal:
```Erlang
#{ type => connect }
```
Complete:
```erlang
#{
type => connect,
client_id => <<"foobar">>,
username => <<"someone">>,
password => <<"secret">>,
clean_start => true,
keep_alive => 120,
properties => #{
<<"foo">> => <<"bar">>,
will_delay_interval => 10
},
will_flag => true,
will_payload => <<>>,
will_properties => #{},
will_qos => 0,
will_retain => false,
will_topic => [ <<"good">>, <<"bye">> ]
}
```
CONNACK
-------
Minimal:
```Erlang
#{ type = connack }
```
Complete:
```erlang
#{
type => connack,
reason_code => 16#80,
session_present => true,
properties => #{
<<"foo">> => <<"bar">>
}
}
```
PUBLISH
-------
Minimal:
```Erlang
#{
type => publish,
topic => [ <<"foo">>, <<"bar">>, <<"la">> ]
}
```
Complete:
```erlang
#{
type => publish,
topic => [ <<"foo">>, <<"bar">>, <<"la">> ],
qos => 2,
dup => true,
retain => true,
packet_id => 1234,
payload => <<"aloha">>,
properties => #{
<<"foo">> => <<"bar">>
}
}
```
PUBACK / PUBREC / PUBREL / PUBCOMP
----------------------------------
These for packets are the same. Only the type code is different.
Minimal:
```Erlang
% Type is one of: puback, pubrec, pubrel, or pubcomp
#{ type = puback }
```
Complete:
```erlang
#{
type => puback,
reason_code => 16#81,
packet_id => 4321,
properties => #{
<<"bar">> => <<"fooooo">>
}
}
```
SUBSCRIBE
-------
The topics subscribed to are either maps with options or just a topic.
Minimal:
```Erlang
#{
type => subscribe,
topics => [
[ <<"foo1">>, <<"bar">> ]
]
}
```
Complete:
```erlang
#{
type => subscribe,
packet_id => 1234,
topics => [
#{
topic => [ "foo1", "bar" ],
no_local => true,
qos => 2,
retain_as_published => true,
retain_handling => 2
},
#{
topic => [ <<"foo2">>, <<"bar">> ]
}
],
properties => #{
<<"foo">> => <<"bar">>
}
}
```
SUBACK
-------
All acknowledgements are tuples `{ok, QoS}` or `{error, ReasonCode}`.
Minimal:
```Erlang
#{
type => suback,
acks => [
{ok, 0}
]
}
```
Complete (for four acks):
```erlang
#{
type => suback,
packet_id => 12345,
acks => [
{ok, 2},
{ok, 0},
{ok, 1},
{error, 16#80}
],
properties => #{
<<"foo">> => <<"bar">>
}
}
```
UNSUBSCRIBE
-----------
Minimal:
```Erlang
#{
type => unsubscribe,
topics => [
[ <<"foo">>, <<"bar">> ]
]
}
```
Complete:
```erlang
#{
type => unsubscribe,
packet_id => 42,
topics => [
<<"foo1/bar">>,
[ <<"foo2">>, <<"bar">> ]
],
properties => #{
<<"foo">> => <<"bar">>
}
}
```
UNSUBACK
-----------
The acknowledgements are one of:
* `{ok, found}`
* `{ok, notfound}`
* `{error, ReasonCode}`
Minimal:
```Erlang
#{
type => unsuback,
acks => [
{ok, found}
]
}
```
Complete (for three acks):
```erlang
#{
type => unsuback,
packet_id => 12345,
acks => [
{ok, found},
{ok, notfound},
{error, 16#80}
],
properties => #{
<<"foo">> => <<"bar">>
}
}
```
PINGREQ
-------
No special fields.
```Erlang
#{ type => pingreq }
```
PINGRESP
--------
No special fields
```Erlang
#{ type => pingresp }
```
DISCONNECT
----------
The default reason code for disconnects is `0`.
Minimal:
```Erlang
#{ type => disconnect }
```
Complete:
```erlang
#{
type => disconnect,
reason_code => 16#81,
properties => #{
<<"foo">> => <<"bar">>
}
}
```
AUTH
----
Minimal:
```Erlang
#{ type => auth }
```
Complete:
```erlang
{
type => auth,
reason_code => 16#80,
properties => #{
<<"foo">> => <<"bar">>,
authentication_method => <<"...">>,
authentication_data => <<"...">>
}
}
```
License
=======
This library is licensed under the Apache License version 2.0.
See the LICENSE file.