Skip to content
Permalink
Browse files

Anjay 1.15.5

Bug fixes:
- Updated avs_commons to 3.10.0, which includes a fix that drastically (one
  order of magnitude in some cases) changes the result of
  `avs_coap_exchange_lifetime()`. Previously the results were not in line
  with RFC7252 requirements.

Improvements:
- The client will no longer wait indefinitely for Bootstrap Finish, but
  rather for an interval of at most EXCHANGE_LIFETIME seconds since last
  Bootstrap Interface operation.
  • Loading branch information...
sznaider committed Apr 24, 2019
1 parent a488f1c commit 9f5131b7200e94e2afdc419ce462a5c1b2868372
@@ -15,7 +15,7 @@
cmake_minimum_required(VERSION 2.8.12)

project(anjay C)
set(ANJAY_VERSION "1.15.4" CACHE STRING "Anjay library version")
set(ANJAY_VERSION "1.15.5" CACHE STRING "Anjay library version")
set(ANJAY_BINARY_VERSION 1.0.0)

set(ANJAY_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
@@ -526,15 +526,15 @@ log_handler(avs_log_level_t level, const char *module, const char *message) {
(void) module;

char timebuf[128];
struct timeval now;
gettimeofday(&now, NULL);
time_t seconds = now.tv_sec;
avs_time_real_t now = avs_time_real_now();
time_t seconds = now.since_real_epoch.seconds;

struct tm *now_tm = localtime(&seconds);
assert(now_tm);
strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", now_tm);

fprintf(stderr, "%s.%06d %s\n", timebuf, (int) now.tv_usec, message);
fprintf(stderr, "%s.%06d %s\n", timebuf,
(int) now.since_real_epoch.nanoseconds / 1000, message);
}

static void cmdline_args_cleanup(cmdline_args_t *cmdline_args) {
@@ -26,6 +26,7 @@

#include <avsystem/commons/list.h>
#include <avsystem/commons/stream/stream_file.h>
#include <avsystem/commons/time.h>

typedef struct {
char data[1]; // actually a VLA, but struct cannot be empty
@@ -15,6 +15,7 @@
*/

#include "demo_args.h"
#include "demo.h"

#include <assert.h>
#include <ctype.h>
@@ -297,9 +297,9 @@ static bool has_multiple_bootstrap_security_instances(anjay_t *anjay) {
return false;
}

static int bootstrap_write(anjay_t *anjay,
const anjay_uri_path_t *uri,
anjay_input_ctx_t *in_ctx) {
static int bootstrap_write_impl(anjay_t *anjay,
const anjay_uri_path_t *uri,
anjay_input_ctx_t *in_ctx) {
anjay_log(DEBUG, "Bootstrap Write %s", ANJAY_DEBUG_MAKE_PATH(uri));
if (!_anjay_uri_path_has_oid(uri)) {
return ANJAY_ERR_METHOD_NOT_ALLOWED;
@@ -561,6 +561,7 @@ static int bootstrap_finish_impl(anjay_t *anjay, bool perform_timeout) {
start_bootstrap_if_not_already_started(anjay);
} else {
_anjay_schedule_reload_servers(anjay);
_anjay_sched_del(anjay->sched, &anjay->bootstrap.finish_timeout_handle);
}
return retval;
}
@@ -600,45 +601,91 @@ int _anjay_bootstrap_object_write(anjay_t *anjay,
.type = ANJAY_PATH_OBJECT,
.oid = oid
};
return bootstrap_write(anjay, &uri, in_ctx);
return bootstrap_write_impl(anjay, &uri, in_ctx);
}

static int invoke_action(anjay_t *anjay, const anjay_request_t *request) {
static void timeout_bootstrap_finish(anjay_t *anjay, const void *dummy) {
(void) dummy;
anjay_log(ERROR, "Bootstrap Finish not received in time - aborting");

// Abort client-initiated-bootstrap entirely. After that,
// anjay_all_connections_failed() starts returning true (if the
// bootstrap was the only server), which gives the user an opportunity
// to react accordingly.
anjay_server_info_t *server =
_anjay_servers_find_active(anjay, ANJAY_SSID_BOOTSTRAP);
if (server) {
_anjay_server_on_server_communication_error(anjay, server);
}
}

static int schedule_finish_timeout(anjay_t *anjay) {
_anjay_sched_del(anjay->sched, &anjay->bootstrap.finish_timeout_handle);
if (_anjay_sched(anjay->sched, &anjay->bootstrap.finish_timeout_handle,
avs_coap_exchange_lifetime(&anjay->udp_tx_params),
timeout_bootstrap_finish, NULL, 0)) {
anjay_log(ERROR, "could not schedule finish timeout");
return -1;
}
return 0;
}

static int bootstrap_write(anjay_t *anjay, const anjay_request_t *request) {
assert(request->action == ANJAY_ACTION_WRITE);
anjay_input_ctx_t *in_ctx = NULL;
const uint16_t format =
_anjay_translate_legacy_content_format(request->content_format);
int result = -1;
switch (request->action) {
case ANJAY_ACTION_WRITE:
if ((result = _anjay_input_dynamic_create(&in_ctx, &anjay->comm_stream,
false))) {
anjay_log(ERROR, "could not create input context");
return result;
}

if (format == ANJAY_COAP_FORMAT_TLV
&& _anjay_uri_path_has_rid(&request->uri)) {
result = _anjay_dm_check_if_tlv_rid_matches_uri_rid(
in_ctx, request->uri.rid);
}

if (!result) {
result = bootstrap_write(anjay, &request->uri, in_ctx);
}
if (_anjay_input_ctx_destroy(&in_ctx)) {
anjay_log(ERROR, "input ctx cleanup failed");
}
int result;
if ((result = _anjay_input_dynamic_create(&in_ctx, &anjay->comm_stream,
false))) {
anjay_log(ERROR, "could not create input context");
return result;
}

if (format == ANJAY_COAP_FORMAT_TLV
&& _anjay_uri_path_has_rid(&request->uri)) {
result = _anjay_dm_check_if_tlv_rid_matches_uri_rid(in_ctx,
request->uri.rid);
}

if (!result) {
result = bootstrap_write_impl(anjay, &request->uri, in_ctx);
}
if (_anjay_input_ctx_destroy(&in_ctx)) {
anjay_log(ERROR, "input ctx cleanup failed");
}
return result;
}

static int invoke_action(anjay_t *anjay, const anjay_request_t *request) {
_anjay_sched_del(anjay->sched, &anjay->bootstrap.finish_timeout_handle);

int result;
switch (request->action) {
case ANJAY_ACTION_WRITE:
result = bootstrap_write(anjay, request);
break;
case ANJAY_ACTION_DELETE:
return bootstrap_delete(anjay, request);
result = bootstrap_delete(anjay, request);
break;
case ANJAY_ACTION_DISCOVER:
return bootstrap_discover(anjay, request);
result = bootstrap_discover(anjay, request);
break;
case ANJAY_ACTION_BOOTSTRAP_FINISH:
return bootstrap_finish(anjay);
result = bootstrap_finish(anjay);
break;
default:
anjay_log(ERROR, "Invalid action for Bootstrap Interface");
return ANJAY_ERR_METHOD_NOT_ALLOWED;
result = ANJAY_ERR_METHOD_NOT_ALLOWED;
break;
}

if ((request->action != ANJAY_ACTION_BOOTSTRAP_FINISH || result)
&& schedule_finish_timeout(anjay)) {
result = -1;
}
return result;
}

int _anjay_bootstrap_perform_action(anjay_t *anjay,
@@ -807,6 +854,9 @@ static void request_bootstrap_job(anjay_t *anjay, const void *dummy) {
_anjay_server_on_registration_timeout(anjay, server);
} else if (result) {
_anjay_server_on_server_communication_error(anjay, server);
} else if (schedule_finish_timeout(anjay)) {
_anjay_server_on_failure(anjay, server,
"cannot schedule finish timeout job");
}
}
}
@@ -864,6 +914,7 @@ void _anjay_bootstrap_cleanup(anjay_t *anjay) {
reset_client_initiated_bootstrap_backoff(&anjay->bootstrap);
abort_bootstrap(anjay);
_anjay_sched_del(anjay->sched, &anjay->bootstrap.purge_bootstrap_handle);
_anjay_sched_del(anjay->sched, &anjay->bootstrap.finish_timeout_handle);
_anjay_notify_clear_queue(&anjay->bootstrap.notification_queue);
}

@@ -35,6 +35,7 @@ typedef struct {
anjay_notify_queue_t notification_queue;
anjay_sched_handle_t purge_bootstrap_handle;
anjay_sched_handle_t client_initiated_bootstrap_handle;
anjay_sched_handle_t finish_timeout_handle;
avs_time_monotonic_t client_initiated_bootstrap_last_attempt;
avs_time_duration_t client_initiated_bootstrap_holdoff;
} anjay_bootstrap_t;
@@ -453,10 +453,15 @@ void _anjay_server_update_registration_info(
anjay_update_parameters_t *move_params);

/**
* Handles a network communication error on the primary connection of the
* server. Effectively disables the server, and might schedule Client-Initiated
* Bootstrap if applicable.
* Handles a critical error (including network communication error) on the
* primary connection of the server. Effectively disables the server, and might
* schedule Client-Initiated Bootstrap if applicable.
*/
void _anjay_server_on_failure(anjay_t *anjay,
anjay_server_info_t *server,
const char *debug_msg);

/** Alias for @ref _anjay_server_on_failure(). */
void _anjay_server_on_server_communication_error(anjay_t *anjay,
anjay_server_info_t *server);

@@ -32,14 +32,16 @@

VISIBILITY_SOURCE_BEGIN

void _anjay_server_on_server_communication_error(anjay_t *anjay,
anjay_server_info_t *server) {
void _anjay_server_on_failure(anjay_t *anjay,
anjay_server_info_t *server,
const char *debug_msg) {
_anjay_server_clean_active_data(anjay, server);
server->refresh_failed = true;

if (server->ssid == ANJAY_SSID_BOOTSTRAP) {
anjay_log(DEBUG, "Bootstrap Server could not be reached. "
"Disabling all communication.");
anjay_log(DEBUG,
"Bootstrap Server: %s. Disabling all communication.",
debug_msg);
// Abort any further bootstrap retries.
_anjay_bootstrap_cleanup(anjay);
} else if (_anjay_should_retry_bootstrap(anjay)) {
@@ -49,14 +51,18 @@ void _anjay_server_on_server_communication_error(anjay_t *anjay,
anjay_enable_server(anjay, ANJAY_SSID_BOOTSTRAP);
}
} else {
anjay_log(DEBUG,
"Non-Bootstrap Server %" PRIu16 " could not be reached.",
server->ssid);
anjay_log(DEBUG, "Non-Bootstrap Server %" PRIu16 ": %s.", server->ssid,
debug_msg);
}
// make sure that the server will not be reactivated at next refresh
server->reactivate_time = AVS_TIME_REAL_INVALID;
}

void _anjay_server_on_server_communication_error(anjay_t *anjay,
anjay_server_info_t *server) {
_anjay_server_on_failure(anjay, server, "not reachable");
}

void _anjay_server_on_registration_timeout(anjay_t *anjay,
anjay_server_info_t *server) {
anjay_connection_ref_t ref = {
@@ -651,7 +651,6 @@ def get_transport(self, socket_index=-1):
def get_all_connections_failed(self):
return bool(int(self.communicate('get-all-connections-failed', match_regex='ALL_CONNECTIONS_FAILED==([0-9])\n').group(1)))


class SingleServerAccessor:
@property
def serv(self) -> Lwm2mServer:
@@ -17,6 +17,7 @@
import binascii
import struct
import tempfile
import collections

from typing import Optional

@@ -307,5 +308,35 @@ class ResPath:
Portfolio = _Lwm2mResourcePathHelper.from_rid_object(RID.Portfolio, oid=OID.Portfolio, multi_instance=True)


class TxParams(collections.namedtuple('TxParams',
['ack_timeout',
'ack_random_factor',
'max_retransmit',
'max_latency'],
defaults=(2.0, 1.5, 4.0, 100.0))):
def max_transmit_wait(self):
return self.ack_timeout * self.ack_random_factor * (2**(self.max_retransmit + 1) - 1)

def max_transmit_span(self):
return self.ack_timeout * (2**self.max_retransmit - 1) * self.ack_random_factor

def exchange_lifetime(self):
"""
From RFC7252: "PROCESSING_DELAY is the time a node takes to turn
around a Confirmable message into an acknowledgement. We assume
the node will attempt to send an ACK before having the sender time
out, so as a conservative assumption we set it equal to ACK_TIMEOUT"
Thus we use self.ack_timeout as a PROCESSING_DELAY in the formula below.
"""
return self.max_transmit_span() + 2 * self.max_latency + self.ack_timeout

def first_retransmission_timeout(self):
return self.ack_random_factor * self.ack_timeout

def last_retransmission_timeout(self):
return self.first_retransmission_timeout() * 2**self.max_retransmit


DEMO_ENDPOINT_NAME = 'urn:dev:os:0023C7-000001'
DEMO_LWM2M_VERSION = '1.0'
@@ -396,3 +396,54 @@ def runTest(self):
class ClientInitiatedBootstrapOnlyWithIncorrectData(BootstrapIncorrectData):
def setUp(self):
super().setUp(server_initiated_bootstrap_allowed=False)

class BootstrapNoInteractionFromBootstrapServer(BootstrapTest.Test):
ACK_TIMEOUT = 1
MAX_RETRANSMIT = 1

def setUp(self):
# Done to have a relatively short EXCHANGE_LIFETIME
super().setUp(extra_cmdline_args=['--ack-random-factor', '1',
'--ack-timeout', '%s' % self.ACK_TIMEOUT,
'--max-retransmit', '%s' % self.MAX_RETRANSMIT])

def tearDown(self):
super().tearDown(auto_deregister=False)

def runTest(self):
exchange_lifetime = TxParams(ack_timeout=self.ACK_TIMEOUT,
max_retransmit=self.MAX_RETRANSMIT).exchange_lifetime()
# We should get Bootstrap Request now
self.assertDemoRequestsBootstrap()

self.assertEqual(1, self.get_socket_count())
leeway = 2
self.wait_until_socket_count(0, exchange_lifetime + leeway)


class BootstrapNoInteractionFromBootstrapServerAfterSomeExchanges(BootstrapTest.Test):
ACK_TIMEOUT = 1
MAX_RETRANSMIT = 1

def setUp(self):
# Done to have a relatively short EXCHANGE_LIFETIME
super().setUp(extra_cmdline_args=['--ack-random-factor', '1',
'--ack-timeout', '%s' % self.ACK_TIMEOUT,
'--max-retransmit', '%s' % self.MAX_RETRANSMIT])

def tearDown(self):
super().tearDown(auto_deregister=False)

def runTest(self):
exchange_lifetime = TxParams(ack_timeout=self.ACK_TIMEOUT,
max_retransmit=self.MAX_RETRANSMIT).exchange_lifetime()
# Some random bootstrap operation, the data won't be used anyway.
self.perform_typical_bootstrap(server_iid=1,
security_iid=2,
server_uri='coap://127.0.0.1:9123',
security_mode=SecurityMode.NoSec,
finish=False)

self.assertEqual(1, self.get_socket_count())
leeway = 2
self.wait_until_socket_count(0, exchange_lifetime + leeway)
Oops, something went wrong.

0 comments on commit 9f5131b

Please sign in to comment.
You can’t perform that action at this time.