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