1 // Copyright (c) 2023 Huawei Device Co., Ltd. 2 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // you may not use this file except in compliance with the License. 4 // You may obtain a copy of the License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 use std::collections::VecDeque; 15 use std::ops::Add; 16 17 /// `TableSearcher` is used to find specified content in static and dynamic 18 /// tables. 19 pub(crate) struct TableSearcher<'a> { 20 dynamic: &'a DynamicTable, 21 } 22 23 impl<'a> TableSearcher<'a> { new(dynamic: &'a DynamicTable) -> Self24 pub(crate) fn new(dynamic: &'a DynamicTable) -> Self { 25 Self { dynamic } 26 } 27 28 /// Searches `HeaderName` in static and dynamic tables. search_header_name(&self, index: usize) -> Option<Header>29 pub(crate) fn search_header_name(&self, index: usize) -> Option<Header> { 30 if index <= 61 { 31 StaticTable::header_name(index) 32 } else { 33 self.dynamic.header_name(index - 62) 34 } 35 } 36 37 /// Searches `Header` in static and dynamic tables. search_header(&self, index: usize) -> Option<(Header, String)>38 pub(crate) fn search_header(&self, index: usize) -> Option<(Header, String)> { 39 if index <= 61 { 40 StaticTable::header(index) 41 } else { 42 self.dynamic.header(index - 62) 43 } 44 } 45 46 /// Searches index in static and dynamic tables. index(&self, header: &Header, value: &str) -> Option<TableIndex>47 pub(crate) fn index(&self, header: &Header, value: &str) -> Option<TableIndex> { 48 match ( 49 StaticTable::index(header, value), 50 self.dynamic.index(header, value), 51 ) { 52 (x @ Some(TableIndex::Header(_)), _) => x, 53 (_, Some(TableIndex::Header(i))) => Some(TableIndex::Header(i + 62)), 54 (x @ Some(TableIndex::HeaderName(_)), _) => x, 55 (_, Some(TableIndex::HeaderName(i))) => Some(TableIndex::Header(i + 62)), 56 _ => None, 57 } 58 } 59 } 60 61 pub(crate) enum TableIndex { 62 Header(usize), 63 HeaderName(usize), 64 } 65 66 /// The [`Dynamic Table`][dynamic_table] implementation of [HPACK]. 67 /// 68 /// [dynamic_table]: https://httpwg.org/specs/rfc7541.html#dynamic.table 69 /// [HPACK]: https://httpwg.org/specs/rfc7541.html 70 /// 71 /// # Introduction 72 /// The dynamic table consists of a list of header fields maintained in 73 /// first-in, first-out order. The first and newest entry in a dynamic table is 74 /// at the lowest index, and the oldest entry of a dynamic table is at the 75 /// highest index. 76 /// 77 /// The dynamic table is initially empty. Entries are added as each header block 78 /// is decompressed. 79 /// 80 /// The dynamic table can contain duplicate entries (i.e., entries with the same 81 /// name and same value). Therefore, duplicate entries MUST NOT be treated as an 82 /// error by a decoder. 83 /// 84 /// The encoder decides how to update the dynamic table and as such can control 85 /// how much memory is used by the dynamic table. To limit the memory 86 /// requirements of the decoder, the dynamic table size is strictly bounded. 87 /// 88 /// The decoder updates the dynamic table during the processing of a list of 89 /// header field representations. 90 pub(crate) struct DynamicTable { 91 queue: VecDeque<(Header, String)>, 92 curr_size: usize, 93 max_size: usize, 94 } 95 96 impl DynamicTable { 97 /// Creates a `Dynamic Table` based on the size limit. with_max_size(max_size: usize) -> Self98 pub(crate) fn with_max_size(max_size: usize) -> Self { 99 Self { 100 queue: VecDeque::new(), 101 curr_size: 0, 102 max_size, 103 } 104 } 105 curr_size(&self) -> usize106 pub(crate) fn curr_size(&self) -> usize { 107 self.curr_size 108 } 109 max_size(&self) -> usize110 pub(crate) fn max_size(&self) -> usize { 111 self.max_size 112 } 113 114 /// Gets a `Header` by the given index. header_name(&self, index: usize) -> Option<Header>115 pub(crate) fn header_name(&self, index: usize) -> Option<Header> { 116 self.queue.get(index).map(|(h, _)| h.clone()) 117 } 118 119 /// Gets a `Header` and a value by the given index. header(&self, index: usize) -> Option<(Header, String)>120 pub(crate) fn header(&self, index: usize) -> Option<(Header, String)> { 121 self.queue.get(index).cloned() 122 } 123 124 /// Updates `DynamicTable` by a given `Header` and value pair. update(&mut self, header: Header, value: String)125 pub(crate) fn update(&mut self, header: Header, value: String) { 126 // RFC7541-4.1: The additional 32 octets account for an estimated 127 // overhead associated with an entry. For example, an entry 128 // structure using two 64-bit pointers to reference the name and the 129 // value of the entry and two 64-bit integers for counting the 130 // number of references to the name and value would have 32 octets 131 // of overhead. 132 self.curr_size += header.len() + value.len() + 32; 133 self.queue.push_front((header, value)); 134 self.fit_size(); 135 } 136 137 /// Updates `DynamicTable`'s size. update_size(&mut self, max_size: usize)138 pub(crate) fn update_size(&mut self, max_size: usize) { 139 self.max_size = max_size; 140 self.fit_size(); 141 } 142 143 /// Adjusts dynamic table content to fit its size. fit_size(&mut self)144 fn fit_size(&mut self) { 145 while self.curr_size > self.max_size && !self.queue.is_empty() { 146 let (key, string) = self.queue.pop_back().unwrap(); 147 self.curr_size -= key.len() + string.len() + 32; 148 } 149 } 150 151 /// Tries get the index of a `Header`. index(&self, header: &Header, value: &str) -> Option<TableIndex>152 fn index(&self, header: &Header, value: &str) -> Option<TableIndex> { 153 let mut index = None; 154 for (n, (h, v)) in self.queue.iter().enumerate() { 155 match (header == h, value == v, &index) { 156 (true, true, _) => return Some(TableIndex::Header(n)), 157 (true, false, None) => index = Some(TableIndex::HeaderName(n)), 158 _ => {} 159 } 160 } 161 index 162 } 163 } 164 165 /// The [`Static Table`][static_table] implementation of [HPACK]. 166 /// 167 /// [static_table]: https://httpwg.org/specs/rfc7541.html#static.table 168 /// [HPACK]: https://httpwg.org/specs/rfc7541.html 169 /// 170 /// # Introduction 171 /// The static table consists of a predefined static list of header fields. 172 /// 173 /// # List 174 /// | Index | Header Name | Header Value | 175 /// | :---: | :---: | :---: | 176 /// | 1 | :authority | | 177 /// | 2 | :method | GET | 178 /// | 3 | :method | POST | 179 /// | 4 | :path | / | 180 /// | 5 | :path | /index.html | 181 /// | 6 | :scheme | http | 182 /// | 7 | :scheme | https | 183 /// | 8 | :status | 200 | 184 /// | 9 | :status | 204 | 185 /// | 10 | :status | 206 | 186 /// | 11 | :status | 304 | 187 /// | 12 | :status | 400 | 188 /// | 13 | :status | 404 | 189 /// | 14 | :status | 500 | 190 /// | 15 | accept-charset | | 191 /// | 16 | accept-encoding | gzip, deflate | 192 /// | 17 | accept-language | | 193 /// | 18 | accept-ranges | | 194 /// | 19 | accept | | 195 /// | 20 | access-control-allow-origin | | 196 /// | 21 | age | | 197 /// | 22 | allow | | 198 /// | 23 | authorization | | 199 /// | 24 | cache-control | | 200 /// | 25 | content-disposition | | 201 /// | 26 | content-encoding | | 202 /// | 27 | content-language | | 203 /// | 28 | content-length | | 204 /// | 29 | content-location | | 205 /// | 30 | content-range | | 206 /// | 31 | content-type | | 207 /// | 32 | cookie | | 208 /// | 33 | date | | 209 /// | 34 | etag | | 210 /// | 35 | expect | | 211 /// | 36 | expires | | 212 /// | 37 | from | | 213 /// | 38 | host | | 214 /// | 39 | if-match | | 215 /// | 40 | if-modified-since | | 216 /// | 41 | if-none-match | | 217 /// | 42 | if-range | | 218 /// | 43 | if-unmodified-since | | 219 /// | 44 | last-modified | | 220 /// | 45 | link | | 221 /// | 46 | location | | 222 /// | 47 | max-forwards | | 223 /// | 48 | proxy-authenticate | | 224 /// | 49 | proxy-authorization | | 225 /// | 50 | range | | 226 /// | 51 | referer | | 227 /// | 52 | refresh | | 228 /// | 53 | retry-after | | 229 /// | 54 | server | | 230 /// | 55 | set-cookie | | 231 /// | 56 | strict-transport-security | | 232 /// | 57 | transfer-encoding | | 233 /// | 58 | user-agent | | 234 /// | 59 | vary | | 235 /// | 60 | via | | 236 /// | 61 | www-authenticate | | 237 struct StaticTable; 238 239 impl StaticTable { 240 /// Gets a `Header` by the given index. header_name(index: usize) -> Option<Header>241 fn header_name(index: usize) -> Option<Header> { 242 match index { 243 1 => Some(Header::Authority), 244 2..=3 => Some(Header::Method), 245 4..=5 => Some(Header::Path), 246 6..=7 => Some(Header::Scheme), 247 8..=14 => Some(Header::Status), 248 15 => Some(Header::Other(String::from("accept-charset"))), 249 16 => Some(Header::Other(String::from("accept-encoding"))), 250 17 => Some(Header::Other(String::from("accept-language"))), 251 18 => Some(Header::Other(String::from("accept-ranges"))), 252 19 => Some(Header::Other(String::from("accept"))), 253 20 => Some(Header::Other(String::from("access-control-allow-origin"))), 254 21 => Some(Header::Other(String::from("age"))), 255 22 => Some(Header::Other(String::from("allow"))), 256 23 => Some(Header::Other(String::from("authorization"))), 257 24 => Some(Header::Other(String::from("cache-control"))), 258 25 => Some(Header::Other(String::from("content-disposition"))), 259 26 => Some(Header::Other(String::from("content-encoding"))), 260 27 => Some(Header::Other(String::from("content-language"))), 261 28 => Some(Header::Other(String::from("content-length"))), 262 29 => Some(Header::Other(String::from("content-location"))), 263 30 => Some(Header::Other(String::from("content-range"))), 264 31 => Some(Header::Other(String::from("content-type"))), 265 32 => Some(Header::Other(String::from("cookie"))), 266 33 => Some(Header::Other(String::from("date"))), 267 34 => Some(Header::Other(String::from("etag"))), 268 35 => Some(Header::Other(String::from("expect"))), 269 36 => Some(Header::Other(String::from("expires"))), 270 37 => Some(Header::Other(String::from("from"))), 271 38 => Some(Header::Other(String::from("host"))), 272 39 => Some(Header::Other(String::from("if-match"))), 273 40 => Some(Header::Other(String::from("if-modified-since"))), 274 41 => Some(Header::Other(String::from("if-none-match"))), 275 42 => Some(Header::Other(String::from("if-range"))), 276 43 => Some(Header::Other(String::from("if-unmodified-since"))), 277 44 => Some(Header::Other(String::from("last-modified"))), 278 45 => Some(Header::Other(String::from("link"))), 279 46 => Some(Header::Other(String::from("location"))), 280 47 => Some(Header::Other(String::from("max-forwards"))), 281 48 => Some(Header::Other(String::from("proxy-authenticate"))), 282 49 => Some(Header::Other(String::from("proxy-authorization"))), 283 50 => Some(Header::Other(String::from("range"))), 284 51 => Some(Header::Other(String::from("referer"))), 285 52 => Some(Header::Other(String::from("refresh"))), 286 53 => Some(Header::Other(String::from("retry-after"))), 287 54 => Some(Header::Other(String::from("server"))), 288 55 => Some(Header::Other(String::from("set-cookie"))), 289 56 => Some(Header::Other(String::from("strict-transport-security"))), 290 57 => Some(Header::Other(String::from("transfer-encoding"))), 291 58 => Some(Header::Other(String::from("user-agent"))), 292 59 => Some(Header::Other(String::from("vary"))), 293 60 => Some(Header::Other(String::from("via"))), 294 61 => Some(Header::Other(String::from("www-authenticate"))), 295 _ => None, 296 } 297 } 298 299 /// Tries to get a `Header` and a value by the given index. header(index: usize) -> Option<(Header, String)>300 fn header(index: usize) -> Option<(Header, String)> { 301 match index { 302 2 => Some((Header::Method, String::from("GET"))), 303 3 => Some((Header::Method, String::from("POST"))), 304 4 => Some((Header::Path, String::from("/"))), 305 5 => Some((Header::Path, String::from("/index.html"))), 306 6 => Some((Header::Scheme, String::from("http"))), 307 7 => Some((Header::Scheme, String::from("https"))), 308 8 => Some((Header::Status, String::from("200"))), 309 9 => Some((Header::Status, String::from("204"))), 310 10 => Some((Header::Status, String::from("206"))), 311 11 => Some((Header::Status, String::from("304"))), 312 12 => Some((Header::Status, String::from("400"))), 313 13 => Some((Header::Status, String::from("404"))), 314 14 => Some((Header::Status, String::from("500"))), 315 16 => Some(( 316 Header::Other(String::from("accept-encoding")), 317 String::from("gzip, deflate"), 318 )), 319 _ => None, 320 } 321 } 322 323 /// Tries to get a `Index` by the given header and value. index(header: &Header, value: &str) -> Option<TableIndex>324 fn index(header: &Header, value: &str) -> Option<TableIndex> { 325 // TODO: 优化此处的比较逻辑,考虑使用单例哈希表。 326 match (header, value) { 327 (Header::Authority, _) => Some(TableIndex::HeaderName(1)), 328 (Header::Method, "GET") => Some(TableIndex::Header(2)), 329 (Header::Method, "POST") => Some(TableIndex::Header(3)), 330 (Header::Method, _) => Some(TableIndex::HeaderName(2)), 331 (Header::Path, "/") => Some(TableIndex::Header(4)), 332 (Header::Path, "/index.html") => Some(TableIndex::Header(5)), 333 (Header::Path, _) => Some(TableIndex::HeaderName(4)), 334 (Header::Scheme, "http") => Some(TableIndex::Header(6)), 335 (Header::Scheme, "https") => Some(TableIndex::Header(7)), 336 (Header::Scheme, _) => Some(TableIndex::HeaderName(6)), 337 (Header::Status, "200") => Some(TableIndex::Header(8)), 338 (Header::Status, "204") => Some(TableIndex::Header(9)), 339 (Header::Status, "206") => Some(TableIndex::Header(10)), 340 (Header::Status, "304") => Some(TableIndex::Header(11)), 341 (Header::Status, "400") => Some(TableIndex::Header(12)), 342 (Header::Status, "404") => Some(TableIndex::Header(13)), 343 (Header::Status, "500") => Some(TableIndex::Header(14)), 344 (Header::Status, _) => Some(TableIndex::HeaderName(8)), 345 (Header::Other(s), v) => Self::index_headers(s.as_str(), v), 346 } 347 } 348 index_headers(key: &str, value: &str) -> Option<TableIndex>349 fn index_headers(key: &str, value: &str) -> Option<TableIndex> { 350 match (key, value) { 351 ("accept-charset", _) => Some(TableIndex::HeaderName(15)), 352 ("accept-encoding", "gzip, deflate") => Some(TableIndex::Header(16)), 353 ("accept-encoding", _) => Some(TableIndex::HeaderName(16)), 354 ("accept-language", _) => Some(TableIndex::HeaderName(17)), 355 ("accept-ranges", _) => Some(TableIndex::HeaderName(18)), 356 ("accept", _) => Some(TableIndex::HeaderName(19)), 357 ("access-control-allow-origin", _) => Some(TableIndex::HeaderName(20)), 358 ("age", _) => Some(TableIndex::HeaderName(21)), 359 ("allow", _) => Some(TableIndex::HeaderName(22)), 360 ("authorization", _) => Some(TableIndex::HeaderName(23)), 361 ("cache-control", _) => Some(TableIndex::HeaderName(24)), 362 ("content-disposition", _) => Some(TableIndex::HeaderName(25)), 363 ("content-encoding", _) => Some(TableIndex::HeaderName(26)), 364 ("content-language", _) => Some(TableIndex::HeaderName(27)), 365 ("content-length", _) => Some(TableIndex::HeaderName(28)), 366 ("content-location", _) => Some(TableIndex::HeaderName(29)), 367 ("content-range", _) => Some(TableIndex::HeaderName(30)), 368 ("content-type", _) => Some(TableIndex::HeaderName(31)), 369 ("cookie", _) => Some(TableIndex::HeaderName(32)), 370 ("date", _) => Some(TableIndex::HeaderName(33)), 371 ("etag", _) => Some(TableIndex::HeaderName(34)), 372 ("expect", _) => Some(TableIndex::HeaderName(35)), 373 ("expires", _) => Some(TableIndex::HeaderName(36)), 374 ("from", _) => Some(TableIndex::HeaderName(37)), 375 ("host", _) => Some(TableIndex::HeaderName(38)), 376 ("if-match", _) => Some(TableIndex::HeaderName(39)), 377 ("if-modified-since", _) => Some(TableIndex::HeaderName(40)), 378 ("if-none-match", _) => Some(TableIndex::HeaderName(41)), 379 ("if-range", _) => Some(TableIndex::HeaderName(42)), 380 ("if-unmodified-since", _) => Some(TableIndex::HeaderName(43)), 381 ("last-modified", _) => Some(TableIndex::HeaderName(44)), 382 ("link", _) => Some(TableIndex::HeaderName(45)), 383 ("location", _) => Some(TableIndex::HeaderName(46)), 384 ("max-forwards", _) => Some(TableIndex::HeaderName(47)), 385 ("proxy-authenticate", _) => Some(TableIndex::HeaderName(48)), 386 ("proxy-authorization", _) => Some(TableIndex::HeaderName(49)), 387 ("range", _) => Some(TableIndex::HeaderName(50)), 388 ("referer", _) => Some(TableIndex::HeaderName(51)), 389 ("refresh", _) => Some(TableIndex::HeaderName(52)), 390 ("retry-after", _) => Some(TableIndex::HeaderName(53)), 391 ("server", _) => Some(TableIndex::HeaderName(54)), 392 ("set-cookie", _) => Some(TableIndex::HeaderName(55)), 393 ("strict-transport-security", _) => Some(TableIndex::HeaderName(56)), 394 ("transfer-encoding", _) => Some(TableIndex::HeaderName(57)), 395 ("user-agent", _) => Some(TableIndex::HeaderName(58)), 396 ("vary", _) => Some(TableIndex::HeaderName(59)), 397 ("via", _) => Some(TableIndex::HeaderName(60)), 398 ("www-authenticate", _) => Some(TableIndex::HeaderName(61)), 399 _ => None, 400 } 401 } 402 } 403 404 /// Possible header types in `Dynamic Table` and `Static Table`. 405 #[derive(Clone, PartialEq, Eq)] 406 pub(crate) enum Header { 407 Authority, 408 Method, 409 Path, 410 Scheme, 411 Status, 412 Other(String), 413 } 414 415 impl Header { len(&self) -> usize416 pub(crate) fn len(&self) -> usize { 417 match self { 418 // 10 is the length of ":authority". 419 Header::Authority => 10, 420 // 7 is the length of ":method". 421 Header::Method => 7, 422 // 5 is the length of ":path". 423 Header::Path => 5, 424 // 7 is the length of "scheme". 425 Header::Scheme => 7, 426 // 7 is the length of "status". 427 Header::Status => 7, 428 Header::Other(s) => s.len(), 429 } 430 } 431 into_string(self) -> String432 pub(crate) fn into_string(self) -> String { 433 match self { 434 Header::Authority => String::from(":authority"), 435 Header::Method => String::from(":method"), 436 Header::Path => String::from(":path"), 437 Header::Scheme => String::from(":scheme"), 438 Header::Status => String::from(":status"), 439 Header::Other(s) => s, 440 } 441 } 442 } 443 444 #[cfg(test)] 445 mod ut_dynamic_table { 446 use crate::h2::hpack::table::{DynamicTable, Header, StaticTable}; 447 448 /// UT test cases for `DynamicTable::with_max_size`. 449 /// 450 /// # Brief 451 /// 1. Calls `DynamicTable::with_max_size` to create a `DynamicTable`. 452 /// 2. Checks the results. 453 #[test] ut_dynamic_table_with_max_size()454 fn ut_dynamic_table_with_max_size() { 455 let table = DynamicTable::with_max_size(4096); 456 assert_eq!(table.queue.len(), 0); 457 assert_eq!(table.curr_size, 0); 458 assert_eq!(table.max_size, 4096); 459 } 460 461 /// UT test cases for `DynamicTable::header_name`. 462 /// 463 /// # Brief 464 /// 1. Creates a `DynamicTable`. 465 /// 2. Calls `DynamicTable::header_name` to get a header name. 466 /// 3. Checks the results. 467 #[test] ut_dynamic_table_header_name()468 fn ut_dynamic_table_header_name() { 469 let mut table = DynamicTable::with_max_size(52); 470 assert!(table.header_name(0).is_none()); 471 472 assert!(table.header_name(0).is_none()); 473 table.update(Header::Authority, String::from("Authority")); 474 match table.header_name(0) { 475 Some(Header::Authority) => {} 476 _ => panic!("DynamicTable::header_name() failed!"), 477 } 478 } 479 480 /// UT test cases for `DynamicTable::header`. 481 /// 482 /// # Brief 483 /// 1. Creates a `DynamicTable`. 484 /// 2. Calls `DynamicTable::header` to get a header and a value. 485 /// 3. Checks the results. 486 #[test] ut_dynamic_table_header()487 fn ut_dynamic_table_header() { 488 let mut table = DynamicTable::with_max_size(52); 489 assert!(table.header(0).is_none()); 490 491 assert!(table.header(0).is_none()); 492 table.update(Header::Authority, String::from("Authority")); 493 match table.header(0) { 494 Some((Header::Authority, x)) if x == *"Authority" => {} 495 _ => panic!("DynamicTable::header() failed!"), 496 } 497 } 498 499 /// UT test cases for `DynamicTable::update`. 500 /// 501 /// # Brief 502 /// 1. Creates a `DynamicTable`. 503 /// 2. Calls `DynamicTable::update` to insert a header and a value. 504 /// 3. Checks the results. 505 #[test] ut_dynamic_table_update()506 fn ut_dynamic_table_update() { 507 let mut table = DynamicTable::with_max_size(52); 508 table.update(Header::Authority, String::from("Authority")); 509 assert_eq!(table.queue.len(), 1); 510 match table.header(0) { 511 Some((Header::Authority, x)) if x == *"Authority" => {} 512 _ => panic!("DynamicTable::header() failed!"), 513 } 514 515 table.update(Header::Method, String::from("Method")); 516 assert_eq!(table.queue.len(), 1); 517 match table.header(0) { 518 Some((Header::Method, x)) if x == *"Method" => {} 519 _ => panic!("DynamicTable::header() failed!"), 520 } 521 } 522 523 /// UT test cases for `DynamicTable::update_size`. 524 /// 525 /// # Brief 526 /// 1. Creates a `DynamicTable`. 527 /// 2. Calls `DynamicTable::update_size` to update its max size. 528 /// 3. Checks the results. 529 #[test] ut_dynamic_table_update_size()530 fn ut_dynamic_table_update_size() { 531 let mut table = DynamicTable::with_max_size(52); 532 table.update(Header::Authority, String::from("Authority")); 533 assert_eq!(table.queue.len(), 1); 534 match table.header(0) { 535 Some((Header::Authority, x)) if x == *"Authority" => {} 536 _ => panic!("DynamicTable::header() failed!"), 537 } 538 539 table.update_size(0); 540 assert_eq!(table.queue.len(), 0); 541 assert!(table.header(0).is_none()); 542 } 543 544 /// UT test cases for `StaticTable::header_name` and `StaticTable::header`. 545 /// 546 /// # Brief 547 /// 1. Iterates over a range of indices, testing both 548 /// `StaticTable::header_name` and `StaticTable::header`. 549 /// 2. Verifies the presence or absence of header names and headers based on 550 /// the given index. 551 #[test] ut_static_table()552 fn ut_static_table() { 553 // Checking header names for indices 1 to 64 554 for index in 1..65 { 555 if index < 62 { 556 assert!(StaticTable::header_name(index).is_some()) 557 } else { 558 assert!(StaticTable::header_name(index).is_none()) 559 } 560 } 561 562 // Checking headers for indices 2 to 19 563 for index in 2..20 { 564 if index < 17 && index != 15 { 565 assert!(StaticTable::header(index).is_some()) 566 } else { 567 assert!(StaticTable::header(index).is_none()) 568 } 569 } 570 } 571 } 572