├── LICENSE ├── README.md ├── client.c ├── client.h ├── client_test.c ├── mqtt.c └── mqtt.h /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tiny-MQTT-c 2 | 3 | A small incomplete undocumented and poorly tested MQTT 3.1.1 client library written in <500 lines of C99. 4 | The library uses no dynamic allocation and compiles down to ~1K on ARM thumb. 5 | 6 | NOTE: This is very much a work in progress still. It should be fairly easy to hack on though. 7 | 8 | 9 | See [mqtt.h](https://github.com/kokke/tiny-MQTT-c/blob/master/mqtt.h) and [mqtt.c](https://github.com/kokke/tiny-MQTT-c/blob/master/mqtt.c) for the implementation of the MQTT protocol. 10 | 11 | [client.c](https://github.com/kokke/tiny-MQTT-c/blob/master/client.c), [client.h](https://github.com/kokke/tiny-MQTT-c/blob/master/client.h) and [client_test.c](https://github.com/kokke/tiny-MQTT-c/blob/master/client_test.c).c are just TCP drivers to test the MQTT library. The test is performed by connecting to a public MQTT broker and publishing some gibberish. 12 | 13 | Compile and try by running 14 | 15 | gcc client.c mqtt.c client_test.c -Wall -Wextra 16 | ./a.out & 17 | ./a.out pub 18 | 19 | .... and sit back and watch the horrors unfold 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /client.c: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | /* Helper functions: */ 18 | static void _dummy_connect (client_t* s, int f, const char* b) { (void) s; (void) f; (void) b; } 19 | static void _dummy_recv_data(client_t* s, int f, char* d, int l) { (void) s; (void) f; (void) d; (void) l; } 20 | 21 | static void _change_state(client_t* psClnt, conn_state_t new_state) 22 | { 23 | require(psClnt != 0); 24 | 25 | psClnt->state = new_state; 26 | psClnt->last_active = time(0); 27 | } 28 | 29 | 30 | 31 | /* 32 | Implementation of exported interface begins here 33 | */ 34 | 35 | void client_init(client_t* psClnt, char* dst_addr, uint16_t dst_port, char* rxbuf, uint32_t rxbufsize) 36 | { 37 | require(psClnt != 0); 38 | require(dst_addr != 0); 39 | require(rxbuf != 0); 40 | 41 | strcpy(psClnt->addr, dst_addr); 42 | psClnt->port = dst_port; 43 | psClnt->sockfd = 0; 44 | psClnt->rxbuf = rxbuf; 45 | psClnt->rxbufsz = rxbufsize; 46 | psClnt->client_connected = (void*)_dummy_connect; 47 | psClnt->client_disconnected = (void*)_dummy_connect; 48 | psClnt->client_new_data = (void*)_dummy_recv_data; 49 | 50 | /* 51 | Set the socket I/O mode: In this case FIONBIO 52 | enables or disables the blocking mode for the 53 | socket based on the numerical value of iMode. 54 | If iMode = 0, blocking is enabled; 55 | If iMode != 0, non-blocking mode is enabled. 56 | */ 57 | int iMode = 0; 58 | ioctl(psClnt->sockfd, FIONBIO, &iMode); 59 | 60 | /* 61 | // WINDOWS 62 | DWORD timeout = SOCKET_READ_TIMEOUT_SEC * 1000; 63 | setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)); 64 | */ 65 | 66 | _change_state(psClnt, CREATED); 67 | 68 | /* Ignore SIGPIPE - "broken pipe" / disconnected socket */ 69 | signal(SIGPIPE, SIG_IGN); 70 | } 71 | 72 | int client_set_callback(client_t* psClnt, cb_type eTyp, void* funcptr) 73 | { 74 | require(psClnt != 0); 75 | require(funcptr != 0); 76 | 77 | int success = 1; 78 | 79 | switch(eTyp) 80 | { 81 | case CB_ON_CONNECTION: psClnt->client_connected = funcptr; break; 82 | case CB_ON_DISCONNECT: psClnt->client_disconnected = funcptr; break; 83 | case CB_RECEIVED_DATA: psClnt->client_new_data = funcptr; break; 84 | default: success = 0; /* unknown callback-type */ break; 85 | } 86 | 87 | return success; 88 | } 89 | 90 | int client_send(client_t* psClnt, char* data, uint32_t nbytes) 91 | { 92 | require(psClnt != 0); 93 | require(data != 0); 94 | 95 | psClnt->last_active = time(0); 96 | printf("CLNT%u: sending %u bytes.\n", psClnt->sockfd, nbytes); 97 | 98 | int success = send(psClnt->sockfd, data, nbytes, 0); 99 | 100 | if (success < 0) 101 | { 102 | perror("send"); 103 | client_disconnect(psClnt); 104 | } 105 | 106 | return success; 107 | } 108 | 109 | int client_recv(client_t* psClnt, uint32_t timeout_us) 110 | { 111 | require(psClnt != 0); 112 | 113 | struct timeval tv; 114 | tv.tv_sec = 0; 115 | while (timeout_us >= 1000000) 116 | { 117 | timeout_us -= 1000000; 118 | tv.tv_sec += 1; 119 | } 120 | tv.tv_usec = timeout_us; /* Not init'ing this can cause strange errors */ 121 | setsockopt(psClnt->sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv,sizeof(struct timeval)); 122 | 123 | int nbytes = recv(psClnt->sockfd, psClnt->rxbuf, psClnt->rxbufsz, 0); 124 | if (nbytes <= 0) 125 | { 126 | /* got error or connection closed by server? */ 127 | if ( (nbytes == -1) /* nothing for us */ 128 | || (nbytes == EAGAIN) /* try again later */ 129 | || (nbytes == EWOULDBLOCK)) /* same as above */ 130 | { 131 | /* do nothing */ 132 | } 133 | else 134 | { 135 | /* connection lost / reset by peer */ 136 | perror("recv"); 137 | client_disconnect(psClnt); 138 | } 139 | } 140 | else 141 | { 142 | psClnt->client_new_data(psClnt, psClnt->rxbuf, nbytes); 143 | psClnt->last_active = time(0); 144 | } 145 | return nbytes; 146 | } 147 | 148 | int client_state(client_t* psClnt) 149 | { 150 | return ((psClnt != 0) ? psClnt->state : 0); 151 | } 152 | 153 | void client_poll(client_t* psClnt, uint32_t timeout_us) 154 | { 155 | /* 10usec timeout is a reasonable minimum I think */ 156 | timeout_us = ((timeout_us >= 10) ? timeout_us : 10); 157 | 158 | require(psClnt != 0); 159 | 160 | switch (psClnt->state) 161 | { 162 | case CREATED: 163 | { 164 | _change_state(psClnt, DISCONNECTED); 165 | } break; 166 | 167 | case CONNECTED: 168 | { 169 | client_recv(psClnt, timeout_us); 170 | /* 171 | static time_t timeLastMsg = 0; 172 | if ((time(0) - timeLastMsg) > 0) 173 | { 174 | timeLastMsg = time(0); 175 | client_send(psClnt, "hej mor!", 8); 176 | } 177 | */ 178 | } break; 179 | 180 | case DISCONNECTED: 181 | { 182 | /* ~1 second cooldown */ 183 | if (time(0) > psClnt->last_active) 184 | { 185 | client_connect(psClnt); 186 | } 187 | } break; 188 | } 189 | 190 | } 191 | 192 | 193 | void client_disconnect(client_t* psClnt) 194 | { 195 | require(psClnt != 0); 196 | 197 | shutdown(psClnt->sockfd, 2); 198 | close(psClnt->sockfd); 199 | 200 | _change_state(psClnt, DISCONNECTED); 201 | psClnt->client_disconnected(psClnt); 202 | } 203 | 204 | int client_connect(client_t* psClnt) 205 | { 206 | require(psClnt != 0); 207 | 208 | int success = 0; 209 | 210 | struct sockaddr_in serv_addr; 211 | struct hostent* server; 212 | 213 | psClnt->sockfd = socket(AF_INET, SOCK_STREAM, 0); 214 | if (psClnt->sockfd < 0) 215 | { 216 | perror("socket"); 217 | } 218 | else 219 | { 220 | server = gethostbyname(psClnt->addr); 221 | if (server == NULL) 222 | { 223 | fprintf(stderr,"ERROR, no such host\n"); 224 | } 225 | else 226 | { 227 | memset(&serv_addr, 0, sizeof(serv_addr)); 228 | serv_addr.sin_family = AF_INET; 229 | memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length); 230 | serv_addr.sin_port = htons(psClnt->port); 231 | 232 | if (connect(psClnt->sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) 233 | { 234 | perror("connect"); 235 | /* Clean up by calling close() on socket before allocating a new socket. */ 236 | client_disconnect(psClnt); 237 | } 238 | else 239 | { 240 | _change_state(psClnt, CONNECTED); 241 | psClnt->client_connected(psClnt); 242 | success = 1; 243 | } 244 | } 245 | } 246 | 247 | return success; 248 | } 249 | 250 | -------------------------------------------------------------------------------- /client.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define NCONNECTIONS 1 5 | #define BUFFER_SIZE_BYTES 1024 6 | 7 | /* Assertion macro */ 8 | #define require(predicate) assert((predicate)) 9 | 10 | 11 | /* Socket connection state */ 12 | typedef enum 13 | { 14 | CREATED, 15 | CONNECTED, 16 | DISCONNECTED, 17 | } conn_state_t; 18 | 19 | /* Type definitions */ 20 | typedef enum 21 | { 22 | CB_ON_CONNECTION, 23 | CB_ON_DISCONNECT, 24 | CB_RECEIVED_DATA, 25 | } cb_type; 26 | 27 | typedef struct 28 | { 29 | int sockfd; 30 | char* rxbuf; 31 | uint32_t rxbufsz; 32 | conn_state_t state; 33 | uint16_t port; 34 | char addr[32]; 35 | time_t last_active; 36 | 37 | /* Callbacks */ 38 | void (*client_connected) (void* psClnt); /* new connection established */ 39 | void (*client_disconnected)(void* psClnt); /* a connection was closed */ 40 | void (*client_new_data) (void* psClnt, char* data, int nbytes); /* data has been received */ 41 | } client_t; 42 | 43 | 44 | 45 | void client_init(client_t* psClnt, char* dst_addr, uint16_t dst_port, char* rxbuf, uint32_t rxbufsize); 46 | int client_set_callback(client_t* psClnt, cb_type eTyp, void* funcptr); 47 | int client_send(client_t* psClnt, char* data, uint32_t nbytes); 48 | int client_recv(client_t* psClnt, uint32_t timeout_us); 49 | void client_poll(client_t* psClnt, uint32_t timeout_us); 50 | void client_disconnect(client_t* psClnt); 51 | int client_connect(client_t* psClnt); 52 | int client_state(client_t* psClnt); 53 | 54 | 55 | -------------------------------------------------------------------------------- /client_test.c: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "mqtt.h" 8 | 9 | int keepalive_sec = 4; 10 | client_t c; 11 | char buffer[BUFFER_SIZE_BYTES]; 12 | uint8_t buf[128]; 13 | int nbytes; 14 | int is_subscriber = 1; 15 | 16 | static void got_connection(client_t* psClnt) 17 | { 18 | require(psClnt != 0); 19 | 20 | printf("CLNT%d: connected to server.\n", psClnt->sockfd); 21 | 22 | if (is_subscriber) 23 | nbytes = mqtt_encode_connect_msg2(buf, 0x02, keepalive_sec, (uint8_t*)"DIGI", 4); 24 | else 25 | nbytes = mqtt_encode_connect_msg2(buf, 0x02, keepalive_sec, (uint8_t*)"DOGO", 4); 26 | 27 | client_send(&c, (char*)buf, nbytes); 28 | } 29 | 30 | static void lost_connection(client_t* psClnt) 31 | { 32 | require(psClnt != 0); 33 | 34 | printf("CLNT%d: disconnected from server.\n", psClnt->sockfd); 35 | } 36 | 37 | static void got_data(client_t* psClnt, unsigned char* data, int nbytes) 38 | { 39 | require(psClnt != 0); 40 | 41 | printf("CLNT%d: got %d bytes (", psClnt->sockfd, nbytes); 42 | 43 | uint8_t ctrl = data[0] >> 4; 44 | if (ctrl == CTRL_CONNACK) { printf("CONNACK"); } 45 | if (ctrl == CTRL_PINGRESP) { printf("PINGRESP"); } 46 | if (ctrl == CTRL_PUBACK) { printf("PUBACK"); } 47 | if (ctrl == CTRL_SUBACK) { printf("SUBACK"); } 48 | if (ctrl == CTRL_PUBLISH) 49 | { 50 | uint8_t qos; 51 | uint16_t msg_id; 52 | uint16_t topic_len; 53 | uint8_t* topic; 54 | uint8_t* payload = 0; 55 | 56 | printf("PUBLISH"); 57 | 58 | if (mqtt_decode_publish_msg(data, nbytes, &qos, &msg_id, &topic_len, &topic, &payload)) 59 | { 60 | int msg_len = (int)(nbytes-2)-(int)(data-payload); 61 | printf(" topic='%.*s', msg='%.*s'", (int)topic_len, topic, msg_len, payload); 62 | } 63 | } 64 | printf(") '"); 65 | int i; 66 | for (i = 0; i < nbytes; ++i) 67 | { 68 | uint8_t c = data[i]; 69 | if (c == '\n') 70 | { 71 | printf("\\n"); 72 | } 73 | else 74 | { 75 | printf("%c", (c < 32 || c > 126) ? '?' : c); 76 | } 77 | } 78 | printf("'\n"); 79 | } 80 | 81 | void inthandler(int dummy) 82 | { 83 | (void)dummy; 84 | 85 | int nbytes = mqtt_encode_disconnect_msg(buf); 86 | if (nbytes != 0) 87 | { 88 | client_send(&c, (char*)buf, nbytes); 89 | } 90 | 91 | client_poll(&c, 10000000); 92 | 93 | exit(0); 94 | } 95 | 96 | 97 | 98 | 99 | int main(int argc, char* argv[]) 100 | { 101 | (void) argv; 102 | is_subscriber = (argc == 1); 103 | printf("client, %s\n", (is_subscriber ? "subscriber" : "publisher")); 104 | 105 | client_init(&c, "test.mosquitto.org", 1883, buffer, BUFFER_SIZE_BYTES); 106 | //client_init(&c, "mqtt.fluux.io", 1883, buffer, BUFFER_SIZE_BYTES); 107 | 108 | assert(client_set_callback(&c, CB_RECEIVED_DATA, got_data) == 1); 109 | assert(client_set_callback(&c, CB_ON_CONNECTION, got_connection) == 1); 110 | assert(client_set_callback(&c, CB_ON_DISCONNECT, lost_connection) == 1); 111 | 112 | signal(SIGINT, inthandler); 113 | 114 | const time_t ping_interval = (keepalive_sec - 1); 115 | int subscribed = 0; 116 | time_t next_pub = time(0) + 10; 117 | time_t next_ping = time(0) + ping_interval; 118 | 119 | char buf2[1024]; 120 | int nbytes; 121 | while (1) 122 | { 123 | client_poll(&c, 0); 124 | 125 | if (is_subscriber && !subscribed && ((time(0) - c.last_active) >= 2)) 126 | { 127 | nbytes = mqtt_encode_subscribe_msg((uint8_t*)buf2, (uint8_t*)"a/b", 3, 1, 12345); 128 | client_send(&c, buf2, nbytes); 129 | subscribed = 1; 130 | client_poll(&c, 0); 131 | } 132 | 133 | if ((time(0) ) >= next_ping) 134 | { 135 | nbytes = mqtt_encode_ping_msg(buf); 136 | if (nbytes != 0) 137 | { 138 | printf("ping!\n"); 139 | client_send(&c, (char*)buf, nbytes); 140 | client_poll(&c, 0); 141 | next_ping = time(0) + ping_interval; 142 | } 143 | } 144 | 145 | if ((time(0) > next_pub) && !is_subscriber) 146 | { 147 | nbytes = mqtt_encode_publish_msg((uint8_t*)buf2, (uint8_t*)"a/b", 3, 1, 10, (uint8_t*)"hi mom!", 7); 148 | client_send(&c, buf2, nbytes); 149 | next_pub = time(0) + 10; 150 | client_poll(&c, 1000000); 151 | } 152 | 153 | usleep(1000); 154 | } 155 | 156 | return 0; 157 | } 158 | 159 | 160 | -------------------------------------------------------------------------------- /mqtt.c: -------------------------------------------------------------------------------- 1 | #include "mqtt.h" 2 | #include 3 | #include 4 | 5 | 6 | 7 | 8 | static int mqtt_encode_length(unsigned int nbytes, uint8_t* au8data) 9 | { 10 | int nbytes_encoded = 0; 11 | if (nbytes < 0x10000000) /* max MQTT packet size */ 12 | { 13 | unsigned int x = nbytes; 14 | do 15 | { 16 | uint8_t u8digit = x % 128; 17 | x /= 128; /* >> 7 */ 18 | if (x != 0) 19 | u8digit |= 0x80; 20 | au8data[nbytes_encoded++] = u8digit; 21 | } 22 | while (x > 0); 23 | } 24 | return nbytes_encoded; 25 | } 26 | 27 | 28 | 29 | static int mqtt_decode_length(uint8_t* au8data) 30 | { 31 | int nbytes = 0; 32 | int multiplier = 1; 33 | unsigned int array_idx = 0; 34 | uint8_t u8digit; 35 | do 36 | { 37 | u8digit = au8data[array_idx++]; 38 | nbytes += (u8digit & 127) * multiplier; 39 | multiplier *= 128; 40 | } 41 | while ( (++array_idx < 4) 42 | && ((u8digit & 128) != 0)); 43 | return nbytes; 44 | } 45 | 46 | 47 | static int mqtt_encode_msg(uint8_t* pu8dst, uint8_t u8ctrl_type, uint8_t u8flgs, uint8_t** apu8data_in, uint32_t* au32input_len, uint32_t u32nargs, uint32_t u32input_len) 48 | { 49 | int nbytes_encoded = 0; 50 | if ( (pu8dst != 0) 51 | && (u8ctrl_type > 0) /* cmd type = [1:14] .. 0 + 15 are reserved */ 52 | && (u8ctrl_type < 15) 53 | && (u8flgs < 16) /* flag are 4 bits wide, [0:15] */ 54 | && ( (u32nargs == 0) /* Allow for msg's without payload */ 55 | || ( (apu8data_in != 0) /* ... otherwise, check the args */ 56 | && (au32input_len != 0)))) 57 | { 58 | pu8dst[0] = (u8ctrl_type << 4) | (u8flgs); 59 | 60 | int idx = mqtt_encode_length(u32input_len, &pu8dst[1]); 61 | uint32_t i, j; 62 | for (i = 0; i < u32nargs; ++i) 63 | { 64 | for (j = 0; j < au32input_len[i]; ++j, ++nbytes_encoded) 65 | { 66 | pu8dst[idx + nbytes_encoded + 1] = apu8data_in[i][j]; 67 | } 68 | } 69 | nbytes_encoded += (1 + idx); 70 | } 71 | return nbytes_encoded; 72 | } 73 | 74 | 75 | int mqtt_decode_msg(uint8_t* pu8src, uint8_t* pu8ctrl_type, uint8_t* pu8flgs, uint8_t* pu8data_out, uint32_t* pu32output_len) 76 | { 77 | int nbytes_decoded = 0; 78 | if ( (pu8src != 0) 79 | && (pu8ctrl_type != 0) 80 | && (pu8flgs != 0) 81 | && (pu8data_out != 0) 82 | && (pu32output_len != 0)) 83 | { 84 | *pu8ctrl_type = ((*pu8src) >> 4); 85 | *pu8flgs = ((*pu8src) & 0x0F); 86 | int idx = 1; 87 | int nbytes = mqtt_decode_length(&pu8src[idx]); 88 | //printf("nbytes = %d\n", nbytes); 89 | if (nbytes < 0x80) /* 0 <= nbytes < 128 */ 90 | { 91 | idx += 1; 92 | } 93 | else if (nbytes < 0x4000) /* 128 <= nbytes < 16384 */ 94 | { 95 | idx += 2; 96 | } 97 | else if (nbytes < 0x200000) /* 16384 <= nbytes < 2097152 */ 98 | { 99 | idx += 3; 100 | } 101 | else 102 | { 103 | idx += 4; 104 | assert(nbytes < 0x10000000); 105 | } 106 | /* Copy decoded data to output-buffer */ 107 | int i; 108 | for (i = 0; i < nbytes; ++i) 109 | { 110 | pu8data_out[i] = pu8src[idx + i]; 111 | } 112 | //memcpy(pu8data_out, &pu8src[idx], nbytes); 113 | *pu32output_len = nbytes; 114 | nbytes_decoded = nbytes + idx; 115 | } 116 | return nbytes_decoded; 117 | } 118 | 119 | 120 | 121 | /* Advanced connect: More options available */ 122 | int mqtt_encode_connect_msg2(uint8_t* pu8dst, uint8_t u8conn_flgs, uint16_t u16keepalive, uint8_t* pu8clientid, uint16_t u16clientid_len) 123 | { 124 | int nbytes_encoded = 0; 125 | if ( (pu8dst != 0) 126 | && (pu8clientid != 0)) 127 | { 128 | const uint32_t u32hdr_len = 12; 129 | uint8_t u8keepalive_msb = (u16keepalive & 0xFF00) >> 8; /* Bug if on Big-Endian machine */ 130 | uint8_t u8keepalive_lsb = (u16keepalive & 0x00FF); 131 | uint8_t u8clientid_len_msb = (u16clientid_len & 0xFF00) >> 8; /* Bug if on Big-Endian machine */ 132 | uint8_t u8clientid_len_lsb = (u16clientid_len & 0x00FF); 133 | uint8_t au8conn_buf[u32hdr_len]; 134 | /* Variable part of header for CONNECT msg */ 135 | int idx = 0; 136 | au8conn_buf[idx++] = 0x00; /* protocol version length U16 */ 137 | au8conn_buf[idx++] = 0x04; /* 4 bytes long (MQTT) */ 138 | au8conn_buf[idx++] = 'M'; 139 | au8conn_buf[idx++] = 'Q'; 140 | au8conn_buf[idx++] = 'T'; 141 | au8conn_buf[idx++] = 'T'; 142 | au8conn_buf[idx++] = 0x04; /* 4 == MQTT version 3.1.1 */ 143 | au8conn_buf[idx++] = u8conn_flgs; 144 | au8conn_buf[idx++] = u8keepalive_msb; 145 | au8conn_buf[idx++] = u8keepalive_lsb; 146 | au8conn_buf[idx++] = u8clientid_len_msb; 147 | au8conn_buf[idx++] = u8clientid_len_lsb; 148 | 149 | uint8_t* buffers[] = { au8conn_buf, pu8clientid }; 150 | uint32_t sizes[] = { u32hdr_len, u16clientid_len }; 151 | 152 | nbytes_encoded = mqtt_encode_msg(pu8dst, CTRL_CONNECT, 0, buffers, sizes, 2, u32hdr_len + u16clientid_len); 153 | } 154 | return nbytes_encoded; 155 | } 156 | 157 | 158 | /* Simple connect: No username/password, no QoS etc. */ 159 | int mqtt_encode_connect_msg(uint8_t* pu8dst, uint8_t* pu8clientid, uint16_t u16clientid_len) /* u8conn_flgs = 2, u16keepalive = 60 */ 160 | { 161 | return mqtt_encode_connect_msg2(pu8dst, 0x02, 60, pu8clientid, u16clientid_len); 162 | } 163 | 164 | int mqtt_encode_disconnect_msg(uint8_t* pu8dst) 165 | { 166 | return mqtt_encode_msg(pu8dst, CTRL_DISCONNECT, 0x02, 0, 0, 0, 0); 167 | } 168 | 169 | int mqtt_encode_ping_msg(uint8_t* pu8dst) 170 | { 171 | return mqtt_encode_msg(pu8dst, CTRL_PINGREQ, 0x02, 0, 0, 0, 0); 172 | } 173 | 174 | 175 | int mqtt_encode_publish_msg(uint8_t* pu8dst, uint8_t* pu8topic, uint16_t u16topic_len, uint8_t u8qos, uint16_t u16msg_id, uint8_t* pu8payload, uint32_t u32data_len) 176 | { 177 | int nbytes_encoded = 0; 178 | if (pu8topic != 0) 179 | { 180 | uint32_t u32msg_len = sizeof(uint16_t) + u16topic_len + sizeof(uint16_t) + u32data_len; 181 | uint8_t u8topic_len_msb = (u16topic_len & 0xFF00) >> 8; /* Bug if on Big-Endian machine */ 182 | uint8_t u8topic_len_lsb = (u16topic_len & 0x00FF); 183 | uint8_t u8msg_id_msb = (u16msg_id & 0xFF00) >> 8; /* Bug if on Big-Endian machine */ 184 | uint8_t u8msg_id_lsb = (u16msg_id & 0x00FF); 185 | uint8_t au8topic_len_buf[sizeof(uint16_t)] = { u8topic_len_msb, u8topic_len_lsb }; 186 | uint8_t au8msg_id_buf[sizeof(uint16_t)] = { u8msg_id_msb, u8msg_id_lsb }; 187 | uint8_t* buffers[] = { au8topic_len_buf, pu8topic, au8msg_id_buf, pu8payload }; 188 | uint32_t sizes[] = { sizeof(uint16_t), u16topic_len, sizeof(uint16_t), u32data_len }; 189 | nbytes_encoded = mqtt_encode_msg(pu8dst, CTRL_PUBLISH, u8qos << 1, buffers, sizes, 4, u32msg_len); 190 | } 191 | return nbytes_encoded; 192 | } 193 | 194 | 195 | 196 | 197 | static int encode_pubsub_msg2(uint8_t* pu8dst, uint8_t u8ctrl, uint8_t** apu8topic, uint16_t* au16topic_len, uint8_t* au8qos, uint32_t u32nargs, uint16_t u16msg_id) 198 | { 199 | int nbytes_encoded = 0; 200 | if ( (apu8topic != 0) 201 | && (u32nargs < MSG_SUB_MAXNTOPICS)) 202 | { 203 | uint8_t topicsizes[sizeof(uint16_t)][MSG_SUB_MAXNTOPICS]; 204 | uint32_t sizes[1 + (3 * MSG_SUB_MAXNTOPICS)]; /* for each topic a topic-len, the topic itself, and the qos -- hence 3 * max(subs_pr_msg) */ 205 | uint8_t* buffers[1 + (3 * MSG_SUB_MAXNTOPICS)]; /* msgid, [topic-len + topic + qos]+ */ 206 | uint8_t u8msg_id_msb = (u16msg_id & 0xFF00) >> 8; /* Bug if on Big-Endian machine */ 207 | uint8_t u8msg_id_lsb = (u16msg_id & 0x00FF); 208 | uint8_t au8msg_id_buf[sizeof(uint16_t)] = { u8msg_id_msb, u8msg_id_lsb }; 209 | int bufidx = 0; 210 | buffers[bufidx] = au8msg_id_buf; 211 | sizes[bufidx++] = sizeof(uint16_t); 212 | uint32_t u32msg_len = sizeof(uint16_t); /* msgid */ 213 | uint8_t u8highest_qos = 0; 214 | uint32_t i; 215 | for (i = 0; i < u32nargs; ++i) 216 | { 217 | u32msg_len += sizeof(uint16_t) + au16topic_len[i] + sizeof(uint8_t); /* topic-len + topic + qos */ 218 | uint8_t u8topic_len_msb = (au16topic_len[i] & 0xFF00) >> 8; /* Bug if on Big-Endian machine */ 219 | uint8_t u8topic_len_lsb = (au16topic_len[i] & 0x00FF); 220 | topicsizes[i][0] = u8topic_len_msb; 221 | topicsizes[i][1] = u8topic_len_lsb; 222 | buffers[bufidx] = topicsizes[i]; 223 | sizes[bufidx++] = sizeof(uint16_t); 224 | buffers[bufidx] = apu8topic[i]; 225 | sizes[bufidx++] = au16topic_len[i]; 226 | buffers[bufidx] = &au8qos[i]; 227 | sizes[bufidx++] = sizeof(uint8_t); 228 | /* Send MQTT SUBSCRIBE msg with highest QoS of all subscribtions */ 229 | if (au8qos[i] > u8highest_qos) 230 | { 231 | u8highest_qos = au8qos[i]; 232 | } 233 | } 234 | nbytes_encoded = mqtt_encode_msg(pu8dst, u8ctrl, u8highest_qos << 1, buffers, sizes, bufidx, u32msg_len); 235 | } 236 | return nbytes_encoded; 237 | } 238 | 239 | 240 | 241 | int mqtt_encode_subscribe_msg(uint8_t* pu8dst, uint8_t* pu8topic, uint16_t u16topic_len, uint8_t u8qos, uint16_t u16msg_id) 242 | { 243 | return encode_pubsub_msg2(pu8dst, CTRL_SUBSCRIBE, &pu8topic, &u16topic_len, &u8qos, 1, u16msg_id); 244 | } 245 | 246 | int mqtt_encode_unsubscribe_msg(uint8_t* pu8dst, uint8_t* pu8topic, uint16_t u16topic_len, uint8_t u8qos, uint16_t u16msg_id) 247 | { 248 | return encode_pubsub_msg2(pu8dst, CTRL_UNSUBSCRIBE, &pu8topic, &u16topic_len, &u8qos, 1, u16msg_id); 249 | } 250 | int mqtt_encode_subscribe_msg2(uint8_t* pu8dst, uint8_t** apu8topic, uint16_t* au16topic_len, uint8_t* au8qos, uint32_t u32nargs, uint16_t u16msg_id) 251 | { 252 | return encode_pubsub_msg2(pu8dst, CTRL_SUBSCRIBE, apu8topic, au16topic_len, au8qos, u32nargs, u16msg_id); 253 | } 254 | 255 | 256 | int mqtt_encode_unsubscribe_msg2(uint8_t* pu8dst, uint8_t** apu8topic, uint16_t* au16topic_len, uint8_t* au8qos, uint32_t u32nargs, uint16_t u16msg_id) 257 | { 258 | return encode_pubsub_msg2(pu8dst, CTRL_UNSUBSCRIBE, apu8topic, au16topic_len, au8qos, u32nargs, u16msg_id); 259 | } 260 | 261 | 262 | 263 | 264 | int mqtt_decode_connack_msg(uint8_t* pu8src, uint32_t u32nbytes) 265 | { 266 | return ( (pu8src != 0) 267 | && (u32nbytes >= 4) 268 | && (pu8src[0] == 0x20) /* 0x20 : CTRL_CONNACK << 4 */ 269 | && (pu8src[1] == 0x02) /* 0x02 : bytes after fixed header */ 270 | // && (pu8src[3] == 0x00) /* 0x00 : 3rd byte is reserved */ 271 | && (pu8src[3] == 0x00)); /* 0x00 : Connection Accepted */ 272 | } 273 | 274 | 275 | int mqtt_decode_pingresp_msg(uint8_t* pu8src, uint32_t u32nbytes) 276 | { 277 | return ( (pu8src != 0) 278 | && (u32nbytes >= 2) 279 | && (pu8src[0] == 0xd0) /* 0xD0 : CTRL_PINGRESP << 4 */ 280 | && (pu8src[1] == 0x00)); /* 0x00 : bytes after fixed header */ 281 | } 282 | 283 | int mqtt_decode_puback_msg(uint8_t* pu8src, uint32_t u32nbytes, uint16_t* pu16msg_id_out) 284 | { 285 | int success = 0; 286 | if ( (pu8src != 0) 287 | && (u32nbytes >= 4) 288 | && (pu8src[0] == 0x40) /* 0x40 : CTRL_PUBACK << 4 */ 289 | && (pu8src[1] == 0x02) /* 0x02 : bytes after fixed header */ 290 | && (pu16msg_id_out != 0)) 291 | { 292 | *pu16msg_id_out = (pu8src[2] << 8) | pu8src[3]; 293 | success = 1; 294 | } 295 | return success; 296 | } 297 | 298 | 299 | int mqtt_decode_suback_msg(uint8_t* pu8src, uint32_t u32nbytes, uint16_t* pu16msg_id_out) 300 | { 301 | int success = 0; 302 | if ( (pu8src != 0) 303 | && (u32nbytes >= 4) 304 | && (pu8src[0] == 0x90) /* 0x90 : CTRL_SUBACK << 4 */ 305 | && (pu8src[1] == 0x02) /* 0x02 : bytes after fixed header */ 306 | && (pu16msg_id_out != 0)) 307 | { 308 | *pu16msg_id_out = (pu8src[2] << 8) | pu8src[3]; 309 | success = 1; 310 | } 311 | return success; 312 | } 313 | 314 | int mqtt_decode_publish_msg(uint8_t* pu8src, uint32_t u32nbytes, uint8_t* pu8qos, uint16_t* pu16msg_id_out, uint16_t* pu16topic_len, uint8_t** ppu8topic, uint8_t** ppu8payload) 315 | { 316 | int success = 0; 317 | if ( (pu8src != 0) 318 | && (u32nbytes >= 6) 319 | && (pu8src[0] >> 4 == CTRL_PUBLISH) 320 | && (pu16msg_id_out != 0) 321 | && (pu16topic_len != 0) 322 | && (ppu8topic != 0) 323 | && (ppu8payload != 0) ) 324 | { 325 | *pu8qos = (pu8src[0] >> 1) & 3; 326 | uint16_t u16topic_len = (pu8src[2] << 8) | pu8src[3]; 327 | *pu16topic_len = u16topic_len; 328 | *ppu8topic = &pu8src[4]; 329 | *pu16msg_id_out = (pu8src[4 + u16topic_len] << 8) | pu8src[5 + u16topic_len]; 330 | *ppu8payload = &pu8src[6 + u16topic_len]; 331 | success = 1; 332 | } 333 | return success; 334 | } 335 | 336 | 337 | 338 | #if defined(TEST) && (TEST == 1) 339 | 340 | int main(void) 341 | { 342 | // int mqtt_encode_connect_msg2(uint8_t* pu8dst, uint8_t u8conn_flgs, uint16_t u16keepalive, uint8_t* pu8clientid, uint16_t u16clientid_len) 343 | uint8_t buf[128]; 344 | int nbytes; 345 | int i; 346 | 347 | nbytes = mqtt_encode_connect_msg2(buf, 0x02, 60, (uint8_t*)"DIGI", 4); 348 | printf("con: "); 349 | for (i = 0; i < nbytes; ++i) 350 | printf("0x%.02x ", buf[i]); 351 | printf("\n"); 352 | 353 | nbytes = mqtt_encode_connect_msg(buf, (uint8_t*)"DIGI", 4); 354 | printf("con: "); 355 | for (i = 0; i < nbytes; ++i) 356 | printf("0x%.02x ", buf[i]); 357 | printf("\n"); 358 | 359 | nbytes = mqtt_encode_disconnect_msg(buf); 360 | for (i = 0; i < nbytes; ++i) 361 | printf("0x%.02x ", buf[i]); 362 | printf("\n"); 363 | 364 | nbytes = mqtt_encode_ping_msg(buf); 365 | for (i = 0; i < nbytes; ++i) 366 | printf("0x%.02x ", buf[i]); 367 | printf("\n"); 368 | 369 | nbytes = mqtt_encode_publish_msg(buf, (uint8_t*)"a/b", 3, 1, 32767, (uint8_t*)"payload", 8); 370 | //nbytes = mqtt_encode_publish_msg(buf, (uint8_t*)"a/b", 3, 1, 10, 0, 0); 371 | printf("pub: "); 372 | for (i = 0; i < nbytes; ++i) 373 | { 374 | if (buf[i] >= 'A' && buf[i] <= 'z') 375 | printf("%c ", buf[i]); 376 | else 377 | printf("0x%.02x ", buf[i]); 378 | } 379 | printf("\n"); 380 | 381 | { 382 | uint16_t msg_id, topic_len; 383 | uint8_t qos; 384 | uint8_t* topic; 385 | uint8_t* payload; 386 | int rc = mqtt_decode_publish_msg(buf, nbytes, &qos, &msg_id, &topic_len, &topic, &payload); 387 | printf("nbytes = %u\n", nbytes); 388 | printf("decode pub msg = %d:\n", rc); 389 | printf(" qos = %u\n", qos); 390 | printf(" msg id = %u\n", msg_id); 391 | printf(" topic-len = %u\n", topic_len); 392 | printf(" topic = '%s'\n", topic); 393 | printf(" payload = '%s'\n", payload); 394 | } 395 | 396 | nbytes = mqtt_encode_subscribe_msg(buf, (uint8_t*)"a/b", 3, 1, 32767); 397 | printf("sub: "); 398 | for (i = 0; i < nbytes; ++i) 399 | printf("0x%.02x ", buf[i]); 400 | printf("\n"); 401 | 402 | uint8_t* tpcs[] = { (uint8_t*)"a/b" }; 403 | uint16_t tpclens[] = { 3 }; 404 | uint8_t tpcqos[] = { 1 }; 405 | nbytes = mqtt_encode_subscribe_msg2(buf, tpcs, tpclens, tpcqos, 1, 32767); 406 | printf("sub: "); 407 | for (i = 0; i < nbytes; ++i) 408 | printf("0x%.02x ", buf[i]); 409 | printf("\n"); 410 | 411 | nbytes = mqtt_encode_unsubscribe_msg(buf, (uint8_t*)"a/b", 3, 1, 32767); 412 | printf("unsub: "); 413 | for (i = 0; i < nbytes; ++i) 414 | printf("0x%.02x ", buf[i]); 415 | printf("\n"); 416 | 417 | nbytes = mqtt_encode_unsubscribe_msg2(buf, tpcs, tpclens, tpcqos, 1, 32767); 418 | printf("unsub: "); 419 | for (i = 0; i < nbytes; ++i) 420 | printf("0x%.02x ", buf[i]); 421 | printf("\n"); 422 | 423 | 424 | 425 | uint8_t au8connack_msg[] = { 0x20, 0x02, 0x00, 0x00 }; 426 | printf("connack(msg) = %d \n", mqtt_decode_connack_msg(au8connack_msg, sizeof(au8connack_msg))); 427 | 428 | uint8_t au8pingresp_msg[] = { 0xd0, 0x00 }; 429 | printf("pingresp(msg) = %d \n", mqtt_decode_pingresp_msg(au8pingresp_msg, sizeof(au8pingresp_msg))); 430 | 431 | uint16_t u16msg_id; 432 | uint8_t au8puback_msg[] = { 0x40, 0x02, 0x7f, 0xff }; 433 | printf("puback(msg) = %d \n", mqtt_decode_puback_msg(au8puback_msg, sizeof(au8puback_msg), &u16msg_id)); 434 | 435 | 436 | 437 | return 0; 438 | } 439 | 440 | #endif 441 | 442 | -------------------------------------------------------------------------------- /mqtt.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* Max number of topics one can subscribe to in a single SUBSCRIBE message */ 4 | #define MSG_SUB_MAXNTOPICS 8 5 | 6 | /* Control Command Types */ 7 | enum 8 | { 9 | CTRL_RESERVED, /* 0 */ 10 | CTRL_CONNECT, /* 1 */ 11 | CTRL_CONNACK, /* 2 */ 12 | CTRL_PUBLISH, /* 3 */ 13 | CTRL_PUBACK, /* 4 */ 14 | CTRL_PUBREC, /* 5 */ 15 | CTRL_PUBREL, /* 6 */ 16 | CTRL_PUBCOMP, /* 7 */ 17 | CTRL_SUBSCRIBE, /* 8 */ 18 | CTRL_SUBACK, /* 9 */ 19 | CTRL_UNSUBSCRIBE, /* 10 */ 20 | CTRL_UNSUBACK, /* 11 */ 21 | CTRL_PINGREQ, /* 12 */ 22 | CTRL_PINGRESP, /* 13 */ 23 | CTRL_DISCONNECT, /* 14 */ 24 | }; 25 | 26 | enum 27 | { 28 | QOS_AT_MOST_ONCE, /* 0 - "Fire and Forget" */ 29 | QOS_AT_LEAST_ONCE, /* 1 - ARQ until ACK */ 30 | QOS_EXACTLY_ONCE, /* 2 - Transactional delivery */ 31 | }; 32 | 33 | 34 | 35 | /* Simple connect: No username/password, no QoS etc. */ 36 | int mqtt_encode_connect_msg(uint8_t* pu8dst, uint8_t* pu8clientid, uint16_t u16clientid_len); /* u8conn_flgs = 2, u16keepalive = 60 */ 37 | /* Advanced connect: More options available */ 38 | int mqtt_encode_connect_msg2(uint8_t* pu8dst, uint8_t u8conn_flgs, uint16_t u16keepalive, uint8_t* pu8clientid, uint16_t u16clientid_len); 39 | int mqtt_encode_disconnect_msg(uint8_t* pu8dst); 40 | int mqtt_encode_ping_msg(uint8_t* pu8dst); 41 | int mqtt_encode_publish_msg(uint8_t* pu8dst, uint8_t* pu8topic, uint16_t u16topic_len, uint8_t u8qos, uint16_t u16msg_id, uint8_t* pu8payload, uint32_t u32data_len); 42 | int mqtt_encode_subscribe_msg(uint8_t* pu8dst, uint8_t* pu8topic, uint16_t u16topic_len, uint8_t u8qos, uint16_t u16msg_id); 43 | int mqtt_encode_subscribe_msg2(uint8_t* pu8dst, uint8_t** apu8topic, uint16_t* au16topic_len, uint8_t* au8qos, uint32_t u32nargs, uint16_t u16msg_id); 44 | int mqtt_encode_unsubscribe_msg(uint8_t* pu8dst, uint8_t* pu8topic, uint16_t u16topic_len, uint8_t u8qos, uint16_t u16msg_id); 45 | int mqtt_encode_unsubscribe_msg2(uint8_t* pu8dst, uint8_t** apu8topic, uint16_t* au16topic_len, uint8_t* au8qos, uint32_t u32nargs, uint16_t u16msg_id); 46 | 47 | int mqtt_decode_connack_msg(uint8_t* pu8src, uint32_t u32nbytes); 48 | int mqtt_decode_pingresp_msg(uint8_t* pu8src, uint32_t u32nbytes); 49 | int mqtt_decode_puback_msg(uint8_t* pu8src, uint32_t u32nbytes, uint16_t* pu16msg_id); 50 | int mqtt_decode_suback_msg(uint8_t* pu8src, uint32_t u32nbytes, uint16_t* pu16msg_id_out); 51 | 52 | int mqtt_decode_msg(uint8_t* pu8src, uint8_t* pu8ctrl_type, uint8_t* pu8flgs, uint8_t* pu8data_out, uint32_t* pu32output_len); 53 | int mqtt_decode_publish_msg(uint8_t* pu8src, uint32_t u32nbytes, uint8_t* pu8qos, uint16_t* pu16msg_id_out, uint16_t* pu16topic_len, uint8_t** ppu8topic, uint8_t** ppu8payload); 54 | 55 | 56 | --------------------------------------------------------------------------------