1/*
2 * server-coap.c -- RIOT example
3 *
4 * Copyright (C) 2023 Jon Shallow <supjps-libcoap@jpshallow.com>
5 *
6 * SPDX-License-Identifier: BSD-2-Clause
7 *
8 * This file is part of the CoAP library libcoap. Please see README for terms
9 * of use.
10 */
11
12#include "coap_config.h"
13#include <coap3/coap.h>
14#include "server-coap.h"
15#include "macros/utils.h"
16
17#ifdef CONFIG_LIBCOAP_USE_PSK
18#define COAP_USE_PSK CONFIG_LIBCOAP_USE_PSK
19#else /* CONFIG_LIBCOAP_USE_PSK */
20#define COAP_USE_PSK NULL
21#endif /* CONFIG_LIBCOAP_USE_PSK */
22
23static volatile int running = 0;
24static int quit;
25
26coap_context_t *main_coap_context;
27
28static coap_time_t clock_offset;
29/* changeable clock base (see handle_put_time()) */
30static coap_time_t my_clock_base = 0;
31static coap_resource_t *time_resource = NULL; /* just for testing */
32
33static void
34hnd_get_time(coap_resource_t *resource, coap_session_t  *session,
35             const coap_pdu_t *request, const coap_string_t *query,
36             coap_pdu_t *response) {
37  unsigned char buf[40];
38  size_t len;
39  coap_tick_t now;
40  coap_tick_t t;
41
42  (void)resource;
43  (void)session;
44  (void)request;
45  /* FIXME: return time, e.g. in human-readable by default and ticks
46   * when query ?ticks is given. */
47
48  /* if my_clock_base was deleted, we pretend to have no such resource */
49  coap_pdu_set_code(response, my_clock_base ? COAP_RESPONSE_CODE_CONTENT :
50                    COAP_RESPONSE_CODE_NOT_FOUND);
51  if (my_clock_base) {
52    coap_add_option(response, COAP_OPTION_CONTENT_FORMAT,
53                    coap_encode_var_safe(buf, sizeof(buf),
54                                         COAP_MEDIATYPE_TEXT_PLAIN),
55                    buf);
56  }
57
58  coap_add_option(response, COAP_OPTION_MAXAGE,
59                  coap_encode_var_safe(buf, sizeof(buf), 0x01), buf);
60
61  if (my_clock_base) {
62
63    /* calculate current time */
64    coap_ticks(&t);
65    now = my_clock_base + (t / COAP_TICKS_PER_SECOND);
66
67    if (query != NULL
68        && coap_string_equal(query, coap_make_str_const("ticks"))) {
69      /* output ticks */
70      len = snprintf((char *)buf, sizeof(buf), "%u", (unsigned int)now);
71      coap_add_data(response, len, buf);
72    }
73  }
74}
75
76static void
77init_coap_resources(coap_context_t *ctx) {
78  coap_resource_t *r;
79#if 0
80  r = coap_resource_init(NULL, 0, 0);
81  coap_register_handler(r, COAP_REQUEST_GET, hnd_get_index);
82
83  coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0);
84  coap_add_attr(r, coap_make_str_const("title"), coap_make_str_const("\"General Info\""), 0);
85  coap_add_resource(ctx, r);
86#endif
87  /* store clock base to use in /time */
88  my_clock_base = clock_offset;
89
90  r = coap_resource_init(coap_make_str_const("time"), 0);
91  if (!r) {
92    goto error;
93  }
94
95  coap_resource_set_get_observable(r, 1);
96  time_resource = r;
97  coap_register_handler(r, COAP_REQUEST_GET, hnd_get_time);
98#if 0
99  coap_register_handler(r, COAP_REQUEST_PUT, hnd_put_time);
100  coap_register_handler(r, COAP_REQUEST_DELETE, hnd_delete_time);
101#endif
102  coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0);
103  /* coap_add_attr(r, coap_make_str_const("title"),
104                   coap_make_str_const("\"Internal Clock\""), 0); */
105  coap_add_attr(r, coap_make_str_const("rt"), coap_make_str_const("\"ticks\""), 0);
106  coap_add_attr(r, coap_make_str_const("if"), coap_make_str_const("\"clock\""), 0);
107
108  coap_add_resource(ctx, r);
109#if 0
110  if (coap_async_is_supported()) {
111    r = coap_resource_init(coap_make_str_const("async"), 0);
112    coap_register_handler(r, COAP_REQUEST_GET, hnd_get_async);
113
114    coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0);
115    coap_add_resource(ctx, r);
116  }
117#endif
118
119  return;
120error:
121  coap_log_crit("cannot create resource\n");
122}
123
124static int
125init_coap_context_endpoints(const char *use_psk) {
126  coap_address_t listenaddress;
127  gnrc_netif_t *netif = gnrc_netif_iter(NULL);
128  ipv6_addr_t addr;
129  char addr_str[INET6_ADDRSTRLEN + 8];
130  int scheme_hint_bits = 1 << COAP_URI_SCHEME_COAP;
131  coap_addr_info_t *info = NULL;
132  coap_addr_info_t *info_list = NULL;
133  coap_str_const_t local;
134  int have_ep = 0;
135
136  /* Get the first address on the interface */
137  if (gnrc_netif_ipv6_addrs_get(netif, &addr, sizeof(addr)) < 0) {
138    puts("Unable to get first address of the interface");
139    return 0;
140  }
141
142  coap_address_init(&listenaddress);
143  listenaddress.addr.sin6.sin6_family = AF_INET6;
144  memcpy(&listenaddress.addr.sin6.sin6_addr, &addr,
145         sizeof(listenaddress.addr.sin6.sin6_addr));
146  coap_print_ip_addr(&listenaddress, addr_str, sizeof(addr_str));
147  coap_log_info("Server IP [%s]\n", addr_str);
148
149  main_coap_context = coap_new_context(NULL);
150  if (!main_coap_context) {
151    return 0;
152  }
153
154  if (use_psk && coap_dtls_is_supported()) {
155    coap_dtls_spsk_t setup_data;
156
157    /* Need PSK set up before setting up endpoints */
158    memset(&setup_data, 0, sizeof(setup_data));
159    setup_data.version = COAP_DTLS_SPSK_SETUP_VERSION;
160    setup_data.psk_info.key.s = (const uint8_t *)use_psk;
161    setup_data.psk_info.key.length = strlen(use_psk);
162    coap_context_set_psk2(main_coap_context, &setup_data);
163    scheme_hint_bits |= 1 << COAP_URI_SCHEME_COAPS;
164  }
165
166  local.s = (uint8_t *)addr_str;
167  local.length = strlen(addr_str);
168  info_list = coap_resolve_address_info(&local, COAP_DEFAULT_PORT,
169                                        COAPS_DEFAULT_PORT,
170                                        0, 0,
171                                        0,
172                                        scheme_hint_bits,
173                                        COAP_RESOLVE_TYPE_REMOTE);
174  for (info = info_list; info != NULL; info = info->next) {
175    coap_endpoint_t *ep;
176
177    ep = coap_new_endpoint(main_coap_context, &info->addr, info->proto);
178    if (!ep) {
179      coap_log_warn("cannot create endpoint for proto %u\n",
180                    info->proto);
181    } else {
182      have_ep = 1;
183    }
184  }
185  coap_free_address_info(info_list);
186  if (!have_ep) {
187    return 0;
188  }
189
190  return 1;
191}
192
193void *
194server_coap_run(void *arg) {
195  (void)arg;
196
197  /* Initialize libcoap library */
198  coap_startup();
199
200  coap_set_log_level(COAP_MAX_LOGGING_LEVEL);
201
202  if (!init_coap_context_endpoints(COAP_USE_PSK)) {
203    goto fail;
204  }
205
206  /* Limit the number of idle sessions to save RAM */
207  coap_context_set_max_idle_sessions(main_coap_context, 2);
208  clock_offset = 1; /* Need a non-zero value */
209  init_coap_resources(main_coap_context);
210
211  coap_log_info("libcoap server ready\n");
212  /* Keep on processing ... */
213  while (quit == 0) {
214    coap_io_process(main_coap_context, 1000);
215  }
216fail:
217  /* Clean up library usage so client can be run again */
218  coap_free_context(main_coap_context);
219  main_coap_context = NULL;
220  coap_cleanup();
221  running = 0;
222  quit = 0;
223  coap_log_info("libcoap server stopped\n");
224  return NULL;
225}
226
227static char server_stack[THREAD_STACKSIZE_MAIN +
228                                               THREAD_EXTRA_STACKSIZE_PRINTF];
229
230static
231void
232start_server(void) {
233  kernel_pid_t server_pid;
234
235  /* Only one instance of the server */
236  if (running) {
237    puts("Error: server already running");
238    return;
239  }
240
241  /* The server is initialized */
242  server_pid = thread_create(server_stack,
243                             sizeof(server_stack),
244                             THREAD_PRIORITY_MAIN - 1,
245                             THREAD_CREATE_STACKTEST,
246                             server_coap_run, NULL, "libcoap_server");
247
248  /* Uncommon but better be sure */
249  if (server_pid == EINVAL) {
250    puts("ERROR: Thread invalid");
251    return;
252  }
253
254  if (server_pid == EOVERFLOW) {
255    puts("ERROR: Thread overflow!");
256    return;
257  }
258
259  running = 1;
260  return;
261}
262
263static
264void
265stop_server(void) {
266  /* check if server is running at all */
267  if (running == 0) {
268    puts("Error: libcoap server is not running");
269    return;
270  }
271
272  quit = 1;
273
274  puts("Stopping server...");
275}
276
277void
278server_coap_init(int argc, char **argv) {
279  if (argc < 2) {
280    printf("usage: %s start|stop\n", argv[0]);
281    return;
282  }
283  if (strcmp(argv[1], "start") == 0) {
284    start_server();
285  } else if (strcmp(argv[1], "stop") == 0) {
286    stop_server();
287  } else {
288    printf("Error: invalid command. Usage: %s start|stop\n", argv[0]);
289  }
290  return;
291}
292