blob: d6fcc7ada93b6ffa216ac97d2b5751ffc39482e3 [file] [log] [blame]
/*******************************************************************************
*
* Copyright (c) 2015 Intel Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.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
*
*******************************************************************************/
#include "liblwm2m.h"
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/stat.h>
#include <errno.h>
#include <signal.h>
#include <inttypes.h>
#include "commandline.h"
#include "connection.h"
#include "bootstrap_info.h"
#define CMD_STATUS_NEW 0
#define CMD_STATUS_SENT 1
#define CMD_STATUS_OK 2
#define CMD_STATUS_FAIL 3
typedef struct _endpoint_
{
struct _endpoint_ * next;
char * name;
void * handle;
bs_command_t * cmdList;
uint8_t status;
} endpoint_t;
typedef struct
{
lwm2m_context_t * lwm2mH;
bs_info_t * bsInfo;
endpoint_t * endpointList;
} internal_data_t;
/*
* ensure sync with: er_coap_13.h COAP_MAX_PACKET_SIZE!
* or internals.h LWM2M_MAX_PACKET_SIZE!
*/
#define MAX_PACKET_SIZE 198
static int g_quit = 0;
static uint8_t prv_buffer_send(void * sessionH,
uint8_t * buffer,
size_t length,
void * userdata)
{
connection_t * connP = (connection_t*) sessionH;
if (-1 == connection_send(connP, buffer, length))
{
return COAP_500_INTERNAL_SERVER_ERROR;
}
return COAP_NO_ERROR;
}
static void prv_quit(char * buffer,
void * user_data)
{
g_quit = 1;
}
void handle_sigint(int signum)
{
prv_quit(NULL, NULL);
}
void print_usage(char * filename,
char * port)
{
fprintf(stdout, "Usage: bootstap_server [OPTION]\r\n");
fprintf(stderr, "Launch a LWM2M Bootstrap Server.\r\n\n");
fprintf(stdout, "Options:\r\n");
fprintf(stdout, " -f FILE\tSpecify BootStrap Information file. Default: ./%s\r\n", filename);
fprintf(stdout, " -p PORT\tSet the local UDP port of the Client. Default: %s\r\n", port);
fprintf(stdout, "\r\n");
}
static void prv_endpoint_free(endpoint_t * endP)
{
if (endP != NULL)
{
if (endP->name != NULL) free(endP->name);
free(endP);
}
}
static endpoint_t * prv_endpoint_find(internal_data_t * dataP,
void * sessionH)
{
endpoint_t * endP;
endP = dataP->endpointList;
while (endP != NULL
&& endP->handle != sessionH)
{
endP = endP->next;
}
return endP;
}
static endpoint_t * prv_endpoint_new(internal_data_t * dataP,
void * sessionH)
{
endpoint_t * endP;
endP = prv_endpoint_find(dataP, sessionH);
if (endP != NULL)
{
// delete previous state for the endpoint
endpoint_t * parentP;
parentP = dataP->endpointList;
while (parentP != NULL
&& parentP->next != endP)
{
parentP = parentP->next;
}
if (parentP != NULL)
{
parentP->next = endP->next;
}
else
{
dataP->endpointList = endP->next;
}
prv_endpoint_free(endP);
}
endP = (endpoint_t *)malloc(sizeof(endpoint_t));
return endP;
}
static void prv_endpoint_clean(internal_data_t * dataP)
{
endpoint_t * endP;
endpoint_t * parentP;
while (dataP->endpointList != NULL
&& (dataP->endpointList->cmdList == NULL
|| dataP->endpointList->status == CMD_STATUS_FAIL))
{
endP = dataP->endpointList->next;
prv_endpoint_free(dataP->endpointList);
dataP->endpointList = endP;
}
parentP = dataP->endpointList;
if (parentP != NULL)
{
endP = dataP->endpointList->next;
while(endP != NULL)
{
endpoint_t * nextP;
nextP = endP->next;
if (endP->cmdList == NULL
|| endP->status == CMD_STATUS_FAIL)
{
prv_endpoint_free(endP);
parentP->next = nextP;
}
else
{
parentP = endP;
}
endP = nextP;
}
}
}
static void prv_send_command(internal_data_t * dataP,
endpoint_t * endP)
{
int res;
if (endP->cmdList == NULL) return;
switch (endP->cmdList->operation)
{
case BS_DELETE:
res = lwm2m_bootstrap_delete(dataP->lwm2mH, endP->handle, endP->cmdList->uri);
break;
case BS_WRITE_SECURITY:
{
lwm2m_uri_t uri;
bs_server_tlv_t * serverP;
serverP = (bs_server_tlv_t *)LWM2M_LIST_FIND(dataP->bsInfo->serverList, endP->cmdList->serverId);
if (serverP == NULL
|| serverP->securityData == NULL)
{
endP->status = CMD_STATUS_FAIL;
return;
}
uri.flag = LWM2M_URI_FLAG_OBJECT_ID | LWM2M_URI_FLAG_INSTANCE_ID;
uri.objectId = LWM2M_SECURITY_OBJECT_ID;
uri.instanceId = endP->cmdList->serverId;
res = lwm2m_bootstrap_write(dataP->lwm2mH, endP->handle, &uri, serverP->securityData, serverP->securityLen);
}
break;
case BS_WRITE_SERVER:
{
lwm2m_uri_t uri;
bs_server_tlv_t * serverP;
serverP = (bs_server_tlv_t *)LWM2M_LIST_FIND(dataP->bsInfo->serverList, endP->cmdList->serverId);
if (serverP == NULL
|| serverP->serverData == NULL)
{
endP->status = CMD_STATUS_FAIL;
return;
}
uri.flag = LWM2M_URI_FLAG_OBJECT_ID | LWM2M_URI_FLAG_INSTANCE_ID;
uri.objectId = LWM2M_SERVER_OBJECT_ID;
uri.instanceId = endP->cmdList->serverId;
res = lwm2m_bootstrap_write(dataP->lwm2mH, endP->handle, &uri, serverP->serverData, serverP->serverLen);
}
break;
case BS_FINISH:
res = lwm2m_bootstrap_finish(dataP->lwm2mH, endP->handle);
break;
default:
return;
}
if (res == COAP_NO_ERROR)
{
endP->status = CMD_STATUS_SENT;
}
else
{
endP->status = CMD_STATUS_FAIL;
}
}
static int prv_bootstrap_callback(void * sessionH,
uint8_t status,
lwm2m_uri_t * uriP,
char * name,
void * userData)
{
internal_data_t * dataP = (internal_data_t *)userData;
uint8_t result;
endpoint_t * endP;
switch (status)
{
case COAP_NO_ERROR:
{
bs_endpoint_info_t * endInfoP;
// Display
fprintf(stdout, "\r\nBootstrap request from \"%s\"\r\n", name);
// find Bootstrap Info for this endpoint
endInfoP = dataP->bsInfo->endpointList;
while (endInfoP != NULL
&& endInfoP->name != NULL
&& strcmp(name, endInfoP->name) != 0)
{
endInfoP = endInfoP->next;
}
if (endInfoP == NULL)
{
// find default Bootstrap Info
endInfoP = dataP->bsInfo->endpointList;
while (endInfoP != NULL
&& endInfoP->name != NULL)
{
endInfoP = endInfoP->next;
}
}
// Nothing found, discard the request
if (endInfoP == NULL)return COAP_IGNORE;
endP = prv_endpoint_new(dataP, sessionH);
if (endP == NULL) return COAP_500_INTERNAL_SERVER_ERROR;
endP->cmdList = endInfoP->commandList;
endP->handle = sessionH;
endP->name = strdup(name);
endP->status = CMD_STATUS_NEW;
endP->next = dataP->endpointList;
dataP->endpointList = endP;
return COAP_204_CHANGED;
}
default:
// Display
fprintf(stdout, "\r\n Received status ");
print_status(stdout, status);
fprintf(stdout, " for URI /");
if (uriP != NULL)
{
fprintf(stdout, "%hu", uriP->objectId);
if (LWM2M_URI_IS_SET_INSTANCE(uriP))
{
fprintf(stdout, "/%d", uriP->instanceId);
if (LWM2M_URI_IS_SET_RESOURCE(uriP))
fprintf(stdout, "/%d", uriP->resourceId);
}
}
endP = prv_endpoint_find(dataP, sessionH);
if (endP == NULL)
{
fprintf(stdout, " from unknown endpoint.\r\n");
return COAP_NO_ERROR;
}
fprintf(stdout, " from endpoint %s.\r\n", endP->name);
// should not happen
if (endP->cmdList == NULL) return COAP_NO_ERROR;
if (endP->status != CMD_STATUS_SENT) return COAP_NO_ERROR;
switch (endP->cmdList->operation)
{
case BS_DELETE:
if (status == COAP_202_DELETED)
{
endP->status = CMD_STATUS_OK;
}
else
{
endP->status = CMD_STATUS_FAIL;
}
break;
case BS_WRITE_SECURITY:
case BS_WRITE_SERVER:
if (status == COAP_204_CHANGED)
{
endP->status = CMD_STATUS_OK;
}
else
{
endP->status = CMD_STATUS_FAIL;
}
break;
default:
endP->status = CMD_STATUS_FAIL;
break;
}
break;
}
return COAP_NO_ERROR;
}
int main(int argc, char *argv[])
{
int sock;
fd_set readfds;
struct timeval tv;
int result;
connection_t * connList = NULL;
char * port = "5685";
internal_data_t data;
char * filename = "bootstrap_server.ini";
int opt;
FILE * fd;
command_desc_t commands[] =
{
{"q", "Quit the server.", NULL, prv_quit, NULL},
COMMAND_END_LIST
};
while ((opt = getopt(argc, argv, "f:p:")) != -1)
{
switch (opt)
{
case 'f':
filename = optarg;
break;
case 'p':
port = optarg;
break;
default:
print_usage(filename, port);
return 0;
}
}
sock = create_socket(port);
if (sock < 0)
{
fprintf(stderr, "Error opening socket: %d\r\n", errno);
return -1;
}
memset(&data, 0, sizeof(internal_data_t));
data.lwm2mH = lwm2m_init(NULL, prv_buffer_send, NULL);
if (NULL == data.lwm2mH)
{
fprintf(stderr, "lwm2m_init() failed\r\n");
return -1;
}
signal(SIGINT, handle_sigint);
fd = fopen(filename, "r");
if (fd == NULL)
{
fprintf(stderr, "Opening file %s failed.\r\n", filename);
return -1;
}
data.bsInfo = bs_get_info(fd);
fclose(fd);
if (data.bsInfo == NULL)
{
fprintf(stderr, "Reading Bootsrap Info from file %s failed.\r\n", filename);
return -1;
}
lwm2m_set_bootstrap_callback(data.lwm2mH, prv_bootstrap_callback, (void *)&data);
fprintf(stdout, "LWM2M Bootstrap Server now listening on port %s.\r\n\n", port);
fprintf(stdout, "> "); fflush(stdout);
while (0 == g_quit)
{
endpoint_t * endP;
FD_ZERO(&readfds);
FD_SET(sock, &readfds);
FD_SET(STDIN_FILENO, &readfds);
tv.tv_sec = 60;
tv.tv_usec = 0;
result = lwm2m_step(data.lwm2mH, &(tv.tv_sec));
if (result != 0)
{
fprintf(stderr, "lwm2m_step() failed: 0x%X\r\n", result);
return -1;
}
result = select(FD_SETSIZE, &readfds, 0, 0, &tv);
if ( result < 0 )
{
if (errno != EINTR)
{
fprintf(stderr, "Error in select(): %d\r\n", errno);
}
}
else if (result >= 0)
{
uint8_t buffer[MAX_PACKET_SIZE];
int numBytes;
// Packet received
if (FD_ISSET(sock, &readfds))
{
struct sockaddr_storage addr;
socklen_t addrLen;
addrLen = sizeof(addr);
numBytes = recvfrom(sock, buffer, MAX_PACKET_SIZE, 0, (struct sockaddr *)&addr, &addrLen);
if (numBytes == -1)
{
fprintf(stderr, "Error in recvfrom(): %d\r\n", errno);
}
else
{
char s[INET6_ADDRSTRLEN];
in_port_t port;
connection_t * connP;
s[0] = 0;
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, "%d bytes received from [%s]:%hu\r\n", numBytes, s, ntohs(port));
output_buffer(stderr, buffer, numBytes, 0);
connP = connection_find(connList, &addr, addrLen);
if (connP == NULL)
{
connP = connection_new_incoming(connList, sock, (struct sockaddr *)&addr, addrLen);
if (connP != NULL)
{
connList = connP;
}
}
if (connP != NULL)
{
lwm2m_handle_packet(data.lwm2mH, buffer, numBytes, connP);
}
}
}
// command line input
else if (FD_ISSET(STDIN_FILENO, &readfds))
{
numBytes = read(STDIN_FILENO, buffer, MAX_PACKET_SIZE - 1);
if (numBytes > 1)
{
buffer[numBytes] = 0;
handle_command(commands, (char*)buffer);
}
if (g_quit == 0)
{
fprintf(stdout, "\r\n> ");
fflush(stdout);
}
else
{
fprintf(stdout, "\r\n");
}
}
// Do operations on endpoints
prv_endpoint_clean(&data);
endP = data.endpointList;
while (endP != NULL)
{
switch(endP->status)
{
case CMD_STATUS_OK:
endP->cmdList = endP->cmdList->next;
endP->status = CMD_STATUS_NEW;
// fall through
case CMD_STATUS_NEW:
prv_send_command(&data, endP);
break;
default:
break;
}
endP = endP->next;
}
}
}
lwm2m_close(data.lwm2mH);
bs_free_info(data.bsInfo);
while (data.endpointList != NULL)
{
endpoint_t * endP;
endP = data.endpointList;
data.endpointList = data.endpointList->next;
prv_endpoint_free(endP);
}
close(sock);
connection_free(connList);
return 0;
}