aws iot sample: add a config file option to get settings for the sample

The application uses 'libconfuse' to read the settings from the provided
configuration file with the option '-c'. By default, the application looks for
the '/etc/awsiotsdk.conf' file.

https://jira.digi.com/browse/DEL-4149

Signed-off-by: Tatiana Leon <tatiana.leon@digi.com>
This commit is contained in:
Tatiana Leon 2017-05-10 21:19:34 +02:00
parent cbbe31f11e
commit dc2599fd28
5 changed files with 511 additions and 1 deletions

View File

@ -25,7 +25,9 @@ CFLAGS += $(LOG_FLAGS)
CFLAGS += -DGIT_REVISION=\"$(if $(GIT_REVISION),-g$(GIT_REVISION))\"
CFLAGS += $(shell pkg-config --cflags awsiotsdk)
CFLAGS += $(shell pkg-config --cflags libconfuse)
LDLIBS += $(shell pkg-config --libs --static awsiotsdk)
LDLIBS += $(shell pkg-config --libs --static libconfuse)
SRCS = $(wildcard $(SRC)/*.c)
OBJS = $(SRCS:.c=.o)

View File

@ -0,0 +1,49 @@
#===============================================================================
# AWS IoT Device SDK Settings
#===============================================================================
# Thing Name: Thing Name of the Shadow this device is associated with.
# A thing is the representation and record of your physical device in the cloud.
# Any physical device needs a thing to work with AWS IoT. Creating a thing will
# also create a thing shadow.
thing_name = ""
# Client ID: MQTT client ID should be unique for every device.
# By default, it is the same as 'thing_name'.
#client_id = ""
# Host: Customer specific MQTT HOST.
# This is your custom endpoint that allows your device to connect to AWS IoT.
# You can obtain it in your 'AWS IoT Console':
# 1. From the dashboard, in the left navigation pane, choose 'Registry' and
# then choose 'Things'.
# 2. Select the box representing your device to show its details page.
# 3. On the details page, in the left navigation pane, choose 'Interact'
# 4. Look for the HTTPS section, your endpoint will look something like:
# ABCDEFG1234567.iot.us-east-2.amazonaws.com
# where 'ABCDEFG1234567' is the subdomain and 'us-east-2' is the region.
host = ""
# Port: Default port for MQTT/S.
# By default, 8883.
port = 8883
# Certificates path: Absolute path to the certificates directory in the device.
# By default, '/etc/ssl/certs'.
certs_path = "/etc/ssl/certs"
# Root CA file name: Name of the Root CA file located in the configured
# 'certs_path'.
# By default, 'rootCA.crt'
rootca_filename = "rootCA.crt"
# Signed certificate file name: Name of the device signed certificate file
# located in the configured 'certs_path'.
# By default, 'cert.pem'
signed_cert_filename = "cert.pem"
# Device private key filename: Name of the device private key file located in
# the configured 'certs_path'.
# By default, 'privkey.pem'
private_key_filename = "privkey.pem"

View File

@ -0,0 +1,383 @@
/*
* Copyright (c) 2017 Digi International Inc.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*
* Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
* =======================================================================
*/
#include <aws_iot_config.h>
#include <aws_iot_log.h>
#include <confuse.h>
#include <dirent.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include "aws_config.h"
/*------------------------------------------------------------------------------
D E F I N I T I O N S
------------------------------------------------------------------------------*/
#define SETTING_THING_NAME "thing_name"
#define SETTING_CLIENT_ID "client_id"
#define SETTING_HOST "host"
#define SETTING_PORT "port"
#define SETTING_CERTS_PATH "certs_path"
#define SETTING_ROOTCA_NAME "rootca_filename"
#define SETTING_SCERT_NAME "signed_cert_filename"
#define SETTING_PRIVKEY_NAME "private_key_filename"
#define SETTING_UNKNOWN "__unknown"
/*------------------------------------------------------------------------------
F U N C T I O N D E C L A R A T I O N S
------------------------------------------------------------------------------*/
static int fill_aws_iot_config(aws_iot_cfg_t *aws_cfg);
static int cfg_check_thing_name(cfg_t *cfg, cfg_opt_t *opt);
static int cfg_check_client_id(cfg_t *cfg, cfg_opt_t *opt);
static int cfg_check_port(cfg_t *cfg, cfg_opt_t *opt);
static int cfg_check_certificates_path(cfg_t *cfg, cfg_opt_t *opt);
static int cfg_check_cert_file(cfg_t *cfg, cfg_opt_t *opt);
static int cfg_check_int_range(cfg_t *cfg, cfg_opt_t *opt, uint32_t min, uint32_t max);
static int cfg_check_empty_string(cfg_t *cfg, cfg_opt_t *opt);
static int cfg_check_string_length(cfg_t *cfg, cfg_opt_t *opt, uint16_t min, uint16_t max);
static int file_exists(const char *const filename);
static int file_readable(const char *const filename);
/*------------------------------------------------------------------------------
G L O B A L V A R I A B L E S
------------------------------------------------------------------------------*/
static cfg_t *cfg;
/*------------------------------------------------------------------------------
F U N C T I O N D E F I N I T I O N S
------------------------------------------------------------------------------*/
/*
* parse_configuration() - Parse and save the settings of a configuration file
*
* @filename: Name of the file containing the configuration settings.
* @aws_cfg: AWS IoT Device SDK configuration struct (aws_iot_cfg_t) where
* the settings parsed from the configuration file are saved.
*
* Read the provided configuration file and save the settings in the given
* aws_iot_cfg_t struct. If the file does not exist or cannot be read, the
* configuration struct is initialized with the default settings.
*
* Return: 0 if the file is parsed successfully, -1 if there is an error
* parsing the file.
*/
int parse_configuration(const char *const filename, aws_iot_cfg_t *aws_cfg)
{
/* Overall structure of the settings. */
static cfg_opt_t opts[] = {
/* --------------------------------------------------------------------- */
/*| TYPE | SETTING NAME | DEFAULT VALUE | FLAGS |*/
/* --------------------------------------------------------------------- */
CFG_STR (SETTING_THING_NAME, AWS_IOT_MY_THING_NAME, CFGF_NONE),
CFG_STR (SETTING_CLIENT_ID, AWS_IOT_MQTT_CLIENT_ID, CFGF_NONE),
CFG_STR (SETTING_HOST, AWS_IOT_MQTT_HOST, CFGF_NONE),
CFG_INT (SETTING_PORT, AWS_IOT_MQTT_PORT, CFGF_NONE),
CFG_STR (SETTING_CERTS_PATH, DEFAULT_CERTS_PATH, CFGF_NONE),
CFG_STR (SETTING_ROOTCA_NAME, AWS_IOT_ROOT_CA_FILENAME,CFGF_NONE),
CFG_STR (SETTING_SCERT_NAME, AWS_IOT_CERTIFICATE_FILENAME,CFGF_NONE),
CFG_STR (SETTING_PRIVKEY_NAME, AWS_IOT_PRIVATE_KEY_FILENAME,CFGF_NONE),
/* Needed for unknown settings. */
CFG_STR (SETTING_UNKNOWN, NULL, CFGF_NONE),
CFG_END()
};
if (!file_exists(filename)) {
IOT_ERROR("File '%s' does not exist.", filename);
return -1;
}
if (!file_readable(filename)) {
IOT_ERROR("File '%s' cannot be read.", filename);
return -1;
}
cfg = cfg_init(opts, CFGF_IGNORE_UNKNOWN);
cfg_set_validate_func(cfg, SETTING_THING_NAME, cfg_check_thing_name);
cfg_set_validate_func(cfg, SETTING_CLIENT_ID, cfg_check_client_id);
cfg_set_validate_func(cfg, SETTING_HOST, cfg_check_empty_string);
cfg_set_validate_func(cfg, SETTING_PORT, cfg_check_port);
cfg_set_validate_func(cfg, SETTING_CERTS_PATH, cfg_check_certificates_path);
cfg_set_validate_func(cfg, SETTING_ROOTCA_NAME, cfg_check_cert_file);
cfg_set_validate_func(cfg, SETTING_SCERT_NAME, cfg_check_cert_file);
cfg_set_validate_func(cfg, SETTING_PRIVKEY_NAME, cfg_check_cert_file);
/* Parse the configuration file. */
switch (cfg_parse(cfg, filename)) {
case CFG_FILE_ERROR:
IOT_ERROR("Configuration file '%s' could not be read: %s\n",
filename, strerror(errno));
return -1;
case CFG_SUCCESS:
break;
case CFG_PARSE_ERROR:
IOT_ERROR("Error parsing configuration file '%s'\n", filename);
return -1;
}
return fill_aws_iot_config(aws_cfg);
}
/*
* free_configuration() - Release the configuration var
*/
void free_configuration(void)
{
cfg_free(cfg);
}
/*
* fill_aws_iot_config() - Fill the AWS IoT Device SDK configuration struct
*
* @aws_cfg: AWS IoT Device SDK configuration struct (aws_iot_cfg_t).
*
* Return: 0 if the configuration is filled successfully, -1 otherwise.
*/
static int fill_aws_iot_config(aws_iot_cfg_t *aws_cfg)
{
aws_cfg->thing_name = cfg_getstr(cfg, SETTING_THING_NAME);
if (aws_cfg->thing_name == NULL)
return -1;
aws_cfg->client_id = cfg_getstr(cfg, SETTING_CLIENT_ID);
if (aws_cfg->client_id == NULL)
return -1;
aws_cfg->host = cfg_getstr(cfg, SETTING_HOST);
if (aws_cfg->host == NULL)
return -1;
aws_cfg->port = cfg_getint(cfg, SETTING_PORT);
aws_cfg->certs_path = cfg_getstr(cfg, SETTING_CERTS_PATH);
if (aws_cfg->certs_path == NULL)
return -1;
aws_cfg->rootca_fname = cfg_getstr(cfg, SETTING_ROOTCA_NAME);
if (aws_cfg->rootca_fname == NULL)
return -1;
aws_cfg->signed_cert_fname = cfg_getstr(cfg, SETTING_SCERT_NAME);
if (aws_cfg->signed_cert_fname == NULL)
return -1;
aws_cfg->priv_key_fname = cfg_getstr(cfg, SETTING_PRIVKEY_NAME);
if (aws_cfg->priv_key_fname == NULL)
return -1;
return 0;
}
/*
* cfg_check_thing_name() - Validate thing_name in the configuration file
*
* @cfg: The section where the option is defined.
* @opt: The option to check.
*
* @Return: 0 on success, any other value otherwise.
*/
static int cfg_check_thing_name(cfg_t *cfg, cfg_opt_t *opt)
{
return cfg_check_string_length(cfg, opt, 1, MAX_SIZE_OF_THING_NAME);
}
/*
* cfg_check_client_id() - Validate client_id in the configuration file
*
* @cfg: The section where the option is defined.
* @opt: The option to check.
*
* @Return: 0 on success, any other value otherwise.
*/
static int cfg_check_client_id(cfg_t *cfg, cfg_opt_t *opt)
{
return cfg_check_string_length(cfg, opt, 1, MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES);
}
/*
* cfg_check_port() - Validate port is between 0 and 65535
*
* @cfg: The section where the option is defined.
* @opt: The option to check.
*
* @Return: 0 on success, any other value otherwise.
*/
static int cfg_check_port(cfg_t *cfg, cfg_opt_t *opt)
{
return cfg_check_int_range(cfg, opt, 0, 65535);
}
/*
* cfg_check_certificates_path() - Validate certs_path value
*
* @cfg: The section where the option is defined.
* @opt: The option to check.
*
* @Return: 0 on success, any other value otherwise.
*/
static int cfg_check_certificates_path(cfg_t *cfg, cfg_opt_t *opt)
{
DIR *dir = NULL;
char *val = cfg_opt_getnstr(opt, 0);
dir = opendir(val);
if (dir) {
closedir(dir);
return 0;
} else {
cfg_error(cfg,
"Invalid %s: cannot find directory '%s'",
opt->name, val);
return -1;
}
}
/*
* cfg_check_cert_file() - Validate certificate file name value
*
* @cfg: The section where the option is defined.
* @opt: The option to check.
*
* @Return: 0 on success, any other value otherwise.
*/
static int cfg_check_cert_file(cfg_t *cfg, cfg_opt_t *opt)
{
char *certs_path = NULL;
char *file_path = NULL;
char *val = cfg_opt_getnstr(opt, 0);
if (cfg_check_string_length(cfg, opt, 1, 0) != 0)
return -1;
certs_path = cfg_getstr(cfg, SETTING_CERTS_PATH);
if (certs_path == NULL) {
cfg_error(cfg, "Invalid %s: NULL value", SETTING_CERTS_PATH);
return -1;
}
file_path = calloc(1, sizeof(*file_path) * (strlen(certs_path) + strlen(val) + 2));
if (file_path == NULL) {
cfg_error(cfg,
"Error checking %s: unable to allocate memory",
opt->name);
return -1;
}
sprintf(file_path, "%s/%s", certs_path, val);
if (!file_readable(file_path)) {
cfg_error(cfg,
"Invalid %s: cannot find file '%s'", opt->name, val);
free(file_path);
return -1;
}
free(file_path);
return 0;
}
/*
* cfg_check_int_range() - Validate a parameter int value is between the given range
*
* @cfg: The section where the option is defined.
* @opt: The option to check.
* @min: Minimum value of the parameter.
* @max: Maximum value of the parameter.
*
* @Return: 0 on success, any other value otherwise.
*/
static int cfg_check_int_range(cfg_t *cfg, cfg_opt_t *opt, uint32_t min, uint32_t max)
{
unsigned long val = cfg_opt_getnint(opt, 0);
if (val > max || val < min) {
cfg_error(cfg,
"Invalid %s (%d): value must be between %d and %d",
opt->name, val, min, max);
return -1;
}
return 0;
}
/*
* cfg_check_empty_string() - Validate string is empty
*
* @cfg: The section where the option is defined.
* @opt: The option to check.
*
* @Return: 0 on success, any other value otherwise.
*/
static int cfg_check_empty_string(cfg_t *cfg, cfg_opt_t *opt)
{
return cfg_check_string_length(cfg, opt, 1, 0);
}
/*
* cfg_check_string_length() - Validate the length of a string is in range
*
* @cfg: The section where the option is defined.
* @opt: The option to check.
* @min: The string minimum length.
* @max: The string maximum length. 0 to unlimited.
*
* @Return: 0 on success, any other value otherwise.
*/
static int cfg_check_string_length(cfg_t *cfg, cfg_opt_t *opt, uint16_t min, uint16_t max)
{
char *val = cfg_opt_getnstr(opt, 0);
if ((val == NULL) || ((strlen(val) == 0) && (min > 0))) {
cfg_error(cfg, "Invalid %s (%s): cannot be empty", opt->name, val);
return -1;
}
if (strlen(val) < min) {
cfg_error(cfg,
"Invalid %s (%s): cannot be shorter than %d character(s)",
opt->name, val, min);
return -1;
}
if (max != 0 && strlen(val) > max) {
cfg_error(cfg,
"Invalid %s (%s): cannot be longer than %d character(s)",
opt->name, val, max);
return -1;
}
return 0;
}
/**
* file_exists() - Check that the file with the given name exists
*
* @filename: Full path of the file to check if it exists.
*
* Return: 1 if the file exits, 0 if it does not exist.
*/
static int file_exists(const char *const filename)
{
return access(filename, F_OK) == 0;
}
/**
* file_readable() - Check that the file with the given name can be read
*
* @filename: Full path of the file to check if it is readable.
*
* Return: 1 if the file is readable, 0 if it cannot be read.
*/
static int file_readable(const char *const filename)
{
return access(filename, R_OK) == 0;
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2017 Digi International Inc.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*
* Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
* =======================================================================
*/
#ifndef AWS_CONFIG_H_
#define AWS_CONFIG_H_
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
/*------------------------------------------------------------------------------
D E F I N I T I O N S
------------------------------------------------------------------------------*/
#define AWS_IOT_CONFIG_FILE "/etc/awsiotsdk.conf"
#define DEFAULT_CERTS_PATH "/etc/ssl/certs"
/*------------------------------------------------------------------------------
D A T A T Y P E S D E F I N I T I O N S
------------------------------------------------------------------------------*/
/**
* aws_iot_cfg_t - AWS IoT Device SDK configuration type
*
* @thing_name: Thing Name of the Shadow this device is associated with
* @client_id: MQTT client ID. It should be unique for every device
* @host: MQTT host
* @port: Port for MQTT/S
* @certs_path: Absolute path to the certificates directory
* @rootca_fname: Name of the Root CA file
* @signed_cert_fname: Name of the device signed certificate
* @priv_key_fname: Name of the device private key
*/
typedef struct {
char *thing_name;
char *client_id;
char *host;
int port;
char *certs_path;
char *rootca_fname;
char *signed_cert_fname;
char *priv_key_fname;
} aws_iot_cfg_t;
/*------------------------------------------------------------------------------
F U N C T I O N D E C L A R A T I O N S
------------------------------------------------------------------------------*/
int parse_configuration(const char *const filename, aws_iot_cfg_t *aws_cfg);
void free_configuration(void);
#endif /* AWS_CONFIG_H_ */

View File

@ -23,6 +23,7 @@
#include <signal.h>
#include <unistd.h>
#include "aws_config.h"
#include "daemonize.h"
/*------------------------------------------------------------------------------
@ -38,6 +39,8 @@
"\n" \
"Usage: %s [options]\n\n" \
" -d --daemon Daemonize the process\n" \
" -c --config-file=<PATH> Use a custom configuration file instead of\n" \
" the default one located in " AWS_IOT_CONFIG_FILE "\n" \
" -h --help Print help and exit\n" \
"\n"
@ -66,9 +69,10 @@ int main(int argc, char **argv)
static int opt, opt_index;
int create_daemon = 0;
char *config_file = NULL;
static const char *short_options = "dh";
static const char *short_options = "dc:h";
static const struct option long_options[] = {
{"daemon", no_argument, NULL, 'd'},
{"config-file", required_argument, NULL, 'c'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
@ -83,6 +87,9 @@ int main(int argc, char **argv)
case 'd':
create_daemon = 1;
break;
case 'c':
config_file = optarg;
break;
case 'h':
usage(name);
goto done;
@ -119,9 +126,13 @@ done:
static int start_aws_iot(const char *config_file)
{
int result = EXIT_SUCCESS;
aws_iot_cfg_t aws_cfg;
add_sigkill_signal();
if (parse_configuration(config_file ? config_file : AWS_IOT_CONFIG_FILE, &aws_cfg) != 0)
return EXIT_FAILURE;;
do {
sleep(1);
} while (!check_stop());