10.9. SMS Binding

10.9.1. General description

One of LwM2M’s advantages is the possibility to choose from numerous underlying protocol stacks. Although most applications use CoAP over UDP, LwM2M TS: Transport Bindings document specifies how LwM2M messages can be conveyed over other protocols, like HTTP, MQTT, TCP, NIDD, or SMS. Such a wide choice increases flexibility of LwM2M, making it the right choice in a number of different applications.

Anjay’s SMS Binding feature incorporates a feature-complete implementation of SMS binding as specified in the LwM2M specification. Thanks to that, you can:

  • use SMS as a sole transportation method to be able to deploy LwM2M devices in areas where connectivity over IP is unavailable or economically unjustified,

  • integrate Anjay with already existing devices which are not capable of internet communication,

  • use SMS together with UDP, either as an alternative binding to increase the reliability of the connection, or save costs and battery usage by using SMS triggers to wake up the device and then bring on the connection over UDP.

The implementation is also interoperable with DTLS and supports Concatenated SMS (CSMS), both for the inbound and outbound messages, in case the modem used doesn’t have support for that. With CSMS enabled it’s possible to send payloads up to 34170 bytes, without any fragmentation on CoAP level.

SMS Binding feature also provides a couple of utilities for developers:

  • sample implementation of an SMS driver, which uses the standard AT command set to send, receive and manage SMS messages and communicates over a serial port with any standards-compliant cellular modem,

  • utility Python script, which simulates an AT modem and acts as a proxy - all messages are conveyed further over UDP to the target server.

10.9.2. Technical documentation

10.9.2.1. Enabling SMS binding support

If SMS Binding feature is available in your version of Anjay, the support for SMS transport can be enabled by either defining ANJAY_WITH_SMS in anjay_config.h or, if using CMake, enabling WITH_SMS option.

You may also want to enable the ANJAY_WITH_SMS_MULTIPART macro, which enables support for Concatenated SMS, both for incoming and outgoing traffic (for outbound traffic it can also be configured in runtime). Using Concatenated SMS raises the MTU reported to upper layers, which may solve issues with some types of CoAP or DTLS messages that can be larger than 140 bytes. This setting should be also disabled for modems, which are capable of handling CSMS on their own.

Anjay, similarly to how network sockets are implemented, uses an abstraction layer for SMS connections to remain hardware agnostic. It’s the developers’ responsibility to implement a driver which handles specific hardware, although the library comes with a basic reference implementation for AT modems, which communicates with them over a serial port. To compile it in, please define ANJAY_WITH_MODULE_AT_SMS or enable WITH_MODULE_at_sms CMake option.

10.9.2.2. Usage example

Important

For simplicity, we’ll also use the example SMS driver implementation for AT modems through the whole tutorial. You can find more information about features and limitations of that implementation in driver’s documentation.

To make those examples work out-of-the-box with the virtual modem/SMS proxy script, client’s and server’s phone numbers will be set to the default values included in the script.

10.9.2.2.1. Simple, unsecured connection

Note

The full code for the following example can be found in the examples/commercial-features/CF-SMS directory in Anjay sources. Note that to compile and run it, you need to have access to a commercial version of Anjay that includes the SMS binding feature.

As an example, we’ll modify the code from the Installing mandatory Objects tutorial.

To connect to the server over SMS, we have to make a couple of little changes to the original application.

Since the server is reachable by phone number, not by an internet address, we have to reconfigure the LwM2M Security object - server URI should be changed and the security mode for the SMS binding should be specified.

 static int setup_security_object(anjay_t *anjay) {
     if (anjay_security_object_install(anjay)) {
         return -1;
     }

     const anjay_security_instance_t security_instance = {
         .ssid = 1,
         .server_uri = "tel:+12125550178",
         .security_mode = ANJAY_SECURITY_NOSEC,
         .sms_security_mode = ANJAY_SMS_SECURITY_NOSEC
     };

     // Anjay will assign Instance ID automatically
     anjay_iid_t security_instance_id = ANJAY_ID_INVALID;
     if (anjay_security_object_add_instance(anjay, &security_instance,
                                            &security_instance_id)) {
         return -1;
     }

     return 0;
 }

