wakaama/examples/client/lwm2mclient.c
Lukas Woodtli 88c53d7e43 refactor: Move plain connection code to own folder
This is a step to a more modular project setup.
2024-08-14 10:35:46 +02:00

1548 lines
45 KiB
C

/*******************************************************************************
*
* Copyright (c) 2013, 2014 Intel Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v20.html
* The Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* David Navarro, Intel Corporation - initial API and implementation
* Benjamin Cabé - Please refer to git log
* Fabien Fleutot - Please refer to git log
* Simon Bernard - Please refer to git log
* Julien Vermillard - Please refer to git log
* Axel Lorente - Please refer to git log
* Toby Jaffey - Please refer to git log
* Bosch Software Innovations GmbH - Please refer to git log
* Pascal Rieux - Please refer to git log
* Christian Renz - Please refer to git log
* Ricky Liu - Please refer to git log
*
*******************************************************************************/
/*
Copyright (c) 2013, 2014 Intel Corporation
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Intel Corporation nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
David Navarro <david.navarro@intel.com>
Bosch Software Innovations GmbH - Please refer to git log
*/
#include "lwm2mclient.h"
#include "liblwm2m.h"
#include "commandline.h"
#ifdef WITH_TINYDTLS
#include "dtlsconnection.h"
#else
#include "udp/connection.h"
#endif
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <netdb.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define MAX_PACKET_SIZE 2048
#define DEFAULT_SERVER_IPV6 "[::1]"
#define DEFAULT_SERVER_IPV4 "127.0.0.1"
int g_reboot = 0;
static int g_quit = 0;
#define OBJ_COUNT 9
lwm2m_object_t * objArray[OBJ_COUNT];
// only backup security and server objects
# define BACKUP_OBJECT_COUNT 2
lwm2m_object_t * backupObjectArray[BACKUP_OBJECT_COUNT];
typedef struct
{
lwm2m_object_t * securityObjP;
lwm2m_object_t * serverObject;
int sock;
#ifdef WITH_TINYDTLS
dtls_connection_t * connList;
lwm2m_context_t * lwm2mH;
#else
connection_t * connList;
#endif
int addressFamily;
} client_data_t;
static void prv_quit(lwm2m_context_t * lwm2mH,
char * buffer,
void * user_data)
{
/* unused parameters */
(void)lwm2mH;
(void)buffer;
(void)user_data;
g_quit = 1;
}
void handle_sigint(int signum)
{
g_quit = 2;
}
void handle_value_changed(lwm2m_context_t * lwm2mH,
lwm2m_uri_t * uri,
const char * value,
size_t valueLength)
{
lwm2m_object_t * object = (lwm2m_object_t *)LWM2M_LIST_FIND(lwm2mH->objectList, uri->objectId);
if (NULL != object)
{
if (object->writeFunc != NULL)
{
lwm2m_data_t * dataP;
int result;
dataP = lwm2m_data_new(1);
if (dataP == NULL)
{
fprintf(stderr, "Internal allocation failure !\n");
return;
}
dataP->id = uri->resourceId;
#ifndef LWM2M_VERSION_1_0
if (LWM2M_URI_IS_SET_RESOURCE_INSTANCE(uri))
{
lwm2m_data_t *subDataP = lwm2m_data_new(1);
if (subDataP == NULL)
{
fprintf(stderr, "Internal allocation failure !\n");
lwm2m_data_free(1, dataP);
return;
}
subDataP->id = uri->resourceInstanceId;
lwm2m_data_encode_nstring(value, valueLength, subDataP);
lwm2m_data_encode_instances(subDataP, 1, dataP);
}
else
#endif
{
lwm2m_data_encode_nstring(value, valueLength, dataP);
}
result = object->writeFunc(lwm2mH, uri->instanceId, 1, dataP, object, LWM2M_WRITE_PARTIAL_UPDATE);
if (COAP_405_METHOD_NOT_ALLOWED == result)
{
switch (uri->objectId)
{
case LWM2M_DEVICE_OBJECT_ID:
result = device_change(dataP, object);
break;
default:
break;
}
}
if (COAP_204_CHANGED != result)
{
fprintf(stderr, "Failed to change value!\n");
}
else
{
fprintf(stderr, "value changed!\n");
lwm2m_resource_value_changed(lwm2mH, uri);
}
lwm2m_data_free(1, dataP);
return;
}
else
{
fprintf(stderr, "write not supported for specified resource!\n");
}
return;
}
else
{
fprintf(stderr, "Object not found !\n");
}
}
#ifdef WITH_TINYDTLS
void * lwm2m_connect_server(uint16_t secObjInstID,
void * userData)
{
client_data_t * dataP;
lwm2m_list_t * instance;
dtls_connection_t * newConnP = NULL;
dataP = (client_data_t *)userData;
lwm2m_object_t * securityObj = dataP->securityObjP;
instance = LWM2M_LIST_FIND(dataP->securityObjP->instanceList, secObjInstID);
if (instance == NULL) return NULL;
newConnP = connection_create(dataP->connList, dataP->sock, securityObj, instance->id, dataP->lwm2mH, dataP->addressFamily);
if (newConnP == NULL)
{
fprintf(stderr, "Connection creation failed.\n");
return NULL;
}
dataP->connList = newConnP;
return (void *)newConnP;
}
#else
void * lwm2m_connect_server(uint16_t secObjInstID,
void * userData)
{
client_data_t * dataP;
char * uri;
char * host;
char * port;
connection_t * newConnP = NULL;
dataP = (client_data_t *)userData;
uri = get_server_uri(dataP->securityObjP, secObjInstID);
if (uri == NULL) return NULL;
// parse uri in the form "coaps://[host]:[port]"
if (0==strncmp(uri, "coaps://", strlen("coaps://"))) {
host = uri+strlen("coaps://");
}
else if (0==strncmp(uri, "coap://", strlen("coap://"))) {
host = uri+strlen("coap://");
}
else {
goto exit;
}
port = strrchr(host, ':');
if (port == NULL) goto exit;
// remove brackets
if (host[0] == '[')
{
host++;
if (*(port - 1) == ']')
{
*(port - 1) = 0;
}
else goto exit;
}
// split strings
*port = 0;
port++;
fprintf(stderr, "Opening connection to server at %s:%s\r\n", host, port);
newConnP = connection_create(dataP->connList, dataP->sock, host, port, dataP->addressFamily);
if (newConnP == NULL) {
fprintf(stderr, "Connection creation failed.\r\n");
}
else {
dataP->connList = newConnP;
}
exit:
lwm2m_free(uri);
return (void *)newConnP;
}
#endif
void lwm2m_close_connection(void * sessionH,
void * userData)
{
client_data_t * app_data;
#ifdef WITH_TINYDTLS
dtls_connection_t * targetP;
#else
connection_t * targetP;
#endif
app_data = (client_data_t *)userData;
#ifdef WITH_TINYDTLS
targetP = (dtls_connection_t *)sessionH;
#else
targetP = (connection_t *)sessionH;
#endif
if (targetP == app_data->connList)
{
app_data->connList = targetP->next;
lwm2m_free(targetP);
}
else
{
#ifdef WITH_TINYDTLS
dtls_connection_t * parentP;
#else
connection_t * parentP;
#endif
parentP = app_data->connList;
while (parentP != NULL && parentP->next != targetP)
{
parentP = parentP->next;
}
if (parentP != NULL)
{
parentP->next = targetP->next;
lwm2m_free(targetP);
}
}
}
static void prv_output_servers(lwm2m_context_t * lwm2mH,
char * buffer,
void * user_data)
{
lwm2m_server_t * targetP;
/* unused parameter */
(void)user_data;
targetP = lwm2mH->bootstrapServerList;
if (lwm2mH->bootstrapServerList == NULL)
{
fprintf(stdout, "No Bootstrap Server.\r\n");
}
else
{
fprintf(stdout, "Bootstrap Servers:\r\n");
for (targetP = lwm2mH->bootstrapServerList ; targetP != NULL ; targetP = targetP->next)
{
fprintf(stdout, " - Security Object ID %d", targetP->secObjInstID);
fprintf(stdout, "\tHold Off Time: %lu s", (unsigned long)targetP->lifetime);
fprintf(stdout, "\tstatus: ");
switch(targetP->status)
{
case STATE_DEREGISTERED:
fprintf(stdout, "DEREGISTERED\r\n");
break;
case STATE_BS_HOLD_OFF:
fprintf(stdout, "CLIENT HOLD OFF\r\n");
break;
case STATE_BS_INITIATED:
fprintf(stdout, "BOOTSTRAP INITIATED\r\n");
break;
case STATE_BS_PENDING:
fprintf(stdout, "BOOTSTRAP PENDING\r\n");
break;
case STATE_BS_FINISHED:
fprintf(stdout, "BOOTSTRAP FINISHED\r\n");
break;
case STATE_BS_FAILED:
fprintf(stdout, "BOOTSTRAP FAILED\r\n");
break;
default:
fprintf(stdout, "INVALID (%d)\r\n", (int)targetP->status);
}
}
}
if (lwm2mH->serverList == NULL)
{
fprintf(stdout, "No LWM2M Server.\r\n");
}
else
{
fprintf(stdout, "LWM2M Servers:\r\n");
for (targetP = lwm2mH->serverList ; targetP != NULL ; targetP = targetP->next)
{
fprintf(stdout, " - Server ID %d", targetP->shortID);
fprintf(stdout, "\tstatus: ");
switch(targetP->status)
{
case STATE_DEREGISTERED:
fprintf(stdout, "DEREGISTERED\r\n");
break;
case STATE_REG_PENDING:
fprintf(stdout, "REGISTRATION PENDING\r\n");
break;
case STATE_REGISTERED:
fprintf(stdout, "REGISTERED\tlocation: \"%s\"\tLifetime: %lus\r\n", targetP->location, (unsigned long)targetP->lifetime);
break;
case STATE_REG_UPDATE_PENDING:
fprintf(stdout, "REGISTRATION UPDATE PENDING\r\n");
break;
case STATE_DEREG_PENDING:
fprintf(stdout, "DEREGISTRATION PENDING\r\n");
break;
case STATE_REG_FAILED:
fprintf(stdout, "REGISTRATION FAILED\r\n");
break;
default:
fprintf(stdout, "INVALID (%d)\r\n", (int)targetP->status);
}
}
}
}
static void prv_change(lwm2m_context_t * lwm2mH,
char * buffer,
void * user_data)
{
lwm2m_uri_t uri;
char * end = NULL;
int result;
/* unused parameter */
(void)user_data;
end = get_end_of_arg(buffer);
if (end[0] == 0) goto syntax_error;
result = lwm2m_stringToUri(buffer, end - buffer, &uri);
if (result == 0) goto syntax_error;
buffer = get_next_arg(end, &end);
if (buffer[0] == 0)
{
fprintf(stderr, "report change!\n");
lwm2m_resource_value_changed(lwm2mH, &uri);
}
else
{
handle_value_changed(lwm2mH, &uri, buffer, end - buffer);
}
return;
syntax_error:
fprintf(stdout, "Syntax error !\n");
}
static void prv_object_list(lwm2m_context_t * lwm2mH,
char * buffer,
void * user_data)
{
lwm2m_object_t * objectP;
/* unused parameter */
(void)user_data;
for (objectP = lwm2mH->objectList; objectP != NULL; objectP = objectP->next)
{
if (objectP->instanceList == NULL)
{
fprintf(stdout, "/%d ", objectP->objID);
}
else
{
lwm2m_list_t * instanceP;
for (instanceP = objectP->instanceList; instanceP != NULL ; instanceP = instanceP->next)
{
fprintf(stdout, "/%d/%d ", objectP->objID, instanceP->id);
}
}
fprintf(stdout, "\r\n");
}
}
static void prv_instance_dump(lwm2m_context_t * lwm2mH,
lwm2m_object_t * objectP,
uint16_t id)
{
int numData;
lwm2m_data_t * dataArray;
uint16_t res;
numData = 0;
res = objectP->readFunc(lwm2mH, id, &numData, &dataArray, objectP);
if (res != COAP_205_CONTENT)
{
printf("Error ");
print_status(stdout, res);
printf("\r\n");
return;
}
dump_tlv(stdout, numData, dataArray, 0);
}
static void prv_object_dump(lwm2m_context_t * lwm2mH,
char * buffer,
void * user_data)
{
lwm2m_uri_t uri;
char * end = NULL;
int result;
lwm2m_object_t * objectP;
/* unused parameter */
(void)user_data;
end = get_end_of_arg(buffer);
if (end[0] == 0) goto syntax_error;
result = lwm2m_stringToUri(buffer, end - buffer, &uri);
if (result == 0) goto syntax_error;
if (LWM2M_URI_IS_SET_RESOURCE(&uri)) goto syntax_error;
objectP = (lwm2m_object_t *)LWM2M_LIST_FIND(lwm2mH->objectList, uri.objectId);
if (objectP == NULL)
{
fprintf(stdout, "Object not found.\n");
return;
}
if (LWM2M_URI_IS_SET_INSTANCE(&uri))
{
prv_instance_dump(lwm2mH, objectP, uri.instanceId);
}
else
{
lwm2m_list_t * instanceP;
for (instanceP = objectP->instanceList; instanceP != NULL ; instanceP = instanceP->next)
{
fprintf(stdout, "Instance %d:\r\n", instanceP->id);
prv_instance_dump(lwm2mH, objectP, instanceP->id);
fprintf(stdout, "\r\n");
}
}
return;
syntax_error:
fprintf(stdout, "Syntax error !\n");
}
static void prv_update(lwm2m_context_t * lwm2mH,
char * buffer,
void * user_data)
{
/* unused parameter */
(void)user_data;
if (buffer[0] == 0) goto syntax_error;
uint16_t serverId = (uint16_t) atoi(buffer);
int res = lwm2m_update_registration(lwm2mH, serverId, false);
if (res != 0)
{
fprintf(stdout, "Registration update error: ");
print_status(stdout, res);
fprintf(stdout, "\r\n");
}
return;
syntax_error:
fprintf(stdout, "Syntax error !\n");
}
#ifndef LWM2M_VERSION_1_0
static void prv_send(lwm2m_context_t *lwm2mH, char *buffer, void *user_data) {
lwm2m_uri_t uri;
lwm2m_uri_t *uris = NULL;
size_t uriCount = 0;
char *tmp;
char *end = NULL;
int result;
uint16_t serverId;
/* unused parameter */
(void)user_data;
if (buffer[0] == 0)
goto syntax_error;
result = atoi(buffer);
if (result < 0 || result > LWM2M_MAX_ID)
goto syntax_error;
serverId = (uint16_t)result;
tmp = buffer;
do {
tmp = get_next_arg(tmp, &end);
if (tmp[0] == 0)
goto syntax_error;
result = lwm2m_stringToUri(tmp, end - tmp, &uri);
if (result == 0)
goto syntax_error;
uriCount++;
} while (!check_end_of_args(end));
uris = lwm2m_malloc(uriCount * sizeof(lwm2m_uri_t));
if (uris != NULL) {
size_t i;
for (i = 0; i < uriCount; i++) {
buffer = get_next_arg(buffer, &end);
if (buffer[0] == 0)
goto syntax_error;
result = lwm2m_stringToUri(buffer, end - buffer, uris + i);
if (result == 0)
goto syntax_error;
}
result = lwm2m_send(lwm2mH, serverId, uris, uriCount, NULL, NULL);
lwm2m_free(uris);
} else {
result = COAP_500_INTERNAL_SERVER_ERROR;
}
if (result != 0) {
fprintf(stdout, "Send error: ");
print_status(stdout, result);
fprintf(stdout, "\r\n");
}
return;
syntax_error:
if (uris != NULL) {
lwm2m_free(uris);
}
fprintf(stdout, "Syntax error !\n");
}
#endif
static void update_battery_level(lwm2m_context_t * context)
{
static time_t next_change_time = 0;
time_t tv_sec;
tv_sec = lwm2m_gettime();
if (tv_sec < 0) return;
if (next_change_time < tv_sec)
{
char value[15];
int valueLength;
lwm2m_uri_t uri;
int level = rand() % 100;
if (0 > level) level = -level;
if (lwm2m_stringToUri("/3/0/9", 6, &uri))
{
valueLength = sprintf(value, "%d", level);
fprintf(stderr, "New Battery Level: %d\n", level);
handle_value_changed(context, &uri, value, valueLength);
}
level = rand() % 20;
if (0 > level) level = -level;
next_change_time = tv_sec + level + 10;
}
}
static void prv_add(lwm2m_context_t * lwm2mH,
char * buffer,
void * user_data)
{
lwm2m_object_t * objectP;
int res;
/* unused parameter */
(void)user_data;
objectP = get_test_object();
if (objectP == NULL)
{
fprintf(stdout, "Creating object 31024 failed.\r\n");
return;
}
res = lwm2m_add_object(lwm2mH, objectP);
if (res != 0)
{
fprintf(stdout, "Adding object 31024 failed: ");
print_status(stdout, res);
fprintf(stdout, "\r\n");
}
else
{
fprintf(stdout, "Object 31024 added.\r\n");
}
return;
}
static void prv_remove(lwm2m_context_t * lwm2mH,
char * buffer,
void * user_data)
{
int res;
/* unused parameter */
(void)user_data;
res = lwm2m_remove_object(lwm2mH, 31024);
if (res != 0)
{
fprintf(stdout, "Removing object 31024 failed: ");
print_status(stdout, res);
fprintf(stdout, "\r\n");
}
else
{
fprintf(stdout, "Object 31024 removed.\r\n");
}
return;
}
#ifdef LWM2M_BOOTSTRAP
static void prv_initiate_bootstrap(lwm2m_context_t * lwm2mH,
char * buffer,
void * user_data)
{
lwm2m_server_t * targetP;
/* unused parameter */
(void)user_data;
// HACK !!!
lwm2mH->state = STATE_BOOTSTRAP_REQUIRED;
targetP = lwm2mH->bootstrapServerList;
while (targetP != NULL)
{
targetP->lifetime = 0;
targetP = targetP->next;
}
}
static void prv_display_backup(lwm2m_context_t * lwm2mH,
char * buffer,
void * user_data)
{
int i;
/* unused parameters */
(void)lwm2mH;
(void)buffer;
(void)user_data;
for (i = 0 ; i < BACKUP_OBJECT_COUNT ; i++) {
lwm2m_object_t * object = backupObjectArray[i];
if (NULL != object) {
switch (object->objID)
{
case LWM2M_SECURITY_OBJECT_ID:
display_security_object(object);
break;
case LWM2M_SERVER_OBJECT_ID:
display_server_object(object);
break;
default:
break;
}
}
}
}
static void prv_backup_objects(lwm2m_context_t * context)
{
uint16_t i;
for (i = 0; i < BACKUP_OBJECT_COUNT; i++) {
if (NULL != backupObjectArray[i]) {
switch (backupObjectArray[i]->objID)
{
case LWM2M_SECURITY_OBJECT_ID:
clean_security_object(backupObjectArray[i]);
lwm2m_free(backupObjectArray[i]);
break;
case LWM2M_SERVER_OBJECT_ID:
clean_server_object(backupObjectArray[i]);
lwm2m_free(backupObjectArray[i]);
break;
default:
break;
}
}
backupObjectArray[i] = (lwm2m_object_t *)lwm2m_malloc(sizeof(lwm2m_object_t));
memset(backupObjectArray[i], 0, sizeof(lwm2m_object_t));
}
/*
* Backup content of objects 0 (security) and 1 (server)
*/
copy_security_object(backupObjectArray[0], (lwm2m_object_t *)LWM2M_LIST_FIND(context->objectList, LWM2M_SECURITY_OBJECT_ID));
copy_server_object(backupObjectArray[1], (lwm2m_object_t *)LWM2M_LIST_FIND(context->objectList, LWM2M_SERVER_OBJECT_ID));
}
static void prv_restore_objects(lwm2m_context_t * context)
{
lwm2m_object_t * targetP;
/*
* Restore content of objects 0 (security) and 1 (server)
*/
targetP = (lwm2m_object_t *)LWM2M_LIST_FIND(context->objectList, LWM2M_SECURITY_OBJECT_ID);
// first delete internal content
clean_security_object(targetP);
// then restore previous object
copy_security_object(targetP, backupObjectArray[0]);
targetP = (lwm2m_object_t *)LWM2M_LIST_FIND(context->objectList, LWM2M_SERVER_OBJECT_ID);
// first delete internal content
clean_server_object(targetP);
// then restore previous object
copy_server_object(targetP, backupObjectArray[1]);
// restart the old servers
fprintf(stdout, "[BOOTSTRAP] ObjectList restored\r\n");
}
static void update_bootstrap_info(lwm2m_client_state_t * previousBootstrapState,
lwm2m_context_t * context)
{
if (*previousBootstrapState != context->state)
{
*previousBootstrapState = context->state;
switch(context->state)
{
case STATE_BOOTSTRAPPING:
#if LWM2M_LOG_LEVEL != LWM2M_LOG_DISABLED
fprintf(stdout, "[BOOTSTRAP] backup security and server objects\r\n");
#endif
prv_backup_objects(context);
break;
default:
break;
}
}
}
static void close_backup_object(void) {
int i;
for (i = 0; i < BACKUP_OBJECT_COUNT; i++) {
if (NULL != backupObjectArray[i]) {
switch (backupObjectArray[i]->objID)
{
case LWM2M_SECURITY_OBJECT_ID:
clean_security_object(backupObjectArray[i]);
lwm2m_free(backupObjectArray[i]);
break;
case LWM2M_SERVER_OBJECT_ID:
clean_server_object(backupObjectArray[i]);
lwm2m_free(backupObjectArray[i]);
break;
default:
break;
}
}
}
}
#endif
static void prv_display_objects(lwm2m_context_t *lwm2mH, char *buffer, void *user_data) {
lwm2m_object_t *object;
/* unused parameter */
(void)user_data;
for (object = lwm2mH->objectList; object != NULL; object = object->next) {
if (NULL != object) {
switch (object->objID) {
case LWM2M_SECURITY_OBJECT_ID:
display_security_object(object);
break;
case LWM2M_SERVER_OBJECT_ID:
display_server_object(object);
break;
case LWM2M_ACL_OBJECT_ID:
break;
case LWM2M_DEVICE_OBJECT_ID:
display_device_object(object);
break;
case LWM2M_CONN_MONITOR_OBJECT_ID:
break;
case LWM2M_FIRMWARE_UPDATE_OBJECT_ID:
display_firmware_object(object);
break;
case LWM2M_LOCATION_OBJECT_ID:
display_location_object(object);
break;
case LWM2M_CONN_STATS_OBJECT_ID:
break;
case TEST_OBJECT_ID:
display_test_object(object);
break;
default:
fprintf(stdout, "unknown object ID: %" PRIu16 "\n", object->objID);
break;
}
}
}
}
void print_usage(void)
{
fprintf(stdout, "Usage: lwm2mclient [OPTION]\r\n");
fprintf(stdout, "Launch a LWM2M client.\r\n");
fprintf(stdout, "Options:\r\n");
fprintf(stdout, " -n NAME\tSet the endpoint name of the Client. Default: testlwm2mclient\r\n");
fprintf(stdout, " -l PORT\tSet the local UDP port of the Client. Default: 56830\r\n");
fprintf(stdout, " -h HOST\tSet the hostname of the LWM2M Server to connect to. Default: localhost\r\n");
fprintf(stdout, " -p PORT\tSet the port of the LWM2M Server to connect to. Default: "LWM2M_STANDARD_PORT_STR"\r\n");
fprintf(stdout, " -4\t\tUse IPv4 connection. Default: IPv6 connection\r\n");
fprintf(stdout, " -t TIME\tSet the lifetime of the Client. Default: 300\r\n");
fprintf(stdout, " -b\t\tBootstrap requested.\r\n");
fprintf(stdout, " -c\t\tChange battery level over time.\r\n");
fprintf(stdout, " -S BYTES\tCoAP block size. Options: 16, 32, 64, 128, 256, 512, 1024. Default: %" PRIu16 "\r\n",
(uint16_t)LWM2M_COAP_DEFAULT_BLOCK_SIZE);
#ifdef WITH_TINYDTLS
fprintf(stdout, " -i STRING\tSet the device management or bootstrap server PSK identity. If not set use none secure mode\r\n");
fprintf(stdout, " -s HEXSTRING\tSet the device management or bootstrap server Pre-Shared-Key. If not set use none secure mode\r\n");
#endif
fprintf(stdout, "\r\n");
}
int main(int argc, char *argv[])
{
client_data_t data;
int result;
lwm2m_context_t * lwm2mH = NULL;
const char * localPort = "56830";
const char * server = NULL;
const char * serverPort = LWM2M_STANDARD_PORT_STR;
const char *name = "testlwm2mclient";
int lifetime = 300;
int batterylevelchanging = 0;
time_t reboot_time = 0;
int opt;
bool bootstrapRequested = false;
bool serverPortChanged = false;
#ifdef LWM2M_BOOTSTRAP
lwm2m_client_state_t previousState = STATE_INITIAL;
#endif
char * pskId = NULL;
#ifdef WITH_TINYDTLS
char * psk = NULL;
#endif
uint16_t pskLen = -1;
char * pskBuffer = NULL;
/*
* The function start by setting up the command line interface (which may or not be useful depending on your project)
*
* This is an array of commands describes as { name, description, long description, callback, userdata }.
* The firsts tree are easy to understand, the callback is the function that will be called when this command is typed
* and in the last one will be stored the lwm2m context (allowing access to the server settings and the objects).
*/
command_desc_t commands[] = {
{"list", "List known servers.", NULL, prv_output_servers, NULL},
{"change", "Change the value of resource.",
" change URI [DATA]\r\n"
" URI: uri of the resource such as /3/0, /3/0/2\r\n"
" DATA: (optional) new value\r\n",
prv_change, NULL},
{"update", "Trigger a registration update",
" update SERVER\r\n"
" SERVER: short server id such as 123\r\n",
prv_update, NULL},
#ifndef LWM2M_VERSION_1_0
{"send", "Send one or more resources",
" send SERVER URI [URI...]\r\n"
" SERVER: short server id such as 123. 0 for all.\r\n"
" URI: uri of the resource such as /3/0, /3/0/2\r\n",
prv_send, NULL},
#endif
#ifdef LWM2M_BOOTSTRAP
{"bootstrap", "Initiate a DI bootstrap process", NULL, prv_initiate_bootstrap, NULL},
{"dispb",
"Display current backup of objects/instances/resources\r\n"
"\t(only security and server objects are backupped)",
NULL, prv_display_backup, NULL},
#endif
{"ls", "List Objects and Instances", NULL, prv_object_list, NULL},
{"disp", "Display current objects/instances/resources", NULL, prv_display_objects, NULL},
{"dump", "Dump an Object",
"dump URI"
"URI: uri of the Object or Instance such as /3/0, /1\r\n",
prv_object_dump, NULL},
{"add", "Add support of object 31024", NULL, prv_add, NULL},
{"rm", "Remove support of object 31024", NULL, prv_remove, NULL},
{"quit", "Quit the client gracefully.", NULL, prv_quit, NULL},
{"^C", "Quit the client abruptly (without sending a de-register message).", NULL, NULL, NULL},
COMMAND_END_LIST};
memset(&data, 0, sizeof(client_data_t));
data.addressFamily = AF_INET6;
opt = 1;
while (opt < argc)
{
if (argv[opt] == NULL
|| argv[opt][0] != '-'
|| argv[opt][2] != 0)
{
print_usage();
return 0;
}
switch (argv[opt][1])
{
case 'b':
bootstrapRequested = true;
if (!serverPortChanged) serverPort = LWM2M_BSSERVER_PORT_STR;
break;
case 'c':
batterylevelchanging = 1;
break;
case 't':
opt++;
if (opt >= argc)
{
print_usage();
return 0;
}
if (1 != sscanf(argv[opt], "%d", &lifetime))
{
print_usage();
return 0;
}
break;
#ifdef WITH_TINYDTLS
case 'i':
opt++;
if (opt >= argc)
{
print_usage();
return 0;
}
pskId = argv[opt];
break;
case 's':
opt++;
if (opt >= argc)
{
print_usage();
return 0;
}
psk = argv[opt];
break;
#endif
case 'n':
opt++;
if (opt >= argc)
{
print_usage();
return 0;
}
name = argv[opt];
break;
case 'l':
opt++;
if (opt >= argc)
{
print_usage();
return 0;
}
localPort = argv[opt];
break;
case 'h':
opt++;
if (opt >= argc)
{
print_usage();
return 0;
}
server = argv[opt];
break;
case 'p':
opt++;
if (opt >= argc)
{
print_usage();
return 0;
}
serverPort = argv[opt];
serverPortChanged = true;
break;
case '4':
data.addressFamily = AF_INET;
break;
case 'S':
opt++;
if (opt >= argc) {
print_usage();
return 0;
}
uint16_t coap_block_size_arg;
if (1 == sscanf(argv[opt], "%" SCNu16, &coap_block_size_arg) &&
lwm2m_set_coap_block_size(coap_block_size_arg)) {
break;
} else {
print_usage();
return 0;
}
default:
print_usage();
return 0;
}
opt += 1;
}
if (!server)
{
server = (AF_INET == data.addressFamily ? DEFAULT_SERVER_IPV4 : DEFAULT_SERVER_IPV6);
}
/*
*This call an internal function that create an IPV6 socket on the port 5683.
*/
fprintf(stderr, "Trying to bind LWM2M Client to port %s\r\n", localPort);
data.sock = create_socket(localPort, data.addressFamily);
if (data.sock < 0)
{
fprintf(stderr, "Failed to open socket: %d %s\r\n", errno, strerror(errno));
return -1;
}
/*
* Now the main function fill an array with each object, this list will be later passed to liblwm2m.
* Those functions are located in their respective object file.
*/
#ifdef WITH_TINYDTLS
if (psk != NULL)
{
pskLen = strlen(psk) / 2;
pskBuffer = malloc(pskLen);
if (NULL == pskBuffer)
{
fprintf(stderr, "Failed to create PSK binary buffer\r\n");
return -1;
}
// Hex string to binary
char *h = psk;
char *b = pskBuffer;
char xlate[] = "0123456789ABCDEF";
for ( ; *h; h += 2, ++b)
{
char *l = strchr(xlate, toupper(*h));
char *r = strchr(xlate, toupper(*(h+1)));
if (!r || !l)
{
fprintf(stderr, "Failed to parse Pre-Shared-Key HEXSTRING\r\n");
return -1;
}
*b = ((l - xlate) << 4) + (r - xlate);
}
}
#endif
char serverUri[50];
int serverId = 123;
#ifdef WITH_TINYDTLS
sprintf (serverUri, "coaps://%s:%s", server, serverPort);
#else
sprintf (serverUri, "coap://%s:%s", server, serverPort);
#endif
#ifdef LWM2M_BOOTSTRAP
objArray[0] = get_security_object(serverId, serverUri, pskId, pskBuffer, pskLen, bootstrapRequested);
#else
objArray[0] = get_security_object(serverId, serverUri, pskId, pskBuffer, pskLen, false);
#endif
if (NULL == objArray[0])
{
fprintf(stderr, "Failed to create security object\r\n");
return -1;
}
data.securityObjP = objArray[0];
objArray[1] = get_server_object(serverId, "U", lifetime, false);
if (NULL == objArray[1])
{
fprintf(stderr, "Failed to create server object\r\n");
return -1;
}
objArray[2] = get_object_device();
if (NULL == objArray[2])
{
fprintf(stderr, "Failed to create Device object\r\n");
return -1;
}
objArray[3] = get_object_firmware();
if (NULL == objArray[3])
{
fprintf(stderr, "Failed to create Firmware object\r\n");
return -1;
}
objArray[4] = get_object_location();
if (NULL == objArray[4])
{
fprintf(stderr, "Failed to create location object\r\n");
return -1;
}
objArray[5] = get_test_object();
if (NULL == objArray[5])
{
fprintf(stderr, "Failed to create test object\r\n");
return -1;
}
objArray[6] = get_object_conn_m();
if (NULL == objArray[6])
{
fprintf(stderr, "Failed to create connectivity monitoring object\r\n");
return -1;
}
objArray[7] = get_object_conn_s();
if (NULL == objArray[7])
{
fprintf(stderr, "Failed to create connectivity statistics object\r\n");
return -1;
}
int instId = 0;
objArray[8] = acc_ctrl_create_object();
if (NULL == objArray[8])
{
fprintf(stderr, "Failed to create Access Control object\r\n");
return -1;
}
else if (acc_ctrl_obj_add_inst(objArray[8], instId, 3, 0, serverId)==false)
{
fprintf(stderr, "Failed to create Access Control object instance\r\n");
return -1;
}
else if (acc_ctrl_oi_add_ac_val(objArray[8], instId, 0, 0xF /* == 0b000000000001111 */)==false)
{
fprintf(stderr, "Failed to create Access Control ACL default resource\r\n");
return -1;
}
else if (acc_ctrl_oi_add_ac_val(objArray[8], instId, 999, 0x1 /* == 0b000000000000001 */)==false)
{
fprintf(stderr, "Failed to create Access Control ACL resource for serverId: 999\r\n");
return -1;
}
/*
* The liblwm2m library is now initialized with the functions that will be in
* charge of communication
*/
lwm2mH = lwm2m_init(&data);
if (NULL == lwm2mH)
{
fprintf(stderr, "lwm2m_init() failed\r\n");
return -1;
}
#ifdef WITH_TINYDTLS
data.lwm2mH = lwm2mH;
#endif
/*
* We configure the liblwm2m library with the name of the client - which shall be unique for each client -
* the number of objects we will be passing through and the objects array
*/
result = lwm2m_configure(lwm2mH, name, NULL, NULL, OBJ_COUNT, objArray);
if (result != 0)
{
fprintf(stderr, "lwm2m_configure() failed: 0x%X\r\n", result);
return -1;
}
signal(SIGINT, handle_sigint);
/**
* Initialize value changed callback.
*/
init_value_change(lwm2mH);
fprintf(stdout, "LWM2M Client \"%s\" started on port %s\r\n", name, localPort);
fprintf(stdout, "> "); fflush(stdout);
/*
* We now enter in a while loop that will handle the communications from the server
*/
while (0 == g_quit)
{
struct timeval tv;
fd_set readfds;
if (g_reboot)
{
time_t tv_sec;
tv_sec = lwm2m_gettime();
if (0 == reboot_time)
{
reboot_time = tv_sec + 5;
}
if (reboot_time < tv_sec)
{
/*
* Message should normally be lost with reboot ...
*/
fprintf(stderr, "reboot time expired, rebooting ...");
system_reboot();
}
else
{
tv.tv_sec = reboot_time - tv_sec;
}
}
else if (batterylevelchanging)
{
update_battery_level(lwm2mH);
tv.tv_sec = 5;
}
else
{
tv.tv_sec = 60;
}
tv.tv_usec = 0;
FD_ZERO(&readfds);
FD_SET(data.sock, &readfds);
FD_SET(STDIN_FILENO, &readfds);
/*
* This function does two things:
* - first it does the work needed by liblwm2m (eg. (re)sending some packets).
* - Secondly it adjusts the timeout value (default 60s) depending on the state of the transaction
* (eg. retransmission) and the time between the next operation
*/
result = lwm2m_step(lwm2mH, &(tv.tv_sec));
fprintf(stdout, " -> State: ");
switch (lwm2mH->state)
{
case STATE_INITIAL:
fprintf(stdout, "STATE_INITIAL\r\n");
break;
case STATE_BOOTSTRAP_REQUIRED:
fprintf(stdout, "STATE_BOOTSTRAP_REQUIRED\r\n");
break;
case STATE_BOOTSTRAPPING:
fprintf(stdout, "STATE_BOOTSTRAPPING\r\n");
break;
case STATE_REGISTER_REQUIRED:
fprintf(stdout, "STATE_REGISTER_REQUIRED\r\n");
break;
case STATE_REGISTERING:
fprintf(stdout, "STATE_REGISTERING\r\n");
break;
case STATE_READY:
fprintf(stdout, "STATE_READY\r\n");
break;
default:
fprintf(stdout, "Unknown...\r\n");
break;
}
if (result != 0)
{
fprintf(stderr, "lwm2m_step() failed: 0x%X\r\n", result);
#ifdef LWM2M_BOOTSTRAP
if(previousState == STATE_BOOTSTRAPPING)
{
#if LWM2M_LOG_LEVEL != LWM2M_LOG_DISABLED
fprintf(stdout, "[BOOTSTRAP] restore security and server objects\r\n");
#endif
prv_restore_objects(lwm2mH);
lwm2mH->state = STATE_INITIAL;
} else
#endif
return -1;
}
#ifdef LWM2M_BOOTSTRAP
update_bootstrap_info(&previousState, lwm2mH);
#endif
/*
* This part will set up an interruption until an event happen on SDTIN or the socket until "tv" timed out (set
* with the precedent function)
*/
result = select(FD_SETSIZE, &readfds, NULL, NULL, &tv);
if (result < 0)
{
if (errno != EINTR)
{
fprintf(stderr, "Error in select(): %d %s\r\n", errno, strerror(errno));
}
}
else if (result > 0)
{
uint8_t buffer[MAX_PACKET_SIZE];
ssize_t numBytes;
/*
* If an event happens on the socket
*/
if (FD_ISSET(data.sock, &readfds))
{
struct sockaddr_storage addr;
socklen_t addrLen;
addrLen = sizeof(addr);
/*
* We retrieve the data received
*/
numBytes = recvfrom(data.sock, buffer, MAX_PACKET_SIZE, 0, (struct sockaddr *)&addr, &addrLen);
if (0 > numBytes)
{
fprintf(stderr, "Error in recvfrom(): %d %s\r\n", errno, strerror(errno));
}
else if (numBytes >= MAX_PACKET_SIZE)
{
fprintf(stderr, "Received packet >= MAX_PACKET_SIZE\r\n");
}
else if (0 < numBytes)
{
char s[INET6_ADDRSTRLEN];
in_port_t port;
#ifdef WITH_TINYDTLS
dtls_connection_t * connP;
#else
connection_t * connP;
#endif
if (AF_INET == addr.ss_family)
{
struct sockaddr_in *saddr = (struct sockaddr_in *)&addr;
inet_ntop(saddr->sin_family, &saddr->sin_addr, s, INET6_ADDRSTRLEN);
port = saddr->sin_port;
}
else if (AF_INET6 == addr.ss_family)
{
struct sockaddr_in6 *saddr = (struct sockaddr_in6 *)&addr;
inet_ntop(saddr->sin6_family, &saddr->sin6_addr, s, INET6_ADDRSTRLEN);
port = saddr->sin6_port;
}
fprintf(stderr, "%zd bytes received from [%s]:%hu\r\n", numBytes, s, ntohs(port));
/*
* Display it in the STDERR
*/
output_buffer(stderr, buffer, (size_t)numBytes, 0);
connP = connection_find(data.connList, &addr, addrLen);
if (connP != NULL)
{
/*
* Let liblwm2m respond to the query depending on the context
*/
#ifdef WITH_TINYDTLS
result = connection_handle_packet(connP, buffer, numBytes);
if (0 != result)
{
printf("error handling message %d\n",result);
}
#else
lwm2m_handle_packet(lwm2mH, buffer, (size_t)numBytes, connP);
#endif
conn_s_updateRxStatistic(objArray[7], numBytes, false);
}
else
{
fprintf(stderr, "received bytes ignored!\r\n");
}
}
}
/*
* If the event happened on the SDTIN
*/
else if (FD_ISSET(STDIN_FILENO, &readfds))
{
char *line = NULL;
size_t bufLen = 0;
numBytes = getline(&line, &bufLen, stdin);
if (numBytes > 1)
{
line[numBytes] = 0;
/*
* We call the corresponding callback of the typed command passing it the buffer for further arguments
*/
handle_command(lwm2mH, commands, line);
}
if (g_quit == 0)
{
fprintf(stdout, "\r\n> ");
fflush(stdout);
}
else
{
fprintf(stdout, "\r\n");
}
lwm2m_free(line);
}
}
}
/*
* Finally when the loop is left smoothly - asked by user in the command line interface - we unregister our client from it
*/
if (g_quit == 1)
{
#ifdef WITH_TINYDTLS
free(pskBuffer);
#endif
#ifdef LWM2M_BOOTSTRAP
close_backup_object();
#endif
lwm2m_close(lwm2mH);
}
close(data.sock);
connection_free(data.connList);
clean_security_object(objArray[0]);
lwm2m_free(objArray[0]);
clean_server_object(objArray[1]);
lwm2m_free(objArray[1]);
free_object_device(objArray[2]);
free_object_firmware(objArray[3]);
free_object_location(objArray[4]);
free_test_object(objArray[5]);
free_object_conn_m(objArray[6]);
free_object_conn_s(objArray[7]);
acl_ctrl_free_object(objArray[8]);
return 0;
}