Repository: njh/mqtt-sn-tools
Branch: main
Commit: a1dd870684f6
Files: 24
Total size: 140.0 KB
Directory structure:
gitextract_cmeey4ae/
├── .astylerc
├── .gitignore
├── .travis.yml
├── LICENSE.md
├── Makefile
├── NEWS.md
├── README.md
├── mqtt-sn-dump.c
├── mqtt-sn-pub.c
├── mqtt-sn-serial-bridge.c
├── mqtt-sn-sub.c
├── mqtt-sn.c
├── mqtt-sn.h
└── test/
├── .ruby-version
├── Gemfile
├── Rakefile
├── fake_server.rb
├── mqtt-sn-dump-test.rb
├── mqtt-sn-pub-test.rb
├── mqtt-sn-serial-bridge-test.rb
├── mqtt-sn-sub-test.rb
├── test.txt
├── test_big.txt
└── test_helper.rb
================================================
FILE CONTENTS
================================================
================================================
FILE: .astylerc
================================================
# use the K&R code style
--style=kr
# Indent 4 spaces
--indent=spaces=4
# Indent switch and case statements
--indent-switches
# We don't need .orig files, because we are version controlled
--suffix=none
================================================
FILE: .gitignore
================================================
mqtt-sn-dump
mqtt-sn-pub
mqtt-sn-sub
mqtt-sn-serial-bridge
*.exe
*.o
*.tar.gz
# Code coverage files
*.gcda
*.gcno
coverage/*
# Eclipse project metafiles
.settings
.cproject
.project
================================================
FILE: .travis.yml
================================================
# We use ruby for running the tests
language: ruby
sudo: false
before_install:
- gem update bundler
rvm:
- '2.5.5'
script:
- make
- make test
================================================
FILE: LICENSE.md
================================================
The MIT License (MIT)
=====================
Copyright (c) Nicholas J Humfrey
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: Makefile
================================================
CC=cc
PACKAGE=mqtt-sn-tools
VERSION=0.0.7
CFLAGS=-g -Wall -DVERSION=$(VERSION)
LDFLAGS=
INSTALL?=install
prefix=/usr/local
TARGETS=mqtt-sn-dump mqtt-sn-pub mqtt-sn-sub mqtt-sn-serial-bridge
.PHONY : all install uninstall clean dist test coverage
all: $(TARGETS)
$(TARGETS): %: mqtt-sn.o %.o
$(CC) $(LDFLAGS) -o $@ $^
%.o : %.c mqtt-sn.h
$(CC) $(CFLAGS) -c $<
install: $(TARGETS)
$(INSTALL) -d "$(DESTDIR)$(prefix)/bin"
$(INSTALL) -s $(TARGETS) "$(DESTDIR)$(prefix)/bin"
uninstall:
@for target in $(TARGETS); do \
cmd="rm -f $(DESTDIR)$(prefix)/bin/$$target"; \
echo "$$cmd" && $$cmd; \
done
clean:
-rm -f *.o *.gcda *.gcno $(TARGETS)
-rm -Rf coverage
dist:
distdir='$(PACKAGE)-$(VERSION)'; mkdir $$distdir || exit 1; \
list=`git ls-files`; for file in $$list; do \
cp -pR $$file $$distdir || exit 1; \
done; \
tar -zcf $$distdir.tar.gz $$distdir; \
rm -fr $$distdir
test: all
@(which bundle > /dev/null) || (echo "Ruby Bundler is not installed"; exit -1)
cd test && bundle install && bundle exec rake test
# Use gcc for coverage report - it works better than clang/llvm
coverage: CC=gcc
coverage: CFLAGS += --coverage
coverage: LDFLAGS += --coverage
coverage: clean test
mkdir -p coverage
lcov \
--capture \
--directory . \
--no-external \
--output coverage/lcov.info
genhtml \
--title 'MQTT-SN Tools' \
--output-directory coverage \
coverage/lcov.info
================================================
FILE: NEWS.md
================================================
Release History
===============
Version 0.0.6 (2017-08-23)
--------------------------
- Added support for publishing and subscribing with QoS 1
- Added support for reading from a file using -f
- Added support for reading from STDIN using -s
- Added support for publishing one message per line using -l
- Added support for subscribing to multiple topics
- Added timeout when waiting for packets
- Made Keep Alive configurable in mqtt-sn-pub
- Changed default keep alive for mqtt-sn-pub to 10 seconds
- Fix for displaying IPv6 addresses
- Improved debug error messages when connecting fails
Version 0.0.5 (2015-11-04)
--------------------------
- Added extensive test suite written in Ruby
- Added return code strings to packet dump output
- Display when connecting and disconnecting starts.
- Changed error messages that aren't fatal to warnings
- Added signal handling to mqtt-sn-dump
- Fix to ensure that packet data structures are set to 0 before use
- Added mqtt-sn-dump tool
- Added debug of source IP and port when receiving a packet
- Implemented setting the Clean Session flag
- Fix for subscribing to short/predefined topics
- New command line option -V to display current time and the topic name in verbose mode.
- Added install and uninstall targets to the Makefile
- Added Forwarder Encapsulation support
- Added current time and log level to log messages
- Wait for disconnect acknowledge into mqtt-sn-pub and mqtt-sn-sub.
- Display string, not hex for unexpected packet type
- Fixed incorrect pointer handling in mqtt-sn-sub
Version 0.0.4 (2014-06-01)
--------------------------
- Changed license to MIT
- Added mqtt-sn-serial-bridge
- Simplified the Makefile
- Improvements to cope with multiple packets received in close succession
Version 0.0.3 (2013-12-07)
--------------------------
- Added BSD 2-Clause License
- Renamed 'Unsupported Features' to limitations.
- Added checks to make sure various strings aren’t too long
- Corrected the maximum length of a MQTT-SN packet (in this tool)
- Added support for displaying short and pre-defined topic types in verbose mode
- Added support for pre-defined Topic IDs in the subscribing client (note that this is untested because I don’t have a broker that supports it)
- Added support for displaying topic name for wildcard subscriptions (aka verbose mode)
- Noted that QoS −1 is now supported in the README
- Added support for QoS Level −1 (tested against RSMB)
- Corrected help text for pre-defined topic ID parameter
- Shortened clientid argument help text
- Enabled keep-alive timer in mqtt-sn-pub - will cause sessions to be cleaned up by the broker if client is unexpectedly disconnected
Version 0.0.2 (2013-12-02)
--------------------------
- Renamed from MQTT-S to MQTT-SN to match specification change
- Added support for publishing to short and pre-defined topic ids
- Added setting of QoS flag in matts_send_publish
- Renamed default client id prefix to mqtts-tools-
Version 0.0.1 (2013-04-02)
--------------------------
- Initial Release
- Contains just mqtts-sub and mqtts-pub
================================================
FILE: README.md
================================================
MQTT-SN Tools
=============
[](https://travis-ci.org/njh/mqtt-sn-tools)
Command line tools written in C for the MQTT-SN (MQTT for Sensor Networks) protocol.
Supported Features
------------------
- QoS 0, 1 and -1
- Keep alive pings
- Publishing retained messages
- Publishing empty messages
- Subscribing to named topic
- Clean / unclean sessions
- Manual and automatic client ID generation
- Displaying topic name with wildcard subscriptions
- Pre-defined topic IDs and short topic names
- Forwarder encapsulation according to MQTT-SN Protocol Specification v1.2.
Limitations
-----------
- Packets must be 255 or less bytes long
- No Last Will and Testament
- No QoS 2
- No automatic re-sending of lost packets
- No Automatic gateway discovery
Building
--------
Just run 'make' on a POSIX system.
Publishing
----------
Usage: mqtt-sn-pub [opts] -t <topic> -m <message>
-d Increase debug level by one. -d can occur multiple times.
-f <file> A file to send as the message payload.
-h <host> MQTT-SN host to connect to. Defaults to '127.0.0.1'.
-i <clientid> ID to use for this client. Defaults to 'mqtt-sn-tools-' with process id.
-k <keepalive> keep alive in seconds for this client. Defaults to 10.
-e <sleep> sleep duration in seconds when disconnecting. Defaults to 0.
-m <message> Message payload to send.
-l Read from STDIN, one message per line.
-n Send a null (zero length) message.
-p <port> Network port to connect to. Defaults to 1883.
-q <qos> Quality of Service value (0, 1 or -1). Defaults to 0.
-r Message should be retained.
-s Read one whole message from STDIN.
-t <topic> MQTT-SN topic name to publish to.
-T <topicid> Pre-defined MQTT-SN topic ID to publish to.
--fe Enables Forwarder Encapsulation. Mqtt-sn packets are encapsulated according to MQTT-SN Protocol Specification v1.2, chapter 5.5 Forwarder Encapsulation.
--wlnid If Forwarder Encapsulation is enabled, wireless node ID for this client. Defaults to process id.
--cport <port> Source port for outgoing packets. Uses port in ephemeral range if not specified or set to 0.
Subscribing
-----------
Usage: mqtt-sn-sub [opts] -t <topic>
-1 exit after receiving a single message.
-c disable 'clean session' (store subscription and pending messages when client disconnects).
-d Increase debug level by one. -d can occur multiple times.
-h <host> MQTT-SN host to connect to. Defaults to '127.0.0.1'.
-i <clientid> ID to use for this client. Defaults to 'mqtt-sn-tools-' with process id.
-k <keepalive> keep alive in seconds for this client. Defaults to 10.
-e <sleep> sleep duration in seconds when disconnecting. Defaults to 0.
-p <port> Network port to connect to. Defaults to 1883.
-q <qos> QoS level to subscribe with (0 or 1). Defaults to 0.
-t <topic> MQTT-SN topic name to subscribe to. It may repeat multiple times.
-T <topicid> Pre-defined MQTT-SN topic ID to subscribe to. It may repeat multiple times.
--fe Enables Forwarder Encapsulation. Mqtt-sn packets are encapsulated according to MQTT-SN Protocol Specification v1.2, chapter 5.5 Forwarder Encapsulation.
--wlnid If Forwarder Encapsulation is enabled, wireless node ID for this client. Defaults to process id.
-v Print messages verbosely, showing the topic name.
-V Print messages verbosely, showing current time and the topic name.
--cport <port> Source port for outgoing packets. Uses port in ephemeral range if not specified or set to 0.
Dumping
-------
Displays MQTT-SN packets sent to specified port.
Most useful for listening out for QoS -1 messages being published by a client.
Usage: mqtt-sn-dump [opts] -p <port>
-a Dump all packet types.
-d Increase debug level by one. -d can occur multiple times.
-p <port> Network port to listen on. Defaults to 1883.
-v Print messages verbosely, showing the topic name.
Serial Port Bridge
------------------
The Serial Port bridge can be used to relay packets from a remote device on the end of a
serial port and convert them into UDP packets, which are sent and received from a broker
or MQTT-SN gateway.
Usage: mqtt-sn-serial-bridge [opts] <device>
-b <baud> Set the baud rate. Defaults to 9600.
-d Increase debug level by one. -d can occur multiple times.
-dd Enable extended debugging - display packets in hex.
-h <host> MQTT-SN host to connect to. Defaults to '127.0.0.1'.
-p <port> Network port to connect to. Defaults to 1883.
--fe Enables Forwarder Encapsulation. Mqtt-sn packets are encapsulated according to MQTT-SN Protocol Specification v1.2, chapter 5.5 Forwarder Encapsulation.
--cport <port> Source port for outgoing packets. Uses port in ephemeral range if not specified or set to 0.
License
-------
MQTT-SN Tools is licensed under the [MIT License].
[MIT License]: http://opensource.org/licenses/MIT
================================================
FILE: mqtt-sn-dump.c
================================================
/*
MQTT-SN command-line dump
Copyright (C) Nicholas Humfrey
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include "mqtt-sn.h"
const char *mqtt_sn_port = MQTT_SN_DEFAULT_PORT;
uint8_t dump_all = FALSE;
uint8_t debug = 0;
uint8_t verbose = 0;
uint8_t keep_running = TRUE;
static void usage()
{
fprintf(stderr, "Usage: mqtt-sn-dump [opts] -p <port>\n");
fprintf(stderr, "\n");
fprintf(stderr, " -a Dump all packet types.\n");
fprintf(stderr, " -d Increase debug level by one. -d can occur multiple times.\n");
fprintf(stderr, " -p <port> Network port to listen on. Defaults to %s.\n", mqtt_sn_port);
fprintf(stderr, " -v Print messages verbosely, showing the topic name.\n");
exit(EXIT_FAILURE);
}
static void parse_opts(int argc, char** argv)
{
int ch;
// Parse the options/switches
while((ch = getopt(argc, argv, "adp:v?")) != -1)
switch(ch) {
case 'a':
dump_all = TRUE;
break;
case 'd':
debug++;
break;
case 'p':
mqtt_sn_port = optarg;
break;
case 'v':
verbose++;
break;
case '?':
default:
usage();
break;
}
}
static int bind_udp_socket(const char* port_str)
{
struct sockaddr_in si_me;
short port = atoi(port_str);
int sock;
if ((sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
memset((char *) &si_me, 0, sizeof(si_me));
si_me.sin_family = AF_INET;
si_me.sin_port = htons(port);
si_me.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(sock, (const struct sockaddr *)&si_me, sizeof(si_me)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
mqtt_sn_log_debug("mqtt-sn-dump listening on port %s", port_str);
return sock;
}
static void termination_handler (int signum)
{
switch(signum) {
case SIGHUP:
mqtt_sn_log_debug("Got hangup signal.");
break;
case SIGTERM:
mqtt_sn_log_debug("Got termination signal.");
break;
case SIGINT:
mqtt_sn_log_debug("Got interrupt signal.");
break;
}
// Signal the main thread to stop
keep_running = FALSE;
}
int main(int argc, char* argv[])
{
int sock;
// Disable buffering on STDOUT
setvbuf(stdout, NULL, _IONBF, 0);
// Parse the command-line options
parse_opts(argc, argv);
// Enable debugging?
mqtt_sn_set_debug(debug);
mqtt_sn_set_verbose(verbose);
// Setup signal handlers
signal(SIGTERM, termination_handler);
signal(SIGINT, termination_handler);
signal(SIGHUP, termination_handler);
// Create a listening UDP socket
sock = bind_udp_socket(mqtt_sn_port);
while (keep_running) {
int ret = mqtt_sn_select(sock);
if (ret < 0) {
break;
} else if (ret > 0) {
char* packet = mqtt_sn_receive_packet(sock);
if (dump_all) {
mqtt_sn_dump_packet(packet);
} else if (packet[1] == MQTT_SN_TYPE_PUBLISH) {
mqtt_sn_print_publish_packet((publish_packet_t *)packet);
}
}
}
close(sock);
return 0;
}
================================================
FILE: mqtt-sn-pub.c
================================================
/*
MQTT-SN command-line publishing client
Copyright (C) Nicholas Humfrey
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include "mqtt-sn.h"
const char *client_id = NULL;
const char *topic_name = NULL;
const char *message_data = NULL;
const char *message_file = NULL;
const char *mqtt_sn_host = "127.0.0.1";
const char *mqtt_sn_port = MQTT_SN_DEFAULT_PORT;
uint16_t source_port = 0;
uint16_t topic_id = 0;
uint8_t topic_id_type = MQTT_SN_TOPIC_TYPE_NORMAL;
uint16_t keep_alive = MQTT_SN_DEFAULT_KEEP_ALIVE;
uint16_t sleep_duration = 0;
int8_t qos = 0;
uint8_t retain = FALSE;
uint8_t one_message_per_line = FALSE;
uint8_t debug = 0;
static void usage()
{
fprintf(stderr, "Usage: mqtt-sn-pub [opts] -t <topic> -m <message>\n");
fprintf(stderr, "\n");
fprintf(stderr, " -d Increase debug level by one. -d can occur multiple times.\n");
fprintf(stderr, " -f <file> A file to send as the message payload.\n");
fprintf(stderr, " -h <host> MQTT-SN host to connect to. Defaults to '%s'.\n", mqtt_sn_host);
fprintf(stderr, " -i <clientid> ID to use for this client. Defaults to 'mqtt-sn-tools-' with process id.\n");
fprintf(stderr, " -k <keepalive> keep alive in seconds for this client. Defaults to %d.\n", keep_alive);
fprintf(stderr, " -e <sleep> sleep duration in seconds when disconnecting. Defaults to %d.\n", sleep_duration);
fprintf(stderr, " -m <message> Message payload to send.\n");
fprintf(stderr, " -l Read from STDIN, one message per line.\n");
fprintf(stderr, " -n Send a null (zero length) message.\n");
fprintf(stderr, " -p <port> Network port to connect to. Defaults to %s.\n", mqtt_sn_port);
fprintf(stderr, " -q <qos> Quality of Service value (0, 1 or -1). Defaults to %d.\n", qos);
fprintf(stderr, " -r Message should be retained.\n");
fprintf(stderr, " -s Read one whole message from STDIN.\n");
fprintf(stderr, " -t <topic> MQTT-SN topic name to publish to.\n");
fprintf(stderr, " -T <topicid> Pre-defined MQTT-SN topic ID to publish to.\n");
fprintf(stderr, " --fe Enables Forwarder Encapsulation. Mqtt-sn packets are encapsulated according to MQTT-SN Protocol Specification v1.2, chapter 5.5 Forwarder Encapsulation.\n");
fprintf(stderr, " --wlnid If Forwarder Encapsulation is enabled, wireless node ID for this client. Defaults to process id.\n");
fprintf(stderr, " --cport <port> Source port for outgoing packets. Uses port in ephemeral range if not specified or set to %d.\n", source_port);
exit(EXIT_FAILURE);
}
static void parse_opts(int argc, char** argv)
{
static struct option long_options[] = {
{"fe", no_argument, 0, 1000 },
{"wlnid", required_argument, 0, 1001 },
{"cport", required_argument, 0, 1002 },
{0, 0, 0, 0}
};
int ch;
/* getopt_long stores the option index here. */
int option_index = 0;
// Parse the options/switches
while ((ch = getopt_long (argc, argv, "df:h:i:k:e:lm:np:q:rst:T:?", long_options, &option_index)) != -1) {
switch (ch) {
case 'd':
debug++;
break;
case 'h':
mqtt_sn_host = optarg;
break;
case 'f':
message_file = optarg;
break;
case 'i':
client_id = optarg;
break;
case 'l':
message_file = "-";
one_message_per_line = TRUE;
break;
case 'm':
message_data = optarg;
break;
case 'k':
keep_alive = atoi(optarg);
break;
case 'e':
sleep_duration = atoi(optarg);
break;
case 'n':
message_data = "";
break;
case 'p':
mqtt_sn_port = optarg;
break;
case 'q':
qos = atoi(optarg);
break;
case 'r':
retain = TRUE;
break;
case 's':
message_file = "-";
one_message_per_line = FALSE;
break;
case 't':
topic_name = optarg;
break;
case 'T':
topic_id = atoi(optarg);
break;
case 1000:
mqtt_sn_enable_frwdencap();
break;
case 1001:
mqtt_sn_set_frwdencap_parameters((uint8_t*)optarg, strlen(optarg));
break;
case 1002:
source_port = atoi(optarg);
break;
case '?':
default:
usage();
break;
} // switch
} // while
// Missing Parameter?
if (!(topic_name || topic_id) || !(message_data || message_file)) {
usage();
}
if (qos != -1 && qos != 0 && qos != 1) {
mqtt_sn_log_err("Only QoS level 0, 1 or -1 is supported.");
exit(EXIT_FAILURE);
}
// Both topic name and topic id?
if (topic_name && topic_id) {
mqtt_sn_log_err("Please provide either a topic id or a topic name, not both.");
exit(EXIT_FAILURE);
}
// Both message data and file?
if (message_data && message_file) {
mqtt_sn_log_err("Please provide either message data or a message file, not both.");
exit(EXIT_FAILURE);
}
// Check topic is valid for QoS level -1
if (qos == -1 && topic_id == 0 && strlen(topic_name) != 2) {
mqtt_sn_log_err("Either a pre-defined topic id or a short topic name must be given for QoS -1.");
exit(EXIT_FAILURE);
}
}
static void publish_file(int sock, const char* filename)
{
char buffer[MQTT_SN_MAX_PAYLOAD_LENGTH];
uint16_t message_len = 0;
FILE* file = NULL;
if (strcmp(filename, "-") == 0) {
file = stdin;
} else {
file = fopen(filename, "rb");
}
if (!file) {
perror("Failed to open message file");
exit(EXIT_FAILURE);
}
if (one_message_per_line) {
// One message per line
while(!feof(file) && !ferror(file)) {
char* message = fgets(buffer, MQTT_SN_MAX_PAYLOAD_LENGTH, file);
if (message) {
char* end = strpbrk(message, "\n\r");
if (end) {
uint16_t message_len = (end - message);
mqtt_sn_send_publish(sock, topic_id, topic_id_type, message, message_len, qos, retain);
} else {
mqtt_sn_log_err("Failed to find newline when reading message");
}
} else if (ferror(file)) {
perror("Failed to read message line");
exit(EXIT_FAILURE);
}
}
} else {
// One message until EOF
message_len = fread(buffer, 1, MQTT_SN_MAX_PAYLOAD_LENGTH, file);
if (ferror(file)) {
perror("Failed to read message file");
exit(EXIT_FAILURE);
} else if (!feof(file)) {
mqtt_sn_log_warn("Input file is longer than the maximum message size");
}
mqtt_sn_send_publish(sock, topic_id, topic_id_type, buffer, message_len, qos, retain);
}
fclose(file);
}
int main(int argc, char* argv[])
{
int sock;
mqtt_sn_disable_frwdencap();
// Parse the command-line options
parse_opts(argc, argv);
// Enable debugging?
mqtt_sn_set_debug(debug);
mqtt_sn_set_timeout(keep_alive / 2);
// Create a UDP socket
sock = mqtt_sn_create_socket(mqtt_sn_host, mqtt_sn_port, source_port);
if (sock) {
// Connect to gateway
if (qos >= 0) {
mqtt_sn_log_debug("Connecting...");
mqtt_sn_send_connect(sock, client_id, keep_alive, TRUE);
mqtt_sn_receive_connack(sock);
}
if (topic_id) {
// Use pre-defined topic ID
topic_id_type = MQTT_SN_TOPIC_TYPE_PREDEFINED;
} else if (strlen(topic_name) == 2) {
// Convert the 2 character topic name into a 2 byte topic id
topic_id = (topic_name[0] << 8) + topic_name[1];
topic_id_type = MQTT_SN_TOPIC_TYPE_SHORT;
} else if (qos >= 0) {
// Register the topic name
mqtt_sn_send_register(sock, topic_name);
topic_id = mqtt_sn_receive_regack(sock);
topic_id_type = MQTT_SN_TOPIC_TYPE_NORMAL;
}
// Publish to the topic
if (message_file) {
publish_file(sock, message_file);
} else {
uint16_t message_len = strlen(message_data);
mqtt_sn_send_publish(sock, topic_id, topic_id_type, message_data, message_len, qos, retain);
}
// Finally, disconnect
if (qos >= 0) {
mqtt_sn_log_debug("Disconnecting...");
mqtt_sn_send_disconnect(sock, sleep_duration);
mqtt_sn_receive_disconnect(sock);
}
close(sock);
}
mqtt_sn_cleanup();
return 0;
}
================================================
FILE: mqtt-sn-serial-bridge.c
================================================
/*
MQTT-SN serial bridge
Copyright (C) Nicholas Humfrey
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
//#define _POSIX_SOURCE 1 /* POSIX compliant source */
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <netdb.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <signal.h>
#include <errno.h>
#include "mqtt-sn.h"
const char *mqtt_sn_host = "127.0.0.1";
const char *mqtt_sn_port = MQTT_SN_DEFAULT_PORT;
const char *serial_device = NULL;
uint16_t source_port = 0;
int serial_baud = 9600;
uint8_t debug = 0;
uint8_t frwdencap = FALSE;
uint8_t keep_running = TRUE;
static speed_t baud_lookup(int baud)
{
switch(baud) {
case 0:
return B0;
case 50:
return B50;
case 75:
return B75;
case 110:
return B110;
case 134:
return B134;
case 150:
return B150;
case 200:
return B200;
case 300:
return B300;
case 600:
return B600;
case 1200:
return B1200;
case 1800:
return B1800;
case 2400:
return B2400;
case 4800:
return B4800;
case 9600:
return B9600;
case 19200:
return B19200;
case 38400:
return B38400;
case 57600:
return B57600;
case 115200:
return B115200;
case 230400:
return B230400;
default:
fprintf(stderr, "Unsupported baud rate: %d\n", baud);
exit(1);
}
}
static void usage()
{
fprintf(stderr, "Usage: mqtt-sn-serial-bridge [opts] <device>\n");
fprintf(stderr, "\n");
fprintf(stderr, " -b <baud> Set the baud rate. Defaults to %d.\n", (int)serial_baud);
fprintf(stderr, " -d Increase debug level by one. -d can occur multiple times.\n");
fprintf(stderr, " -dd Enable extended debugging - display packets in hex.\n");
fprintf(stderr, " -h <host> MQTT-SN host to connect to. Defaults to '%s'.\n", mqtt_sn_host);
fprintf(stderr, " -p <port> Network port to connect to. Defaults to %s.\n", mqtt_sn_port);
fprintf(stderr, " --fe Enables Forwarder Encapsulation. Mqtt-sn packets are encapsulated according to MQTT-SN Protocol Specification v1.2, chapter 5.5 Forwarder Encapsulation.\n");
fprintf(stderr, " --cport <port> Source port for outgoing packets. Uses port in ephemeral range if not specified or set to %d.\n", source_port);
exit(EXIT_FAILURE);
}
static void parse_opts(int argc, char** argv)
{
static struct option long_options[] = {
{"fe", no_argument, 0, 1000 },
{"cport", required_argument, 0, 1001 },
{0, 0, 0, 0}
};
int ch;
/* getopt_long stores the option index here. */
int option_index = 0;
// Parse the options/switches
while ((ch = getopt_long (argc, argv, "b:dh:p:?", long_options, &option_index)) != -1) {
switch (ch) {
case 'b':
serial_baud = atoi(optarg);
baud_lookup(serial_baud);
break;
case 'd':
debug++;
break;
case 'h':
mqtt_sn_host = optarg;
break;
case 'p':
mqtt_sn_port = optarg;
break;
case 1000:
mqtt_sn_enable_frwdencap();
frwdencap = TRUE;
break;
case 1001:
source_port = atoi(optarg);
break;
case '?':
default:
usage();
break;
} // switch
} // while
// Final argument is the serial port device path
if (argc-optind < 1) {
fprintf(stderr, "Missing serial port.\n");
usage();
} else {
serial_device = argv[optind];
}
}
static int serial_open(const char* device_path)
{
struct termios tios;
int fd;
mqtt_sn_disable_frwdencap();
mqtt_sn_log_debug("Opening %s", device_path);
fd = open(device_path, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd < 0) {
perror(device_path);
exit(EXIT_FAILURE);
}
// Turn back on blocking reads
fcntl(fd, F_SETFL, 0);
// Read existing serial port settings
tcgetattr(fd, &tios);
// Set the input and output baud rates
cfsetispeed(&tios, baud_lookup(serial_baud));
cfsetospeed(&tios, baud_lookup(serial_baud));
// Set to local mode
tios.c_cflag |= CLOCAL | CREAD;
// Set serial port mode to 8N1
tios.c_cflag &= ~PARENB;
tios.c_cflag &= ~CSTOPB;
tios.c_cflag &= ~CSIZE;
tios.c_cflag |= CS8;
// Turn off flow control and ignore parity
tios.c_iflag &= ~(IXON | IXOFF | IXANY);
tios.c_iflag |= IGNPAR;
// Turn off output post-processing
tios.c_oflag &= ~OPOST;
// set input mode (non-canonical, no echo,...)
tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// Set VMIN high, so that we wait for a gap in the data
// http://www.unixwiz.net/techtips/termios-vmin-vtime.html
tios.c_cc[VMIN] = 255;
tios.c_cc[VTIME] = 1;
tcsetattr(fd, TCSAFLUSH, &tios);
// Flush the input buffer
sleep(1);
tcflush(fd, TCIOFLUSH);
return fd;
}
static void* serial_read_packet(int fd)
{
static uint8_t buf[MQTT_SN_MAX_PACKET_LENGTH+1];
// First get the length of the packet
int bytes_read = read(fd, buf, 1);
if (bytes_read != 1) {
mqtt_sn_log_err("Error reading packet length from serial port: %d, %d", bytes_read, errno);
return NULL;
}
if (buf[0] == 0x00) {
mqtt_sn_log_err("Packets of length 0 are invalid.");
return NULL;
}
// Read in the rest of the packet
bytes_read = read(fd, &buf[1], buf[0]-1);
if (bytes_read <= 0) {
mqtt_sn_log_err("Error reading rest of packet from serial port: %d, %d", bytes_read, errno);
return NULL;
} else {
bytes_read += 1;
}
if (mqtt_sn_validate_packet(buf, bytes_read) == FALSE) {
return NULL;
}
// NULL-terminate the packet
buf[bytes_read] = '\0';
if (debug) {
const char* type = mqtt_sn_type_string(buf[1]);
mqtt_sn_log_debug("Serial -> UDP (bytes_read=%d, type=%s)", bytes_read, type);
if (debug > 1) {
int i;
fprintf(stderr, " ");
for (i=0; i<buf[0]; i++) {
fprintf(stderr, "0x%2.2X ", buf[i]);
}
fprintf(stderr, "\n");
}
}
return buf;
}
void serial_write_packet(int fd, const void* packet)
{
size_t sent, len = ((uint8_t*)packet)[0];
sent = write(fd, packet, len);
if (sent != len) {
mqtt_sn_log_warn("Warning: only sent %d of %d bytes", (int)sent, (int)len);
}
}
static void termination_handler (int signum)
{
switch(signum) {
case SIGHUP:
mqtt_sn_log_debug("Got hangup signal.");
break;
case SIGTERM:
mqtt_sn_log_debug("Got termination signal.");
break;
case SIGINT:
mqtt_sn_log_debug("Got interrupt signal.");
break;
}
// Signal the main thead to stop
keep_running = FALSE;
}
int main(int argc, char* argv[])
{
int fd = -1;
int sock = -1;
// Parse the command-line options
parse_opts(argc, argv);
mqtt_sn_set_debug(debug);
// Setup signal handlers
signal(SIGTERM, termination_handler);
signal(SIGINT, termination_handler);
signal(SIGHUP, termination_handler);
// Create a UDP socket
sock = mqtt_sn_create_socket(mqtt_sn_host, mqtt_sn_port, source_port);
// Open the serial port
fd = serial_open(serial_device);
while (keep_running) {
fd_set fdset;
FD_ZERO(&fdset); // Clear the socket set
FD_SET(fd, &fdset); // Add serial into fdset
FD_SET(sock, &fdset); // Add socket into fdset
if (select(FD_SETSIZE, &fdset, NULL, NULL, NULL) < 0) {
if (errno != EINTR) {
perror("select");
}
break;
}
// Read serial line
if (FD_ISSET(fd, &fdset)) {
void *packet = serial_read_packet(fd);
if (packet) {
if (frwdencap) {
mqtt_sn_send_frwdencap_packet(sock, packet, NULL, 0);
} else {
mqtt_sn_send_packet(sock, packet);
}
}
}
if (FD_ISSET(sock, &fdset)) {
void *packet = mqtt_sn_receive_packet(sock);
if (packet) {
serial_write_packet(fd, packet);
}
}
}
close(sock);
close(fd);
mqtt_sn_cleanup();
return 0;
}
================================================
FILE: mqtt-sn-sub.c
================================================
/*
MQTT-SN command-line subscribe client
Copyright (C) Nicholas Humfrey
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include "mqtt-sn.h"
const char *client_id = NULL;
// Array of pointers to topic names
char **topic_name_ar = NULL;
// Topic names array size. When too small it is incremented by ARRAY_INCREMENT.
uint16_t topic_name_ar_size = 0;
uint16_t topic_name_index = 0;
// Array of predefined topic IDs to subscribe to
uint16_t *predef_topic_id_ar = NULL;
// Predefined topic IDs array size. When too small it is incremented by ARRAY_INCREMENT.
uint16_t predef_topic_id_ar_size = 0;
uint16_t predef_topic_id_index = 0;
#define ARRAY_INCREMENT 10
const char *mqtt_sn_host = "127.0.0.1";
const char *mqtt_sn_port = MQTT_SN_DEFAULT_PORT;
uint16_t source_port = 0;
uint16_t keep_alive = MQTT_SN_DEFAULT_KEEP_ALIVE;
uint16_t sleep_duration = 0;
int8_t qos = 0;
uint8_t retain = FALSE;
uint8_t debug = 0;
uint8_t single_message = FALSE;
uint8_t clean_session = TRUE;
uint8_t verbose = 0;
uint8_t keep_running = TRUE;
static void usage()
{
fprintf(stderr, "Usage: mqtt-sn-sub [opts] -t <topic>\n");
fprintf(stderr, "\n");
fprintf(stderr, " -1 exit after receiving a single message.\n");
fprintf(stderr, " -c disable 'clean session' (store subscription and pending messages when client disconnects).\n");
fprintf(stderr, " -d Increase debug level by one. -d can occur multiple times.\n");
fprintf(stderr, " -h <host> MQTT-SN host to connect to. Defaults to '%s'.\n", mqtt_sn_host);
fprintf(stderr, " -i <clientid> ID to use for this client. Defaults to 'mqtt-sn-tools-' with process id.\n");
fprintf(stderr, " -k <keepalive> keep alive in seconds for this client. Defaults to %d.\n", keep_alive);
fprintf(stderr, " -e <sleep> sleep duration in seconds when disconnecting. Defaults to %d.\n", sleep_duration);
fprintf(stderr, " -p <port> Network port to connect to. Defaults to %s.\n", mqtt_sn_port);
fprintf(stderr, " -q <qos> QoS level to subscribe with (0 or 1). Defaults to %d.\n", qos);
fprintf(stderr, " -t <topic> MQTT-SN topic name to subscribe to. It may repeat multiple times.\n");
fprintf(stderr, " -T <topicid> Pre-defined MQTT-SN topic ID to subscribe to. It may repeat multiple times.\n");
fprintf(stderr, " --fe Enables Forwarder Encapsulation. Mqtt-sn packets are encapsulated according to MQTT-SN Protocol Specification v1.2, chapter 5.5 Forwarder Encapsulation.\n");
fprintf(stderr, " --wlnid If Forwarder Encapsulation is enabled, wireless node ID for this client. Defaults to process id.\n");
fprintf(stderr, " --cport <port> Source port for outgoing packets. Uses port in ephemeral range if not specified or set to %d.\n", source_port);
fprintf(stderr, " -v Print messages verbosely, showing the topic name.\n");
fprintf(stderr, " -V Print messages verbosely, showing current time and the topic name.\n");
exit(EXIT_FAILURE);
}
static void parse_opts(int argc, char** argv)
{
static struct option long_options[] = {
{"fe", no_argument, 0, 1000 },
{"wlnid", required_argument, 0, 1001 },
{"cport", required_argument, 0, 1002 },
{0, 0, 0, 0}
};
int ch;
/* getopt_long stores the option index here. */
int option_index = 0;
// Parse the options/switches
while ((ch = getopt_long(argc, argv, "1cdh:i:k:e:p:q:t:T:vV?", long_options, &option_index)) != -1)
switch (ch) {
case '1':
single_message = TRUE;
break;
case 'c':
clean_session = FALSE;
break;
case 'd':
debug++;
break;
case 'h':
mqtt_sn_host = optarg;
break;
case 'i':
client_id = optarg;
break;
case 'k':
keep_alive = atoi(optarg);
break;
case 'e':
sleep_duration = atoi(optarg);
break;
case 'p':
mqtt_sn_port = optarg;
break;
case 'q':
qos = atoi(optarg);
break;
case 't':
// Resize topic names array when too small
if (topic_name_index == topic_name_ar_size) {
topic_name_ar = realloc(topic_name_ar, (topic_name_ar_size + ARRAY_INCREMENT) * sizeof(char*)) ;
topic_name_ar_size += ARRAY_INCREMENT;
}
topic_name_ar[topic_name_index++] = optarg;
break;
case 'T':
// Resize predefined topic IDs array when too small
if (predef_topic_id_index == predef_topic_id_ar_size) {
predef_topic_id_ar = realloc(predef_topic_id_ar, (predef_topic_id_ar_size + ARRAY_INCREMENT) * sizeof(uint16_t));
predef_topic_id_ar_size += ARRAY_INCREMENT;
}
predef_topic_id_ar[predef_topic_id_index++] = atoi(optarg);
break;
case 1000:
mqtt_sn_enable_frwdencap();
break;
case 1001:
mqtt_sn_set_frwdencap_parameters((uint8_t*)optarg, strlen(optarg));
break;
case 1002:
source_port = atoi(optarg);
break;
case 'v':
// Prevent -v setting verbose level back down to 1 if already set to 2 by -V
verbose = (verbose == 0) ? 1 : verbose;
break;
case 'V':
verbose = 2;
break;
case '?':
default:
usage();
break;
}
// Missing Parameter?
if (!topic_name_index && !predef_topic_id_index) {
usage();
}
}
static void termination_handler (int signum)
{
switch(signum) {
case SIGHUP:
mqtt_sn_log_debug("Got hangup signal.");
break;
case SIGTERM:
mqtt_sn_log_debug("Got termination signal.");
break;
case SIGINT:
mqtt_sn_log_debug("Got interrupt signal.");
break;
}
// Signal the main thread to stop
keep_running = FALSE;
}
int main(int argc, char* argv[])
{
int sock;
mqtt_sn_disable_frwdencap();
// Parse the command-line options
parse_opts(argc, argv);
// Enable debugging?
mqtt_sn_set_debug(debug);
mqtt_sn_set_verbose(verbose);
mqtt_sn_set_timeout(keep_alive / 2);
// Setup signal handlers
signal(SIGTERM, termination_handler);
signal(SIGINT, termination_handler);
signal(SIGHUP, termination_handler);
// Create a UDP socket
sock = mqtt_sn_create_socket(mqtt_sn_host, mqtt_sn_port, source_port);
if (sock) {
// Connect to server
mqtt_sn_log_debug("Connecting...");
mqtt_sn_send_connect(sock, client_id, keep_alive, clean_session);
mqtt_sn_receive_connack(sock);
uint16_t i;
// Subscribe to the each topic name
for (i = 0; i < topic_name_index; i++) {
mqtt_sn_log_debug("Subscribing to topic name: %s ...", topic_name_ar[i]);
mqtt_sn_send_subscribe_topic_name(sock, topic_name_ar[i], qos);
// Wait for the subscription acknowledgment
uint16_t topic_id = mqtt_sn_receive_suback(sock);
if (topic_id && strlen(topic_name_ar[i]) > 2) {
mqtt_sn_register_topic(topic_id, topic_name_ar[i]);
}
}
// Subscribe to the each predefined topic ID
for (i = 0; i < predef_topic_id_index; i++) {
mqtt_sn_log_debug("Subscribing to predefined topic ID: %u ...", predef_topic_id_ar[i]);
mqtt_sn_send_subscribe_topic_id(sock, predef_topic_id_ar[i], qos);
// Wait for the subscription acknowledgment
mqtt_sn_receive_suback(sock);
}
// Keep processing packets until process is terminated
while(keep_running) {
publish_packet_t *packet = mqtt_sn_wait_for(MQTT_SN_TYPE_PUBLISH, sock);
if (packet) {
uint8_t packet_qos = packet->flags & MQTT_SN_FLAG_QOS_MASK;
if (packet_qos == MQTT_SN_FLAG_QOS_1) {
mqtt_sn_send_puback(sock, packet, MQTT_SN_ACCEPTED);
}
if (single_message) {
break;
}
}
}
// Finally, disconnect
mqtt_sn_log_debug("Disconnecting...");
mqtt_sn_send_disconnect(sock, sleep_duration);
mqtt_sn_receive_disconnect(sock);
close(sock);
}
mqtt_sn_cleanup();
free(topic_name_ar);
free(predef_topic_id_ar);
return 0;
}
================================================
FILE: mqtt-sn.c
================================================
/*
Common functions used by the MQTT-SN Tools
Copyright (C) Nicholas Humfrey
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <stdarg.h>
#include "mqtt-sn.h"
#ifndef AI_DEFAULT
#define AI_DEFAULT (AI_ADDRCONFIG|AI_V4MAPPED)
#endif
static uint8_t debug = 0;
static uint8_t verbose = 0;
static uint8_t timeout = MQTT_SN_DEFAULT_TIMEOUT;
static uint16_t next_message_id = 1;
static time_t last_transmit = 0;
static time_t last_receive = 0;
static time_t keep_alive = 0;
static uint8_t forwarder_encapsulation = FALSE;
const uint8_t *wireless_node_id = NULL;
uint8_t wireless_node_id_len = 0;
topic_map_t *topic_map = NULL;
void mqtt_sn_set_debug(uint8_t value)
{
debug = value;
mqtt_sn_log_debug("Debug level is: %d.", debug);
}
void mqtt_sn_set_verbose(uint8_t value)
{
verbose = value;
mqtt_sn_log_debug("Verbose level is: %d.", verbose);
}
void mqtt_sn_set_timeout(uint8_t value)
{
if (value < 1) {
timeout = MQTT_SN_DEFAULT_TIMEOUT;
} else {
timeout = value;
}
mqtt_sn_log_debug("Network timeout is: %d seconds.", timeout);
}
int mqtt_sn_create_socket(const char* host, const char* port, uint16_t source_port)
{
struct addrinfo hints;
struct addrinfo *result, *rp;
struct timeval tv;
int fd, ret;
// Set options for the resolver
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
hints.ai_flags = 0;
hints.ai_protocol = 0; /* Any protocol */
hints.ai_canonname = NULL;
hints.ai_addr = NULL;
hints.ai_next = NULL;
// Lookup address
ret = getaddrinfo(host, port, &hints, &result);
if (ret != 0) {
mqtt_sn_log_err("getaddrinfo: %s", gai_strerror(ret));
exit(EXIT_FAILURE);
}
/* getaddrinfo() returns a list of address structures.
Try each address until we successfully connect(2).
If socket(2) (or connect(2)) fails, we (close the socket and)
try the next address. */
for (rp = result; rp != NULL; rp = rp->ai_next) {
char hoststr[NI_MAXHOST] = "";
int error = 0;
// Display the IP address in debug mode
error = getnameinfo(rp->ai_addr, rp->ai_addrlen,
hoststr, sizeof(hoststr), NULL, 0,
NI_NUMERICHOST | NI_NUMERICSERV);
if (error == 0) {
mqtt_sn_log_debug("Trying %s...", hoststr);
} else {
mqtt_sn_log_debug("getnameinfo: %s", gai_strerror(ret));
}
// Create a socket
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (fd == -1) {
mqtt_sn_log_debug("Failed to create socket: %s", strerror(errno));
continue;
}
if (source_port != 0) {
// Bind socket to the correct port
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = rp->ai_family;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(source_port);
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
mqtt_sn_log_debug("Failed to bind socket: %s", strerror(errno));
continue;
}
}
// Connect socket to the remote host
if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) {
// Success
break;
} else {
mqtt_sn_log_debug("Connect failed: %s", strerror(errno));
}
close(fd);
}
if (rp == NULL) {
mqtt_sn_log_err("Could not connect to remote host.");
exit(EXIT_FAILURE);
}
freeaddrinfo(result);
// FIXME: set the Don't Fragment flag
// Setup timeout on the socket
tv.tv_sec = timeout;
tv.tv_usec = 0;
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
perror("Error setting timeout on socket");
}
return fd;
}
void mqtt_sn_send_packet(int sock, const void* data)
{
ssize_t sent = 0;
size_t len = ((uint8_t*)data)[0];
// If forwarder encapsulation enabled, wrap packet
if (forwarder_encapsulation) {
return mqtt_sn_send_frwdencap_packet(sock, data, wireless_node_id, wireless_node_id_len);
}
if (debug > 1) {
mqtt_sn_log_debug("Sending %2lu bytes. Type=%s on Socket: %d.", (long unsigned int)len,
mqtt_sn_type_string(((uint8_t*)data)[1]), sock);
}
sent = send(sock, data, len, 0);
if (sent != len) {
mqtt_sn_log_warn("Only sent %d of %d bytes", (int)sent, (int)len);
}
// Store the last time that we sent a packet
last_transmit = time(NULL);
}
void mqtt_sn_send_frwdencap_packet(int sock, const void* data, const uint8_t *wireless_node_id, uint8_t wireless_node_id_len)
{
ssize_t sent = 0;
size_t len = ((uint8_t*)data)[0];
uint8_t orig_packet_type = ((uint8_t*)data)[1];
frwdencap_packet_t *packet;
packet = mqtt_sn_create_frwdencap_packet(data, &len, wireless_node_id, wireless_node_id_len);
if (debug > 1) {
mqtt_sn_log_debug("Sending %2lu bytes. Type=%s with %s inside on Socket: %d.", (long unsigned int)len,
mqtt_sn_type_string(packet->type), mqtt_sn_type_string(orig_packet_type), sock);
}
sent = send(sock, packet, len, 0);
if (sent != len) {
mqtt_sn_log_debug("Warning: only sent %d of %d bytes.", (int)sent, (int)len);
}
// Store the last time that we sent a packet
last_transmit = time(NULL);
free(packet);
}
uint8_t mqtt_sn_validate_packet(const void *packet, size_t length)
{
const uint8_t* buf = packet;
if (buf[0] == 0x00) {
mqtt_sn_log_warn("Packet length header is not valid");
return FALSE;
}
if (buf[0] == 0x01) {
mqtt_sn_log_warn("Packet received is longer than this tool can handle");
return FALSE;
}
// When forwarder encapsulation is enabled each packet must be FRWDENCAP type
if (forwarder_encapsulation && buf[1] != MQTT_SN_TYPE_FRWDENCAP) {
mqtt_sn_log_warn("Expecting FRWDENCAP packet and got Type=%s.", mqtt_sn_type_string(buf[1]));
return FALSE;
}
// If packet is forwarder encapsulation expected packet length is sum of forwarder encapsulation
// header and length of encapsulated packet.
if ((buf[1] == MQTT_SN_TYPE_FRWDENCAP && buf[0] + buf[buf[0]] != length) ||
(buf[1] != MQTT_SN_TYPE_FRWDENCAP && buf[0] != length)) {
mqtt_sn_log_warn("Read %d bytes but packet length is %d bytes.", (int)length,
buf[1] != MQTT_SN_TYPE_FRWDENCAP ? (int)buf[0] : (int)(buf[0] + buf[buf[0]]));
return FALSE;
}
return TRUE;
}
void* mqtt_sn_receive_packet(int sock)
{
uint8_t *wireless_node_id = NULL;
uint8_t wireless_node_id_len = 0;
return mqtt_sn_receive_frwdencap_packet(sock, &wireless_node_id, &wireless_node_id_len);
}
void* mqtt_sn_receive_frwdencap_packet(int sock, uint8_t **wireless_node_id, uint8_t *wireless_node_id_len)
{
static uint8_t buffer[MQTT_SN_MAX_PACKET_LENGTH + MQTT_SN_MAX_WIRELESS_NODE_ID_LENGTH + 3 + 1];
struct sockaddr_storage addr;
socklen_t slen = sizeof(addr);
uint8_t *packet = buffer;
ssize_t bytes_read;
*wireless_node_id = NULL;
*wireless_node_id_len = 0;
mqtt_sn_log_debug("waiting for packet...");
// Read in the packet
bytes_read = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *)&addr, &slen);
if (bytes_read < 0) {
if (errno == EAGAIN) {
mqtt_sn_log_debug("Timed out waiting for packet.");
return NULL;
} else {
perror("recv failed");
exit(EXIT_FAILURE);
}
}
// Convert the source address into a string
if (debug) {
char addrstr[INET6_ADDRSTRLEN] = "unknown";
uint16_t port = 0;
if (addr.ss_family == AF_INET) {
struct sockaddr_in *in = (struct sockaddr_in *)&addr;
inet_ntop(AF_INET, &in->sin_addr, addrstr, sizeof(struct sockaddr_in));
port = ntohs(in->sin_port);
} else if (addr.ss_family == AF_INET6) {
struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&addr;
inet_ntop(AF_INET6, &in6->sin6_addr, addrstr, sizeof(struct sockaddr_in6));
port = ntohs(in6->sin6_port);
}
if (packet[1] == MQTT_SN_TYPE_FRWDENCAP) {
mqtt_sn_log_debug("Received %2d bytes from %s:%d. Type=%s with %s inside on Socket: %d",
(int)bytes_read, addrstr, port,
mqtt_sn_type_string(buffer[1]), mqtt_sn_type_string(packet[packet[0] + 1]), sock);
} else {
mqtt_sn_log_debug("Received %2d bytes from %s:%d. Type=%s on Socket: %d",
(int)bytes_read, addrstr, port,
mqtt_sn_type_string(buffer[1]), sock);
}
}
if (mqtt_sn_validate_packet(buffer, bytes_read) == FALSE) {
return NULL;
}
// NULL-terminate the packet
buffer[bytes_read] = '\0';
if (packet[1] == MQTT_SN_TYPE_FRWDENCAP) {
*wireless_node_id = &packet[3];
*wireless_node_id_len = packet[0] - 3;
// Shift packet by the actual length of FRWDENCAP header
packet += packet[0];
}
// Store the last time that we received a packet
last_receive = time(NULL);
return packet;
}
void mqtt_sn_send_connect(int sock, const char* client_id, uint16_t keepalive, uint8_t clean_session)
{
connect_packet_t packet;
memset(&packet, 0, sizeof(packet));
// Check that it isn't too long
if (client_id && strlen(client_id) > MQTT_SN_MAX_CLIENT_ID_LENGTH) {
mqtt_sn_log_err("Client id is too long");
exit(EXIT_FAILURE);
}
// Create the CONNECT packet
packet.type = MQTT_SN_TYPE_CONNECT;
packet.flags = clean_session ? MQTT_SN_FLAG_CLEAN : 0;
packet.protocol_id = MQTT_SN_PROTOCOL_ID;
packet.duration = htons(keepalive);
// Generate a Client ID if none given
if (client_id == NULL || client_id[0] == '\0') {
snprintf(packet.client_id, MQTT_SN_MAX_CLIENT_ID_LENGTH, "mqtt-sn-tools-%d", getpid());
} else {
memcpy(packet.client_id, client_id, strlen(client_id));
}
packet.length = 0x06 + strlen(packet.client_id);
mqtt_sn_log_debug("Sending CONNECT packet...");
// Store the keep alive period
if (keepalive) {
keep_alive = keepalive;
}
mqtt_sn_send_packet(sock, &packet);
}
void mqtt_sn_send_register(int sock, const char* topic_name)
{
size_t topic_name_len = strlen(topic_name);
register_packet_t packet;
memset(&packet, 0, sizeof(packet));
if (topic_name_len > MQTT_SN_MAX_TOPIC_LENGTH) {
mqtt_sn_log_err("Topic name is too long");
exit(EXIT_FAILURE);
}
packet.type = MQTT_SN_TYPE_REGISTER;
packet.topic_id = 0;
packet.message_id = htons(next_message_id++);
strncpy(packet.topic_name, topic_name, sizeof(packet.topic_name));
packet.length = 0x06 + topic_name_len;
mqtt_sn_log_debug("Sending REGISTER packet...");
mqtt_sn_send_packet(sock, &packet);
}
void mqtt_sn_send_regack(int sock, int topic_id, int mesage_id)
{
regack_packet_t packet;
memset(&packet, 0, sizeof(packet));
packet.type = MQTT_SN_TYPE_REGACK;
packet.topic_id = htons(topic_id);
packet.message_id = htons(mesage_id);
packet.return_code = 0x00;
packet.length = 0x07;
mqtt_sn_log_debug("Sending REGACK packet...");
mqtt_sn_send_packet(sock, &packet);
}
static uint8_t mqtt_sn_get_qos_flag(int8_t qos)
{
switch (qos) {
case -1:
return MQTT_SN_FLAG_QOS_N1;
case 0:
return MQTT_SN_FLAG_QOS_0;
case 1:
return MQTT_SN_FLAG_QOS_1;
case 2:
return MQTT_SN_FLAG_QOS_2;
default:
return 0;
}
}
void mqtt_sn_send_publish(int sock, uint16_t topic_id, uint8_t topic_type, const void* data, uint16_t data_len, int8_t qos, uint8_t retain)
{
publish_packet_t packet;
memset(&packet, 0, sizeof(packet));
if (data_len > sizeof(packet.data)) {
mqtt_sn_log_err("Payload is too big");
exit(EXIT_FAILURE);
}
packet.type = MQTT_SN_TYPE_PUBLISH;
packet.flags = 0x00;
if (retain)
packet.flags += MQTT_SN_FLAG_RETAIN;
packet.flags += mqtt_sn_get_qos_flag(qos);
packet.flags += (topic_type & 0x3);
packet.topic_id = htons(topic_id);
if (qos > 0) {
packet.message_id = htons(next_message_id++);
} else {
packet.message_id = 0x0000;
}
memcpy(packet.data, data, sizeof(packet.data));
packet.length = 0x07 + data_len;
mqtt_sn_log_debug("Sending PUBLISH packet...");
mqtt_sn_send_packet(sock, &packet);
if (qos == 1) {
// Now wait for a PUBACK
puback_packet_t *packet = mqtt_sn_wait_for(MQTT_SN_TYPE_PUBACK, sock);
if (packet) {
mqtt_sn_log_debug("Received PUBACK");
} else {
mqtt_sn_log_warn("Failed to receive PUBACK after PUBLISH");
}
}
}
void mqtt_sn_send_puback(int sock, publish_packet_t* publish, uint8_t return_code)
{
puback_packet_t puback;
memset(&puback, 0, sizeof(puback));
puback.type = MQTT_SN_TYPE_PUBACK;
puback.topic_id = publish->topic_id;
puback.message_id = publish->message_id;
puback.return_code = return_code;
puback.length = 0x07;
mqtt_sn_log_debug("Sending PUBACK packet...");
mqtt_sn_send_packet(sock, &puback);
}
void mqtt_sn_send_subscribe_topic_name(int sock, const char* topic_name, uint8_t qos)
{
size_t topic_name_len = strlen(topic_name);
subscribe_packet_t packet;
memset(&packet, 0, sizeof(packet));
packet.type = MQTT_SN_TYPE_SUBSCRIBE;
packet.flags = 0x00;
packet.flags += mqtt_sn_get_qos_flag(qos);
if (topic_name_len == 2) {
packet.flags += MQTT_SN_TOPIC_TYPE_SHORT;
} else {
packet.flags += MQTT_SN_TOPIC_TYPE_NORMAL;
}
packet.message_id = htons(next_message_id++);
strncpy(packet.topic_name, topic_name, sizeof(packet.topic_name));
packet.topic_name[sizeof(packet.topic_name)-1] = '\0';
packet.length = 0x05 + topic_name_len;
mqtt_sn_log_debug("Sending SUBSCRIBE packet...");
mqtt_sn_send_packet(sock, &packet);
}
void mqtt_sn_send_subscribe_topic_id(int sock, uint16_t topic_id, uint8_t qos)
{
subscribe_packet_t packet;
memset(&packet, 0, sizeof(packet));
packet.type = MQTT_SN_TYPE_SUBSCRIBE;
packet.flags = 0x00;
packet.flags += mqtt_sn_get_qos_flag(qos);
packet.flags += MQTT_SN_TOPIC_TYPE_PREDEFINED;
packet.message_id = htons(next_message_id++);
packet.topic_id = htons(topic_id);
packet.length = 0x05 + 2;
mqtt_sn_log_debug("Sending SUBSCRIBE packet...");
mqtt_sn_send_packet(sock, &packet);
}
void mqtt_sn_send_pingreq(int sock)
{
char packet[2];
packet[0] = 2;
packet[1] = MQTT_SN_TYPE_PINGREQ;
mqtt_sn_log_debug("Sending PINGREQ packet...");
mqtt_sn_send_packet(sock, &packet);
}
void mqtt_sn_send_disconnect(int sock, uint16_t duration)
{
disconnect_packet_t packet;
memset(&packet, 0, sizeof(packet));
packet.type = MQTT_SN_TYPE_DISCONNECT;
if (duration == 0) {
packet.length = 0x02;
mqtt_sn_log_debug("Sending DISCONNECT packet...");
} else {
packet.length = sizeof(packet);
packet.duration = htons(duration);
mqtt_sn_log_debug("Sending DISCONNECT packet with Duration %d...", duration);
}
mqtt_sn_send_packet(sock, &packet);
}
void mqtt_sn_receive_disconnect(int sock)
{
disconnect_packet_t *packet = mqtt_sn_wait_for(MQTT_SN_TYPE_DISCONNECT, sock);
if (packet == NULL) {
mqtt_sn_log_err("Failed to disconnect from MQTT-SN gateway.");
exit(EXIT_FAILURE);
}
// Check Disconnect return duration
if (packet->length == 4) {
mqtt_sn_log_warn("DISCONNECT warning. Gateway returned duration in disconnect packet: 0x%2.2x", packet->duration);
}
}
void mqtt_sn_receive_connack(int sock)
{
connack_packet_t *packet = mqtt_sn_receive_packet(sock);
if (packet == NULL) {
mqtt_sn_log_err("Failed to connect to MQTT-SN gateway.");
exit(EXIT_FAILURE);
}
if (packet->type != MQTT_SN_TYPE_CONNACK) {
mqtt_sn_log_err("Was expecting CONNACK packet but received: %s", mqtt_sn_type_string(packet->type));
exit(EXIT_FAILURE);
}
// Check Connack return code
mqtt_sn_log_debug("CONNACK return code: 0x%2.2x", packet->return_code);
if (packet->return_code) {
mqtt_sn_log_err("CONNECT error: %s", mqtt_sn_return_code_string(packet->return_code));
exit(packet->return_code);
}
}
static int mqtt_sn_process_register(int sock, const register_packet_t *packet)
{
int message_id = ntohs(packet->message_id);
int topic_id = ntohs(packet->topic_id);
const char* topic_name = packet->topic_name;
// Add it to the topic map
mqtt_sn_register_topic(topic_id, topic_name);
// Respond to gateway with REGACK
mqtt_sn_send_regack(sock, topic_id, message_id);
return 0;
}
void mqtt_sn_register_topic(int topic_id, const char* topic_name)
{
topic_map_t **ptr = &topic_map;
// Check topic ID is valid
if (topic_id == 0x0000 || topic_id == 0xFFFF) {
mqtt_sn_log_err("Attempted to register invalid topic id: 0x%4.4x", topic_id);
return;
}
// Check topic name is valid
if (topic_name == NULL || strlen(topic_name) <= 0) {
mqtt_sn_log_err("Attempted to register invalid topic name.");
return;
}
mqtt_sn_log_debug("Registering topic 0x%4.4x: %s", topic_id, topic_name);
// Look for the topic id
while (*ptr) {
if ((*ptr)->topic_id == topic_id) {
break;
} else {
ptr = &((*ptr)->next);
}
}
// Allocate memory for a new entry, if we reached the end of the list
if (*ptr == NULL) {
*ptr = (topic_map_t *)malloc(sizeof(topic_map_t));
if (!*ptr) {
mqtt_sn_log_err("Failed to allocate memory for new topic map entry.");
exit(EXIT_FAILURE);
}
(*ptr)->next = NULL;
}
// Copy in the name to the entry
strncpy((*ptr)->topic_name, topic_name, MQTT_SN_MAX_TOPIC_LENGTH);
(*ptr)->topic_id = topic_id;
}
const char* mqtt_sn_lookup_topic(int topic_id)
{
topic_map_t **ptr = &topic_map;
while (*ptr) {
if ((*ptr)->topic_id == topic_id) {
return (*ptr)->topic_name;
}
ptr = &((*ptr)->next);
}
mqtt_sn_log_warn("Failed to lookup topic id: 0x%4.4x", topic_id);
return NULL;
}
uint16_t mqtt_sn_receive_regack(int sock)
{
regack_packet_t *packet = mqtt_sn_wait_for(MQTT_SN_TYPE_REGACK, sock);
uint16_t received_message_id, received_topic_id;
if (packet == NULL) {
mqtt_sn_log_err("Failed to connect to register topic.");
exit(EXIT_FAILURE);
}
// Check Regack return code
mqtt_sn_log_debug("REGACK return code: 0x%2.2x", packet->return_code);
if (packet->return_code) {
mqtt_sn_log_err("REGISTER failed: %s", mqtt_sn_return_code_string(packet->return_code));
exit(packet->return_code);
}
// Check that the Message ID matches
received_message_id = ntohs(packet->message_id);
if (received_message_id != next_message_id-1) {
mqtt_sn_log_warn("Message id in Regack does not equal message id sent");
}
// Return the topic ID returned by the gateway
received_topic_id = ntohs(packet->topic_id);
mqtt_sn_log_debug("REGACK topic id: 0x%4.4x", received_topic_id);
return received_topic_id;
}
void mqtt_sn_dump_packet(char* packet)
{
printf("%s: len=%d", mqtt_sn_type_string(packet[1]), packet[0]);
switch(packet[1]) {
case MQTT_SN_TYPE_CONNECT: {
connect_packet_t* cpkt = (connect_packet_t*)packet;
printf(" protocol_id=%d", cpkt->protocol_id);
printf(" duration=%d", ntohs(cpkt->duration));
printf(" client_id=%s", cpkt->client_id);
break;
}
case MQTT_SN_TYPE_CONNACK: {
connack_packet_t* capkt = (connack_packet_t*)packet;
printf(" return_code=%d (%s)", capkt->return_code, mqtt_sn_return_code_string(capkt->return_code));
break;
}
case MQTT_SN_TYPE_REGISTER: {
register_packet_t* rpkt = (register_packet_t*)packet;
printf(" topic_id=0x%4.4x", ntohs(rpkt->topic_id));
printf(" message_id=0x%4.4x", ntohs(rpkt->message_id));
printf(" topic_name=%s", rpkt->topic_name);
break;
}
case MQTT_SN_TYPE_REGACK: {
regack_packet_t* rapkt = (regack_packet_t*)packet;
printf(" topic_id=0x%4.4x", ntohs(rapkt->topic_id));
printf(" message_id=0x%4.4x", ntohs(rapkt->message_id));
printf(" return_code=%d (%s)", rapkt->return_code, mqtt_sn_return_code_string(rapkt->return_code));
break;
}
case MQTT_SN_TYPE_PUBLISH: {
publish_packet_t* ppkt = (publish_packet_t*)packet;
printf(" topic_id=0x%4.4x", ntohs(ppkt->topic_id));
printf(" message_id=0x%4.4x", ntohs(ppkt->message_id));
printf(" data=%s", ppkt->data);
break;
}
case MQTT_SN_TYPE_SUBSCRIBE: {
subscribe_packet_t* spkt = (subscribe_packet_t*)packet;
printf(" message_id=0x%4.4x", ntohs(spkt->message_id));
break;
}
case MQTT_SN_TYPE_SUBACK: {
suback_packet_t* sapkt = (suback_packet_t*)packet;
printf(" topic_id=0x%4.4x", ntohs(sapkt->topic_id));
printf(" message_id=0x%4.4x", ntohs(sapkt->message_id));
printf(" return_code=%d (%s)", sapkt->return_code, mqtt_sn_return_code_string(sapkt->return_code));
break;
}
case MQTT_SN_TYPE_DISCONNECT: {
disconnect_packet_t* dpkt = (disconnect_packet_t*)packet;
printf(" duration=%d", ntohs(dpkt->duration));
break;
}
}
printf("\n");
fflush(stdout);
}
void mqtt_sn_print_publish_packet(publish_packet_t* packet)
{
if (verbose) {
int topic_type = packet->flags & 0x3;
int topic_id = ntohs(packet->topic_id);
if (verbose == 2) {
time_t rcv_time;
char tm_buffer [40];
time(&rcv_time) ;
strftime(tm_buffer, 40, "%F %T ", localtime(&rcv_time));
fputs(tm_buffer, stdout);
}
switch (topic_type) {
case MQTT_SN_TOPIC_TYPE_NORMAL: {
const char *topic_name = mqtt_sn_lookup_topic(topic_id);
if (topic_name) {
printf("%s: %s\n", topic_name, packet->data);
}
break;
};
case MQTT_SN_TOPIC_TYPE_PREDEFINED: {
printf("%4.4x: %s\n", topic_id, packet->data);
break;
};
case MQTT_SN_TOPIC_TYPE_SHORT: {
const char *str = (const char*)&packet->topic_id;
printf("%c%c: %s\n", str[0], str[1], packet->data);
break;
};
}
} else {
printf("%s\n", packet->data);
}
fflush(stdout);
}
uint16_t mqtt_sn_receive_suback(int sock)
{
suback_packet_t *packet = mqtt_sn_wait_for(MQTT_SN_TYPE_SUBACK, sock);
uint16_t received_message_id, received_topic_id;
if (packet == NULL) {
mqtt_sn_log_err("Failed to subscribe to topic.");
exit(EXIT_FAILURE);
}
// Check Suback return code
mqtt_sn_log_debug("SUBACK return code: 0x%2.2x", packet->return_code);
if (packet->return_code) {
mqtt_sn_log_err("SUBSCRIBE error: %s", mqtt_sn_return_code_string(packet->return_code));
exit(packet->return_code);
}
// Check that the Message ID matches
received_message_id = ntohs(packet->message_id);
if (received_message_id != next_message_id-1) {
mqtt_sn_log_warn("Message id in SUBACK does not equal message id sent");
mqtt_sn_log_debug(" Expecting: %d", next_message_id-1);
mqtt_sn_log_debug(" Actual: %d", received_message_id);
}
// Return the topic ID returned by the gateway
received_topic_id = ntohs(packet->topic_id);
mqtt_sn_log_debug("SUBACK topic id: 0x%4.4x", received_topic_id);
return received_topic_id;
}
int mqtt_sn_select(int sock)
{
struct timeval tv;
fd_set rfd;
int ret;
FD_ZERO(&rfd);
FD_SET(sock, &rfd);
tv.tv_sec = timeout;
tv.tv_usec = 0;
ret = select(sock + 1, &rfd, NULL, NULL, &tv);
if (ret < 0 && errno != EINTR) {
// Something is wrong.
perror("select");
exit(EXIT_FAILURE);
}
return ret;
}
void* mqtt_sn_wait_for(uint8_t type, int sock)
{
time_t started_waiting = time(NULL);
while(TRUE) {
time_t now = time(NULL);
int ret;
// Time to send a ping?
if (keep_alive > 0 && (now - last_transmit) >= keep_alive) {
mqtt_sn_send_pingreq(sock);
}
ret = mqtt_sn_select(sock);
if (ret < 0) {
break;
} else if (ret > 0) {
char* packet = mqtt_sn_receive_packet(sock);
if (packet) {
switch(packet[1]) {
case MQTT_SN_TYPE_PUBLISH:
mqtt_sn_print_publish_packet((publish_packet_t *)packet);
break;
case MQTT_SN_TYPE_REGISTER:
mqtt_sn_process_register(sock, (register_packet_t*)packet);
break;
case MQTT_SN_TYPE_PINGRESP:
// do nothing
break;
case MQTT_SN_TYPE_DISCONNECT:
if (type != MQTT_SN_TYPE_DISCONNECT) {
mqtt_sn_log_warn("Received DISCONNECT from gateway.");
exit(EXIT_FAILURE);
}
break;
default:
if (packet[1] != type) {
mqtt_sn_log_warn(
"Was expecting %s packet but received: %s",
mqtt_sn_type_string(type),
mqtt_sn_type_string(packet[1])
);
}
break;
}
// Did we find what we were looking for?
if (packet[1] == type) {
return packet;
}
}
}
// Check for receive timeout
if (keep_alive > 0 && (now - last_receive) >= (keep_alive * 1.5)) {
mqtt_sn_log_err("Keep alive error: timed out while waiting for a %s from gateway.", mqtt_sn_type_string(type));
exit(EXIT_FAILURE);
}
// Check if we have timed out waiting for the packet we are looking for
if ((now - started_waiting) >= timeout) {
mqtt_sn_log_debug("Timed out while waiting for a %s from gateway.", mqtt_sn_type_string(type));
break;
}
}
return NULL;
}
const char* mqtt_sn_type_string(uint8_t type)
{
switch(type) {
case MQTT_SN_TYPE_ADVERTISE:
return "ADVERTISE";
case MQTT_SN_TYPE_SEARCHGW:
return "SEARCHGW";
case MQTT_SN_TYPE_GWINFO:
return "GWINFO";
case MQTT_SN_TYPE_CONNECT:
return "CONNECT";
case MQTT_SN_TYPE_CONNACK:
return "CONNACK";
case MQTT_SN_TYPE_WILLTOPICREQ:
return "WILLTOPICREQ";
case MQTT_SN_TYPE_WILLTOPIC:
return "WILLTOPIC";
case MQTT_SN_TYPE_WILLMSGREQ:
return "WILLMSGREQ";
case MQTT_SN_TYPE_WILLMSG:
return "WILLMSG";
case MQTT_SN_TYPE_REGISTER:
return "REGISTER";
case MQTT_SN_TYPE_REGACK:
return "REGACK";
case MQTT_SN_TYPE_PUBLISH:
return "PUBLISH";
case MQTT_SN_TYPE_PUBACK:
return "PUBACK";
case MQTT_SN_TYPE_PUBCOMP:
return "PUBCOMP";
case MQTT_SN_TYPE_PUBREC:
return "PUBREC";
case MQTT_SN_TYPE_PUBREL:
return "PUBREL";
case MQTT_SN_TYPE_SUBSCRIBE:
return "SUBSCRIBE";
case MQTT_SN_TYPE_SUBACK:
return "SUBACK";
case MQTT_SN_TYPE_UNSUBSCRIBE:
return "UNSUBSCRIBE";
case MQTT_SN_TYPE_UNSUBACK:
return "UNSUBACK";
case MQTT_SN_TYPE_PINGREQ:
return "PINGREQ";
case MQTT_SN_TYPE_PINGRESP:
return "PINGRESP";
case MQTT_SN_TYPE_DISCONNECT:
return "DISCONNECT";
case MQTT_SN_TYPE_WILLTOPICUPD:
return "WILLTOPICUPD";
case MQTT_SN_TYPE_WILLTOPICRESP:
return "WILLTOPICRESP";
case MQTT_SN_TYPE_WILLMSGUPD:
return "WILLMSGUPD";
case MQTT_SN_TYPE_WILLMSGRESP:
return "WILLMSGRESP";
case MQTT_SN_TYPE_FRWDENCAP:
return "FRWDENCAP";
default:
return "UNKNOWN";
}
}
const char* mqtt_sn_return_code_string(uint8_t return_code)
{
switch(return_code) {
case MQTT_SN_ACCEPTED:
return "Accepted";
case MQTT_SN_REJECTED_CONGESTION:
return "Rejected: congestion";
case MQTT_SN_REJECTED_INVALID:
return "Rejected: invalid topic ID";
case MQTT_SN_REJECTED_NOT_SUPPORTED:
return "Rejected: not supported";
default:
return "Rejected: unknown reason";
}
}
void mqtt_sn_cleanup()
{
topic_map_t *ptr = topic_map;
topic_map_t *ptr2 = NULL;
// Walk through the topic map, deleting each entry
while (ptr) {
ptr2 = ptr;
ptr = ptr->next;
free(ptr2);
}
topic_map = NULL;
}
uint8_t mqtt_sn_enable_frwdencap()
{
return forwarder_encapsulation = TRUE;
}
uint8_t mqtt_sn_disable_frwdencap()
{
return forwarder_encapsulation = FALSE;
}
void mqtt_sn_set_frwdencap_parameters(const uint8_t *wlnid, uint8_t wlnid_len)
{
wireless_node_id = wlnid;
wireless_node_id_len = wlnid_len;
}
frwdencap_packet_t* mqtt_sn_create_frwdencap_packet(const void *data, size_t *len, const uint8_t *wireless_node_id, uint8_t wireless_node_id_len)
{
frwdencap_packet_t* packet = NULL;
// Check that it isn't too long
if (wireless_node_id_len > MQTT_SN_MAX_WIRELESS_NODE_ID_LENGTH) {
mqtt_sn_log_err("Wireless node id is longer than %d", MQTT_SN_MAX_WIRELESS_NODE_ID_LENGTH);
exit(EXIT_FAILURE);
}
// Allocates a block of memory for an array of num elements, each of them size bytes long,
// and initializes all its bits to zero.
packet = malloc(sizeof(frwdencap_packet_t));
packet->type = MQTT_SN_TYPE_FRWDENCAP;
packet->ctrl = 0;
// Generate a Wireless Node ID if none given
if (wireless_node_id == NULL || wireless_node_id_len == 0) {
// A null character is automatically appended after the content written.
snprintf((char*)packet->wireless_node_id, sizeof(packet->wireless_node_id)-1, "%X", getpid());
wireless_node_id_len = strlen((char*)packet->wireless_node_id);
} else {
memcpy(packet->wireless_node_id, wireless_node_id, wireless_node_id_len);
}
packet->length = wireless_node_id_len + 3;
// Copy mqtt-sn packet into forwarder encapsulation packet
memcpy(&(packet->wireless_node_id[wireless_node_id_len]), data, ((uint8_t*)data)[0]);
// Set new packet length to send
*len = packet->length + ((uint8_t*)data)[0];
if (debug > 2) {
char wlnd[65];
char* buf_ptr = wlnd;
int i;
for (i = 0; i < wireless_node_id_len; i++) {
buf_ptr += sprintf(buf_ptr, "%02X", packet->wireless_node_id[i]);
}
*(++buf_ptr) = '\0';
mqtt_sn_log_debug("Node id: 0x%s, N. id len: %d, Wrapped packet len: %d, Total len: %lu",
wlnd, wireless_node_id_len, ((uint8_t*)data)[0], (long unsigned int)*len);
}
return packet;
}
static void mqtt_sn_log_msg(const char* level, const char* format, va_list arglist)
{
time_t mqtt_sn_log_time;
char tm_buffer[40];
time(&mqtt_sn_log_time);
strftime(tm_buffer, sizeof(tm_buffer), "%F %T ", localtime(&mqtt_sn_log_time));
fputs(tm_buffer, stderr);
fputs(level, stderr);
vfprintf(stderr, format, arglist);
fputs("\n", stderr);
}
void mqtt_sn_log_debug(const char * format, ...)
{
if (debug) {
va_list arglist;
va_start(arglist, format);
mqtt_sn_log_msg("DEBUG ", format, arglist);
va_end(arglist);
}
}
void mqtt_sn_log_warn(const char * format, ...)
{
va_list arglist;
va_start(arglist, format);
mqtt_sn_log_msg("WARN ", format, arglist);
va_end(arglist);
}
void mqtt_sn_log_err(const char * format, ...)
{
va_list arglist;
va_start(arglist, format);
mqtt_sn_log_msg("ERROR ", format, arglist);
va_end(arglist);
}
================================================
FILE: mqtt-sn.h
================================================
/*
Common functions used by the MQTT-SN Tools
Copyright (C) Nicholas Humfrey
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdarg.h>
#ifndef MQTT_SN_H
#define MQTT_SN_H
#ifndef FALSE
#define FALSE (0)
#endif
#ifndef TRUE
#define TRUE (1)
#endif
#define MQTT_SN_DEFAULT_PORT "1883"
#define MQTT_SN_DEFAULT_TIMEOUT (10)
#define MQTT_SN_DEFAULT_KEEP_ALIVE (10)
#define MQTT_SN_MAX_PACKET_LENGTH (255)
#define MQTT_SN_MAX_PAYLOAD_LENGTH (MQTT_SN_MAX_PACKET_LENGTH-7)
#define MQTT_SN_MAX_TOPIC_LENGTH (MQTT_SN_MAX_PACKET_LENGTH-6)
#define MQTT_SN_MAX_CLIENT_ID_LENGTH (23)
#define MQTT_SN_MAX_WIRELESS_NODE_ID_LENGTH (252)
#define MQTT_SN_TYPE_ADVERTISE (0x00)
#define MQTT_SN_TYPE_SEARCHGW (0x01)
#define MQTT_SN_TYPE_GWINFO (0x02)
#define MQTT_SN_TYPE_CONNECT (0x04)
#define MQTT_SN_TYPE_CONNACK (0x05)
#define MQTT_SN_TYPE_WILLTOPICREQ (0x06)
#define MQTT_SN_TYPE_WILLTOPIC (0x07)
#define MQTT_SN_TYPE_WILLMSGREQ (0x08)
#define MQTT_SN_TYPE_WILLMSG (0x09)
#define MQTT_SN_TYPE_REGISTER (0x0A)
#define MQTT_SN_TYPE_REGACK (0x0B)
#define MQTT_SN_TYPE_PUBLISH (0x0C)
#define MQTT_SN_TYPE_PUBACK (0x0D)
#define MQTT_SN_TYPE_PUBCOMP (0x0E)
#define MQTT_SN_TYPE_PUBREC (0x0F)
#define MQTT_SN_TYPE_PUBREL (0x10)
#define MQTT_SN_TYPE_SUBSCRIBE (0x12)
#define MQTT_SN_TYPE_SUBACK (0x13)
#define MQTT_SN_TYPE_UNSUBSCRIBE (0x14)
#define MQTT_SN_TYPE_UNSUBACK (0x15)
#define MQTT_SN_TYPE_PINGREQ (0x16)
#define MQTT_SN_TYPE_PINGRESP (0x17)
#define MQTT_SN_TYPE_DISCONNECT (0x18)
#define MQTT_SN_TYPE_WILLTOPICUPD (0x1A)
#define MQTT_SN_TYPE_WILLTOPICRESP (0x1B)
#define MQTT_SN_TYPE_WILLMSGUPD (0x1C)
#define MQTT_SN_TYPE_WILLMSGRESP (0x1D)
#define MQTT_SN_TYPE_FRWDENCAP (0xFE)
#define MQTT_SN_ACCEPTED (0x00)
#define MQTT_SN_REJECTED_CONGESTION (0x01)
#define MQTT_SN_REJECTED_INVALID (0x02)
#define MQTT_SN_REJECTED_NOT_SUPPORTED (0x03)
#define MQTT_SN_TOPIC_TYPE_NORMAL (0x00)
#define MQTT_SN_TOPIC_TYPE_PREDEFINED (0x01)
#define MQTT_SN_TOPIC_TYPE_SHORT (0x02)
#define MQTT_SN_FLAG_DUP (0x1 << 7)
#define MQTT_SN_FLAG_QOS_0 (0x0 << 5)
#define MQTT_SN_FLAG_QOS_1 (0x1 << 5)
#define MQTT_SN_FLAG_QOS_2 (0x2 << 5)
#define MQTT_SN_FLAG_QOS_N1 (0x3 << 5)
#define MQTT_SN_FLAG_QOS_MASK (0x3 << 5)
#define MQTT_SN_FLAG_RETAIN (0x1 << 4)
#define MQTT_SN_FLAG_WILL (0x1 << 3)
#define MQTT_SN_FLAG_CLEAN (0x1 << 2)
#define MQTT_SN_PROTOCOL_ID (0x01)
typedef struct {
uint8_t length;
uint8_t type;
uint8_t flags;
uint8_t protocol_id;
uint16_t duration;
char client_id[MQTT_SN_MAX_CLIENT_ID_LENGTH];
} connect_packet_t;
typedef struct {
uint8_t length;
uint8_t type;
uint8_t return_code;
} connack_packet_t;
typedef struct {
uint8_t length;
uint8_t type;
uint16_t topic_id;
uint16_t message_id;
char topic_name[MQTT_SN_MAX_TOPIC_LENGTH];
} register_packet_t;
typedef struct {
uint8_t length;
uint8_t type;
uint16_t topic_id;
uint16_t message_id;
uint8_t return_code;
} regack_packet_t;
typedef struct __attribute__((packed)) {
uint8_t length;
uint8_t type;
uint8_t flags;
uint16_t topic_id;
uint16_t message_id;
char data[MQTT_SN_MAX_PAYLOAD_LENGTH];
}
publish_packet_t;
typedef struct __attribute__((packed)) {
uint8_t length;
uint8_t type;
uint16_t topic_id;
uint16_t message_id;
uint8_t return_code;
}
puback_packet_t;
typedef struct __attribute__((packed)) {
uint8_t length;
uint8_t type;
uint8_t flags;
uint16_t message_id;
union {
char topic_name[MQTT_SN_MAX_TOPIC_LENGTH];
uint16_t topic_id;
};
}
subscribe_packet_t;
typedef struct __attribute__((packed)) {
uint8_t length;
uint8_t type;
uint8_t flags;
uint16_t topic_id;
uint16_t message_id;
uint8_t return_code;
}
suback_packet_t;
typedef struct {
uint8_t length;
uint8_t type;
uint16_t duration;
} disconnect_packet_t;
typedef struct __attribute__((packed)) {
uint8_t length;
uint8_t type;
uint8_t ctrl;
uint8_t wireless_node_id[MQTT_SN_MAX_WIRELESS_NODE_ID_LENGTH];
char data[MQTT_SN_MAX_PACKET_LENGTH];
}
frwdencap_packet_t;
typedef struct topic_map {
uint16_t topic_id;
char topic_name[MQTT_SN_MAX_TOPIC_LENGTH];
struct topic_map *next;
} topic_map_t;
// Library functions
int mqtt_sn_create_socket(const char* host, const char* port, uint16_t source_port);
void mqtt_sn_send_connect(int sock, const char* client_id, uint16_t keepalive, uint8_t clean_session);
void mqtt_sn_send_register(int sock, const char* topic_name);
void mqtt_sn_send_publish(int sock, uint16_t topic_id, uint8_t topic_type, const void* data, uint16_t data_len, int8_t qos, uint8_t retain);
void mqtt_sn_send_puback(int sock, publish_packet_t* publish, uint8_t return_code);
void mqtt_sn_send_subscribe_topic_name(int sock, const char* topic_name, uint8_t qos);
void mqtt_sn_send_subscribe_topic_id(int sock, uint16_t topic_id, uint8_t qos);
void mqtt_sn_send_pingreq(int sock);
void mqtt_sn_send_disconnect(int sock, uint16_t duration);
void mqtt_sn_receive_disconnect(int sock);
void mqtt_sn_receive_connack(int sock);
uint16_t mqtt_sn_receive_regack(int sock);
uint16_t mqtt_sn_receive_suback(int sock);
void mqtt_sn_dump_packet(char* packet);
void mqtt_sn_print_publish_packet(publish_packet_t* packet);
int mqtt_sn_select(int sock);
void* mqtt_sn_wait_for(uint8_t type, int sock);
void mqtt_sn_register_topic(int topic_id, const char* topic_name);
const char* mqtt_sn_lookup_topic(int topic_id);
void mqtt_sn_cleanup();
void mqtt_sn_set_debug(uint8_t value);
void mqtt_sn_set_verbose(uint8_t value);
void mqtt_sn_set_timeout(uint8_t value);
const char* mqtt_sn_type_string(uint8_t type);
const char* mqtt_sn_return_code_string(uint8_t return_code);
uint8_t mqtt_sn_validate_packet(const void *packet, size_t length);
void mqtt_sn_send_packet(int sock, const void* data);
void mqtt_sn_send_frwdencap_packet(int sock, const void* data, const uint8_t *wireless_node_id, uint8_t wireless_node_id_len);
void* mqtt_sn_receive_packet(int sock);
void* mqtt_sn_receive_frwdencap_packet(int sock, uint8_t **wireless_node_id, uint8_t *wireless_node_id_len);
// Functions to turn on and off forwarder encapsulation according to MQTT-SN Protocol Specification v1.2,
// chapter 5.5 Forwarder Encapsulation.
uint8_t mqtt_sn_enable_frwdencap();
uint8_t mqtt_sn_disable_frwdencap();
// Set wireless node ID and wireless node ID length
void mqtt_sn_set_frwdencap_parameters(const uint8_t *wlnid, uint8_t wlnid_len);
// Wrap mqtt-sn packet into a forwarder encapsulation packet
frwdencap_packet_t* mqtt_sn_create_frwdencap_packet(const void *data, size_t *len, const uint8_t *wireless_node_id, uint8_t wireless_node_id_len);
void mqtt_sn_log_debug(const char * format, ...);
void mqtt_sn_log_warn(const char * format, ...);
void mqtt_sn_log_err(const char * format, ...);
#endif
================================================
FILE: test/.ruby-version
================================================
2.5.5
================================================
FILE: test/Gemfile
================================================
source 'https://rubygems.org'
ruby File.read('.ruby-version').chomp
gem 'bundler', '>= 1.5.0'
gem 'rake', '>= 0.10.0'
gem 'mqtt', '>= 0.4.0'
gem 'minitest', '>= 5.0.0'
================================================
FILE: test/Rakefile
================================================
#!/usr/bin/env ruby
require 'rubygems'
require 'rake/testtask'
Rake::TestTask.new do |t|
t.pattern = "*-test.rb"
end
task :default => :test
================================================
FILE: test/fake_server.rb
================================================
#!/usr/bin/env ruby
#
# This is a 'fake' MQTT-SN server to help with testing client implementations
#
# It behaves in the following ways:
# * Responds to CONNECT with a successful CONACK
# * Responds to PUBLISH by keeping a copy of the packet
# * Responds to SUBSCRIBE with SUBACK and a PUBLISH to the topic
# * Responds to PINGREQ with PINGRESP and keeps a count
# * Responds to DISCONNECT with a DISCONNECT packet
#
# It has the following restrictions
# * Doesn't deal with timeouts
# * Only handles a single client
#
require 'logger'
require 'socket'
require 'mqtt'
class MQTT::SN::FakeServer
attr_reader :address, :port
attr_reader :thread
attr_reader :packets_received
# Create a new fake MQTT server
#
# If no port is given, bind to a random port number
# If no bind address is given, bind to localhost
def initialize(port=0, bind_address='127.0.0.1')
@port = port
@address = bind_address
@packets_received = []
end
# Get the logger used by the server
def logger
@logger ||= Logger.new(STDOUT)
end
def logger=(logger)
@logger = logger
end
# Start the thread and open the socket that will process client connections
def start
@thread ||= Thread.new do
Thread.current.abort_on_exception = true
Socket.udp_server_sockets(@address, @port) do |sockets|
@address = sockets.first.local_address.ip_address
@port = sockets.first.local_address.ip_port
logger.info "Started a fake MQTT-SN server on #{@address}:#{@port}"
Socket.udp_server_loop_on(sockets) do |data, client|
response = process_packet(data)
unless response.nil?
response = [response] unless response.kind_of?(Enumerable)
response.each do |packet|
logger.debug "Replying with: #{packet.inspect}"
client.reply(packet.to_s)
end
end
end
end
end
end
# Stop the thread and close the socket
def stop
logger.info "Stopping fake MQTT-SN server"
@thread.kill if @thread and @thread.alive?
@thread = nil
end
# Start the server thread and wait for it to finish (possibly never)
def run
start
begin
@thread.join
rescue Interrupt
stop
end
end
def wait_for_port_number
while @port.to_i == 0
Thread.pass
end
@port
end
def wait_for_packet(klass=nil, timeout=3)
begin
Timeout.timeout(timeout) do
if block_given?
@packets_received = []
yield
end
loop do
if klass.nil?
unless @packets_received.empty?
return @packets_received.last_packet
end
else
@packets_received.each do |packet|
return packet if packet.class == klass
end
end
sleep(0.01)
end
end
rescue Timeout::Error
logger.warn "FakeServer timed out waiting for a #{klass}"
return nil
end
end
protected
def process_packet(data)
packet = MQTT::SN::Packet.parse(data)
@packets_received << packet
logger.debug "Received: #{packet.inspect}"
method = 'handle_' + packet.class.name.split('::').last.downcase
if respond_to?(method, true)
send(method, packet)
else
logger.warn "Unhandled packet type: #{packet.class}"
nil
end
rescue MQTT::SN::ProtocolException => e
logger.warn "Protocol error: #{e}"
nil
end
def handle_connect(packet)
MQTT::SN::Packet::Connack.new(:return_code => 0x00)
end
def handle_publish(packet)
if packet.qos > 0
MQTT::SN::Packet::Puback.new(
:id => packet.id,
:topic_id => packet.topic_id,
:return_code => 0x00
)
end
end
def handle_pingreq(packet)
MQTT::SN::Packet::Pingresp.new
end
def handle_subscribe(packet, publish_data=nil)
case packet.topic_id_type
when :short
topic_id = packet.topic_name
publish_data ||= "Message for #{packet.topic_name}"
when :predefined
topic_id = packet.topic_id
publish_data ||= "Message for ##{packet.topic_id}"
when :normal
topic_id = 1
publish_data ||= "Message for #{packet.topic_name}"
else
logger.warn "Unknown Topic Id Type: #{packet.topic_id_type}"
end
[
MQTT::SN::Packet::Suback.new(
:id => packet.id,
:topic_id_type => packet.topic_id_type,
:topic_id => topic_id,
:return_code => 0
),
MQTT::SN::Packet::Publish.new(
:topic_id_type => packet.topic_id_type,
:topic_id => topic_id,
:qos => packet.qos,
:data => publish_data
)
]
end
def handle_disconnect(packet)
MQTT::SN::Packet::Disconnect.new
end
def handle_register(packet)
MQTT::SN::Packet::Regack.new(
:id => packet.id,
:topic_id => 1,
:return_code => 0x00
)
end
def handle_puback(packet)
nil
end
def handle_regack(packet)
nil
end
end
if __FILE__ == $0
server = MQTT::SN::FakeServer.new(MQTT::SN::DEFAULT_PORT)
server.logger.level = Logger::DEBUG
server.run
end
================================================
FILE: test/mqtt-sn-dump-test.rb
================================================
$:.unshift(File.dirname(__FILE__))
require 'test_helper'
class MqttSnDumpTest < Minitest::Test
def test_usage
@cmd_result = run_cmd('mqtt-sn-dump', '-?')
assert_match(/^Usage: mqtt-sn-dump/, @cmd_result[0])
end
def publish_packet(port, packet)
# FIXME: better way to wait until socket is open?
sleep 0.2
socket = UDPSocket.new
socket.connect('localhost', port)
socket << packet.to_s
socket.close
end
def publish_qos_n1_packet(port)
publish_packet(port,
MQTT::SN::Packet::Publish.new(
:topic_id => 'TT',
:topic_id_type => :short,
:data => "Message for TT",
:qos => -1
)
)
end
def test_receive_qos_n1
@port = random_port
@cmd_result = run_cmd(
'mqtt-sn-dump',
['-p', @port]
) do |cmd|
publish_qos_n1_packet(@port)
wait_for_output_then_kill(cmd)
end
assert_equal(["Message for TT"], @cmd_result)
end
def test_receive_qos_n1_debug
@port = random_port
@cmd_result = run_cmd(
'mqtt-sn-dump',
['-d', '-p', @port]
) do |cmd|
publish_qos_n1_packet(@port)
wait_for_output_then_kill(cmd)
end
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG mqtt-sn-dump listening on port \d+/, @cmd_result)
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG waiting for packet/, @cmd_result)
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG Received 21 bytes from 127.0.0.1\:\d+. Type=PUBLISH/, @cmd_result)
end
def test_receive_qos_n1_verbose
@port = random_port
@cmd_result = run_cmd(
'mqtt-sn-dump',
['-v', '-p', @port]
) do |cmd|
publish_qos_n1_packet(@port)
wait_for_output_then_kill(cmd)
end
assert_equal(["TT: Message for TT"], @cmd_result)
end
def test_receive_qos_n1_dump_all
@port = random_port
@cmd_result = run_cmd(
'mqtt-sn-dump',
['-a', '-p', @port]
) do |cmd|
publish_qos_n1_packet(@port)
wait_for_output_then_kill(cmd)
end
assert_equal(["PUBLISH: len=21 topic_id=0x5454 message_id=0x0000 data=Message for TT"], @cmd_result)
end
def test_receive_qos_n1_term
@port = random_port
@cmd_result = run_cmd(
'mqtt-sn-dump',
['-p', @port]
) do |cmd|
publish_qos_n1_packet(@port)
wait_for_output_then_kill(cmd, 'TERM')
end
assert_equal(["Message for TT"], @cmd_result)
end
def test_receive_qos_n1_hup
@port = random_port
@cmd_result = run_cmd(
'mqtt-sn-dump',
['-p', @port]
) do |cmd|
publish_qos_n1_packet(@port)
wait_for_output_then_kill(cmd, 'HUP')
end
assert_equal(["Message for TT"], @cmd_result)
end
def test_receive_connect
@port = random_port
@cmd_result = run_cmd(
'mqtt-sn-dump',
['-a', '-p', @port]
) do |cmd|
publish_packet(@port,
MQTT::SN::Packet::Connect.new(
:client_id => 'my_client_id',
:keep_alive => 10
)
)
wait_for_output_then_kill(cmd)
end
assert_equal(["CONNECT: len=18 protocol_id=1 duration=10 client_id=my_client_id"], @cmd_result)
end
def test_receive_connack
@port = random_port
@cmd_result = run_cmd(
'mqtt-sn-dump',
['-a', '-p', @port]
) do |cmd|
publish_packet(@port,
MQTT::SN::Packet::Connack.new(
:return_code => 1
)
)
wait_for_output_then_kill(cmd)
end
assert_equal(["CONNACK: len=3 return_code=1 (Rejected: congestion)"], @cmd_result)
end
def test_receive_connack_not_supported
@port = random_port
@cmd_result = run_cmd(
'mqtt-sn-dump',
['-a', '-p', @port]
) do |cmd|
publish_packet(@port,
MQTT::SN::Packet::Connack.new(
:return_code => 3
)
)
wait_for_output_then_kill(cmd)
end
assert_equal(["CONNACK: len=3 return_code=3 (Rejected: not supported)"], @cmd_result)
end
def test_receive_register
@port = random_port
@cmd_result = run_cmd(
'mqtt-sn-dump',
['-a', '-p', @port]
) do |cmd|
publish_packet(@port,
MQTT::SN::Packet::Register.new(
:id => 10,
:topic_id => 20,
:topic_name => 'Topic Name'
)
)
wait_for_output_then_kill(cmd)
end
assert_equal(["REGISTER: len=16 topic_id=0x0014 message_id=0x000a topic_name=Topic Name"], @cmd_result)
end
def test_receive_regack
@port = random_port
@cmd_result = run_cmd(
'mqtt-sn-dump',
['-a', '-p', @port]
) do |cmd|
publish_packet(@port,
MQTT::SN::Packet::Regack.new(
:id => 30,
:topic_id => 40,
:return_code => 0
)
)
wait_for_output_then_kill(cmd)
end
assert_equal(["REGACK: len=7 topic_id=0x0028 message_id=0x001e return_code=0 (Accepted)"], @cmd_result)
end
def test_receive_subscribe
@port = random_port
@cmd_result = run_cmd(
'mqtt-sn-dump',
['-a', '-p', @port]
) do |cmd|
publish_packet(@port,
MQTT::SN::Packet::Subscribe.new(:id => 50)
)
wait_for_output_then_kill(cmd)
end
assert_equal(["SUBSCRIBE: len=5 message_id=0x0032"], @cmd_result)
end
def test_receive_suback
@port = random_port
@cmd_result = run_cmd(
'mqtt-sn-dump',
['-a', '-p', @port]
) do |cmd|
publish_packet(@port,
MQTT::SN::Packet::Suback.new(
:id => 60,
:topic_id => 70,
:return_code => 0
)
)
wait_for_output_then_kill(cmd)
end
assert_equal(["SUBACK: len=8 topic_id=0x0046 message_id=0x003c return_code=0 (Accepted)"], @cmd_result)
end
def test_receive_suback_unknown_error
@port = random_port
@cmd_result = run_cmd(
'mqtt-sn-dump',
['-a', '-p', @port]
) do |cmd|
publish_packet(@port,
MQTT::SN::Packet::Suback.new(
:id => 60,
:topic_id => 70,
:return_code => 5
)
)
wait_for_output_then_kill(cmd)
end
assert_equal(["SUBACK: len=8 topic_id=0x0046 message_id=0x003c return_code=5 (Rejected: unknown reason)"], @cmd_result)
end
def test_receive_pingreq
@port = random_port
@cmd_result = run_cmd(
'mqtt-sn-dump',
['-a', '-p', @port]
) do |cmd|
publish_packet(@port, MQTT::SN::Packet::Pingreq.new)
wait_for_output_then_kill(cmd)
end
assert_equal(["PINGREQ: len=2"], @cmd_result)
end
def test_receive_pingresp
@port = random_port
@cmd_result = run_cmd(
'mqtt-sn-dump',
['-a', '-p', @port]
) do |cmd|
publish_packet(@port, MQTT::SN::Packet::Pingresp.new)
wait_for_output_then_kill(cmd)
end
assert_equal(["PINGRESP: len=2"], @cmd_result)
end
def test_receive_disconnect
@port = random_port
@cmd_result = run_cmd(
'mqtt-sn-dump',
['-a', '-p', @port]
) do |cmd|
publish_packet(@port, MQTT::SN::Packet::Disconnect.new)
wait_for_output_then_kill(cmd)
end
assert_equal(["DISCONNECT: len=2 duration=0"], @cmd_result)
end
def test_receive_unknown
@port = random_port
@cmd_result = run_cmd(
'mqtt-sn-dump',
['-a', '-p', @port]
) do |cmd|
publish_packet(@port, "\x03\xFF\x00")
wait_for_output_then_kill(cmd)
end
assert_equal(["UNKNOWN: len=3"], @cmd_result)
end
def test_receive_invalid_length
@port = random_port
@cmd_result = run_cmd(
'mqtt-sn-dump',
['-a', '-p', @port]
) do |cmd|
publish_packet(@port, "\x00\x00\x00\x00")
wait_for_output_then_kill(cmd)
end
assert_match(/WARN Packet length header is not valid/, @cmd_result[0])
end
end
================================================
FILE: test/mqtt-sn-pub-test.rb
================================================
$:.unshift(File.dirname(__FILE__))
require 'test_helper'
class MqttSnPubTest < Minitest::Test
def test_usage
@cmd_result = run_cmd('mqtt-sn-pub', '-?')
assert_match(/^Usage: mqtt-sn-pub/, @cmd_result[0])
end
def test_no_arguments
@cmd_result = run_cmd('mqtt-sn-pub')
assert_match(/^Usage: mqtt-sn-pub/, @cmd_result[0])
end
def test_default_client_id
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Connect) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-T' => 10,
'-m' => 'message',
'-p' => fs.port,
'-h' => fs.address
)
end
end
assert_empty(@cmd_result)
assert_match(/^mqtt-sn-tools-(\d+)$/, @packet.client_id)
assert_equal(10, @packet.keep_alive)
assert_equal(true, @packet.clean_session)
end
def test_custom_client_id
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Connect) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-i' => 'test_custom_client_id',
'-T' => 10,
'-m' => 'message',
'-p' => fs.port,
'-h' => fs.address
)
end
end
assert_empty(@cmd_result)
assert_equal('test_custom_client_id', @packet.client_id)
assert_equal(10, @packet.keep_alive)
assert_equal(true, @packet.clean_session)
end
def test_23char_client_id
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Connect) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-i' => 'ABCDEFGHIJKLMNOPQRSTUVW',
'-T' => 10,
'-m' => 'message',
'-p' => fs.port,
'-h' => fs.address
)
end
end
assert_empty(@cmd_result)
assert_equal('ABCDEFGHIJKLMNOPQRSTUVW', @packet.client_id)
assert_equal(10, @packet.keep_alive)
assert_equal(true, @packet.clean_session)
end
def test_connack_congestion
fake_server do |fs|
def fs.handle_connect(packet)
MQTT::SN::Packet::Connack.new(:return_code => 0x01)
end
fs.wait_for_packet(MQTT::SN::Packet::Connect) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-T' => 10,
'-m' => 'message',
'-p' => fs.port,
'-h' => fs.address
)
end
end
assert_match(/CONNECT error: Rejected: congestion/, @cmd_result[0])
end
def test_no_connack
fake_server do |fs|
def fs.handle_connect(packet)
MQTT::SN::Packet::Disconnect.new
end
fs.wait_for_packet(MQTT::SN::Packet::Connect) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-T' => 10,
'-m' => 'message',
'-p' => fs.port,
'-h' => fs.address
)
end
end
assert_match(/ERROR Was expecting CONNACK packet but received: DISCONNECT/, @cmd_result[0])
end
def test_too_long_client_id
fake_server do |fs|
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-i' => 'C' * 255,
'-T' => 10,
'-m' => 'message',
'-p' => fs.port,
'-h' => fs.address
)
end
assert_match(/ERROR Client id is too long/, @cmd_result[0])
end
def test_publish_qos_n1
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Publish) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-q' => -1,
'-T' => 10,
'-m' => 'test_publish_qos_n1',
'-p' => fs.port,
'-h' => fs.address
)
end
end
assert_empty(@cmd_result)
assert_equal(10, @packet.topic_id)
assert_equal(:predefined, @packet.topic_id_type)
assert_equal('test_publish_qos_n1', @packet.data)
assert_equal(-1, @packet.qos)
assert_equal(false, @packet.retain)
assert_equal(0, @packet.id)
end
def test_publish_debug
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Publish) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
['-d',
'-t', 'topic',
'-m', 'test_publish_qos_0_debug',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG Debug level is: 1/, @cmd_result)
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG Sending CONNECT packet/, @cmd_result)
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG waiting for packet/, @cmd_result)
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG CONNACK return code: 0x00/, @cmd_result)
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG Sending PUBLISH packet/, @cmd_result)
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG Sending DISCONNECT packet/, @cmd_result)
end
def test_publish_debug_2
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Publish) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
['-d', '-d',
'-i', 'fixed_client_id',
'-t', 'topic',
'-m', 'test_publish_qos_0_debug',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG Debug level is: 2/, @cmd_result)
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG Sending CONNECT packet/, @cmd_result)
assert_includes_match(/Sending 21 bytes\. Type=CONNECT on Socket: 3/, @cmd_result)
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG waiting for packet/, @cmd_result)
assert_includes_match(/Received 3 bytes from 127.0.0.1\:\d+. Type=CONNACK on Socket/, @cmd_result)
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG CONNACK return code: 0x00/, @cmd_result)
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG Sending PUBLISH packet/, @cmd_result)
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG Sending DISCONNECT packet/, @cmd_result)
end
def test_publish_from_file
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Publish) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
['-t', 'topic',
'-f', 'test.txt',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_empty(@cmd_result)
assert_equal(1, @packet.topic_id)
assert_equal(:normal, @packet.topic_id_type)
assert_equal('The is the contents of test.txt', @packet.data)
assert_equal(0, @packet.qos)
end
def test_publish_from_binary_file
## Test for a file that contains null bytes
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Publish) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
['-t', 'topic',
'-f', 'test.bin',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_empty(@cmd_result)
assert_equal(1, @packet.topic_id)
assert_equal(:normal, @packet.topic_id_type)
assert_equal(12, @packet.data.length)
assert_equal("\x01\x02\x03\x04\x05\x00Hello\x00", @packet.data)
assert_equal(0, @packet.qos)
end
def test_publish_from_file_too_big
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Publish) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
['-t', 'topic',
'-f', 'test_big.txt',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_match(/WARN Input file is longer than the maximum message size/, @cmd_result[0])
assert_equal(1, @packet.topic_id)
assert_equal(:normal, @packet.topic_id_type)
assert_equal(248, @packet.data.length)
assert_equal(0, @packet.qos)
end
def test_publish_from_file_hyphen
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Publish) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
['-t', 'topic',
'-f', '-',
'-p', fs.port,
'-h', fs.address],
'Message from file -'
)
end
end
assert_empty(@cmd_result)
assert_equal('Message from file -', @packet.data)
end
def test_publish_from_stdin
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Publish) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
['-t', 'topic',
'-s',
'-p', fs.port,
'-h', fs.address],
'Message from STDIN'
)
end
end
assert_empty(@cmd_result)
assert_equal('Message from STDIN', @packet.data)
end
def test_publish_multiline_from_stdin
server = fake_server do |fs|
fs.wait_for_packet(MQTT::SN::Packet::Disconnect) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
['-t', 'topic',
'-l',
'-p', fs.port,
'-h', fs.address],
"Message 1\nMessage 2\nMessage 3\n"
)
end
end
publish_packets = server.packets_received.select do |packet|
packet.is_a?(MQTT::SN::Packet::Publish)
end
assert_empty(@cmd_result)
assert_equal(['Message 1', 'Message 2', 'Message 3'], publish_packets.map {|p| p.data})
assert_equal([1, 1, 1], publish_packets.map {|p| p.topic_id})
assert_equal([:normal, :normal, :normal], publish_packets.map {|p| p.topic_id_type})
assert_equal([0, 0, 0], publish_packets.map {|p| p.qos})
end
def test_publish_multiline_from_stdin_no_newline
server = fake_server do |fs|
fs.wait_for_packet(MQTT::SN::Packet::Disconnect) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
['-t', 'topic',
'-l',
'-p', fs.port,
'-h', fs.address],
"Message 1\nMessage 2"
)
end
end
publish_packets = server.packets_received.select do |packet|
packet.is_a?(MQTT::SN::Packet::Publish)
end
assert_includes_match(/Failed to find newline when reading message/, @cmd_result)
assert_equal(['Message 1'], publish_packets.map {|p| p.data})
assert_equal([1], publish_packets.map {|p| p.topic_id})
assert_equal([:normal], publish_packets.map {|p| p.topic_id_type})
assert_equal([0], publish_packets.map {|p| p.qos})
end
def test_publish_qos_0
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Publish) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-q' => 0,
'-t' => 'topic',
'-m' => 'test_publish_qos_0',
'-p' => fs.port,
'-h' => fs.address
)
end
end
assert_empty(@cmd_result)
assert_equal(1, @packet.topic_id)
assert_equal(:normal, @packet.topic_id_type)
assert_equal('test_publish_qos_0', @packet.data)
assert_equal(0, @packet.qos)
assert_equal(false, @packet.retain)
assert_equal(0, @packet.id)
end
def test_publish_qos_0_short
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Publish) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-q' => 0,
'-t' => 'TT',
'-m' => 'test_publish_qos_0_short',
'-p' => fs.port,
'-h' => fs.address
)
end
end
assert_empty(@cmd_result)
assert_equal('TT', @packet.topic_id)
assert_equal(:short, @packet.topic_id_type)
assert_equal('test_publish_qos_0_short', @packet.data)
assert_equal(0, @packet.qos)
assert_equal(false, @packet.retain)
assert_equal(0, @packet.id)
end
def test_publish_qos_0_predefined
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Publish) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-q' => 0,
'-T' => 127,
'-m' => 'test_publish_qos_0_predefined',
'-p' => fs.port,
'-h' => fs.address
)
end
end
assert_empty(@cmd_result)
assert_equal(127, @packet.topic_id)
assert_equal(:predefined, @packet.topic_id_type)
assert_equal('test_publish_qos_0_predefined', @packet.data)
assert_equal(0, @packet.qos)
assert_equal(false, @packet.retain)
assert_equal(0, @packet.id)
end
def test_publish_qos_0_retained
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Publish) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
['-r',
'-t', 'topic',
'-m', 'test_publish_retained',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_empty(@cmd_result)
assert_equal(1, @packet.topic_id)
assert_equal(:normal, @packet.topic_id_type)
assert_equal('test_publish_retained', @packet.data)
assert_equal(0, @packet.qos)
assert_equal(true, @packet.retain)
assert_equal(0, @packet.id)
end
def test_publish_qos_0_empty
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Publish) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
['-r', '-n',
'-t', 'topic',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_empty(@cmd_result)
assert_equal(1, @packet.topic_id)
assert_equal(:normal, @packet.topic_id_type)
assert_equal('', @packet.data)
assert_equal(0, @packet.qos)
assert_equal(true, @packet.retain)
assert_equal(0, @packet.id)
end
def test_publish_qos_1
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Publish) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-q' => 1,
'-t' => 'topic',
'-m' => 'test_publish_qos_1',
'-p' => fs.port,
'-h' => fs.address
)
end
end
assert_empty(@cmd_result)
assert_equal(1, @packet.topic_id)
assert_equal(:normal, @packet.topic_id_type)
assert_equal('test_publish_qos_1', @packet.data)
assert_equal(1, @packet.qos)
assert_equal(false, @packet.retain)
assert_equal(2, @packet.id) # REGISTER for topic ID has id 1
end
def test_publish_qos_1_puback_timeout
fake_server do |fs|
def fs.handle_publish(packet)
nil
end
@packet = fs.wait_for_packet(MQTT::SN::Packet::Publish) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-q' => 1,
'-d' => '',
'-k' => 2,
'-t' => 'topic',
'-m' => 'test_publish_qos_1',
'-p' => fs.port,
'-h' => fs.address
)
end
end
assert_includes_match(/Timed out while waiting for a PUBACK from gateway/, @cmd_result)
end
def test_publish_ipv6
unless have_ipv6?
skip("IPv6 is not available on this system")
end
server = fake_server(nil, '::1') do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Publish) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
['-d', '-d',
'-t', 'topic',
'-m', 'test',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_includes_match(/Received 3 bytes from ::1:#{server.port}/, @cmd_result)
assert_equal('test', @packet.data)
end
def test_invalid_qos
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-q' => '2',
'-t' => 'topic',
'-m' => 'message'
)
assert_match(/Only QoS level 0, 1 or -1 is supported/, @cmd_result[0])
end
def test_payload_too_big
fake_server do |fs|
@cmd_result = run_cmd(
'mqtt-sn-pub',
['-t', 'topic',
'-m', 'm' * 255,
'-p', fs.port,
'-h', fs.address]
)
end
assert_match(/Payload is too big/, @cmd_result[0])
end
def test_both_topic_name_and_id
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-t' => 'topic_name',
'-T' => 10,
'-m' => 'message'
)
assert_match(/Please provide either a topic id or a topic name, not both/, @cmd_result[0])
end
def test_both_message_and_file
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-t' => 'topic_name',
'-m' => 'message',
'-f' => '/dev/zero'
)
assert_match(/Please provide either message data or a message file, not both/, @cmd_result[0])
end
def test_file_doesnt_exist
fake_server do |fs|
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-t' => 'topic_name',
'-f' => '/doesnt/exist',
'-p' => fs.port,
'-h' => fs.address
) do |cmd|
wait_for_output_then_kill(cmd)
end
end
assert_match(/Failed to open message file/, @cmd_result[0])
end
def test_both_qos_n1_topic_name
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-q' => -1,
'-t' => 'topic_name',
'-m' => 'message'
)
assert_match(/Either a pre-defined topic id or a short topic name must be given for QoS -1/, @cmd_result[0])
end
def test_topic_name_too_long
fake_server do |fs|
@cmd_result = run_cmd(
'mqtt-sn-pub',
['-t', 'x' * 255,
'-m', 'message',
'-p', fs.port,
'-h', fs.address]
) do |cmd|
wait_for_output_then_kill(cmd)
end
end
assert_match(/ERROR Topic name is too long/, @cmd_result[0])
end
def test_register_invalid_topic_name
fake_server do |fs|
def fs.handle_register(packet)
MQTT::SN::Packet::Regack.new(
:id => packet.id,
:return_code => 2
)
end
@cmd_result = run_cmd(
'mqtt-sn-pub',
['-t', '/!invalid%topic"name',
'-m', 'message',
'-p', fs.port,
'-h', fs.address]
) do |cmd|
wait_for_output_then_kill(cmd)
end
end
assert_match(/ERROR REGISTER failed: Rejected: invalid topic ID/, @cmd_result[0])
end
def test_connect_fail
skip("Unable to get this test to run reliably")
# @cmd_result = run_cmd(
# 'mqtt-sn-pub',
# ['-t', 'topic',
# '-m', 'message',
# '-h', '0.0.0.1',
# '-p', '29567']
# ) do |cmd|
# wait_for_output_then_kill(cmd)
# end
#
# assert_match(/ERROR Could not connect to remote host/, @cmd_result[0])
end
def test_hostname_lookup_fail
@cmd_result = run_cmd(
'mqtt-sn-pub',
['-t', 'topic',
'-m', 'message',
'-p', '29567',
'-h', '!(invalid)']
) do |cmd|
wait_for_output_then_kill(cmd)
end
assert_match(/nodename nor servname provided, or not known|Name or service not known/, @cmd_result[0])
end
def test_disconnect_after_publish
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Disconnect) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-T' => 10,
'-m' => 'message',
'-p' => fs.port,
'-h' => fs.address
)
end
end
assert_empty(@cmd_result)
assert_kind_of(MQTT::SN::Packet::Disconnect, @packet)
assert_nil(@packet.duration)
end
def test_disconnect_after_publish_with_sleep
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Disconnect) do
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-e' => 3600,
'-T' => 10,
'-m' => 'message',
'-p' => fs.port,
'-h' => fs.address
)
end
end
assert_empty(@cmd_result)
assert_equal(MQTT::SN::Packet::Disconnect, @packet.class)
assert_equal(3600, @packet.duration)
end
def test_disconnect_duration_warning
fake_server do |fs|
def fs.handle_disconnect(packet)
MQTT::SN::Packet::Disconnect.new(
:duration => 10
)
end
@cmd_result = run_cmd(
'mqtt-sn-pub',
'-T' => 10,
'-m' => 'message',
'-p' => fs.port,
'-h' => fs.address
)
end
assert_match(/DISCONNECT warning. Gateway returned duration in disconnect packet/, @cmd_result[0])
end
end
================================================
FILE: test/mqtt-sn-serial-bridge-test.rb
================================================
$:.unshift(File.dirname(__FILE__))
require 'test_helper'
class MqttSnSerialBridgeTest < Minitest::Test
def test_usage
@cmd_result = run_cmd('mqtt-sn-serial-bridge', '-?')
assert_match(/^Usage: mqtt-sn-serial-bridge/, @cmd_result[0])
end
end
================================================
FILE: test/mqtt-sn-sub-test.rb
================================================
$:.unshift(File.dirname(__FILE__))
require 'test_helper'
class MqttSnSubTest < Minitest::Test
def test_usage
@cmd_result = run_cmd('mqtt-sn-sub', '-?')
assert_match(/^Usage: mqtt-sn-sub/, @cmd_result[0])
end
def test_no_arguments
@cmd_result = run_cmd('mqtt-sn-sub')
assert_match(/^Usage: mqtt-sn-sub/, @cmd_result[0])
end
def test_default_client_id
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Connect) do
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-1',
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_equal(["Message for test"], @cmd_result)
assert_match(/^mqtt-sn-tools-(\d+)$/, @packet.client_id)
assert_equal(10, @packet.keep_alive)
assert_equal(true, @packet.clean_session)
end
def test_custom_client_id
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Connect) do
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-1',
'-i', 'test_custom_client_id',
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_equal(["Message for test"], @cmd_result)
assert_equal('test_custom_client_id', @packet.client_id)
assert_equal(10, @packet.keep_alive)
assert_equal(true, @packet.clean_session)
end
def test_custom_keep_alive
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Connect) do
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-1',
'-k', 5,
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_equal(["Message for test"], @cmd_result)
assert_match(/^mqtt-sn-tools/, @packet.client_id)
assert_equal(5, @packet.keep_alive)
assert_equal(true, @packet.clean_session)
end
def test_connack_timeout
fake_server do |fs|
def fs.handle_connect(packet)
nil
end
fs.wait_for_packet(MQTT::SN::Packet::Connect) do
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-1',
'-d',
'-k', 2,
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_includes_match(/Timed out waiting for packet/, @cmd_result)
end
def test_subscribe_one
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Subscribe) do
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-1',
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_equal(["Message for test"], @cmd_result)
assert_equal('test', @packet.topic_name)
assert_equal(:normal, @packet.topic_id_type)
assert_equal(0, @packet.qos)
end
def test_subscribe_one_debug
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Subscribe) do
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-d', '-1',
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG Debug level is: 1/, @cmd_result)
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG Sending CONNECT packet/, @cmd_result)
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG waiting for packet/, @cmd_result)
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG CONNACK return code: 0x00/, @cmd_result)
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG Sending SUBSCRIBE packet/, @cmd_result)
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG SUBACK return code: 0x00/, @cmd_result)
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG Sending DISCONNECT packet/, @cmd_result)
end
def test_subscribe_one_verbose
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Subscribe) do
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-1', '-v',
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_equal(["test: Message for test"], @cmd_result)
assert_equal('test', @packet.topic_name)
assert_equal(:normal, @packet.topic_id_type)
assert_equal(0, @packet.qos)
end
def test_subscribe_one_verbose_time
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Subscribe) do
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-1', '-V',
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_equal(1, @cmd_result.count)
assert_match(/\d{4}\-\d{2}\-\d{2} \d{2}\:\d{2}\:\d{2} test: Message for test/, @cmd_result[0])
assert_equal('test', @packet.topic_name)
assert_equal(:normal, @packet.topic_id_type)
assert_equal(0, @packet.qos)
end
def test_subscribe_one_short
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Subscribe) do
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-1', '-v',
'-t', 'tt',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_equal(["tt: Message for tt"], @cmd_result)
assert_equal('tt', @packet.topic_name)
assert_equal(:short, @packet.topic_id_type)
assert_equal(0, @packet.qos)
end
def test_subscribe_one_predefined
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Subscribe) do
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-1', '-v',
'-T', 17,
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_equal(["0011: Message for #17"], @cmd_result)
assert_nil(@packet.topic_name)
assert_equal(17, @packet.topic_id)
assert_equal(:predefined, @packet.topic_id_type)
assert_equal(0, @packet.qos)
end
def test_subscribe_then_int
fake_server do |fs|
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-v',
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
) do |cmd|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Subscribe)
wait_for_output_then_kill(cmd, 'INT')
end
end
assert_equal(["test: Message for test"], @cmd_result)
assert_equal('test', @packet.topic_name)
assert_equal(:normal, @packet.topic_id_type)
assert_equal(0, @packet.qos)
end
def test_subscribe_then_term
fake_server do |fs|
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-v',
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
) do |cmd|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Subscribe)
wait_for_output_then_kill(cmd, 'TERM')
end
end
assert_equal(["test: Message for test"], @cmd_result)
assert_equal('test', @packet.topic_name)
assert_equal(:normal, @packet.topic_id_type)
assert_equal(0, @packet.qos)
end
def test_subscribe_then_hup
fake_server do |fs|
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-v',
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
) do |cmd|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Subscribe)
wait_for_output_then_kill(cmd, 'HUP')
end
end
assert_equal(["test: Message for test"], @cmd_result)
assert_equal('test', @packet.topic_name)
assert_equal(:normal, @packet.topic_id_type)
assert_equal(0, @packet.qos)
end
def test_subscribe_two_topic_names
fake_server do |fs|
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-v',
'-q', 1,
'-t', 'test1',
'-t', 'test2',
'-p', fs.port,
'-h', fs.address]
) do |cmd|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Puback)
wait_for_output_then_kill(cmd, 'INT')
end
end
assert_equal(2, @cmd_result.length)
assert_equal("test1: Message for test1", @cmd_result[0])
assert_equal("test2: Message for test2", @cmd_result[1])
end
def test_subscribe_multiple_topics
fake_server do |fs|
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-v',
'-q', 1,
'-t', 'name',
'-t', 'TT',
'-T', 0x10,
'-p', fs.port,
'-h', fs.address]
) do |cmd|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Puback)
wait_for_output_then_kill(cmd, 'INT')
end
end
assert_equal(3, @cmd_result.length)
assert_equal("name: Message for name", @cmd_result[0])
assert_equal("TT: Message for TT", @cmd_result[1])
assert_equal("0010: Message for #16", @cmd_result[2])
end
def test_subscribe_thirty_topics
topics = (1..30).map { |t| ['-t', "topic#{t}"] }
fake_server do |fs|
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-v',
'-q', 1,
topics,
'-p', fs.port,
'-h', fs.address].compact
) do |cmd|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Puback)
wait_for_output_then_kill(cmd, 'INT')
end
end
assert_equal(30, @cmd_result.length)
assert_equal("topic1: Message for topic1", @cmd_result[0])
assert_equal("topic30: Message for topic30", @cmd_result[29])
end
def test_subscribe_invalid_topic_id
fake_server do |fs|
def fs.handle_subscribe(packet)
MQTT::SN::Packet::Suback.new(
:id => packet.id,
:topic_id => 0,
:topic_id_type => packet.topic_id_type,
:return_code => 0x02
)
end
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-v',
'-T', 123,
'-p', fs.port,
'-h', fs.address]
) do |cmd|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Subscribe)
wait_for_output_then_kill(cmd, 'HUP')
end
end
assert_includes_match(/ERROR SUBSCRIBE error: Rejected: invalid topic ID/, @cmd_result)
end
def test_subscribe_one_qos1
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Puback) do
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-1',
'-q', 1,
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_equal(["Message for test"], @cmd_result)
assert_equal(1, @packet.topic_id)
assert_equal(0, @packet.id)
end
def test_subscribe_invalid_connack_packet
fake_server do |fs|
def fs.handle_connect(packet)
"\x00\x05\x00"
end
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-v',
'-T', 123,
'-p', fs.port,
'-h', fs.address]
) do |cmd|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Connect)
wait_for_output_then_kill(cmd)
end
end
assert_includes_match(/Packet length header is not valid/, @cmd_result)
end
def test_subscribe_incorrect_connack_packet_length
fake_server do |fs|
def fs.handle_connect(packet)
"\x04\x05\x00"
end
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-v',
'-T', 123,
'-p', fs.port,
'-h', fs.address]
) do |cmd|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Connect)
wait_for_output_then_kill(cmd)
end
end
assert_includes_match(/Read 3 bytes but packet length is 4 bytes/, @cmd_result)
end
def test_subscribe_id_mismatch
fake_server do |fs|
def fs.handle_subscribe(packet)
MQTT::SN::Packet::Suback.new(
:id => 222,
:topic_id => 0,
:topic_id_type => packet.topic_id_type,
:return_code => 0x00
)
end
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-v', '-d',
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
) do |cmd|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Subscribe)
wait_for_output_then_kill(cmd)
end
end
assert_includes_match(/WARN Message id in SUBACK does not equal message id sent/, @cmd_result)
assert_includes_match(/DEBUG Expecting: 1/, @cmd_result)
assert_includes_match(/DEBUG Actual: 222/, @cmd_result)
end
def test_subscribe_then_interupt_debug
fake_server do |fs|
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-v', '-d',
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
) do |cmd|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Subscribe)
wait_for_output_then_kill(cmd)
end
end
assert_includes_match(/DEBUG Debug level is: 1/, @cmd_result)
assert_includes_match(/DEBUG Got interrupt signal/, @cmd_result)
assert_includes_match(/^test: Message for test$/, @cmd_result)
assert_equal('test', @packet.topic_name)
assert_equal(:normal, @packet.topic_id_type)
assert_equal(0, @packet.qos)
end
def test_subscribe_no_clean_session
@fs = fake_server do |fs|
def fs.handle_connect(packet)
[
MQTT::SN::Packet::Connack.new(:return_code => 0x00),
MQTT::SN::Packet::Register.new(:topic_id => 5, :topic_name => 'old_topic'),
MQTT::SN::Packet::Publish.new(:topic_id => 5, :data => 'old_msg')
]
end
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-c', '-v',
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
) do |cmd|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Connect)
wait_for_output_then_kill(cmd)
end
end
assert_equal(["old_topic: old_msg", "test: Message for test"], @cmd_result)
assert_match(/^mqtt-sn-tools/, @packet.client_id)
assert_equal(10, @packet.keep_alive)
assert_equal(false, @packet.clean_session)
end
def test_register_invalid_topic_id
@fs = fake_server do |fs|
def fs.handle_connect(packet)
[
MQTT::SN::Packet::Connack.new(:return_code => 0x00),
MQTT::SN::Packet::Register.new(:topic_id => 0, :topic_name => 'old_topic')
]
end
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-c', '-v',
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
) do |cmd|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Connect)
wait_for_output_then_kill(cmd)
end
end
assert_match(/ERROR Attempted to register invalid topic id: 0x0000/, @cmd_result[0])
assert_match(/test: Message for test/, @cmd_result[1])
end
def test_register_invalid_topic_name
@fs = fake_server do |fs|
def fs.handle_connect(packet)
[
MQTT::SN::Packet::Connack.new(:return_code => 0x00),
MQTT::SN::Packet::Register.new(:topic_id => 5, :topic_name => ''),
MQTT::SN::Packet::Publish.new(:topic_id => 5, :data => 'old_msg')
]
end
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-c', '-v',
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
) do |cmd|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Connect)
wait_for_output_then_kill(cmd)
end
end
assert_match(/ERROR Attempted to register invalid topic name/, @cmd_result[0])
assert_match(/WARN Failed to lookup topic id: 0x0005/, @cmd_result[1])
assert_match(/test: Message for test/, @cmd_result[2])
end
def test_recieve_non_registered_topic_id
@fs = fake_server do |fs|
def fs.handle_connect(packet)
[
MQTT::SN::Packet::Connack.new(:return_code => 0x00),
MQTT::SN::Packet::Publish.new(:topic_id => 5, :data => 'not registered')
]
end
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-c', '-v',
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
) do |cmd|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Connect)
wait_for_output_then_kill(cmd)
end
end
assert_match(/Failed to lookup topic id: 0x0005/, @cmd_result[0])
assert_match(/test: Message for test/, @cmd_result[1])
end
def test_packet_too_long
fake_server do |fs|
def fs.handle_subscribe(packet)
super(packet, 'x' * 256)
end
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-v', '-d',
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
) do |cmd|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Subscribe)
wait_for_output_then_kill(cmd)
end
end
assert_includes_match(/[\d\-]+ [\d\:]+ DEBUG Received 265 bytes from/, @cmd_result)
assert_includes_match(/[\d\-]+ [\d\:]+ WARN Packet received is longer than this tool can handle/, @cmd_result)
end
def test_disconnect_after_recieve
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Disconnect) do
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-1',
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_equal(["Message for test"], @cmd_result)
assert_kind_of(MQTT::SN::Packet::Disconnect, @packet)
assert_nil(@packet.duration)
end
def test_disconnect_after_recieve_with_sleep
fake_server do |fs|
@packet = fs.wait_for_packet(MQTT::SN::Packet::Disconnect) do
@cmd_result = run_cmd(
'mqtt-sn-sub',
['-1',
'-e', 3600,
'-t', 'test',
'-p', fs.port,
'-h', fs.address]
)
end
end
assert_equal(["Message for test"], @cmd_result)
assert_equal(MQTT::SN::Packet::Disconnect, @packet.class)
assert_equal(3600, @packet.duration)
end
end
================================================
FILE: test/test.txt
================================================
The is the contents of test.txt
================================================
FILE: test/test_big.txt
================================================
00000000 f3 a7 4f ee 23 58 28 e8 48 df bc 96 42 6a 45 e6 |..O.#X(.H...BjE.|
00000010 8e ca 44 51 7d 3a 08 80 1f b8 58 fc 7c ca e6 37 |..DQ}:....X.|..7|
00000020 ac 71 28 aa d8 a7 b8 77 76 dc 5f 80 f0 c9 1e 46 |.q(....wv._....F|
00000030 5b 5b 89 5e 6e a5 22 f1 13 cd 1f ba 1a 5f 5b 5e |[[.^n."......_[^|
00000040 73 34 90 b3 ad c7 72 61 24 32 bb c1 21 f4 a7 8a |s4....ra$2..!...|
00000050 7f 15 68 d7 61 9c 68 fd 80 ef 48 ba 25 a6 8b f3 |..h.a.h...H.%...|
00000060 5d a9 ea 86 ac da 7b 21 82 be a7 3d 2c f7 f4 dd |].....{!...=,...|
00000070 52 79 53 4c e6 6f 35 32 80 92 92 7a 48 f8 98 ca |RySL.o52...zH...|
00000080 a2 79 df f5 4b c6 7c 43 5b c2 da f3 10 2e da 53 |.y..K.|C[......S|
00000090 09 fb 96 bb 9f a7 48 8e 27 c1 1d 51 32 59 a3 2e |......H.'..Q2Y..|
000000a0 5c de 4a 4d d2 8d a8 d7 6b 00 8d 63 a9 3d d1 1c |\.JM....k..c.=..|
000000b0 73 78 db 94 94 82 f6 f6 2e e0 16 ca e9 52 2b 89 |sx...........R+.|
000000c0 b0 8d 21 22 e1 a7 2a 26 12 9a c5 97 25 da c9 17 |..!"..*&....%...|
000000d0 ad 96 55 f2 a1 75 0d 8a be ed 13 41 51 74 c2 10 |..U..u.....AQt..|
000000e0 2e 2e cf 49 26 00 36 0a c4 ce 17 46 10 06 e9 f9 |...I&.6....F....|
000000f0 ad 6b 44 4e 82 8c 8c 79 fc 27 26 06 f9 f1 24 39 |.kDN...y.'&...$9|
00000100 e0 62 3a 3a 7d d0 78 cc 95 24 de f9 c2 b3 16 60 |.b::}.x..$.....`|
================================================
FILE: test/test_helper.rb
================================================
$:.unshift(File.join(File.dirname(__FILE__),'..','lib'))
require 'rubygems'
require 'bundler'
Bundler.require(:default)
require 'minitest/autorun'
require 'fake_server'
CMD_DIR = File.realpath('../..', __FILE__)
def run_cmd(name, args=[], data=nil)
args = [args] unless args.respond_to?(:flatten)
cmd = [CMD_DIR + '/' + name] + args.flatten.map {|i| i.to_s}
IO.popen([*cmd, :err => [:child, :out]], 'r+b') do |io|
if block_given?
yield(io)
end
unless data.nil?
io.write data
end
io.close_write
io.readlines.map {|line| line.strip}
end
end
def wait_for_output_then_kill(io, signal='INT', timeout=0.5)
IO.select([io], nil, nil, timeout)
sleep(0.1)
Process.kill(signal, io.pid)
end
def random_port
10000 + ((rand(10000) + Time.now.to_i) % 10000)
end
def fake_server(*args)
fs = MQTT::SN::FakeServer.new(*args)
fs.logger.level = Logger::WARN
fs.start
fs.wait_for_port_number
yield(fs)
fs.stop
return fs
end
def have_ipv6?
Socket.ip_address_list.any? { |addr| addr.ipv6? }
end
module Minitest::Assertions
def assert_includes_match(regexp, array, msg=nil)
msg = message(msg) { "Expected #{mu_pp(array)} to match #{mu_pp(regexp)}" }
array.each do |item|
if item =~ regexp
assert true, msg
return
end
end
assert false, msg
end
end
gitextract_cmeey4ae/
├── .astylerc
├── .gitignore
├── .travis.yml
├── LICENSE.md
├── Makefile
├── NEWS.md
├── README.md
├── mqtt-sn-dump.c
├── mqtt-sn-pub.c
├── mqtt-sn-serial-bridge.c
├── mqtt-sn-sub.c
├── mqtt-sn.c
├── mqtt-sn.h
└── test/
├── .ruby-version
├── Gemfile
├── Rakefile
├── fake_server.rb
├── mqtt-sn-dump-test.rb
├── mqtt-sn-pub-test.rb
├── mqtt-sn-serial-bridge-test.rb
├── mqtt-sn-sub-test.rb
├── test.txt
├── test_big.txt
└── test_helper.rb
SYMBOL INDEX (196 symbols across 12 files)
FILE: mqtt-sn-dump.c
function usage (line 45) | static void usage()
function parse_opts (line 56) | static void parse_opts(int argc, char** argv)
function bind_udp_socket (line 86) | static int bind_udp_socket(const char* port_str)
function termination_handler (line 111) | static void termination_handler (int signum)
function main (line 129) | int main(int argc, char* argv[])
FILE: mqtt-sn-pub.c
function usage (line 56) | static void usage()
function parse_opts (line 81) | static void parse_opts(int argc, char** argv)
function publish_file (line 208) | static void publish_file(int sock, const char* filename)
function main (line 258) | int main(int argc, char* argv[])
FILE: mqtt-sn-serial-bridge.c
function speed_t (line 57) | static speed_t baud_lookup(int baud)
function usage (line 104) | static void usage()
function parse_opts (line 118) | static void parse_opts(int argc, char** argv)
function serial_open (line 176) | static int serial_open(const char* device_path)
function serial_write_packet (line 281) | void serial_write_packet(int fd, const void* packet)
function termination_handler (line 291) | static void termination_handler (int signum)
function main (line 310) | int main(int argc, char* argv[])
FILE: mqtt-sn-sub.c
function usage (line 66) | static void usage()
function parse_opts (line 89) | static void parse_opts(int argc, char** argv)
function termination_handler (line 193) | static void termination_handler (int signum)
function main (line 211) | int main(int argc, char* argv[])
FILE: mqtt-sn.c
function mqtt_sn_set_debug (line 60) | void mqtt_sn_set_debug(uint8_t value)
function mqtt_sn_set_verbose (line 66) | void mqtt_sn_set_verbose(uint8_t value)
function mqtt_sn_set_timeout (line 72) | void mqtt_sn_set_timeout(uint8_t value)
function mqtt_sn_create_socket (line 82) | int mqtt_sn_create_socket(const char* host, const char* port, uint16_t s...
function mqtt_sn_send_packet (line 174) | void mqtt_sn_send_packet(int sock, const void* data)
function mqtt_sn_send_frwdencap_packet (line 198) | void mqtt_sn_send_frwdencap_packet(int sock, const void* data, const uin...
function mqtt_sn_validate_packet (line 223) | uint8_t mqtt_sn_validate_packet(const void *packet, size_t length)
type sockaddr_storage (line 266) | struct sockaddr_storage
type sockaddr (line 277) | struct sockaddr
type sockaddr_in (line 294) | struct sockaddr_in
type sockaddr_in (line 294) | struct sockaddr_in
type sockaddr_in (line 295) | struct sockaddr_in
type sockaddr_in6 (line 298) | struct sockaddr_in6
type sockaddr_in6 (line 298) | struct sockaddr_in6
type sockaddr_in6 (line 299) | struct sockaddr_in6
function mqtt_sn_send_connect (line 334) | void mqtt_sn_send_connect(int sock, const char* client_id, uint16_t keep...
function mqtt_sn_send_register (line 370) | void mqtt_sn_send_register(int sock, const char* topic_name)
function mqtt_sn_send_regack (line 392) | void mqtt_sn_send_regack(int sock, int topic_id, int mesage_id)
function mqtt_sn_get_qos_flag (line 408) | static uint8_t mqtt_sn_get_qos_flag(int8_t qos)
function mqtt_sn_send_publish (line 424) | void mqtt_sn_send_publish(int sock, uint16_t topic_id, uint8_t topic_typ...
function mqtt_sn_send_puback (line 463) | void mqtt_sn_send_puback(int sock, publish_packet_t* publish, uint8_t re...
function mqtt_sn_send_subscribe_topic_name (line 479) | void mqtt_sn_send_subscribe_topic_name(int sock, const char* topic_name,...
function mqtt_sn_send_subscribe_topic_id (line 503) | void mqtt_sn_send_subscribe_topic_id(int sock, uint16_t topic_id, uint8_...
function mqtt_sn_send_pingreq (line 521) | void mqtt_sn_send_pingreq(int sock)
function mqtt_sn_send_disconnect (line 533) | void mqtt_sn_send_disconnect(int sock, uint16_t duration)
function mqtt_sn_receive_disconnect (line 551) | void mqtt_sn_receive_disconnect(int sock)
function mqtt_sn_receive_connack (line 567) | void mqtt_sn_receive_connack(int sock)
function mqtt_sn_process_register (line 590) | static int mqtt_sn_process_register(int sock, const register_packet_t *p...
function mqtt_sn_register_topic (line 605) | void mqtt_sn_register_topic(int topic_id, const char* topic_name)
function mqtt_sn_receive_regack (line 662) | uint16_t mqtt_sn_receive_regack(int sock)
function mqtt_sn_dump_packet (line 693) | void mqtt_sn_dump_packet(char* packet)
function mqtt_sn_print_publish_packet (line 754) | void mqtt_sn_print_publish_packet(publish_packet_t* packet)
function mqtt_sn_receive_suback (line 790) | uint16_t mqtt_sn_receive_suback(int sock)
function mqtt_sn_select (line 823) | int mqtt_sn_select(int sock)
function mqtt_sn_cleanup (line 998) | void mqtt_sn_cleanup()
function mqtt_sn_enable_frwdencap (line 1013) | uint8_t mqtt_sn_enable_frwdencap()
function mqtt_sn_disable_frwdencap (line 1019) | uint8_t mqtt_sn_disable_frwdencap()
function mqtt_sn_set_frwdencap_parameters (line 1025) | void mqtt_sn_set_frwdencap_parameters(const uint8_t *wlnid, uint8_t wlni...
function frwdencap_packet_t (line 1032) | frwdencap_packet_t* mqtt_sn_create_frwdencap_packet(const void *data, si...
function mqtt_sn_log_msg (line 1082) | static void mqtt_sn_log_msg(const char* level, const char* format, va_li...
function mqtt_sn_log_debug (line 1096) | void mqtt_sn_log_debug(const char * format, ...)
function mqtt_sn_log_warn (line 1106) | void mqtt_sn_log_warn(const char * format, ...)
function mqtt_sn_log_err (line 1114) | void mqtt_sn_log_err(const char * format, ...)
FILE: mqtt-sn.h
type connect_packet_t (line 98) | typedef struct {
type connack_packet_t (line 107) | typedef struct {
type register_packet_t (line 113) | typedef struct {
type regack_packet_t (line 121) | typedef struct {
type publish_packet_t (line 129) | typedef struct __attribute__((packed)) {
type puback_packet_t (line 139) | typedef struct __attribute__((packed)) {
type subscribe_packet_t (line 148) | typedef struct __attribute__((packed)) {
type suback_packet_t (line 160) | typedef struct __attribute__((packed)) {
type disconnect_packet_t (line 170) | typedef struct {
type frwdencap_packet_t (line 176) | typedef struct __attribute__((packed)) {
type topic_map_t (line 185) | typedef struct topic_map {
FILE: test/fake_server.rb
class MQTT::SN::FakeServer (line 22) | class MQTT::SN::FakeServer
method initialize (line 31) | def initialize(port=0, bind_address='127.0.0.1')
method logger (line 38) | def logger
method logger= (line 42) | def logger=(logger)
method start (line 47) | def start
method stop (line 69) | def stop
method run (line 76) | def run
method wait_for_port_number (line 85) | def wait_for_port_number
method wait_for_packet (line 92) | def wait_for_packet(klass=nil, timeout=3)
method process_packet (line 120) | def process_packet(data)
method handle_connect (line 138) | def handle_connect(packet)
method handle_publish (line 142) | def handle_publish(packet)
method handle_pingreq (line 152) | def handle_pingreq(packet)
method handle_subscribe (line 156) | def handle_subscribe(packet, publish_data=nil)
method handle_disconnect (line 186) | def handle_disconnect(packet)
method handle_register (line 190) | def handle_register(packet)
method handle_puback (line 198) | def handle_puback(packet)
method handle_regack (line 202) | def handle_regack(packet)
FILE: test/mqtt-sn-dump-test.rb
class MqttSnDumpTest (line 5) | class MqttSnDumpTest < Minitest::Test
method test_usage (line 7) | def test_usage
method publish_packet (line 12) | def publish_packet(port, packet)
method publish_qos_n1_packet (line 21) | def publish_qos_n1_packet(port)
method test_receive_qos_n1 (line 32) | def test_receive_qos_n1
method test_receive_qos_n1_debug (line 45) | def test_receive_qos_n1_debug
method test_receive_qos_n1_verbose (line 60) | def test_receive_qos_n1_verbose
method test_receive_qos_n1_dump_all (line 73) | def test_receive_qos_n1_dump_all
method test_receive_qos_n1_term (line 86) | def test_receive_qos_n1_term
method test_receive_qos_n1_hup (line 99) | def test_receive_qos_n1_hup
method test_receive_connect (line 112) | def test_receive_connect
method test_receive_connack (line 130) | def test_receive_connack
method test_receive_connack_not_supported (line 147) | def test_receive_connack_not_supported
method test_receive_register (line 164) | def test_receive_register
method test_receive_regack (line 183) | def test_receive_regack
method test_receive_subscribe (line 202) | def test_receive_subscribe
method test_receive_suback (line 217) | def test_receive_suback
method test_receive_suback_unknown_error (line 236) | def test_receive_suback_unknown_error
method test_receive_pingreq (line 255) | def test_receive_pingreq
method test_receive_pingresp (line 268) | def test_receive_pingresp
method test_receive_disconnect (line 281) | def test_receive_disconnect
method test_receive_unknown (line 294) | def test_receive_unknown
method test_receive_invalid_length (line 307) | def test_receive_invalid_length
FILE: test/mqtt-sn-pub-test.rb
class MqttSnPubTest (line 5) | class MqttSnPubTest < Minitest::Test
method test_usage (line 7) | def test_usage
method test_no_arguments (line 12) | def test_no_arguments
method test_default_client_id (line 17) | def test_default_client_id
method test_custom_client_id (line 36) | def test_custom_client_id
method test_23char_client_id (line 56) | def test_23char_client_id
method test_connack_congestion (line 76) | def test_connack_congestion
method test_no_connack (line 96) | def test_no_connack
method test_too_long_client_id (line 116) | def test_too_long_client_id
method test_publish_qos_n1 (line 131) | def test_publish_qos_n1
method test_publish_debug (line 154) | def test_publish_debug
method test_publish_debug_2 (line 176) | def test_publish_debug_2
method test_publish_from_file (line 201) | def test_publish_from_file
method test_publish_from_binary_file (line 221) | def test_publish_from_binary_file
method test_publish_from_file_too_big (line 243) | def test_publish_from_file_too_big
method test_publish_from_file_hyphen (line 264) | def test_publish_from_file_hyphen
method test_publish_from_stdin (line 282) | def test_publish_from_stdin
method test_publish_multiline_from_stdin (line 300) | def test_publish_multiline_from_stdin
method test_publish_multiline_from_stdin_no_newline (line 325) | def test_publish_multiline_from_stdin_no_newline
method test_publish_qos_0 (line 350) | def test_publish_qos_0
method test_publish_qos_0_short (line 373) | def test_publish_qos_0_short
method test_publish_qos_0_predefined (line 396) | def test_publish_qos_0_predefined
method test_publish_qos_0_retained (line 419) | def test_publish_qos_0_retained
method test_publish_qos_0_empty (line 442) | def test_publish_qos_0_empty
method test_publish_qos_1 (line 464) | def test_publish_qos_1
method test_publish_qos_1_puback_timeout (line 487) | def test_publish_qos_1_puback_timeout
method test_publish_ipv6 (line 510) | def test_publish_ipv6
method test_invalid_qos (line 532) | def test_invalid_qos
method test_payload_too_big (line 542) | def test_payload_too_big
method test_both_topic_name_and_id (line 555) | def test_both_topic_name_and_id
method test_both_message_and_file (line 565) | def test_both_message_and_file
method test_file_doesnt_exist (line 575) | def test_file_doesnt_exist
method test_both_qos_n1_topic_name (line 590) | def test_both_qos_n1_topic_name
method test_topic_name_too_long (line 600) | def test_topic_name_too_long
method test_register_invalid_topic_name (line 615) | def test_register_invalid_topic_name
method test_connect_fail (line 638) | def test_connect_fail
method test_hostname_lookup_fail (line 653) | def test_hostname_lookup_fail
method test_disconnect_after_publish (line 667) | def test_disconnect_after_publish
method test_disconnect_after_publish_with_sleep (line 685) | def test_disconnect_after_publish_with_sleep
method test_disconnect_duration_warning (line 704) | def test_disconnect_duration_warning
FILE: test/mqtt-sn-serial-bridge-test.rb
class MqttSnSerialBridgeTest (line 5) | class MqttSnSerialBridgeTest < Minitest::Test
method test_usage (line 7) | def test_usage
FILE: test/mqtt-sn-sub-test.rb
class MqttSnSubTest (line 5) | class MqttSnSubTest < Minitest::Test
method test_usage (line 7) | def test_usage
method test_no_arguments (line 12) | def test_no_arguments
method test_default_client_id (line 17) | def test_default_client_id
method test_custom_client_id (line 36) | def test_custom_client_id
method test_custom_keep_alive (line 56) | def test_custom_keep_alive
method test_connack_timeout (line 76) | def test_connack_timeout
method test_subscribe_one (line 98) | def test_subscribe_one
method test_subscribe_one_debug (line 117) | def test_subscribe_one_debug
method test_subscribe_one_verbose (line 139) | def test_subscribe_one_verbose
method test_subscribe_one_verbose_time (line 158) | def test_subscribe_one_verbose_time
method test_subscribe_one_short (line 178) | def test_subscribe_one_short
method test_subscribe_one_predefined (line 197) | def test_subscribe_one_predefined
method test_subscribe_then_int (line 217) | def test_subscribe_then_int
method test_subscribe_then_term (line 237) | def test_subscribe_then_term
method test_subscribe_then_hup (line 257) | def test_subscribe_then_hup
method test_subscribe_two_topic_names (line 277) | def test_subscribe_two_topic_names
method test_subscribe_multiple_topics (line 298) | def test_subscribe_multiple_topics
method test_subscribe_thirty_topics (line 321) | def test_subscribe_thirty_topics
method test_subscribe_invalid_topic_id (line 343) | def test_subscribe_invalid_topic_id
method test_subscribe_one_qos1 (line 369) | def test_subscribe_one_qos1
method test_subscribe_invalid_connack_packet (line 388) | def test_subscribe_invalid_connack_packet
method test_subscribe_incorrect_connack_packet_length (line 409) | def test_subscribe_incorrect_connack_packet_length
method test_subscribe_id_mismatch (line 430) | def test_subscribe_id_mismatch
method test_subscribe_then_interupt_debug (line 458) | def test_subscribe_then_interupt_debug
method test_subscribe_no_clean_session (line 480) | def test_subscribe_no_clean_session
method test_register_invalid_topic_id (line 508) | def test_register_invalid_topic_id
method test_register_invalid_topic_name (line 533) | def test_register_invalid_topic_name
method test_recieve_non_registered_topic_id (line 560) | def test_recieve_non_registered_topic_id
method test_packet_too_long (line 585) | def test_packet_too_long
method test_disconnect_after_recieve (line 607) | def test_disconnect_after_recieve
method test_disconnect_after_recieve_with_sleep (line 625) | def test_disconnect_after_recieve_with_sleep
FILE: test/test_helper.rb
function run_cmd (line 11) | def run_cmd(name, args=[], data=nil)
function wait_for_output_then_kill (line 26) | def wait_for_output_then_kill(io, signal='INT', timeout=0.5)
function random_port (line 32) | def random_port
function fake_server (line 36) | def fake_server(*args)
function have_ipv6? (line 46) | def have_ipv6?
type Minitest::Assertions (line 50) | module Minitest::Assertions
function assert_includes_match (line 51) | def assert_includes_match(regexp, array, msg=nil)
Condensed preview — 24 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (150K chars).
[
{
"path": ".astylerc",
"chars": 206,
"preview": "# use the K&R code style\n--style=kr\n\n# Indent 4 spaces\n--indent=spaces=4\n\n# Indent switch and case statements\n--indent-s"
},
{
"path": ".gitignore",
"chars": 184,
"preview": "mqtt-sn-dump\nmqtt-sn-pub\nmqtt-sn-sub\nmqtt-sn-serial-bridge\n*.exe\n*.o\n*.tar.gz\n\n# Code coverage files\n*.gcda\n*.gcno\ncover"
},
{
"path": ".travis.yml",
"chars": 152,
"preview": "# We use ruby for running the tests\nlanguage: ruby\nsudo: false\n\nbefore_install:\n - gem update bundler\nrvm:\n - '2.5.5'\n"
},
{
"path": "LICENSE.md",
"chars": 1102,
"preview": "The MIT License (MIT)\n=====================\n\nCopyright (c) Nicholas J Humfrey\n\nPermission is hereby granted, free of cha"
},
{
"path": "Makefile",
"chars": 1417,
"preview": "CC=cc\nPACKAGE=mqtt-sn-tools\nVERSION=0.0.7\nCFLAGS=-g -Wall -DVERSION=$(VERSION)\nLDFLAGS=\nINSTALL?=install\nprefix=/usr/loc"
},
{
"path": "NEWS.md",
"chars": 3068,
"preview": "Release History\n===============\n\nVersion 0.0.6 (2017-08-23)\n--------------------------\n\n- Added support for publishing a"
},
{
"path": "README.md",
"chars": 5401,
"preview": "MQTT-SN Tools\n=============\n\n[](https://travis"
},
{
"path": "mqtt-sn-dump.c",
"chars": 4636,
"preview": "/*\n MQTT-SN command-line dump\n Copyright (C) Nicholas Humfrey\n\n Permission is hereby granted, free of charge, to any "
},
{
"path": "mqtt-sn-pub.c",
"chars": 10496,
"preview": "/*\n MQTT-SN command-line publishing client\n Copyright (C) Nicholas Humfrey\n\n Permission is hereby granted, free of ch"
},
{
"path": "mqtt-sn-serial-bridge.c",
"chars": 10145,
"preview": "/*\n MQTT-SN serial bridge\n Copyright (C) Nicholas Humfrey\n\n Permission is hereby granted, free of charge, to any pers"
},
{
"path": "mqtt-sn-sub.c",
"chars": 10224,
"preview": "/*\n MQTT-SN command-line subscribe client\n Copyright (C) Nicholas Humfrey\n\n Permission is hereby granted, free of cha"
},
{
"path": "mqtt-sn.c",
"chars": 34787,
"preview": "/*\n Common functions used by the MQTT-SN Tools\n Copyright (C) Nicholas Humfrey\n\n Permission is hereby granted, free o"
},
{
"path": "mqtt-sn.h",
"chars": 8069,
"preview": "/*\n Common functions used by the MQTT-SN Tools\n Copyright (C) Nicholas Humfrey\n\n Permission is hereby granted, free o"
},
{
"path": "test/.ruby-version",
"chars": 5,
"preview": "2.5.5"
},
{
"path": "test/Gemfile",
"chars": 178,
"preview": "source 'https://rubygems.org'\nruby File.read('.ruby-version').chomp\n\ngem 'bundler', '>= 1.5.0'\ngem 'rake', '>= 0.10"
},
{
"path": "test/Rakefile",
"chars": 145,
"preview": "#!/usr/bin/env ruby\n\nrequire 'rubygems'\nrequire 'rake/testtask'\n\nRake::TestTask.new do |t|\n t.pattern = \"*-test.rb\"\nend"
},
{
"path": "test/fake_server.rb",
"chars": 5182,
"preview": "#!/usr/bin/env ruby\n#\n# This is a 'fake' MQTT-SN server to help with testing client implementations\n#\n# It behaves in th"
},
{
"path": "test/mqtt-sn-dump-test.rb",
"chars": 7723,
"preview": "$:.unshift(File.dirname(__FILE__))\n\nrequire 'test_helper'\n\nclass MqttSnDumpTest < Minitest::Test\n\n def test_usage\n @"
},
{
"path": "test/mqtt-sn-pub-test.rb",
"chars": 19677,
"preview": "$:.unshift(File.dirname(__FILE__))\n\nrequire 'test_helper'\n\nclass MqttSnPubTest < Minitest::Test\n\n def test_usage\n @c"
},
{
"path": "test/mqtt-sn-serial-bridge-test.rb",
"chars": 257,
"preview": "$:.unshift(File.dirname(__FILE__))\n\nrequire 'test_helper'\n\nclass MqttSnSerialBridgeTest < Minitest::Test\n\n def test_usa"
},
{
"path": "test/mqtt-sn-sub-test.rb",
"chars": 17612,
"preview": "$:.unshift(File.dirname(__FILE__))\n\nrequire 'test_helper'\n\nclass MqttSnSubTest < Minitest::Test\n\n def test_usage\n @c"
},
{
"path": "test/test.txt",
"chars": 31,
"preview": "The is the contents of test.txt"
},
{
"path": "test/test_big.txt",
"chars": 1343,
"preview": "00000000 f3 a7 4f ee 23 58 28 e8 48 df bc 96 42 6a 45 e6 |..O.#X(.H...BjE.|\n00000010 8e ca 44 51 7d 3a 08 80 1f b8 "
},
{
"path": "test/test_helper.rb",
"chars": 1347,
"preview": "$:.unshift(File.join(File.dirname(__FILE__),'..','lib'))\n\nrequire 'rubygems'\nrequire 'bundler'\nBundler.require(:default)"
}
]
About this extraction
This page contains the full source code of the njh/mqtt-sn-tools GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 24 files (140.0 KB), approximately 38.5k tokens, and a symbol index with 196 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.