lsquic_trans_params.c revision a4f5dac3
1/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */ 2/* 3 * lsquic_trans_params.c 4 */ 5 6#include <assert.h> 7#include <errno.h> 8#include <inttypes.h> 9#include <stddef.h> 10#include <stdint.h> 11#include <string.h> 12 13#include <arpa/inet.h> 14#include <sys/socket.h> 15 16#include "lsquic_byteswap.h" 17#include "lsquic_int_types.h" 18#include "lsquic_types.h" 19#include "lsquic_version.h" 20#include "lsquic_sizes.h" 21#include "lsquic_trans_params.h" 22#include "lsquic_util.h" 23#include "lsquic_varint.h" 24 25#define LSQUIC_LOGGER_MODULE LSQLM_TRAPA 26#include "lsquic_logger.h" 27 28 29static const uint64_t def_vals[MAX_TPI + 1] = 30{ 31 [TPI_MAX_PACKET_SIZE] = TP_DEF_MAX_PACKET_SIZE, 32 [TPI_ACK_DELAY_EXPONENT] = TP_DEF_ACK_DELAY_EXP, 33 [TPI_INIT_MAX_STREAMS_UNI] = TP_DEF_INIT_MAX_STREAMS_UNI, 34 [TPI_INIT_MAX_STREAMS_BIDI] = TP_DEF_INIT_MAX_STREAMS_BIDI, 35 [TPI_INIT_MAX_DATA] = TP_DEF_INIT_MAX_DATA, 36 [TPI_INIT_MAX_STREAM_DATA_BIDI_LOCAL] = TP_DEF_INIT_MAX_STREAM_DATA_BIDI_LOCAL, 37 [TPI_INIT_MAX_STREAM_DATA_BIDI_REMOTE] = TP_DEF_INIT_MAX_STREAM_DATA_BIDI_REMOTE, 38 [TPI_INIT_MAX_STREAM_DATA_UNI] = TP_DEF_INIT_MAX_STREAM_DATA_UNI, 39 [TPI_IDLE_TIMEOUT] = TP_DEF_IDLE_TIMEOUT, 40 [TPI_MAX_ACK_DELAY] = TP_DEF_MAX_ACK_DELAY, 41 [TPI_ACTIVE_CONNECTION_ID_LIMIT] = TP_DEF_ACTIVE_CONNECTION_ID_LIMIT, 42}; 43 44 45static const uint64_t max_vals[MAX_TPI + 1] = 46{ 47 [TPI_MAX_PACKET_SIZE] = VINT_MAX_VALUE, 48 [TPI_ACK_DELAY_EXPONENT] = VINT_MAX_VALUE, 49 [TPI_INIT_MAX_STREAMS_UNI] = VINT_MAX_VALUE, 50 [TPI_INIT_MAX_STREAMS_BIDI] = VINT_MAX_VALUE, 51 [TPI_INIT_MAX_DATA] = VINT_MAX_VALUE, 52 [TPI_INIT_MAX_STREAM_DATA_BIDI_LOCAL] = VINT_MAX_VALUE, 53 [TPI_INIT_MAX_STREAM_DATA_BIDI_REMOTE] = VINT_MAX_VALUE, 54 [TPI_INIT_MAX_STREAM_DATA_UNI] = VINT_MAX_VALUE, 55 [TPI_IDLE_TIMEOUT] = VINT_MAX_VALUE, 56 [TPI_MAX_ACK_DELAY] = TP_MAX_MAX_ACK_DELAY, 57 [TPI_ACTIVE_CONNECTION_ID_LIMIT] = VINT_MAX_VALUE, 58}; 59 60 61#define TP_OFF(name_) ((uint64_t *) &((struct transport_params *) 0 \ 62 )->tp_numerics_u.s.name_ - (uint64_t *) &((struct transport_params *) \ 63 0)->tp_numerics_u.s) 64 65/* Map enum transport_params to index of tp_numerics_u.a; for numeric values only */ 66static const unsigned tpi2idx[MAX_TPI + 1] = 67{ 68 [TPI_MAX_PACKET_SIZE] = TP_OFF(max_packet_size), 69 [TPI_ACK_DELAY_EXPONENT] = TP_OFF(ack_delay_exponent), 70 [TPI_INIT_MAX_STREAMS_UNI] = TP_OFF(init_max_streams_uni), 71 [TPI_INIT_MAX_STREAMS_BIDI] = TP_OFF(init_max_streams_bidi), 72 [TPI_INIT_MAX_DATA] = TP_OFF(init_max_data), 73 [TPI_INIT_MAX_STREAM_DATA_BIDI_LOCAL] = TP_OFF(init_max_stream_data_bidi_local), 74 [TPI_INIT_MAX_STREAM_DATA_BIDI_REMOTE] = TP_OFF(init_max_stream_data_bidi_remote), 75 [TPI_INIT_MAX_STREAM_DATA_UNI] = TP_OFF(init_max_stream_data_uni), 76 [TPI_IDLE_TIMEOUT] = TP_OFF(idle_timeout), 77 [TPI_MAX_ACK_DELAY] = TP_OFF(max_ack_delay), 78 [TPI_ACTIVE_CONNECTION_ID_LIMIT] = TP_OFF(active_connection_id_limit), 79}; 80 81 82static size_t 83preferred_address_size (const struct transport_params *params) 84{ 85 return sizeof(params->tp_preferred_address.ipv4_addr) 86 + sizeof(params->tp_preferred_address.ipv4_port) 87 + sizeof(params->tp_preferred_address.ipv6_addr) 88 + sizeof(params->tp_preferred_address.ipv6_port) 89 + 1 + params->tp_preferred_address.cid.len 90 + sizeof(params->tp_preferred_address.srst) 91 ; 92} 93 94 95int 96lsquic_tp_encode (const struct transport_params *params, 97 unsigned char *const buf, size_t bufsz) 98{ 99 unsigned char *p; 100 size_t need = 2; 101 uint16_t u16; 102 enum transport_param_id tpi; 103 unsigned bits[MAX_TPI + 1]; 104 105 if (params->tp_flags & TRAPA_SERVER) 106 { 107 if (params->tp_flags & TRAPA_ORIGINAL_CID) 108 need += 4 + params->tp_original_cid.len; 109 if (params->tp_flags & TRAPA_RESET_TOKEN) 110 need += 4 + sizeof(params->tp_stateless_reset_token); 111 if (params->tp_flags & (TRAPA_PREFADDR_IPv4|TRAPA_PREFADDR_IPv6)) 112 need += 4 + preferred_address_size(params); 113 } 114#if LSQUIC_TEST_QUANTUM_READINESS 115 else if (params->tp_flags & TRAPA_QUANTUM_READY) 116 need += 4 + QUANTUM_READY_SZ; 117#endif 118 119 for (tpi = 0; tpi <= MAX_TPI; ++tpi) 120 if ((NUMERIC_TRANS_PARAMS & (1 << tpi)) 121 && params->tp_numerics_u.a[tpi2idx[tpi]] != def_vals[tpi]) 122 { 123 if (params->tp_numerics_u.a[tpi2idx[tpi]] < max_vals[tpi]) 124 { 125 bits[tpi] = vint_val2bits(params->tp_numerics_u.a[tpi2idx[tpi]]); 126 need += 4 + (1 << bits[tpi]); 127 } 128 else 129 { 130 LSQ_DEBUG("numeric value is too large (%"PRIu64" vs maximum " 131 "of %"PRIu64, params->tp_numerics_u.a[tpi2idx[tpi]], 132 max_vals[tpi]); 133 return -1; 134 } 135 } 136 137 if (params->tp_disable_active_migration != TP_DEF_DISABLE_ACTIVE_MIGRATION) 138 need += 4 + 0; 139 140 if (params->tp_flags & TRAPA_QL_BITS_OLD) 141 need += 4 + 0; 142 else if (params->tp_flags & TRAPA_QL_BITS) 143 need += 4 + 1; 144 145 if (need > bufsz || need > UINT16_MAX) 146 { 147 errno = ENOBUFS; 148 return -1; 149 } 150 151 p = buf; 152 153#define WRITE_TO_P(src, len) do { \ 154 memcpy(p, src, len); \ 155 p += len; \ 156} while (0) 157 158#if __BYTE_ORDER == __LITTLE_ENDIAN 159#define WRITE_UINT_TO_P(val, width) do { \ 160 u##width = bswap_##width(val); \ 161 WRITE_TO_P(&u##width, sizeof(u##width)); \ 162} while (0) 163#else 164#define WRITE_UINT_TO_P(val, width) do { \ 165 u##width = val; \ 166 WRITE_TO_P(&u##width, sizeof(u##width)); \ 167} while (0) 168#endif 169 170#define WRITE_PARAM_TO_P(tpidx, tpval, width) do { \ 171 WRITE_UINT_TO_P(tpidx, 16); \ 172 WRITE_UINT_TO_P(width / 8, 16); \ 173 if (width > 8) \ 174 WRITE_UINT_TO_P(tpval, width); \ 175 else if (width) \ 176 *p++ = tpval; \ 177} while (0) 178 179 WRITE_UINT_TO_P(need - 2 + buf - p, 16); 180 181 for (tpi = 0; tpi <= MAX_TPI; ++tpi) 182 if (NUMERIC_TRANS_PARAMS & (1 << tpi)) 183 { 184 if (params->tp_numerics_u.a[tpi2idx[tpi]] != def_vals[tpi]) 185 { 186 WRITE_UINT_TO_P(tpi, 16); 187 WRITE_UINT_TO_P(1 << bits[tpi], 16); 188 vint_write(p, params->tp_numerics_u.a[tpi2idx[tpi]], bits[tpi], 189 1 << bits[tpi]); 190 p += 1 << bits[tpi]; 191 } 192 } 193 else 194 switch (tpi) 195 { 196 case TPI_ORIGINAL_CONNECTION_ID: 197 if (params->tp_flags & TRAPA_ORIGINAL_CID) 198 { 199 WRITE_UINT_TO_P(TPI_ORIGINAL_CONNECTION_ID, 16); 200 WRITE_UINT_TO_P(params->tp_original_cid.len, 16); 201 WRITE_TO_P(params->tp_original_cid.idbuf, 202 params->tp_original_cid.len); 203 } 204 break; 205 case TPI_STATELESS_RESET_TOKEN: 206 if (params->tp_flags & TRAPA_RESET_TOKEN) 207 { 208 WRITE_UINT_TO_P(TPI_STATELESS_RESET_TOKEN, 16); 209 WRITE_UINT_TO_P(sizeof(params->tp_stateless_reset_token), 210 16); 211 WRITE_TO_P(params->tp_stateless_reset_token, 212 sizeof(params->tp_stateless_reset_token)); 213 } 214 break; 215 case TPI_PREFERRED_ADDRESS: 216 if (params->tp_flags 217 & (TRAPA_PREFADDR_IPv4|TRAPA_PREFADDR_IPv6)) 218 { 219 WRITE_UINT_TO_P(TPI_PREFERRED_ADDRESS, 16); 220 WRITE_UINT_TO_P(preferred_address_size(params), 16); 221 if (params->tp_flags & TRAPA_PREFADDR_IPv4) 222 { 223 WRITE_TO_P(¶ms->tp_preferred_address.ipv4_addr, 224 sizeof(params->tp_preferred_address.ipv4_addr)); 225 WRITE_UINT_TO_P(params->tp_preferred_address.ipv4_port, 226 16); 227 } 228 else 229 { 230 memset(p, 0, 6); 231 p += 6; 232 } 233 if (params->tp_flags & TRAPA_PREFADDR_IPv6) 234 { 235 WRITE_TO_P(¶ms->tp_preferred_address.ipv6_addr, 236 sizeof(params->tp_preferred_address.ipv6_addr)); 237 WRITE_UINT_TO_P(params->tp_preferred_address.ipv6_port, 238 16); 239 } 240 else 241 { 242 memset(p, 0, 18); 243 p += 18; 244 } 245 *p++ = params->tp_preferred_address.cid.len; 246 WRITE_TO_P(params->tp_preferred_address.cid.idbuf, 247 params->tp_preferred_address.cid.len); 248 WRITE_TO_P(params->tp_preferred_address.srst, 249 sizeof(params->tp_preferred_address.srst)); 250 } 251 break; 252 case TPI_DISABLE_ACTIVE_MIGRATION: 253 if (params->tp_disable_active_migration != TP_DEF_DISABLE_ACTIVE_MIGRATION) 254 { 255 WRITE_UINT_TO_P(TPI_DISABLE_ACTIVE_MIGRATION, 16); 256 WRITE_UINT_TO_P(0, 16); 257 } 258 break; 259 default: 260 assert(0); 261 return -1; 262 } 263 264 if (params->tp_flags & TRAPA_QL_BITS_OLD) 265 { 266 WRITE_UINT_TO_P(TPI_QL_BITS, 16); 267 WRITE_UINT_TO_P(0, 16); 268 } 269 else if (params->tp_flags & TRAPA_QL_BITS) 270 { 271 WRITE_UINT_TO_P(TPI_QL_BITS, 16); 272 WRITE_UINT_TO_P(1, 16); 273 *p++ = !!params->tp_loss_bits; 274 } 275 276#if LSQUIC_TEST_QUANTUM_READINESS 277 if (params->tp_flags & TRAPA_QUANTUM_READY) 278 { 279 WRITE_UINT_TO_P(TPI_QUANTUM_READINESS, 16); 280 WRITE_UINT_TO_P(QUANTUM_READY_SZ, 16); 281 memset(p, 'Q', QUANTUM_READY_SZ); 282 p += QUANTUM_READY_SZ; 283 } 284#endif 285 286 assert(buf + need == p); 287 return (int) (p - buf); 288 289#undef WRITE_TO_P 290#undef WRITE_UINT_TO_P 291} 292 293 294int 295lsquic_tp_decode (const unsigned char *const buf, size_t bufsz, 296 int is_server, 297 struct transport_params *params) 298{ 299 const unsigned char *p, *end, *q; 300 uint16_t len, param_id, tlen; 301 unsigned set_of_ids; 302 int s; 303 uint64_t tmp64; 304 305 p = buf; 306 end = buf + bufsz; 307 308 *params = TP_INITIALIZER(); 309 310 if (is_server) 311 params->tp_flags |= TRAPA_SERVER; 312 313 if (end - p < 2) 314 return -1; 315 READ_UINT(len, 16, p, 2); 316 p += 2; 317 if (len > end - p) 318 return -1; 319 end = p + len; 320 321#define EXPECT_LEN(expected_len) do { \ 322 if (expected_len != len) \ 323 return -1; \ 324} while (0) 325 326#define EXPECT_AT_LEAST(expected_len) do { \ 327 if ((expected_len) > (uintptr_t) (p + len - q)) \ 328 return -1; \ 329} while (0) 330 331 set_of_ids = 0; 332 while (p + 4 <= end) 333 { 334 READ_UINT(param_id, 16, p, 2); 335 p += 2; 336 READ_UINT(len, 16, p, 2); 337 p += 2; 338 if (len > end - p) 339 return -1; 340 /* If we need to support parameter IDs 31 and up, we will need to 341 * change this code: 342 */ 343 if (param_id < sizeof(set_of_ids) * 8) 344 { 345 /* Only check duplicates for IDs <= 31: all standard parameters 346 * fit in a bitmask 32 bits wide. 347 */ 348 if (set_of_ids & (1 << param_id)) 349 return -1; 350 set_of_ids |= 1 << param_id; 351 } 352 else 353 goto gt32; 354 if (NUMERIC_TRANS_PARAMS & (1u << param_id)) 355 { 356 switch (len) 357 { 358 case 1: 359 case 2: 360 case 4: 361 case 8: 362 s = vint_read(p, p + len, 363 ¶ms->tp_numerics_u.a[tpi2idx[param_id]]); 364 if (s == len) 365 { 366 if (params->tp_numerics_u.a[tpi2idx[param_id]] 367 > max_vals[param_id]) 368 { 369 LSQ_DEBUG("numeric value of parameter 0x%X is too " 370 "large (%"PRIu64" vs maximum of %"PRIu64, 371 param_id, 372 params->tp_numerics_u.a[tpi2idx[param_id]], 373 max_vals[param_id]); 374 return -1; 375 } 376 p += s; 377 break; 378 } 379 else 380 { 381 LSQ_DEBUG("cannot read the value of numeric transport " 382 "param %u of length %u", param_id, len); 383 return -1; 384 } 385 default: 386 LSQ_DEBUG("invalid length=%u for numeric transport parameter", 387 len); 388 return -1; 389 } 390 } 391 else 392 { 393 gt32: switch (param_id) 394 { 395 case TPI_DISABLE_ACTIVE_MIGRATION: 396 EXPECT_LEN(0); 397 params->tp_disable_active_migration = 1; 398 break; 399 case TPI_STATELESS_RESET_TOKEN: 400 /* Client MUST not include reset token, 401 * see [draft-ietf-quic-transport-11], Section 6.4.1 402 */ 403 if (!is_server) 404 return -1; 405 EXPECT_LEN(sizeof(params->tp_stateless_reset_token)); 406 memcpy(params->tp_stateless_reset_token, p, 407 sizeof(params->tp_stateless_reset_token)); 408 params->tp_flags |= TRAPA_RESET_TOKEN; 409 break; 410 case TPI_ORIGINAL_CONNECTION_ID: 411 /* Client MUST not original connecti ID, 412 * see [draft-ietf-quic-transport-15], Section 6.6.1 413 */ 414 if (!is_server) 415 return -1; 416 if (len > MAX_CID_LEN) 417 return -1; 418 memcpy(params->tp_original_cid.idbuf, p, len); 419 params->tp_original_cid.len = len; 420 params->tp_flags |= TRAPA_ORIGINAL_CID; 421 break; 422 case TPI_PREFERRED_ADDRESS: 423 /* Client MUST not include preferred address, 424 * see [draft-ietf-quic-transport-12], Section 6.4.1 425 */ 426 if (!is_server) 427 return -1; 428 q = p; 429 EXPECT_AT_LEAST(sizeof(params->tp_preferred_address.ipv4_addr)); 430 memcpy(params->tp_preferred_address.ipv4_addr, q, 431 sizeof(params->tp_preferred_address.ipv4_addr)); 432 q += sizeof(params->tp_preferred_address.ipv4_addr); 433 EXPECT_AT_LEAST(sizeof(params->tp_preferred_address.ipv4_port)); 434 READ_UINT(params->tp_preferred_address.ipv4_port, 16, q, 2); 435 q += 2; 436 EXPECT_AT_LEAST(sizeof(params->tp_preferred_address.ipv6_addr)); 437 memcpy(params->tp_preferred_address.ipv6_addr, q, 438 sizeof(params->tp_preferred_address.ipv6_addr)); 439 q += sizeof(params->tp_preferred_address.ipv6_addr); 440 EXPECT_AT_LEAST(sizeof(params->tp_preferred_address.ipv6_port)); 441 READ_UINT(params->tp_preferred_address.ipv6_port, 16, q, 2); 442 q += 2; 443 EXPECT_AT_LEAST(1); 444 tlen = *q; 445 q += 1; 446 if (tlen < 4 || tlen > MAX_CID_LEN) 447 { 448 LSQ_DEBUG("preferred server address contains invalid " 449 "CID length of %"PRIu16" bytes", tlen); 450 return -1; 451 } 452 EXPECT_AT_LEAST(tlen); 453 memcpy(params->tp_preferred_address.cid.idbuf, q, tlen); 454 params->tp_preferred_address.cid.len = tlen; 455 q += tlen; 456 EXPECT_AT_LEAST(sizeof(params->tp_preferred_address.srst)); 457 memcpy(params->tp_preferred_address.srst, q, 458 sizeof(params->tp_preferred_address.srst)); 459 q += sizeof(params->tp_preferred_address.srst); 460 if (q != p + len) 461 return -1; 462 if (params->tp_preferred_address.ipv4_port 463 && !lsquic_is_zero(params->tp_preferred_address.ipv4_addr, 464 sizeof(params->tp_preferred_address.ipv4_addr))) 465 params->tp_flags |= TRAPA_PREFADDR_IPv4; 466 if (params->tp_preferred_address.ipv6_port 467 && !lsquic_is_zero(params->tp_preferred_address.ipv6_addr, 468 sizeof(params->tp_preferred_address.ipv6_addr))) 469 params->tp_flags |= TRAPA_PREFADDR_IPv6; 470 break; 471 case TPI_QL_BITS: 472 switch (len) 473 { 474 case 0: 475 /* Old-school boolean */ 476 params->tp_flags |= TRAPA_QL_BITS; 477 params->tp_loss_bits = 1; 478 break; 479 case 1: 480 case 2: 481 case 4: 482 case 8: 483 s = vint_read(p, p + len, &tmp64); 484 if (s != len) 485 { 486 LSQ_DEBUG("cannot read the value of numeric transport " 487 "param loss_bits of length %u", len); 488 return -1; 489 } 490 if (!(tmp64 == 0 || tmp64 == 1)) 491 { 492 LSQ_DEBUG("unexpected value of loss_bits TP: %"PRIu64, 493 tmp64); 494 return -1; 495 } 496 params->tp_loss_bits = tmp64; 497 params->tp_flags |= TRAPA_QL_BITS; 498 break; 499 default: 500 return -1; 501 } 502 break; 503 } 504 p += len; 505 } 506 } 507 508 if (p != end) 509 return -1; 510 511 return (int) (end - buf); 512#undef EXPECT_LEN 513} 514 515 516void 517lsquic_tp_to_str (const struct transport_params *params, char *buf, size_t sz) 518{ 519 char *const end = buf + sz; 520 int nw; 521 char tok_str[sizeof(params->tp_stateless_reset_token) * 2 + 1]; 522 char addr_str[INET6_ADDRSTRLEN]; 523 524#define SEMICOLON "; " 525#define WRITE_ONE_PARAM(name, fmt) do { \ 526 nw = snprintf(buf, end - buf, #name ": " fmt SEMICOLON, params->tp_##name); \ 527 buf += nw; \ 528 if (buf >= end) \ 529 return; \ 530} while (0) 531 532 WRITE_ONE_PARAM(init_max_stream_data_bidi_local, "%"PRIu64); 533 WRITE_ONE_PARAM(init_max_stream_data_bidi_remote, "%"PRIu64); 534 WRITE_ONE_PARAM(init_max_stream_data_uni, "%"PRIu64); 535 WRITE_ONE_PARAM(init_max_data, "%"PRIu64); 536 WRITE_ONE_PARAM(idle_timeout, "%"PRIu64); 537 WRITE_ONE_PARAM(init_max_streams_bidi, "%"PRIu64); 538 WRITE_ONE_PARAM(init_max_streams_uni, "%"PRIu64); 539 WRITE_ONE_PARAM(max_packet_size, "%"PRIu64); 540 WRITE_ONE_PARAM(ack_delay_exponent, "%"PRIu64); 541 WRITE_ONE_PARAM(active_connection_id_limit, "%"PRIu64); 542 WRITE_ONE_PARAM(disable_active_migration, "%hhd"); 543#undef SEMICOLON 544#define SEMICOLON "" 545 WRITE_ONE_PARAM(max_ack_delay, "%"PRIu64); 546 if (params->tp_flags & TRAPA_RESET_TOKEN) 547 { 548 lsquic_hexstr(params->tp_stateless_reset_token, 549 sizeof(params->tp_stateless_reset_token), tok_str, sizeof(tok_str)); 550 nw = snprintf(buf, end - buf, "; stateless_reset_token: %s", tok_str); 551 buf += nw; 552 if (buf >= end) 553 return; 554 } 555 if (params->tp_flags & TRAPA_RESET_TOKEN) 556 { 557 char cidbuf_[MAX_CID_LEN * 2 + 1]; 558 nw = snprintf(buf, end - buf, "; original DCID (ODCID): %"CID_FMT, 559 CID_BITS(¶ms->tp_original_cid)); 560 buf += nw; 561 if (buf >= end) 562 return; 563 } 564 if (params->tp_flags & TRAPA_PREFADDR_IPv4) 565 { 566 if (inet_ntop(AF_INET, params->tp_preferred_address.ipv4_addr, 567 addr_str, sizeof(addr_str))) 568 { 569 nw = snprintf(buf, end - buf, "; IPv4 preferred address: %s:%u", 570 addr_str, params->tp_preferred_address.ipv4_port); 571 buf += nw; 572 if (buf >= end) 573 return; 574 } 575 } 576 if (params->tp_flags & TRAPA_PREFADDR_IPv6) 577 { 578 if (inet_ntop(AF_INET6, params->tp_preferred_address.ipv6_addr, 579 addr_str, sizeof(addr_str))) 580 { 581 nw = snprintf(buf, end - buf, "; IPv6 preferred address: %s:%u", 582 addr_str, params->tp_preferred_address.ipv6_port); 583 buf += nw; 584 if (buf >= end) 585 return; 586 } 587 } 588 if (params->tp_flags & TRAPA_QL_BITS) 589 { 590 nw = snprintf(buf, end - buf, "; QL loss bits: %hhu", 591 params->tp_loss_bits); 592 buf += nw; 593 if (buf >= end) 594 return; 595 } 596 597#undef SEMICOLON 598#undef WRITE_ONE_PARAM 599} 600