1// -*- mode:doc; -*- 2// vim: set syntax=asciidoc,tw=0: 3 4coap_observe(3) 5================= 6:doctype: manpage 7:man source: coap_observe 8:man version: @PACKAGE_VERSION@ 9:man manual: libcoap Manual 10 11NAME 12---- 13coap_observe, 14coap_resource_set_get_observable, 15coap_resource_notify_observers, 16coap_cancel_observe 17- work with CoAP observe 18 19SYNOPSIS 20-------- 21*#include <coap@LIBCOAP_API_VERSION@/coap.h>* 22 23*void coap_resource_set_get_observable(coap_resource_t *_resource_, 24int _mode_);* 25 26*int coap_resource_notify_observers(coap_resource_t *_resource_, 27const coap_string_t *_query_);* 28 29*int coap_cancel_observe(coap_session_t *_session_, coap_binary_t *_token_, 30coap_pdu_type_t _message_type_);* 31 32For specific (D)TLS library support, link with 33*-lcoap-@LIBCOAP_API_VERSION@-notls*, *-lcoap-@LIBCOAP_API_VERSION@-gnutls*, 34*-lcoap-@LIBCOAP_API_VERSION@-openssl*, *-lcoap-@LIBCOAP_API_VERSION@-mbedtls* 35or *-lcoap-@LIBCOAP_API_VERSION@-tinydtls*. Otherwise, link with 36*-lcoap-@LIBCOAP_API_VERSION@* to get the default (D)TLS library support. 37 38DESCRIPTION 39----------- 40RFC 7641 extends the CoAP protocol to be able to monitor the state of a 41resource over time. 42 43This enables clients to "observe" resources with a defined query, i.e., to 44retrieve a representation of a resource and keep this representation updated 45by the server over a period of time. 46 47The server has to flag a resource as "observable", and then the client has 48to request in a GET request that it wants to observe this resource by the use 49of the COAP_OPTION_OBSERVE Option with a value of COAP_OBSERVE_ESTABLISH. 50Optionally, the client can specify query options for the resource, or by using 51a FETCH request instead of a GET to define a query (RFC8132). 52 53To remove the "observe" subscription, the client has to issue a GET (or FETCH) 54request with the COAP_OPTION_OBSERVE Option with a value of 55COAP_OBSERVE_CANCEL using the same token and other options used for making the 56initial "observe" request. Alternatively, "observe" can be cancelled using 57*coap_cancel_observe*() instead. 58 59The underlying library adds in and removes "subscribers" to "observe" the 60resource as appropriate in the server side logic. 61 62Within the server application, it needs to determine that there is a change of 63state of the resource under observation, and then cause the CoAP library 64layer to initiate a "fake GET/FETCH request" so that an observe GET/FETCH 65response gets sent back to all the clients that are observing the resource. The 66appropriate GET/FETCH handler within the server application is called to fill 67in the response packet with the appropriate information. This "fake GET/FETCH 68request" is triggered by a call to *coap_resource_notify_observers*(). 69 70The call to *coap_io_process*() in the main server application i/o loop will do 71all the necessary processing of sending any outstanding "fake GET/FETCH 72requests". 73 74Whenever the server sends a copy of the state of the "observed" resource to 75the client, it will use the same token used by the client when the client 76requested the "observe". The client will receive this observe response 77in the handler defined by *coap_register_response_handler*(3). It is the 78responsibility of the client application to match the supplied token and 79update the appropriate internal information. 80 81The *coap_resource_set_get_observable*() function enables or disables the 82observable status of the _resource_ by the setting of _mode_. If _mode_ is 831, then the _resource_ is observable. If _mode_ is 0, then the 84_resource_ is no longer observable. 85 86*NOTE:* It is not possible for the Unknown Resource, created by 87*coap_resource_unknown_init*(3), to be observable as the Uri-Path is not known 88when libcoap creates a "fake GET/FETCH request". The Unknown Resource PUT 89handler must create a new resource and mark the resource as "observable" if 90a specific resource needs to be observable. The application must then 91manage the deletion of the resource at the appropriate time. 92 93*NOTE:* The type (confirmable or non-confirmable) of the triggered observe 94GET response is determined not by the initial GET/FETCH request, but 95independently by the server as per 96https://tools.ietf.org/html/rfc7641#section-3.5. This is controlled by the 97flags (one of COAP_RESOURCE_FLAGS_NOTIFY_NON, 98COAP_RESOURCE_FLAGS_NOTIFY_NON_ALWAYS or COAP_RESOURCE_FLAGS_NOTIFY_CON) 99used when creating the resource using *coap_resource_init*(3). 100 101*NOTE:* Furthermore, the server must send at least one "observe" response as 102confirmable, when generally sending non-confirmable, at least every 24 hours 103as per https://tools.ietf.org/html/rfc7641#section-4.5. 104Libcoap automatically handles this by sending every fifth (COAP_OBS_MAX_NON) 105response as a confirmable response for detection that the client is still 106responding unless if COAP_RESOURCE_FLAGS_NOTIFY_NON_ALWAYS is set, which is a 107RFC7641 violation, where non-confirmable "observe" responses are always sent 108as required by some higher layer protocols. 109 110The *coap_resource_notify_observers*() function needs to be called whenever the 111server application determines that there has been a change to the state of 112_resource_. The _query_ parameter is obsolete and ignored. 113 114The *coap_cancel_observe*() function can be used by the client to cancel an 115observe request that is being tracked. This will cause the 116appropriate PDU to be sent to the server to cancel the observation, based on 117the _session_ and _token_ used to set up the observe and the PDU is of type 118_message_type_ (use COAP_MESSAGE_NON or COAP_MESSAGE_CON). 119 120RETURN VALUES 121------------- 122The *coap_resource_set_get_observable*() function return 0 on failure, 1 on 123success. 124 125The *coap_cancel_observe*() function return 0 on failure, 1 on success. 126 127EXAMPLES 128-------- 129*Simple Time Server* 130 131[source, c] 132---- 133#include <coap@LIBCOAP_API_VERSION@/coap.h> 134 135#include <stdio.h> 136 137coap_resource_t *time_resource = NULL; 138 139/* specific GET "time" handler, called from hnd_get_generic() */ 140 141static void 142hnd_get_time(coap_resource_t *resource, coap_session_t *session, 143const coap_pdu_t *request, const coap_string_t *query, coap_pdu_t *response) { 144 145 unsigned char buf[40]; 146 size_t len; 147 time_t now; 148 (void)resource; 149 (void)session; 150 151 /* ... Additional analysis code for resource, request pdu etc. ... */ 152 153 /* After analysis, generate a suitable response */ 154 155 /* Note that token, if set, is already in the response pdu */ 156 157 now = time(NULL); 158 159 if (query != NULL && coap_string_equal(query, coap_make_str_const("secs"))) { 160 /* Output secs since Jan 1 1970 */ 161 len = snprintf((char *)buf, sizeof(buf), "%lu", now); 162 } 163 else { 164 /* Output human-readable time */ 165 struct tm *tmp; 166 tmp = gmtime(&now); 167 if (!tmp) { 168 /* If 'now' is not valid */ 169 coap_pdu_set_code(response, COAP_RESPONSE_CODE_NOT_FOUND); 170 return; 171 } 172 len = strftime((char *)buf, sizeof(buf), "%b %d %H:%M:%S", tmp); 173 } 174 coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT); 175 /* 176 * Invoke coap_add_data_large_response() to do all the hard work. 177 * 178 * Define the format - COAP_MEDIATYPE_TEXT_PLAIN - to add in 179 * Define how long this response is valid for (secs) - 1 - to add in. 180 * ETAG Option added internally with unique value as param set to 0 181 * 182 * OBSERVE Option added internally if needed within the function 183 * BLOCK2 Option added internally if output too large 184 * SIZE2 Option added internally 185 */ 186 coap_add_data_large_response(resource, session, request, response, 187 query, COAP_MEDIATYPE_TEXT_PLAIN, 1, 0, 188 len, 189 buf, NULL, NULL); 190} 191 192/* Generic GET handler */ 193 194static void 195hnd_get_generic(coap_resource_t *resource, coap_session_t *session, 196const coap_pdu_t *request, const coap_string_t *query, coap_pdu_t *response) { 197 198 coap_str_const_t *uri_path = coap_resource_get_uri_path(resource); 199 200 if (!uri_path) { 201 /* Unexpected Failure */ 202 coap_pdu_set_code(response, COAP_RESPONSE_CODE_BAD_REQUEST); 203 return; 204 } 205 206 /* Is this the "time" resource" ? */ 207 if (coap_string_equal(uri_path, coap_make_str_const("time"))) { 208 hnd_get_time(resource, session, request, query, response); 209 return; 210 } 211 212 /* Other resources code */ 213 214 /* Failure response */ 215 coap_pdu_set_code(response, COAP_RESPONSE_CODE_BAD_REQUEST); 216} 217 218/* Initialize generic GET handler */ 219 220static void 221init_resources(coap_context_t *ctx) 222{ 223 224 coap_resource_t *r; 225 226 /* Create a resource to return return or update time */ 227 r = coap_resource_init(coap_make_str_const("time"), 228 COAP_RESOURCE_FLAGS_NOTIFY_CON); 229 230 /* We are using a generic GET handler here */ 231 coap_register_handler(r, COAP_REQUEST_GET, hnd_get_generic); 232 233 coap_resource_set_get_observable(r, 1); 234 235 coap_add_resource(ctx, r); 236 time_resource = r; 237 238} 239 240int main(int argc, char *argv[]){ 241 242 coap_context_t *ctx = NULL; 243 coap_endpoint_t *ep = NULL; 244 coap_address_t addr; 245 unsigned wait_ms; 246 struct timeval tv_last = {0, 0}; 247 /* Remove (void) definition if variable is used */ 248 (void)argc; 249 (void)argv; 250 251 memset (&tv_last, 0, sizeof(tv_last)); 252 253 /* Create the libcoap context */ 254 ctx = coap_new_context(NULL); 255 if (!ctx) { 256 exit(1); 257 } 258 /* See coap_block(3) */ 259 coap_context_set_block_mode(ctx, 260 COAP_BLOCK_USE_LIBCOAP | COAP_BLOCK_SINGLE_BODY); 261 262 coap_address_init(&addr); 263 addr.addr.sa.sa_family = AF_INET; 264 addr.addr.sin.sin_port = ntohs(COAP_DEFAULT_PORT); 265 ep = coap_new_endpoint(ctx, &addr, COAP_PROTO_UDP); 266 267 /* Other Set up Code */ 268 269 init_resources(ctx); 270 271 wait_ms = COAP_RESOURCE_CHECK_TIME * 1000; 272 273 while (1) { 274 int result = coap_io_process( ctx, wait_ms ); 275 if ( result < 0 ) { 276 break; 277 } else if ( result && (unsigned)result < wait_ms ) { 278 /* decrement if there is a result wait time returned */ 279 wait_ms -= result; 280 } else { 281 /* 282 * result == 0, or result >= wait_ms 283 * (wait_ms could have decremented to a small value, below 284 * the granularity of the timer in coap_io_process() and hence 285 * result == 0) 286 */ 287 wait_ms = COAP_RESOURCE_CHECK_TIME * 1000; 288 } 289 if (time_resource) { 290 struct timeval tv_now; 291 if (gettimeofday (&tv_now, NULL) == 0) { 292 if (tv_last.tv_sec != tv_now.tv_sec) { 293 /* Happens once per second */ 294 tv_last = tv_now; 295 coap_resource_notify_observers(time_resource, NULL); 296 } 297 /* need to wait until next second starts if wait_ms is too large */ 298 unsigned next_sec_ms = 1000 - (tv_now.tv_usec / 1000); 299 300 if (next_sec_ms && next_sec_ms < wait_ms) 301 wait_ms = next_sec_ms; 302 } 303 } 304 } 305 exit(0); 306 307} 308---- 309 310*Client Observe Request Setup* 311 312[source, c] 313---- 314#include <coap@LIBCOAP_API_VERSION@/coap.h> 315 316/* Usually, requests are sent confirmable */ 317 318static unsigned char msgtype = COAP_MESSAGE_CON; 319 320static unsigned int token = 0; 321 322static coap_pdu_t * 323coap_new_request(coap_context_t *context, coap_session_t *session, char request_code, 324coap_optlist_t **options, unsigned char *data, size_t length, int observe) { 325 326 coap_pdu_t *pdu; 327 /* Remove (void) definition if variable is used */ 328 (void)context; 329 330 /* Create the pdu with the appropriate options */ 331 pdu = coap_pdu_init(msgtype, request_code, coap_new_message_id(session), 332 coap_session_max_pdu_size(session)); 333 if (!pdu) 334 return NULL; 335 336 /* 337 * Create uniqueness token for this request for handling unsolicited / 338 * delayed responses 339 */ 340 token++; 341 if (!coap_add_token(pdu, sizeof(token), (unsigned char*)&token)) { 342 coap_log(LOG_DEBUG, "cannot add token to request\n"); 343 goto error; 344 } 345 346 if (request_code == COAP_REQUEST_GET && observe) { 347 /* Indicate that we want to observe this resource */ 348 if (!coap_insert_optlist(options, 349 coap_new_optlist(COAP_OPTION_OBSERVE, 350 COAP_OBSERVE_ESTABLISH, NULL))) 351 goto error; 352 } 353 354 /* ... Other code / options etc. ... */ 355 356 /* Add in all the options (after internal sorting) to the pdu */ 357 if (!coap_add_optlist_pdu(pdu, options)) 358 goto error; 359 360 if (data && length) { 361 /* Add in the specified data */ 362 if (!coap_add_data(pdu, length, data)) 363 goto error; 364 } 365 366 return pdu; 367 368error: 369 370 coap_delete_pdu(pdu); 371 return NULL; 372 373} 374---- 375 376SEE ALSO 377-------- 378*coap_attribute*(3), *coap_block*(3), *coap_context*(3), *coap_handler*(3), 379*coap_pdu_setup*(3) and *coap_resource*(3) 380 381FURTHER INFORMATION 382------------------- 383"RFC7252: The Constrained Application Protocol (CoAP)" 384 385"RFC7641: Observing Resources in the Constrained Application Protocol (CoAP)" 386 387"RFC8132: PATCH and FETCH Methods for the Constrained Application Protocol (CoAP)" 388 389BUGS 390---- 391Please report bugs on the mailing list for libcoap: 392libcoap-developers@lists.sourceforge.net or raise an issue on GitHub at 393https://github.com/obgm/libcoap/issues 394 395AUTHORS 396------- 397The libcoap project <libcoap-developers@lists.sourceforge.net> 398