1 /*
2 * Copyright (c) 2023, Emna Rekik
3 * Copyright (c) 2024 Nordic Semiconductor ASA
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7 #include <errno.h>
8 #include <string.h>
9
10 #include <zephyr/logging/log.h>
11 #include <zephyr/net/http/hpack.h>
12 #include <zephyr/net/net_core.h>
13
14 LOG_MODULE_DECLARE(net_http_server, CONFIG_NET_HTTP_SERVER_LOG_LEVEL);
15
http_hpack_key_is_static(uint32_t key)16 static inline bool http_hpack_key_is_static(uint32_t key)
17 {
18 return key > HTTP_SERVER_HPACK_INVALID && key <= HTTP_SERVER_HPACK_WWW_AUTHENTICATE;
19 }
20
21 struct hpack_table_entry {
22 const char *name;
23 const char *value;
24 };
25
26 static const struct hpack_table_entry http_hpack_table_static[] = {
27 [HTTP_SERVER_HPACK_AUTHORITY] = { ":authority", NULL },
28 [HTTP_SERVER_HPACK_METHOD_GET] = { ":method", "GET" },
29 [HTTP_SERVER_HPACK_METHOD_POST] = { ":method", "POST" },
30 [HTTP_SERVER_HPACK_PATH_ROOT] = { ":path", "/" },
31 [HTTP_SERVER_HPACK_PATH_INDEX] = { ":path", "/index.html" },
32 [HTTP_SERVER_HPACK_SCHEME_HTTP] = { ":scheme", "http" },
33 [HTTP_SERVER_HPACK_SCHEME_HTTPS] = { ":scheme", "https" },
34 [HTTP_SERVER_HPACK_STATUS_200] = { ":status", "200" },
35 [HTTP_SERVER_HPACK_STATUS_204] = { ":status", "204" },
36 [HTTP_SERVER_HPACK_STATUS_206] = { ":status", "206" },
37 [HTTP_SERVER_HPACK_STATUS_304] = { ":status", "304" },
38 [HTTP_SERVER_HPACK_STATUS_400] = { ":status", "400" },
39 [HTTP_SERVER_HPACK_STATUS_404] = { ":status", "404" },
40 [HTTP_SERVER_HPACK_STATUS_500] = { ":status", "500" },
41 [HTTP_SERVER_HPACK_ACCEPT_CHARSET] = { "accept-charset", NULL },
42 [HTTP_SERVER_HPACK_ACCEPT_ENCODING] = { "accept-encoding", "gzip, deflate" },
43 [HTTP_SERVER_HPACK_ACCEPT_LANGUAGE] = { "accept-language", NULL },
44 [HTTP_SERVER_HPACK_ACCEPT_RANGES] = { "accept-ranges", NULL },
45 [HTTP_SERVER_HPACK_ACCEPT] = { "accept", NULL },
46 [HTTP_SERVER_HPACK_ACCESS_CONTROL_ALLOW_ORIGIN] = { "access-control-allow-origin", NULL },
47 [HTTP_SERVER_HPACK_AGE] = { "age", NULL },
48 [HTTP_SERVER_HPACK_ALLOW] = { "allow", NULL },
49 [HTTP_SERVER_HPACK_AUTHORIZATION] = { "authorization", NULL },
50 [HTTP_SERVER_HPACK_CACHE_CONTROL] = { "cache-control", NULL },
51 [HTTP_SERVER_HPACK_CONTENT_DISPOSITION] = { "content-disposition", NULL },
52 [HTTP_SERVER_HPACK_CONTENT_ENCODING] = { "content-encoding", NULL },
53 [HTTP_SERVER_HPACK_CONTENT_LANGUAGE] = { "content-language", NULL },
54 [HTTP_SERVER_HPACK_CONTENT_LENGTH] = { "content-length", NULL },
55 [HTTP_SERVER_HPACK_CONTENT_LOCATION] = { "content-location", NULL },
56 [HTTP_SERVER_HPACK_CONTENT_RANGE] = { "content-range", NULL },
57 [HTTP_SERVER_HPACK_CONTENT_TYPE] = { "content-type", NULL },
58 [HTTP_SERVER_HPACK_COOKIE] = { "cookie", NULL },
59 [HTTP_SERVER_HPACK_DATE] = { "date", NULL },
60 [HTTP_SERVER_HPACK_ETAG] = { "etag", NULL },
61 [HTTP_SERVER_HPACK_EXPECT] = { "expect", NULL },
62 [HTTP_SERVER_HPACK_EXPIRES] = { "expires", NULL },
63 [HTTP_SERVER_HPACK_FROM] = { "from", NULL },
64 [HTTP_SERVER_HPACK_HOST] = { "host", NULL },
65 [HTTP_SERVER_HPACK_IF_MATCH] = { "if-match", NULL },
66 [HTTP_SERVER_HPACK_IF_MODIFIED_SINCE] = { "if-modified-since", NULL },
67 [HTTP_SERVER_HPACK_IF_NONE_MATCH] = { "if-none-match", NULL },
68 [HTTP_SERVER_HPACK_IF_RANGE] = { "if-range", NULL },
69 [HTTP_SERVER_HPACK_IF_UNMODIFIED_SINCE] = { "if-unmodified-since", NULL },
70 [HTTP_SERVER_HPACK_LAST_MODIFIED] = { "last-modified", NULL },
71 [HTTP_SERVER_HPACK_LINK] = { "link", NULL },
72 [HTTP_SERVER_HPACK_LOCATION] = { "location", NULL },
73 [HTTP_SERVER_HPACK_MAX_FORWARDS] = { "max-forwards", NULL },
74 [HTTP_SERVER_HPACK_PROXY_AUTHENTICATE] = { "proxy-authenticate", NULL },
75 [HTTP_SERVER_HPACK_PROXY_AUTHORIZATION] = { "proxy-authorization", NULL },
76 [HTTP_SERVER_HPACK_RANGE] = { "range", NULL },
77 [HTTP_SERVER_HPACK_REFERER] = { "referer", NULL },
78 [HTTP_SERVER_HPACK_REFRESH] = { "refresh", NULL },
79 [HTTP_SERVER_HPACK_RETRY_AFTER] = { "retry-after", NULL },
80 [HTTP_SERVER_HPACK_SERVER] = { "server", NULL },
81 [HTTP_SERVER_HPACK_SET_COOKIE] = { "set-cookie", NULL },
82 [HTTP_SERVER_HPACK_STRICT_TRANSPORT_SECURITY] = { "strict-transport-security", NULL },
83 [HTTP_SERVER_HPACK_TRANSFER_ENCODING] = { "transfer-encoding", NULL },
84 [HTTP_SERVER_HPACK_USER_AGENT] = { "user-agent", NULL },
85 [HTTP_SERVER_HPACK_VARY] = { "vary", NULL },
86 [HTTP_SERVER_HPACK_VIA] = { "via", NULL },
87 [HTTP_SERVER_HPACK_WWW_AUTHENTICATE] = { "www-authenticate", NULL },
88 };
89
http_hpack_table_get(uint32_t key)90 const struct hpack_table_entry *http_hpack_table_get(uint32_t key)
91 {
92 if (!http_hpack_key_is_static(key)) {
93 return NULL;
94 }
95
96 return &http_hpack_table_static[key];
97 }
98
http_hpack_find_index(struct http_hpack_header_buf * header,bool * name_only)99 static int http_hpack_find_index(struct http_hpack_header_buf *header,
100 bool *name_only)
101 {
102 const struct hpack_table_entry *entry;
103 int candidate = -1;
104
105 for (int i = HTTP_SERVER_HPACK_AUTHORITY;
106 i <= HTTP_SERVER_HPACK_WWW_AUTHENTICATE; i++) {
107 entry = &http_hpack_table_static[i];
108
109 if (entry->name != NULL &&
110 strlen(entry->name) == header->name_len &&
111 memcmp(entry->name, header->name, header->name_len) == 0) {
112 if (entry->value != NULL &&
113 strlen(entry->value) == header->value_len &&
114 memcmp(entry->value, header->value, header->value_len) == 0) {
115 /* Got exact match. */
116 *name_only = false;
117 return i;
118 }
119
120 if (candidate < 0) {
121 candidate = i;
122 }
123 }
124 }
125
126 if (candidate > 0) {
127 /* Matched name only. */
128 *name_only = true;
129 return candidate;
130 }
131
132 return -ENOENT;
133 }
134
135 #define HPACK_INTEGER_CONTINUATION_FLAG 0x80
136 #define HPACK_STRING_HUFFMAN_FLAG 0x80
137 #define HPACK_STRING_PREFIX_LEN 7
138
139 #define HPACK_PREFIX_INDEXED_MASK 0x80
140 #define HPACK_PREFIX_INDEXED 0x80
141 #define HPACK_PREFIX_LEN_INDEXED 7
142
143 #define HPACK_PREFIX_LITERAL_INDEXING_MASK 0xC0
144 #define HPACK_PREFIX_LITERAL_INDEXING 0x40
145 #define HPACK_PREFIX_LEN_LITERAL_INDEXING 6
146
147 #define HPACK_PREFIX_LITERAL_NO_INDEXING_MASK 0xF0
148 #define HPACK_PREFIX_LITERAL_NO_INDEXING 0x00
149 #define HPACK_PREFIX_LEN_LITERAL_NO_INDEXING 4
150
151 #define HPACK_PREFIX_LITERAL_NEVER_INDEXED_MASK 0xF0
152 #define HPACK_PREFIX_LITERAL_NEVER_INDEXED 0x10
153 #define HPACK_PREFIX_LEN_LITERAL_NEVER_INDEXED 4
154
155 #define HPACK_PREFIX_DYNAMIC_TABLE_SIZE_MASK 0xE0
156 #define HPACK_PREFIX_DYNAMIC_TABLE_SIZE_UPDATE 0x20
157 #define HPACK_PREFIX_LEN_DYNAMIC_TABLE_SIZE_UPDATE 5
158
hpack_integer_decode(const uint8_t * buf,size_t datalen,uint8_t n,uint32_t * value)159 static int hpack_integer_decode(const uint8_t *buf, size_t datalen,
160 uint8_t n, uint32_t *value)
161 {
162 int len = 0;
163 uint8_t m = 0;
164 uint8_t value_mask = (1 << n) - 1;
165
166 NET_ASSERT(n < 8);
167
168 if (datalen == 0) {
169 return -EAGAIN;
170 }
171
172 /* Based on RFC7541, ch 5.1. */
173 len++;
174 *value = *buf & value_mask;
175 if (*value < value_mask) {
176 return len;
177 }
178
179 do {
180 buf++;
181 len++;
182
183 if (--datalen == 0) {
184 return -EAGAIN;
185 }
186
187 if (m > sizeof(uint32_t) * 8) {
188 /* Can't handle integer that large. */
189 return -EBADMSG;
190 }
191
192 *value += (*buf & ~HPACK_INTEGER_CONTINUATION_FLAG) * (1 << m);
193 m += 7;
194
195 } while (*buf & HPACK_INTEGER_CONTINUATION_FLAG);
196
197 return len;
198 }
199
200 enum hpack_string_type {
201 HPACK_HEADER_NAME,
202 HPACK_HEADER_VALUE,
203 };
204
hpack_huffman_decode(const uint8_t * encoded_buf,size_t encoded_len,enum hpack_string_type type,struct http_hpack_header_buf * header)205 static int hpack_huffman_decode(const uint8_t *encoded_buf, size_t encoded_len,
206 enum hpack_string_type type,
207 struct http_hpack_header_buf *header)
208 {
209 uint8_t *buf = header->buf + header->datalen;
210 size_t buflen = sizeof(header->buf) - header->datalen;
211 int ret;
212
213 NET_ASSERT(type == HPACK_HEADER_NAME || type == HPACK_HEADER_VALUE);
214
215 ret = http_hpack_huffman_decode(encoded_buf, encoded_len, buf, buflen);
216 if (ret < 0) {
217 return ret;
218 }
219
220 if (type == HPACK_HEADER_NAME) {
221 header->name = buf;
222 header->name_len = ret;
223 } else if (type == HPACK_HEADER_VALUE) {
224 header->value = buf;
225 header->value_len = ret;
226 }
227
228 header->datalen += ret;
229
230 return 0;
231 }
232
hpack_string_decode(const uint8_t * buf,size_t datalen,enum hpack_string_type type,struct http_hpack_header_buf * header)233 static int hpack_string_decode(const uint8_t *buf, size_t datalen,
234 enum hpack_string_type type,
235 struct http_hpack_header_buf *header)
236 {
237 uint32_t str_len;
238 bool huffman;
239 int len = 0;
240 int ret;
241
242 NET_ASSERT(type == HPACK_HEADER_NAME || type == HPACK_HEADER_VALUE);
243
244 if (datalen == 0) {
245 return -EAGAIN;
246 }
247
248 huffman = *buf & HPACK_STRING_HUFFMAN_FLAG;
249
250 ret = hpack_integer_decode(buf, datalen, HPACK_STRING_PREFIX_LEN,
251 &str_len);
252 if (ret < 0) {
253 return ret;
254 }
255
256 len += ret;
257 datalen -= ret;
258 buf += ret;
259
260 if (str_len > datalen) {
261 return -EAGAIN;
262 }
263
264 if (huffman) {
265 ret = hpack_huffman_decode(buf, str_len, type, header);
266 if (ret < 0) {
267 return ret;
268 }
269 } else {
270 if (type == HPACK_HEADER_NAME) {
271 header->name = buf;
272 header->name_len = str_len;
273 } else if (type == HPACK_HEADER_VALUE) {
274 header->value = buf;
275 header->value_len = str_len;
276 }
277 }
278
279 len += str_len;
280
281 return len;
282 }
283
hpack_handle_indexed(const uint8_t * buf,size_t datalen,struct http_hpack_header_buf * header)284 static int hpack_handle_indexed(const uint8_t *buf, size_t datalen,
285 struct http_hpack_header_buf *header)
286 {
287 const struct hpack_table_entry *entry;
288 uint32_t index;
289 int ret;
290
291 ret = hpack_integer_decode(buf, datalen, HPACK_PREFIX_LEN_INDEXED,
292 &index);
293 if (ret < 0) {
294 return ret;
295 }
296
297 if (index == 0) {
298 return -EBADMSG;
299 }
300
301 entry = http_hpack_table_get(index);
302 if (entry == NULL) {
303 return -EBADMSG;
304 }
305
306 if (entry->name == NULL || entry->value == NULL) {
307 return -EBADMSG;
308 }
309
310 header->name = entry->name;
311 header->name_len = strlen(entry->name);
312 header->value = entry->value;
313 header->value_len = strlen(entry->value);
314
315 return ret;
316 }
317
hpack_handle_literal(const uint8_t * buf,size_t datalen,struct http_hpack_header_buf * header,uint8_t prefix_len)318 static int hpack_handle_literal(const uint8_t *buf, size_t datalen,
319 struct http_hpack_header_buf *header,
320 uint8_t prefix_len)
321 {
322 uint32_t index;
323 int ret, len;
324
325 header->datalen = 0;
326
327 ret = hpack_integer_decode(buf, datalen, prefix_len, &index);
328 if (ret < 0) {
329 return ret;
330 }
331
332 len = ret;
333 buf += ret;
334 datalen -= ret;
335
336 if (index == 0) {
337 /* Literal name. */
338 ret = hpack_string_decode(buf, datalen, HPACK_HEADER_NAME,
339 header);
340 if (ret < 0) {
341 return ret;
342 }
343
344 len += ret;
345 buf += ret;
346 datalen -= ret;
347 } else {
348 /* Indexed name. */
349 const struct hpack_table_entry *entry;
350
351 entry = http_hpack_table_get(index);
352 if (entry == NULL) {
353 return -EBADMSG;
354 }
355
356 if (entry->name == NULL) {
357 return -EBADMSG;
358 }
359
360 header->name = entry->name;
361 header->name_len = strlen(entry->name);
362 }
363
364 ret = hpack_string_decode(buf, datalen, HPACK_HEADER_VALUE, header);
365 if (ret < 0) {
366 return ret;
367 }
368
369 len += ret;
370
371 return len;
372 }
373
hpack_handle_literal_index(const uint8_t * buf,size_t datalen,struct http_hpack_header_buf * header)374 static int hpack_handle_literal_index(const uint8_t *buf, size_t datalen,
375 struct http_hpack_header_buf *header)
376 {
377 /* TODO Add dynamic table support, if needed. */
378
379 return hpack_handle_literal(buf, datalen, header,
380 HPACK_PREFIX_LEN_LITERAL_INDEXING);
381 }
382
hpack_handle_literal_no_index(const uint8_t * buf,size_t datalen,struct http_hpack_header_buf * header)383 static int hpack_handle_literal_no_index(const uint8_t *buf, size_t datalen,
384 struct http_hpack_header_buf *header)
385 {
386 return hpack_handle_literal(buf, datalen, header,
387 HPACK_PREFIX_LEN_LITERAL_NO_INDEXING);
388 }
389
hpack_handle_dynamic_size_update(const uint8_t * buf,size_t datalen)390 static int hpack_handle_dynamic_size_update(const uint8_t *buf, size_t datalen)
391 {
392 uint32_t max_size;
393 int ret;
394
395 ret = hpack_integer_decode(
396 buf, datalen, HPACK_PREFIX_LEN_DYNAMIC_TABLE_SIZE_UPDATE, &max_size);
397 if (ret < 0) {
398 return ret;
399 }
400
401 /* TODO Add dynamic table support, if needed. */
402
403 return ret;
404 }
405
http_hpack_decode_header(const uint8_t * buf,size_t datalen,struct http_hpack_header_buf * header)406 int http_hpack_decode_header(const uint8_t *buf, size_t datalen,
407 struct http_hpack_header_buf *header)
408 {
409 uint8_t prefix;
410 int ret;
411
412 if (buf == NULL || header == NULL) {
413 return -EINVAL;
414 }
415
416 if (datalen == 0) {
417 return -EAGAIN;
418 }
419
420 prefix = *buf;
421
422 if ((prefix & HPACK_PREFIX_INDEXED_MASK) == HPACK_PREFIX_INDEXED) {
423 ret = hpack_handle_indexed(buf, datalen, header);
424 } else if ((prefix & HPACK_PREFIX_LITERAL_INDEXING_MASK) ==
425 HPACK_PREFIX_LITERAL_INDEXING) {
426 ret = hpack_handle_literal_index(buf, datalen, header);
427 } else if (((prefix & HPACK_PREFIX_LITERAL_NO_INDEXING_MASK) ==
428 HPACK_PREFIX_LITERAL_NO_INDEXING) ||
429 ((prefix & HPACK_PREFIX_LITERAL_NEVER_INDEXED_MASK) ==
430 HPACK_PREFIX_LITERAL_NEVER_INDEXED)) {
431 ret = hpack_handle_literal_no_index(buf, datalen, header);
432 } else if ((prefix & HPACK_PREFIX_DYNAMIC_TABLE_SIZE_MASK) ==
433 HPACK_PREFIX_DYNAMIC_TABLE_SIZE_UPDATE) {
434 ret = hpack_handle_dynamic_size_update(buf, datalen);
435 } else {
436 ret = -EINVAL;
437 }
438
439 return ret;
440 }
441
hpack_integer_encode(uint8_t * buf,size_t buflen,int value,uint8_t prefix,uint8_t n)442 static int hpack_integer_encode(uint8_t *buf, size_t buflen, int value,
443 uint8_t prefix, uint8_t n)
444 {
445 uint8_t limit = (1 << n) - 1;
446 int len = 0;
447
448 if (buflen == 0) {
449 return -ENOBUFS;
450 }
451
452 /* Based on RFC7541, ch 5.1. */
453 if (value < limit) {
454 *buf = prefix | (uint8_t)value;
455
456 return 1;
457 }
458
459 *buf++ = prefix | limit;
460 len++;
461 value -= limit;
462
463 while (value >= 128) {
464 if (len >= buflen) {
465 return -ENOBUFS;
466 }
467
468 *buf = (uint8_t)((value % 128) + 128);
469 len++;
470 value /= 128;
471 }
472
473 if (len >= buflen) {
474 return -ENOBUFS;
475 }
476
477 *buf = (uint8_t)value;
478 len++;
479
480 return len;
481 }
482
hpack_string_encode(uint8_t * buf,size_t buflen,enum hpack_string_type type,struct http_hpack_header_buf * header)483 static int hpack_string_encode(uint8_t *buf, size_t buflen,
484 enum hpack_string_type type,
485 struct http_hpack_header_buf *header)
486 {
487 int ret, len = 0;
488 const char *str;
489 size_t str_len;
490 uint8_t prefix = 0;
491
492 if (type == HPACK_HEADER_NAME) {
493 str = header->name;
494 str_len = header->name_len;
495 } else {
496 str = header->value;
497 str_len = header->value_len;
498 }
499
500 /* Try to encode string into intermediate buffer. */
501 ret = http_hpack_huffman_encode(str, str_len, header->buf,
502 sizeof(header->buf));
503 if (ret > 0 && ret < str_len) {
504 /* Use Huffman encoded string only if smaller than the original. */
505 str = header->buf;
506 str_len = ret;
507 prefix = HPACK_STRING_HUFFMAN_FLAG;
508 }
509
510 /* Encode string length. */
511 ret = hpack_integer_encode(buf, buflen, str_len, prefix,
512 HPACK_STRING_PREFIX_LEN);
513 if (ret < 0) {
514 return ret;
515 }
516
517 buf += ret;
518 buflen -= ret;
519 len += ret;
520
521 /* Copy string. */
522 if (str_len > buflen) {
523 return -ENOBUFS;
524 }
525
526 memcpy(buf, str, str_len);
527 len += str_len;
528
529 return len;
530 }
531
hpack_encode_literal(uint8_t * buf,size_t buflen,struct http_hpack_header_buf * header)532 static int hpack_encode_literal(uint8_t *buf, size_t buflen,
533 struct http_hpack_header_buf *header)
534 {
535 int ret, len = 0;
536
537 ret = hpack_integer_encode(buf, buflen, 0,
538 HPACK_PREFIX_LITERAL_NEVER_INDEXED,
539 HPACK_PREFIX_LEN_LITERAL_NEVER_INDEXED);
540 if (ret < 0) {
541 return ret;
542 }
543
544 buf += ret;
545 buflen -= ret;
546 len += ret;
547
548 ret = hpack_string_encode(buf, buflen, HPACK_HEADER_NAME, header);
549 if (ret < 0) {
550 return ret;
551 }
552
553 buf += ret;
554 buflen -= ret;
555 len += ret;
556
557 ret = hpack_string_encode(buf, buflen, HPACK_HEADER_VALUE, header);
558 if (ret < 0) {
559 return ret;
560 }
561
562 len += ret;
563
564 return len;
565 }
566
hpack_encode_literal_value(uint8_t * buf,size_t buflen,int index,struct http_hpack_header_buf * header)567 static int hpack_encode_literal_value(uint8_t *buf, size_t buflen, int index,
568 struct http_hpack_header_buf *header)
569 {
570 int ret, len = 0;
571
572 ret = hpack_integer_encode(buf, buflen, index,
573 HPACK_PREFIX_LITERAL_NEVER_INDEXED,
574 HPACK_PREFIX_LEN_LITERAL_NEVER_INDEXED);
575 if (ret < 0) {
576 return ret;
577 }
578
579 buf += ret;
580 buflen -= ret;
581 len += ret;
582
583 ret = hpack_string_encode(buf, buflen, HPACK_HEADER_VALUE, header);
584 if (ret < 0) {
585 return ret;
586 }
587
588 len += ret;
589
590 return len;
591 }
592
hpack_encode_indexed(uint8_t * buf,size_t buflen,int index)593 static int hpack_encode_indexed(uint8_t *buf, size_t buflen, int index)
594 {
595 return hpack_integer_encode(buf, buflen, index, HPACK_PREFIX_INDEXED,
596 HPACK_PREFIX_LEN_INDEXED);
597 }
598
http_hpack_encode_header(uint8_t * buf,size_t buflen,struct http_hpack_header_buf * header)599 int http_hpack_encode_header(uint8_t *buf, size_t buflen,
600 struct http_hpack_header_buf *header)
601 {
602 int ret, len = 0;
603 bool name_only;
604
605 if (buf == NULL || header == NULL ||
606 header->name == NULL || header->name_len == 0 ||
607 header->value == NULL || header->value_len == 0) {
608 return -EINVAL;
609 }
610
611 if (buflen == 0) {
612 return -ENOBUFS;
613 }
614
615 ret = http_hpack_find_index(header, &name_only);
616 if (ret < 0) {
617 /* All literal */
618 len = hpack_encode_literal(buf, buflen, header);
619 } else if (name_only) {
620 /* Literal value */
621 len = hpack_encode_literal_value(buf, buflen, ret, header);
622 } else {
623 /* Indexed */
624 len = hpack_encode_indexed(buf, buflen, ret);
625 }
626
627 return len;
628 }
629