Next up, we should update the preferred binding information in the configuration of LwM2M Server object.

 static int setup_server_object(anjay_t *anjay) {
     if (anjay_server_object_install(anjay)) {
         return -1;
     }

     const anjay_server_instance_t server_instance = {
         // Server Short ID
         .ssid = 1,
         // Client will send Update message often than every 60 seconds
         .lifetime = 60,
         // Disable Default Minimum Period resource
         .default_min_period = -1,
         // Disable Default Maximum Period resource
         .default_max_period = -1,
         // Disable Disable Timeout resource
         .disable_timeout = -1,
         // Sets preferred transport to SMS
         .binding = "S"
     };

     // Anjay will assign Instance ID automatically
     anjay_iid_t server_instance_id = ANJAY_ID_INVALID;
     if (anjay_server_object_add_instance(anjay, &server_instance,
                                          &server_instance_id)) {
         return -1;
     }

     return 0;
 }

As the example SMS driver expects a path to a file representing an AT terminal, we’ll accept the path as an application’s argument and instantiate the driver. Client’s phone number is a part of the registration message, thus it must be included in the config structure.

Note

Technically speaking, the library expects a MSISDN, which is a country code-prefixed phone number without the ‘+’ sign or other prefixes specific to your location.

 int main(int argc, char *argv[]) {
     if (argc != 3) {
         avs_log(tutorial, ERROR, "usage: %s ENDPOINT_NAME MODEM_DEVICE",
                 argv[0]);
         return -1;
     }

     const anjay_configuration_t CONFIG = {
         .endpoint_name = argv[1],
         .in_buffer_size = 4000,
         .out_buffer_size = 4000,
         .msg_cache_size = 4000,
         .sms_driver = anjay_at_sms_create(argv[2]),
         .local_msisdn = "14155550125"
     };

     anjay_t *anjay = anjay_new(&CONFIG);
     if (!anjay) {
         avs_log(tutorial, ERROR, "Could not create Anjay object");
         return -1;
     }

     int result = 0;
     // Setup necessary objects
     if (setup_security_object(anjay) || setup_server_object(anjay)) {
         result = -1;
     }

     if (!result) {
         result = anjay_event_loop_run(
                 anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));
     }

     anjay_delete(anjay);
     return result;
 }

If you have access to an AT modem with SMS functionality and a LwM2M server with SMS gateway configured, you need to think about a couple of matters:

  • the example AT driver expects the device to not echo the commands sent, that is ATE0 should be either sent to the modem first, or configured earlier,

  • the driver communicates with the modem using plain read() / write() routines and is not aware of the characteristics of the terminal, so it should be configured first. For example, to make this application work with Quectel UG96 modem, on Linux, over 115200-8-N-1 serial connection, following command had to be issued: stty -F /dev/ttyACM0 115200 -cs8 -cstopb -parenb -icrnl.

Otherwise, to run the example, you can use the vmodem.py script instead, which acts as a virtual AT modem, translating SMS messages to UDP packets and vice-versa. You can find it in tests/integration/framework/sms directory.

Example run follows:

$ tests/integration/framework/sms/vmodem.py --host eu.iot.avsystem.cloud --port 5683
2022-03-03 12:41:49 user root[19173] INFO Modem PTY: /dev/pts/5

The scripts informs us that it has opened a virtual terminal at /dev/pts/5. Now you can build the application by executing make commercial_feature_examples and run it with output/bin/examples/anjay-sms endpoint_name /dev/pts/5.

10.9.2.2.2. DTLS over SMS

Note

The full code for the following example can be found in the examples/commercial-features/CF-SMS-PSK directory in Anjay sources. Note that to compile and run it, you need to have access to a commercial version of Anjay that includes the SMS binding feature.

To secure the connection using DTLS over SMS, we’ll introduce similar changes to those described in Enabling secure communication tutorial, but targeting fields explicitly related to the SMS binding.

Note

