1 /* libcoap unit tests
2 *
3 * Copyright (C) 2013,2015-2018 Olaf Bergmann <bergmann@tzi.org>
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 *
7 * This file is part of the CoAP library libcoap. Please see
8 * README for terms of use.
9 */
10
11 #include "test_common.h"
12 #include "test_session.h"
13
14 #include <stdio.h>
15
16 /* The error threshold for timeout calculations. The precision of
17 * coap_calc_timeout() is assumed to be sufficient if the resulting
18 * value deviates at most FP_ERR_THRESHOLD percent from the value that
19 * was calculated with double precision floating point arithmetic
20 * instead of fixed point integers.
21 */
22 #define FP_ERR_THRESHOLD (1.0)
23
24 /* The maximum number of bits for fixed point integer representation.
25 * This number must be identical to the definition of MAX_BITS in
26 * net.c
27 */
28 #define MAX_BITS 8
29
30 static coap_context_t *ctx; /* Holds the coap context for most tests */
31 static coap_session_t *session; /* Holds a reference-counted session object */
32
33 COAP_STATIC_INLINE int
fpeq(const coap_fixed_point_t a,const coap_fixed_point_t b)34 fpeq(const coap_fixed_point_t a, const coap_fixed_point_t b) {
35 return (a.integer_part == b.integer_part) &&
36 (a.fractional_part == b.fractional_part);
37 }
38
39 static void
t_session1(void)40 t_session1(void) {
41 CU_ASSERT(session->ref == 1);
42
43 coap_session_reference(session);
44 CU_ASSERT(session->ref == 2);
45
46 coap_session_release(session);
47 CU_ASSERT(session->ref == 1);
48 }
49
50 static void
t_session2(void)51 t_session2(void) {
52 CU_ASSERT(session->max_retransmit == COAP_DEFAULT_MAX_RETRANSMIT);
53 CU_ASSERT(fpeq(session->ack_timeout, COAP_DEFAULT_ACK_TIMEOUT));
54 CU_ASSERT(fpeq(session->ack_random_factor, COAP_DEFAULT_ACK_RANDOM_FACTOR));
55
56 CU_ASSERT(coap_session_get_max_retransmit(session) == COAP_DEFAULT_MAX_RETRANSMIT);
57 CU_ASSERT(fpeq(coap_session_get_ack_timeout(session), COAP_DEFAULT_ACK_TIMEOUT));
58 CU_ASSERT(fpeq(coap_session_get_ack_random_factor(session), COAP_DEFAULT_ACK_RANDOM_FACTOR));
59 }
60
61 COAP_STATIC_INLINE double
fp_to_double(const coap_fixed_point_t fp)62 fp_to_double(const coap_fixed_point_t fp) {
63 return fp.integer_part + fp.fractional_part/1000.0;
64 }
65
66 COAP_STATIC_INLINE double
q_to_double(uint8_t q)67 q_to_double(uint8_t q) {
68 return (double)(q) / (1 << MAX_BITS);
69 }
70
71 /* Calculates the timeout using the following formula:
72 *
73 * COAP_TICKS_PER_SECOND * ack_timeout * (1 + (ack_random_factor - 1) * r)
74 *
75 * where @p r is the randomization value represented as Q0.MAX_BITS.
76 * This function returns the result as unsigned int to be
77 * checked against the result of coap_calc_timeout().
78 */
79 COAP_STATIC_INLINE unsigned int
timeout(const coap_fixed_point_t ack_timeout,const coap_fixed_point_t ack_random_factor,uint8_t r)80 timeout(const coap_fixed_point_t ack_timeout,
81 const coap_fixed_point_t ack_random_factor,
82 uint8_t r) {
83 const unsigned int ctps = COAP_TICKS_PER_SECOND;
84 double ato = fp_to_double(ack_timeout);
85 double rnd = 1 + (fp_to_double(ack_random_factor) - 1.0) * q_to_double(r);
86
87 return (unsigned int)(ctps * ato * rnd);
88 }
89
90 /* Checks if @p v is within FP_ERR_THRESHOLD percent of @p ref. This
91 * function returns 1 if it is within range, 0 otherwise.
92 */
93 COAP_STATIC_INLINE int
good_enough(unsigned int v,unsigned int ref)94 good_enough(unsigned int v, unsigned int ref) {
95 #define delta(a,b) (((a) < (b)) ? ((b) - (a)) : ((a) - (b)))
96 return (delta(v,ref) / 100.0) <= FP_ERR_THRESHOLD;
97 }
98
99 static void
t_session3(void)100 t_session3(void) {
101 const coap_fixed_point_t ato = COAP_DEFAULT_ACK_TIMEOUT;
102 const coap_fixed_point_t arf = COAP_DEFAULT_ACK_RANDOM_FACTOR;
103
104 CU_ASSERT(good_enough(coap_calc_timeout(session, 0), timeout(ato, arf, 0)));
105 CU_ASSERT(good_enough(coap_calc_timeout(session, 12), timeout(ato, arf, 12)));
106 CU_ASSERT(good_enough(coap_calc_timeout(session, 55), timeout(ato, arf, 55)));
107 CU_ASSERT(good_enough(coap_calc_timeout(session, 83), timeout(ato, arf, 83)));
108 CU_ASSERT(good_enough(coap_calc_timeout(session, 117), timeout(ato, arf, 117)));
109 CU_ASSERT(good_enough(coap_calc_timeout(session, 210), timeout(ato, arf, 210)));
110 CU_ASSERT(good_enough(coap_calc_timeout(session, 255), timeout(ato, arf, 255)));
111 }
112
113 static void
t_session4(void)114 t_session4(void) {
115 const coap_fixed_point_t ato = {8,300};
116 const coap_fixed_point_t arf = {2,011};
117
118 coap_session_set_ack_timeout(session, ato);
119 coap_session_set_ack_random_factor(session, arf);
120
121 CU_ASSERT(fpeq(coap_session_get_ack_timeout(session), ato));
122 CU_ASSERT(fpeq(coap_session_get_ack_random_factor(session), arf));
123
124 CU_ASSERT(good_enough(coap_calc_timeout(session, 0), timeout(ato, arf, 0)));
125 CU_ASSERT(good_enough(coap_calc_timeout(session, 12), timeout(ato, arf, 12)));
126 CU_ASSERT(good_enough(coap_calc_timeout(session, 55), timeout(ato, arf, 55)));
127 CU_ASSERT(good_enough(coap_calc_timeout(session, 83), timeout(ato, arf, 83)));
128 CU_ASSERT(good_enough(coap_calc_timeout(session, 117), timeout(ato, arf, 117)));
129 CU_ASSERT(good_enough(coap_calc_timeout(session, 210), timeout(ato, arf, 210)));
130 CU_ASSERT(good_enough(coap_calc_timeout(session, 255), timeout(ato, arf, 255)));
131 }
132
133 static void
t_session5(void)134 t_session5(void) {
135 coap_session_release(session);
136
137 CU_ASSERT_PTR_NULL(ctx->sessions);
138 session = NULL;
139 }
140
141 /* Test 6 creates a new session after the original session has been
142 * released by test 5 */
143 static void
t_session6(void)144 t_session6(void) {
145 coap_address_t laddr, saddr;
146 coap_address_init(&laddr);
147 coap_address_init(&saddr);
148
149 CU_ASSERT_PTR_NULL(session);
150
151 laddr.size = sizeof(struct sockaddr_in6);
152 laddr.addr.sin6.sin6_family = AF_INET6;
153 laddr.addr.sin6.sin6_addr = in6addr_any;
154 laddr.addr.sin6.sin6_port = htons(COAP_DEFAULT_PORT);
155
156 coap_address_copy(&saddr, &laddr);
157 saddr.addr.sin6.sin6_addr = in6addr_loopback;
158 saddr.addr.sin6.sin6_port = htons(20000);
159
160 session = coap_new_client_session(ctx, &saddr, &laddr, COAP_PROTO_UDP);
161 CU_ASSERT_PTR_NOT_NULL(session);
162 CU_ASSERT_PTR_NOT_NULL(ctx->sessions);
163 CU_ASSERT(session->state == COAP_SESSION_STATE_ESTABLISHED);
164 coap_session_release(session);
165 }
166
167 /* This function creates a set of nodes for testing. These nodes
168 * will exist for all tests and are modified by coap_insert_node()
169 * and coap_remove_from_queue().
170 */
171 static int
t_session_tests_create(void)172 t_session_tests_create(void) {
173 coap_address_t addr;
174 coap_address_init(&addr);
175
176 addr.size = sizeof(struct sockaddr_in6);
177 addr.addr.sin6.sin6_family = AF_INET6;
178 addr.addr.sin6.sin6_addr = in6addr_any;
179 addr.addr.sin6.sin6_port = htons(COAP_DEFAULT_PORT);
180
181 ctx = coap_new_context(&addr);
182
183 if (ctx != NULL) {
184 addr.addr.sin6.sin6_addr = in6addr_loopback;
185 session = coap_new_client_session(ctx, NULL, &addr, COAP_PROTO_UDP);
186 }
187
188 return (ctx == NULL) || (session == NULL);
189 }
190
191 static int
t_session_tests_remove(void)192 t_session_tests_remove(void) {
193 coap_free_context(ctx);
194 return 0;
195 }
196
197 CU_pSuite
t_init_session_tests(void)198 t_init_session_tests(void) {
199 CU_pSuite suite;
200
201 suite = CU_add_suite("session",
202 t_session_tests_create, t_session_tests_remove);
203 if (!suite) { /* signal error */
204 fprintf(stderr, "W: cannot add session test suite (%s)\n",
205 CU_get_error_msg());
206
207 return NULL;
208 }
209
210 #define SESSION_TEST(s,t) \
211 if (!CU_ADD_TEST(s,t)) { \
212 fprintf(stderr, "W: cannot add session test (%s)\n", \
213 CU_get_error_msg()); \
214 }
215
216 SESSION_TEST(suite, t_session1);
217 SESSION_TEST(suite, t_session2);
218 SESSION_TEST(suite, t_session3);
219 SESSION_TEST(suite, t_session4);
220 SESSION_TEST(suite, t_session5);
221 SESSION_TEST(suite, t_session6);
222
223 return suite;
224 }
225