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 #![rustfmt::skip] 15 16 use crate::h3::parts::Parts; 17 use crate::h3::qpack::error::ErrorCode::DecoderStreamError; 18 use crate::h3::qpack::error::H3errorQpack; 19 use crate::h3::qpack::format::encoder::{ 20 DecInstDecoder, InstDecodeState, PartsIter, ReprEncodeState, SetCap, 21 }; 22 use crate::h3::qpack::format::ReprEncoder; 23 use crate::h3::qpack::integer::{Integer, IntegerEncoder}; 24 use crate::h3::qpack::table::{DynamicTable, Field}; 25 use crate::h3::qpack::{DecoderInstruction, PrefixMask}; 26 use std::collections::{HashMap, VecDeque}; 27 28 /// An encoder is used to compress field in a compression format for efficiently representing 29 /// HTTP fields that is to be used in HTTP/3. This is a variation of HPACK compression that seeks 30 /// to reduce head-of-line blocking. 31 /// 32 /// # Examples 33 // ```no_run 34 // use crate::ylong_http::h3::qpack::encoder::QpackEncoder; 35 // use crate::ylong_http::h3::parts::Parts; 36 // use crate::ylong_http::h3::qpack::table::{DynamicTable, Field}; 37 // use crate::ylong_http::test_util::decode; 38 // 39 // 40 // 41 // // the (field, value) is: ("custom-key", "custom-value2") 42 // // Required content: 43 // let mut encoder_buf = [0u8; 1024]; // QPACK stream providing control commands. 44 // let mut stream_buf = [0u8; 1024]; // Field section encoded in QPACK format. 45 // let mut encoder_cur = 0; // index of encoder_buf. 46 // let mut stream_cur = 0; // index of stream_buf. 47 // let mut table = DynamicTable::with_empty(); 48 // 49 // // create a new encoder. 50 // let mut encoder = QpackEncoder::new(&mut table, 0, true, 1); 51 // 52 // // set dynamic table capacity. 53 // encoder_cur += encoder.set_capacity(220, &mut encoder_buf[encoder_buf..]); 54 // 55 // // set field section. 56 // let mut field = Parts::new(); 57 // field.update(Field::Other(String::from("custom-key")), String::from("custom-value")); 58 // encoder.set_parts(field); 59 // 60 // // encode field section. 61 // let (cur1, cur2, _) = encoder.encode(&mut encoder_buf[encoder_cur..], &mut stream_buf[stream_cur..]); 62 // encoder_cur += cur1; 63 // stream_cur += cur2; 64 // 65 // assert_eq!(stream_buf[..encoder_cur].to_vec().as_slice(), decode("028010").unwrap().as_slice()); 66 // assert_eq!(stream_buf[..stream_cur].to_vec().as_slice(), decode("4a637573746f6d2d6b65790c637573746f6d2d76616c7565").unwrap().as_slice()); 67 // 68 // 69 // ``` 70 71 pub struct QpackEncoder<'a> { 72 table: &'a mut DynamicTable, 73 // Headers to be encode. 74 field_iter: Option<PartsIter>, 75 // save the state of encoding field. 76 field_state: Option<ReprEncodeState>, 77 // save the state of decoding instructions. 78 inst_state: Option<InstDecodeState>, 79 // list of fields to be inserted. 80 insert_list: VecDeque<(Field, String)>, 81 insert_length: usize, 82 // `RFC`: the number of insertions that the decoder needs to receive before it can decode the field section. 83 required_insert_count: usize, 84 85 stream_id: usize, 86 // allow reference to the inserting field default is false. 87 allow_post: bool, 88 // RFC9204-2.1.1.1. if index<draining_index, then execute Duplicate. 89 draining_index: usize, 90 } 91 92 impl<'a> QpackEncoder<'a> { 93 94 /// create a new encoder. 95 /// #Examples 96 // ```no_run 97 // use ylong_http::h3::qpack::encoder::QpackEncoder; 98 // use ylong_http::h3::qpack::table::DynamicTable; 99 // let mut encoder_buf = [0u8; 1024]; // QPACK stream providing control commands. 100 // let mut stream_buf = [0u8; 1024]; // Field section encoded in QPACK format. 101 // let mut encoder_cur = 0; // index of encoder_buf. 102 // let mut stream_cur = 0; // index of stream_buf. 103 // let mut table = DynamicTable::with_empty(); 104 // 105 // // create a new encoder. 106 // let mut encoder = QpackEncoder::new(&mut table, 0, true, 1); 107 // ``` new( table: &'a mut DynamicTable, stream_id: usize, allow_post: bool, draining_index: usize, ) -> QpackEncoder108 pub fn new( 109 table: &'a mut DynamicTable, 110 stream_id: usize, 111 allow_post: bool, 112 draining_index: usize, 113 ) -> QpackEncoder { 114 Self { 115 table, 116 field_iter: None, 117 field_state: None, 118 inst_state: None, 119 insert_list: VecDeque::new(), 120 insert_length: 0, 121 required_insert_count: 0, 122 stream_id, 123 allow_post, 124 draining_index, 125 } 126 } 127 128 /// Set the maximum dynamic table size. 129 /// # Examples 130 /// ```no_run 131 /// use ylong_http::h3::qpack::encoder::QpackEncoder; 132 /// use ylong_http::h3::qpack::table::DynamicTable; 133 /// let mut encoder_buf = [0u8; 1024]; // QPACK stream providing control commands. 134 /// let mut stream_buf = [0u8; 1024]; // Field section encoded in QPACK format. 135 /// let mut encoder_cur = 0; // index of encoder_buf. 136 /// let mut stream_cur = 0; // index of stream_buf. 137 /// let mut table = DynamicTable::with_empty(); 138 /// let mut encoder = QpackEncoder::new(&mut table, 0, true, 1); 139 /// let mut encoder_cur = encoder.set_capacity(220, &mut encoder_buf[..]); 140 /// ``` set_capacity(&mut self, max_size: usize, encoder_buf: &mut [u8]) -> usize141 pub fn set_capacity(&mut self, max_size: usize, encoder_buf: &mut [u8]) -> usize { 142 self.table.update_size(max_size); 143 if let Ok(cur) = SetCap::new(max_size).encode(&mut encoder_buf[..]) { 144 return cur; 145 } 146 0 147 } 148 149 150 /// Set the field section to be encoded. 151 /// # Examples 152 // ```no_run 153 // use ylong_http::h3::qpack::encoder::QpackEncoder; 154 // use ylong_http::h3::parts::Parts; 155 // use ylong_http::h3::qpack::table::{DynamicTable, Field}; 156 // let mut table = DynamicTable::with_empty(); 157 // let mut encoder = QpackEncoder::new(&mut table, 0, true, 1); 158 // let mut parts = Parts::new(); 159 // parts.update(Field::Other(String::from("custom-key")), String::from("custom-value")); 160 // encoder.set_parts(parts); 161 // ``` set_parts(&mut self, parts: Parts)162 pub fn set_parts(&mut self, parts: Parts) { 163 self.field_iter = Some(PartsIter::new(parts)); 164 } 165 ack(&mut self, stream_id: usize) -> Result<Option<DecoderInst>, H3errorQpack>166 fn ack(&mut self, stream_id: usize) -> Result<Option<DecoderInst>, H3errorQpack> { 167 assert_eq!(stream_id, self.stream_id); 168 169 if self.table.known_received_count < self.required_insert_count { 170 self.table.known_received_count = self.required_insert_count; 171 } else { 172 return Err(H3errorQpack::ConnectionError(DecoderStreamError)); 173 } 174 175 Ok(Some(DecoderInst::Ack)) 176 } 177 178 /// Users can call `decode_ins` multiple times to decode decoder instructions. 179 /// # Return 180 /// `Ok(None)` means that the decoder instruction is not complete. 181 /// # Examples 182 // ```no_run 183 // use ylong_http::h3::qpack::encoder::QpackEncoder; 184 // use ylong_http::h3::parts::Parts; 185 // use ylong_http::h3::qpack::table::{DynamicTable, Field}; 186 // use ylong_http::test_util::decode; 187 // let mut table = DynamicTable::with_empty(); 188 // let mut encoder = QpackEncoder::new(&mut table, 0, true, 1); 189 // let _ = encoder.decode_ins(&mut decode("80").unwrap().as_slice()); 190 // ``` decode_ins(&mut self, buf: &[u8]) -> Result<Option<DecoderInst>, H3errorQpack>191 pub fn decode_ins(&mut self, buf: &[u8]) -> Result<Option<DecoderInst>, H3errorQpack> { 192 let mut decoder = DecInstDecoder::new(buf); 193 194 match decoder.decode(&mut self.inst_state)? { 195 Some(DecoderInstruction::Ack { stream_id }) => self.ack(stream_id), 196 //todo: stream cancel 197 Some(DecoderInstruction::StreamCancel { stream_id }) => { 198 assert_eq!(stream_id, self.stream_id); 199 Ok(Some(DecoderInst::StreamCancel)) 200 } 201 //todo: insert count increment 202 Some(DecoderInstruction::InsertCountIncrement { increment }) => { 203 self.table.known_received_count += increment; 204 Ok(Some(DecoderInst::InsertCountIncrement)) 205 } 206 None => Ok(None), 207 } 208 } 209 get_prefix(&self, prefix_buf: &mut [u8]) -> usize210 fn get_prefix(&self, prefix_buf: &mut [u8]) -> usize { 211 let mut cur_prefix = 0; 212 let mut wire_ric = 0; 213 if self.required_insert_count != 0 { 214 wire_ric = self.required_insert_count % (2 * self.table.max_entries()) + 1; 215 } 216 217 cur_prefix += Integer::index(0x00, wire_ric, 0xff) 218 .encode(&mut prefix_buf[..]) 219 .unwrap_or(0); 220 let base = self.table.insert_count; 221 println!("base: {}", base); 222 println!("required_insert_count: {}", self.required_insert_count); 223 if base >= self.required_insert_count { 224 cur_prefix += Integer::index(0x00, base - self.required_insert_count, 0x7f) 225 .encode(&mut prefix_buf[cur_prefix..]) 226 .unwrap_or(0); 227 } else { 228 cur_prefix += Integer::index(0x80, self.required_insert_count - base - 1, 0x7f) 229 .encode(&mut prefix_buf[cur_prefix..]) 230 .unwrap_or(0); 231 } 232 cur_prefix 233 } 234 /// Users can call `encode` multiple times to encode multiple complete field sections. 235 /// # Examples 236 // ```no_run 237 // use ylong_http::h3::qpack::encoder::QpackEncoder; 238 // use ylong_http::h3::parts::Parts; 239 // use ylong_http::h3::qpack::table::{DynamicTable, Field}; 240 // use ylong_http::test_util::decode; 241 // let mut encoder_buf = [0u8; 1024]; // QPACK stream providing control commands. 242 // let mut stream_buf = [0u8; 1024]; // Field section encoded in QPACK format. 243 // let mut encoder_cur = 0; // index of encoder_buf. 244 // let mut stream_cur = 0; // index of stream_buf. 245 // let mut table = DynamicTable::with_empty(); 246 // let mut encoder = QpackEncoder::new(&mut table, 0, true, 1); 247 // let mut parts = Parts::new(); 248 // parts.update(Field::Other(String::from("custom-key")), String::from("custom-value")); 249 // encoder.set_parts(parts); 250 // let (cur1, cur2, _) = encoder.encode(&mut encoder_buf[encoder_cur..], &mut stream_buf[stream_cur..]); 251 // encoder_cur += cur1; 252 // stream_cur += cur2; 253 // ``` encode( &mut self, encoder_buf: &mut [u8], stream_buf: &mut [u8], ) -> (usize, usize, Option<([u8; 1024], usize)>)254 pub fn encode( 255 &mut self, 256 encoder_buf: &mut [u8], //instructions encoded results 257 stream_buf: &mut [u8], //headers encoded results 258 ) -> (usize, usize, Option<([u8; 1024], usize)>) { 259 let (mut cur_encoder, mut cur_stream) = (0, 0); 260 if self.is_finished() { 261 // denote an end of field section 262 // self.stream_reference.push_back(None); 263 //todo: size of prefix_buf 264 let mut prefix_buf = [0u8; 1024]; 265 let cur_prefix = self.get_prefix(&mut prefix_buf[0..]); 266 for (field, value) in self.insert_list.iter() { 267 self.table.update(field.clone(), value.clone()); 268 } 269 (cur_encoder, cur_stream, Some((prefix_buf, cur_prefix))) 270 } else { 271 let mut encoder = ReprEncoder::new( 272 self.table, 273 self.draining_index, 274 self.allow_post, 275 &mut self.insert_length, 276 ); 277 (cur_encoder, cur_stream) = encoder.encode( 278 &mut self.field_iter, 279 &mut self.field_state, 280 &mut encoder_buf[0..], 281 &mut stream_buf[0..], 282 &mut self.insert_list, 283 &mut self.required_insert_count, 284 ); 285 (cur_encoder, cur_stream, None) 286 } 287 } 288 289 /// Check the previously set `Parts` if encoding is complete. is_finished(&self) -> bool290 pub(crate) fn is_finished(&self) -> bool { 291 self.field_iter.is_none() && self.field_state.is_none() 292 } 293 } 294 295 pub enum DecoderInst { 296 Ack, 297 StreamCancel, 298 InsertCountIncrement, 299 } 300 301 #[cfg(test)] 302 mod ut_qpack_encoder { 303 use crate::h3::parts::Parts; 304 use crate::h3::qpack::encoder; 305 use crate::h3::qpack::encoder::QpackEncoder; 306 use crate::h3::qpack::table::{DynamicTable, Field}; 307 use crate::util::test_util::decode; 308 macro_rules! qpack_test_cases { 309 ($enc: expr,$encoder_buf:expr,$encoder_cur:expr, $len: expr, $res: literal,$encoder_res: literal, $size: expr, { $($h: expr, $v: expr $(,)?)*} $(,)?) => { 310 let mut _encoder = $enc; 311 let mut stream_buf = [0u8; $len]; 312 let mut stream_cur = 0; 313 $( 314 let mut parts = Parts::new(); 315 parts.update($h, $v); 316 _encoder.set_parts(parts); 317 let (cur1,cur2,_) = _encoder.encode(&mut $encoder_buf[$encoder_cur..],&mut stream_buf[stream_cur..]); 318 $encoder_cur += cur1; 319 stream_cur += cur2; 320 )* 321 let (cur1, cur2, prefix) = _encoder.encode(&mut $encoder_buf[$encoder_cur..],&mut stream_buf[stream_cur..]); 322 $encoder_cur += cur1; 323 stream_cur += cur2; 324 if let Some((prefix_buf,cur_prefix)) = prefix{ 325 stream_buf.copy_within(0..stream_cur,cur_prefix); 326 stream_buf[..cur_prefix].copy_from_slice(&prefix_buf[..cur_prefix]); 327 stream_cur += cur_prefix; 328 } 329 println!("stream_buf: {:#?}",stream_buf); 330 let result = decode($res).unwrap(); 331 if let Some(res) = decode($encoder_res){ 332 assert_eq!($encoder_buf[..$encoder_cur].to_vec().as_slice(), res.as_slice()); 333 } 334 assert_eq!(stream_cur, $len); 335 assert_eq!(stream_buf.as_slice(), result.as_slice()); 336 assert_eq!(_encoder.table.size(), $size); 337 } 338 } 339 #[test] 340 /// The encoder sends an encoded field section containing a literal representation of a field 341 /// with a static name reference. literal_field_line_with_name_reference()342 fn literal_field_line_with_name_reference() { 343 println!("literal_field_line_with_name_reference"); 344 let mut encoder_buf = [0u8; 1024]; 345 let mut table = DynamicTable::with_empty(); 346 let mut encoder = QpackEncoder::new(&mut table, 0, false, 0); 347 let mut encoder_cur = encoder.set_capacity(0, &mut encoder_buf[..]); 348 qpack_test_cases!( 349 encoder, 350 encoder_buf, 351 encoder_cur, 352 15, "0000510b2f696e6465782e68746d6c", 353 "20", 354 0, 355 { 356 Field::Path, 357 String::from("/index.html"), 358 }, 359 ); 360 } 361 362 #[test] 363 ///The encoder sets the dynamic table capacity, inserts a header with a dynamic name 364 /// reference, then sends a potentially blocking, encoded field section referencing 365 /// this new entry. The decoder acknowledges processing the encoded field section, 366 /// which implicitly acknowledges all dynamic table insertions up to the Required 367 /// Insert Count. dynamic_table()368 fn dynamic_table() { 369 let mut encoder_buf = [0u8; 1024]; 370 let mut table = DynamicTable::with_empty(); 371 let mut encoder = QpackEncoder::new(&mut table, 0, true, 0); 372 let mut encoder_cur = encoder.set_capacity(220, &mut encoder_buf[..]); 373 qpack_test_cases!( 374 encoder, 375 encoder_buf, 376 encoder_cur, 377 4, "03811011", 378 "3fbd01c00f7777772e6578616d706c652e636f6dc10c2f73616d706c652f70617468", 379 106, 380 { 381 Field::Authority, 382 String::from("www.example.com"), 383 Field::Path, 384 String::from("/sample/path"), 385 }, 386 ); 387 } 388 389 #[test] 390 ///The encoder inserts a header into the dynamic table with a literal name. 391 /// The decoder acknowledges receipt of the entry. The encoder does not send any 392 /// encoded field sections. speculative_insert()393 fn speculative_insert() { 394 let mut encoder_buf = [0u8; 1024]; 395 let mut table = DynamicTable::with_empty(); 396 let mut encoder = QpackEncoder::new(&mut table, 0, true, 0); 397 let _ = encoder.set_capacity(220, &mut encoder_buf[..]); 398 let mut encoder_cur = 0; 399 qpack_test_cases!( 400 encoder, 401 encoder_buf, 402 encoder_cur, 403 3, "028010", 404 "4a637573746f6d2d6b65790c637573746f6d2d76616c7565", 405 54, 406 { 407 Field::Other(String::from("custom-key")), 408 String::from("custom-value"), 409 }, 410 ); 411 } 412 413 #[test] duplicate_instruction_stream_cancellation()414 fn duplicate_instruction_stream_cancellation() { 415 let mut encoder_buf = [0u8; 1024]; 416 let mut table = DynamicTable::with_empty(); 417 let mut encoder = QpackEncoder::new(&mut table, 0, true, 1); 418 let _ = encoder.set_capacity(4096, &mut encoder_buf[..]); 419 encoder 420 .table 421 .update(Field::Authority, String::from("www.example.com")); 422 encoder 423 .table 424 .update(Field::Path, String::from("/sample/path")); 425 encoder.table.update( 426 Field::Other(String::from("custom-key")), 427 String::from("custom-value"), 428 ); 429 encoder.required_insert_count = 3; 430 let mut encoder_cur = 0; 431 qpack_test_cases!( 432 encoder, 433 encoder_buf, 434 encoder_cur, 435 5, "050080c181", 436 "02", 437 274, 438 { 439 Field::Authority, 440 String::from("www.example.com"), 441 Field::Path, 442 String::from("/"), 443 Field::Other(String::from("custom-key")), 444 String::from("custom-value") 445 }, 446 ); 447 } 448 449 #[test] dynamic_table_insert_eviction()450 fn dynamic_table_insert_eviction() { 451 let mut encoder_buf = [0u8; 1024]; 452 let mut table = DynamicTable::with_empty(); 453 let mut encoder = QpackEncoder::new(&mut table, 0, true, 1); 454 let _ = encoder.set_capacity(4096, &mut encoder_buf[..]); 455 encoder 456 .table 457 .update(Field::Authority, String::from("www.example.com")); 458 encoder 459 .table 460 .update(Field::Path, String::from("/sample/path")); 461 encoder.table.update( 462 Field::Other(String::from("custom-key")), 463 String::from("custom-value"), 464 ); 465 encoder 466 .table 467 .update(Field::Authority, String::from("www.example.com")); 468 encoder.required_insert_count = 3; //acked 469 let mut encoder_cur = 0; 470 qpack_test_cases!( 471 encoder, 472 encoder_buf, 473 encoder_cur, 474 3, "040183", 475 "810d637573746f6d2d76616c756532", 476 272, 477 { 478 Field::Other(String::from("custom-key")), 479 String::from("custom-value2") 480 }, 481 ); 482 } 483 484 #[test] test_ack()485 fn test_ack() { 486 let mut encoder_buf = [0u8; 1024]; 487 let mut table = DynamicTable::with_empty(); 488 let mut encoder = QpackEncoder::new(&mut table, 0, true, 1); 489 let mut encoder_cur = encoder.set_capacity(4096, &mut encoder_buf[..]); 490 491 let field_list: [(Field, String); 3] = [ 492 (Field::Authority, String::from("www.example.com")), 493 (Field::Path, String::from("/sample/path")), 494 ( 495 Field::Other(String::from("custom-key")), 496 String::from("custom-value"), 497 ), 498 ]; 499 let mut stream_cur = 0; 500 for (field, value) in field_list.iter() { 501 let mut parts = Parts::new(); 502 parts.update(field.clone(), value.clone()); 503 encoder.set_parts(parts); 504 let mut stream_buf = [0u8; 1024]; 505 let (cur1, cur2, _) = encoder.encode( 506 &mut encoder_buf[encoder_cur..], 507 &mut stream_buf[stream_cur..], 508 ); 509 encoder_cur += cur1; 510 stream_cur += cur2; 511 } 512 let _ = encoder.decode_ins(decode("80").unwrap().as_slice()); 513 assert_eq!(encoder.table.known_received_count, 3); 514 } 515 516 #[test] encode_post_name()517 fn encode_post_name() { 518 let mut encoder_buf = [0u8; 1024]; 519 let mut table = DynamicTable::with_empty(); 520 let mut encoder = QpackEncoder::new(&mut table, 0, true, 1); 521 let _ = encoder.set_capacity(60, &mut encoder_buf[..]); 522 let mut encoder_cur = 0; 523 let mut stream_buf = [0u8; 100]; 524 let mut stream_cur = 0; 525 let mut parts = Parts::new(); 526 parts.update( 527 Field::Other(String::from("custom-key")), 528 String::from("custom-value1"), 529 ); 530 encoder.set_parts(parts); 531 let (cur1, cur2, _) = encoder.encode( 532 &mut encoder_buf[encoder_cur..], 533 &mut stream_buf[stream_cur..], 534 ); 535 encoder_cur += cur1; 536 stream_cur += cur2; 537 let mut parts = Parts::new(); 538 parts.update( 539 Field::Other(String::from("custom-key")), 540 String::from("custom-value2"), 541 ); 542 encoder.set_parts(parts); 543 let (cur1, cur2, _) = encoder.encode( 544 &mut encoder_buf[encoder_cur..], 545 &mut stream_buf[stream_cur..], 546 ); 547 encoder_cur += cur1; 548 stream_cur += cur2; 549 assert_eq!( 550 [16, 0, 13, 99, 117, 115, 116, 111, 109, 45, 118, 97, 108, 117, 101, 50], 551 stream_buf[..stream_cur] 552 ); 553 assert_eq!( 554 [ 555 74, 99, 117, 115, 116, 111, 109, 45, 107, 101, 121, 13, 99, 117, 115, 116, 111, 556 109, 45, 118, 97, 108, 117, 101, 49 557 ], 558 encoder_buf[..encoder_cur] 559 ) 560 } 561 #[test] test_indexing_with_litreal()562 fn test_indexing_with_litreal() { 563 let mut encoder_buf = [0u8; 1024]; 564 let mut table = DynamicTable::with_empty(); 565 let mut encoder = QpackEncoder::new(&mut table, 0, false, 1); 566 let _ = encoder.set_capacity(60, &mut encoder_buf[..]); 567 let encoder_cur = 0; 568 let mut stream_buf = [0u8; 100]; 569 let mut stream_cur = 0; 570 let mut parts = Parts::new(); 571 parts.update( 572 Field::Other(String::from("custom-key")), 573 String::from("custom-value1"), 574 ); 575 encoder.set_parts(parts); 576 let (_, cur2, _) = encoder.encode( 577 &mut encoder_buf[encoder_cur..], 578 &mut stream_buf[stream_cur..], 579 ); 580 stream_cur += cur2; 581 582 assert_eq!( 583 [ 584 39, 3, 99, 117, 115, 116, 111, 109, 45, 107, 101, 121, 13, 99, 117, 115, 116, 111, 585 109, 45, 118, 97, 108, 117, 101, 49 586 ], 587 stream_buf[..stream_cur] 588 ); 589 } 590 } 591