LwM2M allows only for PSK mode to be used with DTLS over SMS.

 static int setup_security_object(anjay_t *anjay) {
     if (anjay_security_object_install(anjay)) {
         return -1;
     }

     static const char PSK_IDENTITY[] = "identity";
     static const char PSK_KEY[] = "P4s$w0rd";

     const anjay_security_instance_t security_instance = {
         .ssid = 1,
         .server_uri = "tel:+12125550178",
         .security_mode = ANJAY_SECURITY_NOSEC,
         .sms_security_mode = ANJAY_SMS_SECURITY_DTLS_PSK,
         .sms_key_parameters = (const uint8_t *) PSK_IDENTITY,
         .sms_key_parameters_size = strlen(PSK_IDENTITY),
         .sms_secret_key = (const uint8_t *) PSK_KEY,
         .sms_secret_key_size = strlen(PSK_KEY),
         .server_name_indication = "eu.iot.avsystem.cloud"
     };

     // Anjay will assign Instance ID automatically
     anjay_iid_t security_instance_id = ANJAY_ID_INVALID;
     if (anjay_security_object_add_instance(anjay, &security_instance,
                                            &security_instance_id)) {
         return -1;
     }

     return 0;
 }

Notice that the security_mode setting must remain untouched as it’s unused by the application, but LwM2M Security object specification requires its presence.

Important

When using DTLS, Anjay by default sets the Server Name Indication (SNI) extension field to the address extracted from server_uri field, which in our case is the server’s gateway phone number. As in this example we’re using the virtual modem script to make the example more accessible, which also means that in fact UDP is used to connect to the server, we have to override the SNI by assigning server_name_indication field to our target server’s address. Otherwise, the server will attempt to recognize the phone number as correct server name.

To make DTLS over SMS work correctly, we have to ensure that appropriate ciphersuite is used. The MTU of SMS is just 140 bytes, so some ciphersuites have too much overhead to be conveyed over SMS messages.

 const anjay_configuration_t CONFIG = {
     .endpoint_name = argv[1],
     .in_buffer_size = 4000,
     .out_buffer_size = 4000,
     .msg_cache_size = 4000,
     .sms_driver = anjay_at_sms_create(argv[2]),
     .local_msisdn = "14155550125",
     .default_tls_ciphersuites = {
         // TLS_PSK_WITH_AES_128_CCM_8
         .ids = (uint32_t[]){ 0xC0A8 },
         .num_ids = 1
     }
 };

Note

TLS_PSK_WITH_AES_128_CCM_8 is one of LwM2M’s recommended ciphersuites to be used with SMS bindings. The ID assignments of ciphersuites are maintained by IANA and can be found in this document.

Alternatively, one can use Concatenated SMS messages instead, which have logical MTU large enough to work with all types of ciphersuites, but it means that most of the messages sent to the server, even small ones, will be fragmented. To enable multipart SMS messages, set prefer_multipart_sms in the CONFIG structure above to true.

10.9.2.2.3. SMS Trigger Mode

Note

The full code for the following example can be found in the examples/commercial-features/CF-SMS-UDP directory in Anjay sources. Note that to compile and run it, you need to have access to a commercial version of Anjay that includes the SMS binding feature.

LwM2M 1.1 introduces Trigger Mode, which provides the possibility to request a LwM2M Update via SMS (by executing Registration Update Trigger resource) and receive the response by currently used transport binding. Similar behavior can be achieved using US or UQS binding modes available in LwM2M 1.0.

Following example will build upon the code from Simple, unsecured connection section, use LwM2M 1.1 and UDP as transport binding.

Firstly, let’s change the Server URI to use UDP again. Server’s phone number will be passed through another field, server_sms_number, in MSISDN form.

 const anjay_security_instance_t security_instance = {
     .ssid = 1,
     .server_uri = "coap://eu.iot.avsystem.cloud:5683",
     .security_mode = ANJAY_SECURITY_NOSEC,
     .sms_security_mode = ANJAY_SMS_SECURITY_NOSEC,
     .server_sms_number = "12125550178"
 };

To change the binding back to UDP and enable the Trigger resource in Server object which controls Trigger Mode, apply following changes:

 const anjay_server_instance_t server_instance = {
     // Server Short ID
     .ssid = 1,
     // Client will send Update message often than every 60 seconds
     .lifetime = 60,
     // Disable Default Minimum Period resource
     .default_min_period = -1,
     // Disable Default Maximum Period resource
     .default_max_period = -1,
     // Disable Disable Timeout resource
     .disable_timeout = -1,
     // Sets preferred transport to UDP
     .binding = "U",
     // Enables optional Trigger resource and sets it to true
     .trigger = &(const bool) { true }
 };