1 /* block.c -- block transfer
2 *
3 * Copyright (C) 2010--2012,2015-2019 Olaf Bergmann <bergmann@tzi.org> and others
4 *
5 * This file is part of the CoAP library libcoap. Please see
6 * README for terms of use.
7 */
8
9 #include "coap_internal.h"
10
11 #ifndef min
12 #define min(a,b) ((a) < (b) ? (a) : (b))
13 #endif
14
15 #ifndef WITHOUT_BLOCK
16 unsigned int
coap_opt_block_num(const coap_opt_t * block_opt)17 coap_opt_block_num(const coap_opt_t *block_opt) {
18 unsigned int num = 0;
19 uint16_t len;
20
21 len = coap_opt_length(block_opt);
22
23 if (len == 0) {
24 return 0;
25 }
26
27 if (len > 1) {
28 num = coap_decode_var_bytes(coap_opt_value(block_opt),
29 coap_opt_length(block_opt) - 1);
30 }
31
32 return (num << 4) | ((*COAP_OPT_BLOCK_LAST(block_opt) & 0xF0) >> 4);
33 }
34
35 int
coap_get_block(coap_pdu_t * pdu,uint16_t type,coap_block_t * block)36 coap_get_block(coap_pdu_t *pdu, uint16_t type, coap_block_t *block) {
37 coap_opt_iterator_t opt_iter;
38 coap_opt_t *option;
39
40 assert(block);
41 memset(block, 0, sizeof(coap_block_t));
42
43 if (pdu && (option = coap_check_option(pdu, type, &opt_iter)) != NULL) {
44 unsigned int num;
45
46 block->szx = COAP_OPT_BLOCK_SZX(option);
47 if (COAP_OPT_BLOCK_MORE(option))
48 block->m = 1;
49
50 /* The block number is at most 20 bits, so values above 2^20 - 1
51 * are illegal. */
52 num = coap_opt_block_num(option);
53 if (num > 0xFFFFF) {
54 return 0;
55 }
56 block->num = num;
57 return 1;
58 }
59
60 return 0;
61 }
62
63 int
coap_write_block_opt(coap_block_t * block,uint16_t type,coap_pdu_t * pdu,size_t data_length)64 coap_write_block_opt(coap_block_t *block, uint16_t type,
65 coap_pdu_t *pdu, size_t data_length) {
66 size_t start, want, avail;
67 unsigned char buf[4];
68
69 assert(pdu);
70
71 start = block->num << (block->szx + 4);
72 if (data_length <= start) {
73 coap_log(LOG_DEBUG, "illegal block requested\n");
74 return -2;
75 }
76
77 assert(pdu->max_size > 0);
78 avail = pdu->max_size - pdu->used_size - 4;
79 want = (size_t)1 << (block->szx + 4);
80
81 /* check if entire block fits in message */
82 if (want <= avail) {
83 block->m = want < data_length - start;
84 } else {
85 /* Sender has requested a block that is larger than the remaining
86 * space in pdu. This is ok if the remaining data fits into the pdu
87 * anyway. The block size needs to be adjusted only if there is more
88 * data left that cannot be delivered in this message. */
89
90 if (data_length - start <= avail) {
91
92 /* it's the final block and everything fits in the message */
93 block->m = 0;
94 } else {
95 unsigned int szx;
96 int newBlockSize;
97
98 /* we need to decrease the block size */
99 if (avail < 16) { /* bad luck, this is the smallest block size */
100 coap_log(LOG_DEBUG,
101 "not enough space, even the smallest block does not fit");
102 return -3;
103 }
104 newBlockSize = coap_flsll((long long)avail) - 5;
105 coap_log(LOG_DEBUG,
106 "decrease block size for %zu to %d\n", avail, newBlockSize);
107 szx = block->szx;
108 block->szx = newBlockSize;
109 block->m = 1;
110 block->num <<= szx - block->szx;
111 }
112 }
113
114 /* to re-encode the block option */
115 coap_add_option(pdu, type, coap_encode_var_safe(buf, sizeof(buf),
116 ((block->num << 4) |
117 (block->m << 3) |
118 block->szx)),
119 buf);
120
121 return 1;
122 }
123
124 int
coap_add_block(coap_pdu_t * pdu,unsigned int len,const uint8_t * data,unsigned int block_num,unsigned char block_szx)125 coap_add_block(coap_pdu_t *pdu, unsigned int len, const uint8_t *data,
126 unsigned int block_num, unsigned char block_szx) {
127 unsigned int start;
128 start = block_num << (block_szx + 4);
129
130 if (len <= start)
131 return 0;
132
133 return coap_add_data(pdu,
134 min(len - start, (1U << (block_szx + 4))),
135 data + start);
136 }
137
138 /*
139 * Note that the COAP_OPTION_ have to be added in the correct order
140 */
141 void
coap_add_data_blocked_response(coap_resource_t * resource,coap_session_t * session,coap_pdu_t * request,coap_pdu_t * response,const coap_binary_t * token,uint16_t media_type,int maxage,size_t length,const uint8_t * data)142 coap_add_data_blocked_response(coap_resource_t *resource,
143 coap_session_t *session,
144 coap_pdu_t *request,
145 coap_pdu_t *response,
146 const coap_binary_t *token,
147 uint16_t media_type,
148 int maxage,
149 size_t length,
150 const uint8_t* data
151 ) {
152 coap_key_t etag;
153 unsigned char buf[4];
154 coap_block_t block2 = { 0, 0, 0 };
155 int block2_requested = 0;
156 coap_subscription_t *subscription = coap_find_observer(resource, session, token);
157
158 /*
159 * Need to check that a valid block is getting asked for so that the
160 * correct options are put into the PDU.
161 */
162 if (request) {
163 if (coap_get_block(request, COAP_OPTION_BLOCK2, &block2)) {
164 block2_requested = 1;
165 if (length <= (block2.num << (block2.szx + 4))) {
166 coap_log(LOG_DEBUG, "Illegal block requested (%d > last = %zu)\n",
167 block2.num,
168 length >> (block2.szx + 4));
169 response->code = COAP_RESPONSE_CODE(400);
170 goto error;
171 }
172 }
173 }
174 else if (subscription && subscription->has_block2) {
175 block2 = subscription->block2;
176 block2.num = 0;
177 block2_requested = 1;
178 }
179 response->code = COAP_RESPONSE_CODE(205);
180
181 /* add etag for the resource */
182 memset(etag, 0, sizeof(etag));
183 coap_hash(data, length, etag);
184 coap_add_option(response, COAP_OPTION_ETAG, sizeof(etag), etag);
185
186 if ((block2.num == 0) && subscription) {
187 coap_add_option(response, COAP_OPTION_OBSERVE,
188 coap_encode_var_safe(buf, sizeof (buf),
189 resource->observe),
190 buf);
191 }
192
193 coap_add_option(response, COAP_OPTION_CONTENT_TYPE,
194 coap_encode_var_safe(buf, sizeof(buf),
195 media_type),
196 buf);
197
198 if (maxage >= 0) {
199 coap_add_option(response,
200 COAP_OPTION_MAXAGE,
201 coap_encode_var_safe(buf, sizeof(buf), maxage), buf);
202 }
203
204 if (block2_requested) {
205 int res;
206
207 res = coap_write_block_opt(&block2, COAP_OPTION_BLOCK2, response,
208 length);
209
210 switch (res) {
211 case -2: /* illegal block (caught above) */
212 response->code = COAP_RESPONSE_CODE(400);
213 goto error;
214 case -1: /* should really not happen */
215 assert(0);
216 /* fall through if assert is a no-op */
217 case -3: /* cannot handle request */
218 response->code = COAP_RESPONSE_CODE(500);
219 goto error;
220 default: /* everything is good */
221 ;
222 }
223
224 coap_add_option(response,
225 COAP_OPTION_SIZE2,
226 coap_encode_var_safe(buf, sizeof(buf), length),
227 buf);
228
229 coap_add_block(response, length, data,
230 block2.num, block2.szx);
231 return;
232 }
233
234 /*
235 * BLOCK2 not requested
236 */
237 if (!coap_add_data(response, length, data)) {
238 /* set initial block size, will be lowered by
239 * coap_write_block_opt) automatically */
240 block2.num = 0;
241 block2.szx = 6;
242 coap_write_block_opt(&block2, COAP_OPTION_BLOCK2, response,
243 length);
244
245 coap_add_option(response,
246 COAP_OPTION_SIZE2,
247 coap_encode_var_safe(buf, sizeof(buf), length),
248 buf);
249
250 coap_add_block(response, length, data,
251 block2.num, block2.szx);
252 }
253 return;
254
255 error:
256 coap_add_data(response,
257 strlen(coap_response_phrase(response->code)),
258 (const unsigned char *)coap_response_phrase(response->code));
259 }
260
261 #endif /* WITHOUT_BLOCK */
262