lsquic_frame_writer.c revision 5392f7a3
1/* Copyright (c) 2017 - 2019 LiteSpeed Technologies Inc. See LICENSE. */ 2/* 3 * lsquic_frame_writer.c -- write frames to HEADERS stream. 4 * 5 * The frame is first written to list of frame_buf's (frabs) and then 6 * out to the stream. This is done because frame's size is written out 7 * to the stream and we may not have enough room in the stream to fit 8 * the whole frame. 9 */ 10 11#ifndef WIN32 12#include <arpa/inet.h> 13#endif 14#include <assert.h> 15#include <errno.h> 16#include <inttypes.h> 17#include <stdlib.h> 18#include <string.h> 19#include <sys/queue.h> 20 21#include "lshpack.h" 22#include "lsquic_mm.h" 23#include "lsquic.h" 24#include "lsquic_int_types.h" 25#include "lsquic_hash.h" 26#include "lsquic_conn.h" 27 28#include "lsquic_frame_writer.h" 29#include "lsquic_frame_common.h" 30#include "lsquic_frab_list.h" 31#include "lsquic_ev_log.h" 32 33#define LSQUIC_LOGGER_MODULE LSQLM_FRAME_WRITER 34#define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(\ 35 lsquic_stream_conn(fw->fw_stream)) 36#include "lsquic_logger.h" 37 38 39struct lsquic_frame_writer 40{ 41 struct lsquic_stream *fw_stream; 42 fw_writef_f fw_writef; 43 struct lsquic_mm *fw_mm; 44 struct lshpack_enc *fw_henc; 45#if LSQUIC_CONN_STATS 46 struct conn_stats *fw_conn_stats; 47#endif 48 struct frab_list fw_fral; 49 unsigned fw_max_frame_sz; 50 uint32_t fw_max_header_list_sz; /* 0 means unlimited */ 51 enum { 52 FW_SERVER = (1 << 0), 53 } fw_flags; 54}; 55 56 57/* RFC 7540, Section 4.2 */ 58#define MIN_MAX_FRAME_SIZE (1 << 14) 59#define MAX_MAX_FRAME_SIZE ((1 << 24) - 1) 60 61#define MAX(a, b) ((a) > (b) ? (a) : (b)) 62#define SETTINGS_FRAME_SZ 6 63#define ABS_MIN_FRAME_SIZE MAX(SETTINGS_FRAME_SZ, \ 64 sizeof(struct http_prio_frame)) 65 66static void * 67fw_alloc (void *ctx, size_t size) 68{ 69 return lsquic_mm_get_4k(ctx); 70} 71 72 73struct lsquic_frame_writer * 74lsquic_frame_writer_new (struct lsquic_mm *mm, struct lsquic_stream *stream, 75 unsigned max_frame_sz, struct lshpack_enc *henc, fw_writef_f writef, 76#if LSQUIC_CONN_STATS 77 struct conn_stats *conn_stats, 78#endif 79 int is_server) 80{ 81 struct lsquic_frame_writer *fw; 82 83 /* When frame writer is instantiated, limit the maximum size to 84 * MIN_MAX_FRAME_SIZE. The reference implementation has this value 85 * hardcoded and QUIC does not provide a mechanism to advertise a 86 * different value. 87 */ 88 if (0 == max_frame_sz) 89 max_frame_sz = MIN_MAX_FRAME_SIZE; 90 else 91 LSQ_LOG1(LSQ_LOG_WARN, "max frame size specified to be %u bytes " 92 "-- this better be test code!", max_frame_sz); 93 94 if (!is_server && max_frame_sz < ABS_MIN_FRAME_SIZE) 95 { 96 LSQ_LOG1(LSQ_LOG_ERROR, "max frame size must be at least %zd bytes, " 97 "which is the size of priority information that client always " 98 "writes", ABS_MIN_FRAME_SIZE); 99 return NULL; 100 } 101 102 fw = malloc(sizeof(*fw)); 103 if (!fw) 104 return NULL; 105 106 fw->fw_mm = mm; 107 fw->fw_henc = henc; 108 fw->fw_stream = stream; 109 fw->fw_writef = writef; 110 fw->fw_max_frame_sz = max_frame_sz; 111 fw->fw_max_header_list_sz = 0; 112 if (is_server) 113 fw->fw_flags = FW_SERVER; 114 else 115 fw->fw_flags = 0; 116#if LSQUIC_CONN_STATS 117 fw->fw_conn_stats = conn_stats; 118#endif 119 lsquic_frab_list_init(&fw->fw_fral, 0x1000, fw_alloc, 120 (void (*)(void *, void *)) lsquic_mm_put_4k, mm); 121 return fw; 122} 123 124 125void 126lsquic_frame_writer_destroy (struct lsquic_frame_writer *fw) 127{ 128 lsquic_frab_list_cleanup(&fw->fw_fral); 129 free(fw); 130} 131 132 133int 134lsquic_frame_writer_have_leftovers (const struct lsquic_frame_writer *fw) 135{ 136 return !lsquic_frab_list_empty(&fw->fw_fral); 137} 138 139 140int 141lsquic_frame_writer_flush (struct lsquic_frame_writer *fw) 142{ 143 struct lsquic_reader reader = { 144 .lsqr_read = lsquic_frab_list_read, 145 .lsqr_size = lsquic_frab_list_size, 146 .lsqr_ctx = &fw->fw_fral, 147 }; 148 ssize_t nw; 149 150 nw = fw->fw_writef(fw->fw_stream, &reader); 151 152 if (nw >= 0) 153 return 0; 154 else 155 return -1; 156} 157 158 159struct header_framer_ctx 160{ 161 struct lsquic_frame_writer 162 *hfc_fw; 163 struct { 164 struct frame_buf *frab; 165 unsigned short off; 166 } hfc_header_ptr; /* Points to byte *after* current frame header */ 167 unsigned hfc_max_frame_sz; /* Maximum frame size. We always fill it. */ 168 unsigned hfc_cur_sz; /* Number of bytes in the current frame. */ 169 unsigned hfc_n_frames; /* Number of frames written. */ 170 lsquic_stream_id_t 171 hfc_stream_id; /* Stream ID */ 172 enum http_frame_header_flags 173 hfc_first_flags; 174 enum http_frame_type 175 hfc_frame_type; 176}; 177 178 179static void 180hfc_init (struct header_framer_ctx *hfc, struct lsquic_frame_writer *fw, 181 unsigned max_frame_sz, enum http_frame_type frame_type, 182 lsquic_stream_id_t stream_id, enum http_frame_header_flags first_flags) 183{ 184 memset(hfc, 0, sizeof(*hfc)); 185 hfc->hfc_fw = fw; 186 hfc->hfc_frame_type = frame_type; 187 hfc->hfc_stream_id = stream_id; 188 hfc->hfc_first_flags = first_flags; 189 hfc->hfc_max_frame_sz = max_frame_sz; 190 hfc->hfc_cur_sz = max_frame_sz; 191} 192 193 194static void 195hfc_save_ptr (struct header_framer_ctx *hfc) 196{ 197 hfc->hfc_header_ptr.frab = TAILQ_LAST(&hfc->hfc_fw->fw_fral.fl_frabs, frame_buf_head); 198 hfc->hfc_header_ptr.off = hfc->hfc_header_ptr.frab->frab_size; 199} 200 201 202static void 203hfc_terminate_frame (struct header_framer_ctx *hfc, 204 enum http_frame_header_flags flags) 205{ 206 union { 207 struct http_frame_header fh; 208 unsigned char buf[ sizeof(struct http_frame_header) ]; 209 } u; 210 uint32_t stream_id; 211 struct frame_buf *frab; 212 213 /* Construct the frame */ 214 u.fh.hfh_length[0] = hfc->hfc_cur_sz >> 16; 215 u.fh.hfh_length[1] = hfc->hfc_cur_sz >> 8; 216 u.fh.hfh_length[2] = hfc->hfc_cur_sz; 217 u.fh.hfh_flags = flags; 218 if (1 == hfc->hfc_n_frames) 219 { 220 u.fh.hfh_type = hfc->hfc_frame_type; 221 u.fh.hfh_flags |= hfc->hfc_first_flags; 222 } 223 else 224 u.fh.hfh_type = HTTP_FRAME_CONTINUATION; 225 stream_id = htonl(hfc->hfc_stream_id); 226 memcpy(u.fh.hfh_stream_id, &stream_id, sizeof(stream_id)); 227 228 if (hfc->hfc_header_ptr.off >= sizeof(u.fh)) 229 { /* Write in a single chunk */ 230 assert(0 == memcmp("123456789", hfc->hfc_header_ptr.frab->frab_buf + 231 hfc->hfc_header_ptr.off - sizeof(u.buf), sizeof(u.buf))); 232 memcpy(hfc->hfc_header_ptr.frab->frab_buf + hfc->hfc_header_ptr.off - 233 sizeof(u.buf), u.buf, sizeof(u.buf)); 234 } 235 else 236 { /* Write across frab boundary */ 237 memcpy(hfc->hfc_header_ptr.frab->frab_buf, 238 u.buf + sizeof(u.buf) - hfc->hfc_header_ptr.off, 239 hfc->hfc_header_ptr.off); 240 frab = TAILQ_PREV(hfc->hfc_header_ptr.frab, frame_buf_head, frab_next); 241 memcpy(frab->frab_buf + frab->frab_size - sizeof(u.buf) + 242 hfc->hfc_header_ptr.off, u.buf, 243 sizeof(u.buf) - hfc->hfc_header_ptr.off); 244 } 245} 246 247 248static int 249hfc_write (struct header_framer_ctx *hfc, const void *buf, size_t sz) 250{ 251 const unsigned char *p = buf; 252 unsigned avail; 253 int s; 254 255 while (sz > 0) 256 { 257 if (hfc->hfc_max_frame_sz == hfc->hfc_cur_sz) 258 { 259 if (hfc->hfc_n_frames > 0) 260 hfc_terminate_frame(hfc, 0); 261 s = lsquic_frab_list_write(&hfc->hfc_fw->fw_fral, "123456789", 262 sizeof(struct http_frame_header)); 263 if (s < 0) 264 return s; 265 ++hfc->hfc_n_frames; 266 hfc_save_ptr(hfc); 267 hfc->hfc_cur_sz = 0; 268 } 269 270 avail = hfc->hfc_max_frame_sz - hfc->hfc_cur_sz; 271 if (sz < avail) 272 avail = sz; 273 if (avail) 274 { 275 s = lsquic_frab_list_write(&hfc->hfc_fw->fw_fral, p, avail); 276 if (s < 0) 277 return s; 278 hfc->hfc_cur_sz += avail; 279 sz -= avail; 280 p += avail; 281 } 282 } 283 284 return 0; 285} 286 287 288static unsigned 289count_uppercase (const unsigned char *buf, size_t sz) 290{ 291 static const unsigned char uppercase[0x100] = { 292 ['A'] = 1, ['B'] = 1, ['C'] = 1, ['D'] = 1, ['E'] = 1, ['F'] = 1, 293 ['G'] = 1, ['H'] = 1, ['I'] = 1, ['J'] = 1, ['K'] = 1, ['L'] = 1, 294 ['M'] = 1, ['N'] = 1, ['O'] = 1, ['P'] = 1, ['Q'] = 1, ['R'] = 1, 295 ['S'] = 1, ['T'] = 1, ['U'] = 1, ['V'] = 1, ['W'] = 1, ['X'] = 1, 296 ['Y'] = 1, ['Z'] = 1, 297 }; 298 unsigned n_uppercase, i; 299 n_uppercase = 0; 300 for (i = 0; i < sz; ++i) 301 n_uppercase += uppercase[ buf[i] ]; 302 return n_uppercase; 303} 304 305 306static uint32_t 307calc_headers_size (const struct lsquic_http_headers *headers) 308{ 309 int i; 310 uint32_t size = 0; 311 for (i = 0; i < headers->count; ++i) 312 size += 32 + headers->headers[i].name.iov_len + 313 headers->headers[i].value.iov_len; 314 return size; 315} 316 317 318static int 319have_oversize_strings (const struct lsquic_http_headers *headers) 320{ 321 int i, have; 322 for (i = 0, have = 0; i < headers->count; ++i) 323 { 324 have |= headers->headers[i].name.iov_len > LSHPACK_MAX_STRLEN; 325 have |= headers->headers[i].value.iov_len > LSHPACK_MAX_STRLEN; 326 } 327 return have; 328} 329 330 331static int 332check_headers_size (const struct lsquic_frame_writer *fw, 333 const struct lsquic_http_headers *headers, 334 const struct lsquic_http_headers *extra_headers) 335{ 336 uint32_t headers_sz; 337 headers_sz = calc_headers_size(headers); 338 if (extra_headers) 339 headers_sz += calc_headers_size(extra_headers); 340 341 if (headers_sz <= fw->fw_max_header_list_sz) 342 return 0; 343 else if (fw->fw_flags & FW_SERVER) 344 { 345 LSQ_INFO("Sending headers larger (%u bytes) than max allowed (%u)", 346 headers_sz, fw->fw_max_header_list_sz); 347 return 0; 348 } 349 else 350 { 351 LSQ_INFO("Headers size %u is larger than max allowed (%u)", 352 headers_sz, fw->fw_max_header_list_sz); 353 errno = EMSGSIZE; 354 return -1; 355 } 356} 357 358 359static int 360check_headers_case (const struct lsquic_frame_writer *fw, 361 const struct lsquic_http_headers *headers) 362{ 363 unsigned n_uppercase; 364 int i; 365 n_uppercase = 0; 366 for (i = 0; i < headers->count; ++i) 367 n_uppercase += count_uppercase(headers->headers[i].name.iov_base, 368 headers->headers[i].name.iov_len); 369 if (n_uppercase) 370 { 371 LSQ_INFO("Uppercase letters in header names"); 372 errno = EINVAL; 373 return -1; 374 } 375 return 0; 376} 377 378 379static int 380write_headers (struct lsquic_frame_writer *fw, 381 const struct lsquic_http_headers *headers, 382 struct header_framer_ctx *hfc, unsigned char *buf, 383 const unsigned buf_sz) 384{ 385 unsigned char *end; 386 int i, s; 387 388 for (i = 0; i < headers->count; ++i) 389 { 390 end = lshpack_enc_encode(fw->fw_henc, buf, buf + buf_sz, 391 LSHPACK_HDR_UNKNOWN, 392 (const lshpack_header_t *)&headers->headers[i], 393 0); 394 if (end > buf) 395 { 396 s = hfc_write(hfc, buf, end - buf); 397 if (s < 0) 398 return s; 399#if LSQUIC_CONN_STATS 400 fw->fw_conn_stats->out.headers_uncomp += 401 headers->headers[i].name.iov_len 402 + headers->headers[i].value.iov_len; 403 fw->fw_conn_stats->out.headers_comp += end - buf; 404#endif 405 } 406 else 407 { 408 /* Ignore errors, matching HTTP2 behavior in our server code */ 409 } 410 } 411 412 return 0; 413} 414 415 416int 417lsquic_frame_writer_write_headers (struct lsquic_frame_writer *fw, 418 lsquic_stream_id_t stream_id, 419 const struct lsquic_http_headers *headers, 420 int eos, unsigned weight) 421{ 422 struct header_framer_ctx hfc; 423 int s; 424 struct http_prio_frame prio_frame; 425 enum http_frame_header_flags flags; 426 unsigned char *buf; 427 428 /* Internal function: weight must be valid here */ 429 assert(weight >= 1 && weight <= 256); 430 431 if (fw->fw_max_header_list_sz && 0 != check_headers_size(fw, headers, NULL)) 432 return -1; 433 434 if (0 != check_headers_case(fw, headers)) 435 return -1; 436 437 if (have_oversize_strings(headers)) 438 return -1; 439 440 if (eos) 441 flags = HFHF_END_STREAM; 442 else 443 flags = 0; 444 445 if (!(fw->fw_flags & FW_SERVER)) 446 flags |= HFHF_PRIORITY; 447 448 hfc_init(&hfc, fw, fw->fw_max_frame_sz, HTTP_FRAME_HEADERS, stream_id, 449 flags); 450 451 if (!(fw->fw_flags & FW_SERVER)) 452 { 453 memset(&prio_frame.hpf_stream_id, 0, sizeof(prio_frame.hpf_stream_id)); 454 prio_frame.hpf_weight = weight - 1; 455 s = hfc_write(&hfc, &prio_frame, sizeof(struct http_prio_frame)); 456 if (s < 0) 457 return s; 458 } 459 460 buf = malloc(MAX_HEADERS_SIZE); 461 if (!buf) 462 return -1; 463 s = write_headers(fw, headers, &hfc, buf, MAX_HEADERS_SIZE); 464 free(buf); 465 if (0 == s) 466 { 467 EV_LOG_GENERATED_HTTP_HEADERS(LSQUIC_LOG_CONN_ID, stream_id, 468 fw->fw_flags & FW_SERVER, &prio_frame, headers); 469 hfc_terminate_frame(&hfc, HFHF_END_HEADERS); 470 return lsquic_frame_writer_flush(fw); 471 } 472 else 473 return s; 474} 475 476 477int 478lsquic_frame_writer_write_promise (struct lsquic_frame_writer *fw, 479 lsquic_stream_id_t stream_id64, lsquic_stream_id_t promised_stream_id64, 480 const struct iovec *path, const struct iovec *host, 481 const struct lsquic_http_headers *extra_headers) 482{ 483 uint32_t stream_id = stream_id64; 484 uint32_t promised_stream_id = promised_stream_id64; 485 struct header_framer_ctx hfc; 486 struct http_push_promise_frame push_frame; 487 lsquic_http_header_t mpas_headers[4]; 488 struct lsquic_http_headers mpas = { /* method, path, authority, scheme */ 489 .headers = mpas_headers, 490 .count = 4, 491 }; 492 unsigned char *buf; 493 int s; 494 495 mpas_headers[0].name. iov_base = ":method"; 496 mpas_headers[0].name. iov_len = 7; 497 mpas_headers[0].value.iov_base = "GET"; 498 mpas_headers[0].value.iov_len = 3; 499 mpas_headers[1].name .iov_base = ":path"; 500 mpas_headers[1].name .iov_len = 5; 501 mpas_headers[1].value = *path; 502 mpas_headers[2].name .iov_base = ":authority"; 503 mpas_headers[2].name .iov_len = 10; 504 mpas_headers[2].value = *host; 505 mpas_headers[3].name. iov_base = ":scheme"; 506 mpas_headers[3].name. iov_len = 7; 507 mpas_headers[3].value.iov_base = "https"; 508 mpas_headers[3].value.iov_len = 5; 509 510 if (fw->fw_max_header_list_sz && 511 0 != check_headers_size(fw, &mpas, extra_headers)) 512 return -1; 513 514 if (extra_headers && 0 != check_headers_case(fw, extra_headers)) 515 return -1; 516 517 if (have_oversize_strings(&mpas)) 518 return -1; 519 520 if (extra_headers && have_oversize_strings(extra_headers)) 521 return -1; 522 523 hfc_init(&hfc, fw, fw->fw_max_frame_sz, HTTP_FRAME_PUSH_PROMISE, 524 stream_id, 0); 525 526 promised_stream_id = htonl(promised_stream_id); 527 memcpy(push_frame.hppf_promised_id, &promised_stream_id, 4); 528 s = hfc_write(&hfc, &push_frame, sizeof(struct http_push_promise_frame)); 529 if (s < 0) 530 return s; 531 532 buf = malloc(MAX_HEADERS_SIZE); 533 if (!buf) 534 return -1; 535 536 s = write_headers(fw, &mpas, &hfc, buf, MAX_HEADERS_SIZE); 537 if (s != 0) 538 { 539 free(buf); 540 return -1; 541 } 542 543 if (extra_headers) 544 s = write_headers(fw, extra_headers, &hfc, buf, MAX_HEADERS_SIZE); 545 546 free(buf); 547 548 if (0 == s) 549 { 550 EV_LOG_GENERATED_HTTP_PUSH_PROMISE(LSQUIC_LOG_CONN_ID, stream_id, 551 htonl(promised_stream_id), &mpas, extra_headers); 552 hfc_terminate_frame(&hfc, HFHF_END_HEADERS); 553 return lsquic_frame_writer_flush(fw); 554 } 555 else 556 return -1; 557} 558 559 560void 561lsquic_frame_writer_max_header_list_size (struct lsquic_frame_writer *fw, 562 uint32_t max_size) 563{ 564 LSQ_DEBUG("set max_header_list_sz to %u", max_size); 565 fw->fw_max_header_list_sz = max_size; 566} 567 568 569static int 570write_settings (struct lsquic_frame_writer *fw, 571 const struct lsquic_http2_setting *settings, unsigned n_settings) 572{ 573 struct http_frame_header fh; 574 unsigned payload_length; 575 uint32_t val; 576 uint16_t id; 577 int s; 578 579 payload_length = n_settings * 6; 580 581 memset(&fh, 0, sizeof(fh)); 582 fh.hfh_type = HTTP_FRAME_SETTINGS; 583 fh.hfh_length[0] = payload_length >> 16; 584 fh.hfh_length[1] = payload_length >> 8; 585 fh.hfh_length[2] = payload_length; 586 587 s = lsquic_frab_list_write(&fw->fw_fral, &fh, sizeof(fh)); 588 if (s != 0) 589 return s; 590 591 do 592 { 593 id = htons(settings->id); 594 val = htonl(settings->value); 595 if (0 != (s = lsquic_frab_list_write(&fw->fw_fral, &id, sizeof(id))) || 596 0 != (s = lsquic_frab_list_write(&fw->fw_fral, &val, sizeof(val)))) 597 return s; 598 EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "wrote HTTP SETTINGS frame: " 599 "%s=%"PRIu32, lsquic_http_setting_id2str(settings->id), 600 settings->value); 601 ++settings; 602 } 603 while (--n_settings); 604 605 return 0; 606} 607 608int 609lsquic_frame_writer_write_settings (struct lsquic_frame_writer *fw, 610 const struct lsquic_http2_setting *settings, unsigned n_settings) 611{ 612 unsigned settings_per_frame; 613 unsigned n; 614 615 if (0 == n_settings) 616 { 617 errno = EINVAL; 618 return -1; 619 } 620 621 settings_per_frame = fw->fw_max_frame_sz / SETTINGS_FRAME_SZ; 622 n = 0; 623 624 do { 625 if (settings_per_frame > n_settings - n) 626 settings_per_frame = n_settings - n; 627 if (0 != write_settings(fw, &settings[n], settings_per_frame)) 628 return -1; 629 n += settings_per_frame; 630 } while (n < n_settings); 631 632 return lsquic_frame_writer_flush(fw); 633} 634 635 636int 637lsquic_frame_writer_write_priority (struct lsquic_frame_writer *fw, 638 lsquic_stream_id_t stream_id64, int exclusive, 639 lsquic_stream_id_t stream_dep_id64, unsigned weight) 640{ 641 uint32_t stream_id = stream_id64; 642 uint32_t stream_dep_id = stream_dep_id64; 643 unsigned char buf[ sizeof(struct http_frame_header) + 644 sizeof(struct http_prio_frame) ]; 645 struct http_frame_header *fh = (void *) &buf[0]; 646 struct http_prio_frame *prio_frame = (void *) &buf[sizeof(*fh)]; 647 int s; 648 649 if (stream_dep_id & (1UL << 31)) 650 { 651 LSQ_WARN("stream ID too high (%u): cannot write PRIORITY frame", 652 stream_dep_id); 653 return -1; 654 } 655 656 if (weight < 1 || weight > 256) 657 return -1; 658 659 memset(fh, 0, sizeof(*fh)); 660 fh->hfh_type = HTTP_FRAME_PRIORITY; 661 fh->hfh_length[2] = sizeof(struct http_prio_frame); 662 stream_id = htonl(stream_id); 663 memcpy(fh->hfh_stream_id, &stream_id, 4); 664 665 stream_dep_id |= !!exclusive << 31; 666 stream_id = htonl(stream_dep_id); 667 memcpy(prio_frame->hpf_stream_id, &stream_id, 4); 668 prio_frame->hpf_weight = weight - 1; 669 670 s = lsquic_frab_list_write(&fw->fw_fral, buf, sizeof(buf)); 671 if (s != 0) 672 return s; 673 674 EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "wrote HTTP PRIORITY frame: " 675 "stream %"PRIu32"; weight: %u; exclusive: %d", 676 htonl(stream_id), weight, !!exclusive); 677 678 return lsquic_frame_writer_flush(fw); 679} 680 681 682size_t 683lsquic_frame_writer_mem_used (const struct lsquic_frame_writer *fw) 684{ 685 size_t size; 686 687 size = sizeof(*fw) 688 + lsquic_frab_list_mem_used(&fw->fw_fral) 689 - sizeof(fw->fw_fral); 690 691 return size; 692} 693