1#[cfg(feature = "alloc")]
2use alloc::borrow::Cow;
3#[cfg(feature = "alloc")]
4use alloc::vec;
5#[cfg(feature = "alloc")]
6use alloc::vec::Vec;
7
8use crate::block_encoder::{self, BlockEncodeWorkspace};
9use crate::strategy::{self, LevelParams, Strategy};
10use crate::{block_looks_incompressible, dfast, fast, write_frame_header_with_checksum};
11use zrip_core::Sequence;
12use zrip_core::dict::Dictionary;
13use zrip_core::error::CompressError;
14use zrip_core::frame::MAX_BLOCK_SIZE;
15use zrip_core::huffman::encode::HuffmanEncodeTable;
16use zrip_core::xxhash::xxh64;
17
18const ATTACH_THRESHOLD: usize = 16384;
24
25pub(crate) struct PreparedDict {
26 combined: Vec<u8>,
27 hash_snapshot: Vec<u32>,
28 hash_long_snapshot: Vec<u32>,
29 hash_log: u32,
30 prefix_len: usize,
31 rep_offsets: [u32; 3],
32 dict_id: u32,
33 huf_table: Option<HuffmanEncodeTable>,
34 ll_table: Option<block_encoder::FseEncodeTable>,
35 of_table: Option<block_encoder::FseEncodeTable>,
36 ml_table: Option<block_encoder::FseEncodeTable>,
37}
38
39impl PreparedDict {
40 pub fn new(dict: &Dictionary, params: &LevelParams) -> Self {
41 let prefix = dict.content();
42 let prefix_len = prefix.len();
43
44 let mut combined = Vec::with_capacity(prefix_len + MAX_BLOCK_SIZE);
45 combined.extend_from_slice(prefix);
46
47 let (hash_snapshot, hash_long_snapshot) = match params.strategy {
48 Strategy::Fast => {
49 let hash_size = 1usize << params.hash_log;
50 let mut hash_table = vec![0u32; hash_size];
51 fast::prefill_hash_table(&combined, prefix_len, params.hash_log, &mut hash_table);
52 (hash_table, Vec::new())
53 }
54 Strategy::DFast => {
55 let short_size = 1usize << params.chain_log;
56 let long_size = 1usize << params.hash_log;
57 let mut hash_short = vec![0u32; short_size];
58 let mut hash_long = vec![0u32; long_size];
59 dfast::prefill_hash_tables(
60 &combined,
61 prefix_len,
62 params.hash_log,
63 params.chain_log,
64 params.min_match,
65 &mut hash_short,
66 &mut hash_long,
67 );
68 (hash_short, hash_long)
69 }
70 };
71
72 let huf_table = dict
73 .huf_table()
74 .and_then(|(dt, tl)| HuffmanEncodeTable::from_decode_table(dt, tl));
75
76 let ll_table = dict
77 .ll_table()
78 .map(|(dt, al)| block_encoder::FseEncodeTable::from_decode_table(dt, al, 35));
79 let of_table = dict
80 .of_table()
81 .map(|(dt, al)| block_encoder::FseEncodeTable::from_decode_table(dt, al, 31));
82 let ml_table = dict
83 .ml_table()
84 .map(|(dt, al)| block_encoder::FseEncodeTable::from_decode_table(dt, al, 52));
85
86 Self {
87 combined,
88 hash_snapshot,
89 hash_long_snapshot,
90 hash_log: params.hash_log,
91 prefix_len,
92 rep_offsets: *dict.rep_offsets(),
93 dict_id: dict.id(),
94 huf_table,
95 ll_table,
96 of_table,
97 ml_table,
98 }
99 }
100}
101
102pub struct CompressContext {
116 level: i32,
117 content_checksum: bool,
118 prepared: Option<PreparedDict>,
119 hash_table: Vec<u32>,
120 hash_long: Vec<u32>,
121 dict_hash: Vec<u32>,
122 small_hash: Vec<u32>,
123 sequences: Vec<Sequence>,
124 output: Vec<u8>,
125 workspace: BlockEncodeWorkspace,
126 combined: Vec<u8>,
127}
128
129impl CompressContext {
130 pub fn new(level: i32) -> Result<Self, CompressError> {
132 let params = strategy::level_params(level).ok_or(CompressError::InvalidLevel(level))?;
133 let max_log = strategy::max_hash_log(level).expect("level validated above");
134 let alloc_size = 1usize << max_log;
135 let (hash_table, hash_long) = match params.strategy {
136 Strategy::Fast => (vec![0u32; alloc_size], Vec::new()),
137 Strategy::DFast => (vec![0u32; alloc_size], vec![0u32; alloc_size]),
138 };
139 Ok(Self {
140 level,
141 content_checksum: true,
142 prepared: None,
143 hash_table,
144 hash_long,
145 dict_hash: Vec::new(),
146 small_hash: Vec::new(),
147 sequences: Vec::new(),
148 output: Vec::new(),
149 workspace: BlockEncodeWorkspace::new(),
150 combined: Vec::new(),
151 })
152 }
153
154 pub fn with_dict(level: i32, dict: Dictionary) -> Result<Self, CompressError> {
164 Self::with_dict_for_size(level, dict, usize::MAX)
165 }
166
167 pub fn with_dict_for_size(
174 level: i32,
175 dict: Dictionary,
176 expected_size: usize,
177 ) -> Result<Self, CompressError> {
178 let total_window = dict.content().len().saturating_add(expected_size);
179 let params = strategy::level_params_for_size(level, total_window)
180 .ok_or(CompressError::InvalidLevel(level))?;
181 let prepared = PreparedDict::new(&dict, ¶ms);
182 let hash_table = vec![0u32; prepared.hash_snapshot.len()];
183 let hash_long = vec![0u32; prepared.hash_long_snapshot.len()];
184 Ok(Self {
185 level,
186 content_checksum: true,
187 prepared: Some(prepared),
188 hash_table,
189 hash_long,
190 dict_hash: Vec::new(),
191 small_hash: Vec::new(),
192 sequences: Vec::new(),
193 output: Vec::new(),
194 workspace: BlockEncodeWorkspace::new(),
195 combined: Vec::new(),
196 })
197 }
198
199 pub const fn set_content_checksum(&mut self, enabled: bool) {
204 self.content_checksum = enabled;
205 }
206
207 pub const fn content_checksum(&self) -> bool {
209 self.content_checksum
210 }
211
212 pub fn compress(&mut self, input: &[u8]) -> Result<Cow<'_, [u8]>, CompressError> {
214 if self.prepared.is_some() {
215 return self.compress_with_prepared(input);
216 }
217 let params = strategy::level_params_for_size(self.level, input.len())
218 .expect("level validated at construction");
219 compress_core(
220 input,
221 params,
222 None,
223 &[],
224 [1u32, 4, 8],
225 &mut self.hash_table,
226 &mut self.hash_long,
227 &mut self.dict_hash,
228 &mut self.sequences,
229 &mut self.output,
230 &mut self.workspace,
231 &mut self.combined,
232 self.content_checksum,
233 )?;
234 Ok(self.take_or_borrow_output())
235 }
236
237 pub fn compress_with_dict(
239 &mut self,
240 input: &[u8],
241 dict: &Dictionary,
242 ) -> Result<Cow<'_, [u8]>, CompressError> {
243 let total_window = dict.content().len().saturating_add(input.len());
244 let params = strategy::level_params_for_size(self.level, total_window)
245 .expect("level validated at construction");
246 self.workspace.prev_ll = dict
247 .ll_table()
248 .map(|(dt, al)| block_encoder::FseEncodeTable::from_decode_table(dt, al, 35));
249 self.workspace.prev_of = dict
250 .of_table()
251 .map(|(dt, al)| block_encoder::FseEncodeTable::from_decode_table(dt, al, 31));
252 self.workspace.prev_ml = dict
253 .ml_table()
254 .map(|(dt, al)| block_encoder::FseEncodeTable::from_decode_table(dt, al, 52));
255 self.workspace.prev_huffman = dict
256 .huf_table()
257 .and_then(|(dt, tl)| HuffmanEncodeTable::from_decode_table(dt, tl));
258 compress_core(
259 input,
260 params,
261 Some(dict.id()),
262 dict.content(),
263 *dict.rep_offsets(),
264 &mut self.hash_table,
265 &mut self.hash_long,
266 &mut self.dict_hash,
267 &mut self.sequences,
268 &mut self.output,
269 &mut self.workspace,
270 &mut self.combined,
271 self.content_checksum,
272 )?;
273 Ok(self.take_or_borrow_output())
274 }
275
276 fn compress_with_prepared(&mut self, input: &[u8]) -> Result<Cow<'_, [u8]>, CompressError> {
277 let prep = self.prepared.as_ref().unwrap();
278 let total_window = prep.prefix_len + input.len();
279 let mut params = strategy::level_params_for_size(self.level, total_window)
280 .expect("level validated at construction");
281 strategy::apply_raw_literals_size_override(&mut params, input.len());
282
283 let use_attached = !input.is_empty()
284 && input.len() <= ATTACH_THRESHOLD
285 && params.strategy == Strategy::Fast;
286
287 let dict_id = prep.dict_id;
288 let prefix_len = prep.prefix_len;
289 let dict_hash_log = prep.hash_log;
290
291 if !use_attached {
292 let snapshot_matches = match params.strategy {
293 Strategy::Fast => (1usize << params.hash_log) == prep.hash_snapshot.len(),
294 Strategy::DFast => {
295 (1usize << params.chain_log) == prep.hash_snapshot.len()
296 && (1usize << params.hash_log) == prep.hash_long_snapshot.len()
297 }
298 };
299 if !snapshot_matches {
300 return self.compress_with_dict_fallback(input, dict_id, prefix_len);
301 }
302 }
303
304 {
305 let prep = self.prepared.as_mut().unwrap();
306 if !use_attached {
307 self.hash_table.copy_from_slice(&prep.hash_snapshot);
308 if !prep.hash_long_snapshot.is_empty() {
309 self.hash_long.copy_from_slice(&prep.hash_long_snapshot);
310 }
311 }
312 prep.combined.truncate(prep.prefix_len);
313 prep.combined.extend_from_slice(input);
314 }
315
316 let prep = self.prepared.as_ref().unwrap();
317
318 if use_attached {
319 self.workspace.prev_huffman = if params.force_raw_literals {
320 None
321 } else {
322 prep.huf_table.clone()
323 };
324 } else if let Some(ref huf) = prep.huf_table {
325 self.workspace.prev_huffman = Some(huf.clone());
326 } else {
327 self.workspace.prev_huffman = None;
328 }
329 self.workspace.prev_ll = prep.ll_table.clone();
330 self.workspace.prev_of = prep.of_table.clone();
331 self.workspace.prev_ml = prep.ml_table.clone();
332
333 self.output.clear();
334 self.output.reserve(input.len() + 32);
335 write_frame_header_with_checksum(
336 &mut self.output,
337 input.len(),
338 Some(dict_id),
339 params.window_log,
340 self.content_checksum,
341 )?;
342
343 if input.is_empty() {
344 block_encoder::encode_raw_block(&[], true, &mut self.output)?;
345 } else if use_attached {
346 let input_hash_log = if input.len() >= 2 {
347 let src_log = 32 - ((input.len() as u32) - 1).leading_zeros();
348 params.hash_log.min(src_log).max(strategy::HASH_LOG_MIN)
349 } else {
350 strategy::HASH_LOG_MIN
351 };
352 let input_hash_size = 1usize << input_hash_log;
353 self.small_hash.resize(input_hash_size, 0);
354 self.small_hash.fill(0);
355
356 let mut rep_offsets = prep.rep_offsets;
357
358 fast::compress_fast_attached(
359 &prep.combined,
360 prefix_len,
361 prefix_len + input.len(),
362 ¶ms,
363 &prep.rep_offsets,
364 &prep.hash_snapshot,
365 dict_hash_log,
366 &mut self.small_hash,
367 input_hash_log,
368 &mut self.sequences,
369 );
370
371 if params.force_raw_literals {
372 block_encoder::encode_compressed_block_raw(
373 input,
374 &self.sequences,
375 &mut rep_offsets,
376 true,
377 &mut self.output,
378 &mut self.workspace,
379 )?;
380 } else {
381 block_encoder::encode_compressed_block(
382 input,
383 &self.sequences,
384 &mut rep_offsets,
385 true,
386 &mut self.output,
387 &mut self.workspace,
388 strategy::use_custom_sequence_tables(¶ms, input.len()),
389 )?;
390 }
391 } else {
392 let combined = &prep.combined;
393 let mut rep_offsets = prep.rep_offsets;
394
395 if input.len() <= MAX_BLOCK_SIZE {
396 match params.strategy {
397 Strategy::Fast => {
398 fast::compress_fast_block(
399 combined,
400 prefix_len,
401 prefix_len + input.len(),
402 ¶ms,
403 &rep_offsets,
404 &mut self.hash_table,
405 &mut self.sequences,
406 );
407 }
408 Strategy::DFast => {
409 dfast::compress_dfast_block(
410 combined,
411 prefix_len,
412 prefix_len + input.len(),
413 ¶ms,
414 &rep_offsets,
415 &mut self.hash_table,
416 &mut self.hash_long,
417 &mut self.sequences,
418 );
419 }
420 }
421 if params.force_raw_literals {
422 block_encoder::encode_compressed_block_raw(
423 input,
424 &self.sequences,
425 &mut rep_offsets,
426 true,
427 &mut self.output,
428 &mut self.workspace,
429 )?;
430 } else {
431 block_encoder::encode_compressed_block(
432 input,
433 &self.sequences,
434 &mut rep_offsets,
435 true,
436 &mut self.output,
437 &mut self.workspace,
438 strategy::use_custom_sequence_tables(¶ms, input.len()),
439 )?;
440 }
441 } else {
442 let mut offset = 0;
443 while offset < input.len() {
444 let chunk_size = (input.len() - offset).min(MAX_BLOCK_SIZE);
445 let is_last = offset + chunk_size >= input.len();
446 match params.strategy {
447 Strategy::Fast => {
448 fast::compress_fast_block(
449 combined,
450 prefix_len + offset,
451 prefix_len + offset + chunk_size,
452 ¶ms,
453 &rep_offsets,
454 &mut self.hash_table,
455 &mut self.sequences,
456 );
457 }
458 Strategy::DFast => {
459 dfast::compress_dfast_block(
460 combined,
461 prefix_len + offset,
462 prefix_len + offset + chunk_size,
463 ¶ms,
464 &rep_offsets,
465 &mut self.hash_table,
466 &mut self.hash_long,
467 &mut self.sequences,
468 );
469 }
470 }
471 if params.force_raw_literals {
472 block_encoder::encode_compressed_block_raw(
473 &input[offset..offset + chunk_size],
474 &self.sequences,
475 &mut rep_offsets,
476 is_last,
477 &mut self.output,
478 &mut self.workspace,
479 )?;
480 } else {
481 block_encoder::encode_compressed_block(
482 &input[offset..offset + chunk_size],
483 &self.sequences,
484 &mut rep_offsets,
485 is_last,
486 &mut self.output,
487 &mut self.workspace,
488 strategy::use_custom_sequence_tables(¶ms, input.len()),
489 )?;
490 }
491 offset += chunk_size;
492 }
493 }
494 }
495
496 if self.content_checksum {
497 let hash = xxh64(input, 0);
498 let checksum = (hash & 0xFFFF_FFFF) as u32;
499 self.output.extend_from_slice(&checksum.to_le_bytes());
500 }
501
502 Ok(self.take_or_borrow_output())
503 }
504
505 fn compress_with_dict_fallback(
506 &mut self,
507 input: &[u8],
508 dict_id: u32,
509 prefix_len: usize,
510 ) -> Result<Cow<'_, [u8]>, CompressError> {
511 let prep = self.prepared.as_ref().unwrap();
512 let rep_offsets = prep.rep_offsets;
513 let prefix = &prep.combined[..prefix_len];
514
515 let total_window = prefix_len.saturating_add(input.len());
516 let params = strategy::level_params_for_size(self.level, total_window)
517 .expect("level validated at construction");
518 self.workspace.prev_huffman = prep.huf_table.clone();
519 self.workspace.prev_ll = prep.ll_table.clone();
520 self.workspace.prev_of = prep.of_table.clone();
521 self.workspace.prev_ml = prep.ml_table.clone();
522 compress_core(
523 input,
524 params,
525 Some(dict_id),
526 prefix,
527 rep_offsets,
528 &mut self.hash_table,
529 &mut self.hash_long,
530 &mut self.dict_hash,
531 &mut self.sequences,
532 &mut self.output,
533 &mut self.workspace,
534 &mut self.combined,
535 self.content_checksum,
536 )?;
537 Ok(self.take_or_borrow_output())
538 }
539
540 fn take_or_borrow_output(&mut self) -> Cow<'_, [u8]> {
541 if self.output.len() >= zrip_core::LARGE_OUTPUT_THRESHOLD {
542 Cow::Owned(core::mem::take(&mut self.output))
543 } else {
544 Cow::Borrowed(&self.output)
545 }
546 }
547}
548
549#[allow(clippy::too_many_arguments, clippy::unnecessary_wraps)]
550fn compress_core(
551 input: &[u8],
552 params: LevelParams,
553 dict_id: Option<u32>,
554 prefix: &[u8],
555 init_rep_offsets: [u32; 3],
556 hash_table: &mut Vec<u32>,
557 hash_long: &mut Vec<u32>,
558 dict_hash: &mut Vec<u32>,
559 sequences: &mut Vec<Sequence>,
560 output: &mut Vec<u8>,
561 workspace: &mut BlockEncodeWorkspace,
562 combined: &mut Vec<u8>,
563 content_checksum: bool,
564) -> Result<(), CompressError> {
565 let mut params = params;
566 strategy::apply_raw_literals_size_override(&mut params, input.len());
567
568 let hash_size = match params.strategy {
569 Strategy::Fast => 1usize << params.hash_log,
570 Strategy::DFast => 1usize << params.chain_log,
571 };
572 let long_size = 1usize << params.hash_log;
573
574 if prefix.is_empty() {
575 workspace.prev_huffman = None;
576 workspace.prev_ll = None;
577 workspace.prev_of = None;
578 workspace.prev_ml = None;
579 }
580
581 output.clear();
582 output.reserve(input.len() + 32);
583 write_frame_header_with_checksum(
584 output,
585 input.len(),
586 dict_id,
587 params.window_log,
588 content_checksum,
589 )?;
590
591 if input.is_empty() {
592 block_encoder::encode_raw_block(&[], true, output)?;
593 } else {
594 let has_prefix = !prefix.is_empty();
595 let mut rep_offsets = init_rep_offsets;
596 let mut offset = 0;
597
598 if hash_table.len() != hash_size {
599 hash_table.resize(hash_size, 0);
600 }
601
602 match params.strategy {
603 Strategy::Fast => {
604 if has_prefix && input.len() <= MAX_BLOCK_SIZE {
605 if dict_hash.len() != hash_size {
606 dict_hash.resize(hash_size, 0);
607 }
608 fast::compress_fast_with_prefix_reuse(
609 input,
610 ¶ms,
611 &rep_offsets,
612 prefix,
613 dict_hash,
614 hash_table,
615 sequences,
616 combined,
617 );
618 if params.force_raw_literals {
619 block_encoder::encode_compressed_block_raw(
620 input,
621 sequences,
622 &mut rep_offsets,
623 true,
624 output,
625 workspace,
626 )?;
627 } else {
628 block_encoder::encode_compressed_block(
629 input,
630 sequences,
631 &mut rep_offsets,
632 true,
633 output,
634 workspace,
635 strategy::use_custom_sequence_tables(¶ms, input.len()),
636 )?;
637 }
638 } else if has_prefix {
639 combined.clear();
640 combined.reserve(prefix.len() + input.len());
641 combined.extend_from_slice(prefix);
642 combined.extend_from_slice(input);
643 let plen = prefix.len();
644 fast::prefill_hash_table(combined, plen, params.hash_log, hash_table);
645
646 while offset < input.len() {
647 let chunk_size = (input.len() - offset).min(MAX_BLOCK_SIZE);
648 let is_last = offset + chunk_size >= input.len();
649 fast::compress_fast_block(
650 combined,
651 plen + offset,
652 plen + offset + chunk_size,
653 ¶ms,
654 &rep_offsets,
655 hash_table,
656 sequences,
657 );
658 if params.force_raw_literals {
659 block_encoder::encode_compressed_block_raw(
660 &input[offset..offset + chunk_size],
661 sequences,
662 &mut rep_offsets,
663 is_last,
664 output,
665 workspace,
666 )?;
667 } else {
668 block_encoder::encode_compressed_block(
669 &input[offset..offset + chunk_size],
670 sequences,
671 &mut rep_offsets,
672 is_last,
673 output,
674 workspace,
675 strategy::use_custom_sequence_tables(¶ms, input.len()),
676 )?;
677 }
678 offset += chunk_size;
679 }
680 } else {
681 hash_table.fill(0);
682 while offset < input.len() {
683 let chunk_size = (input.len() - offset).min(MAX_BLOCK_SIZE);
684 let block_end = offset + chunk_size;
685 let is_last = block_end >= input.len();
686 let block = &input[offset..block_end];
687
688 if block_looks_incompressible(block) {
689 block_encoder::encode_raw_block(block, is_last, output)?;
690 } else {
691 fast::compress_fast_block(
692 input,
693 offset,
694 block_end,
695 ¶ms,
696 &rep_offsets,
697 hash_table,
698 sequences,
699 );
700 if params.force_raw_literals {
701 block_encoder::encode_compressed_block_raw(
702 block,
703 sequences,
704 &mut rep_offsets,
705 is_last,
706 output,
707 workspace,
708 )?;
709 } else {
710 block_encoder::encode_compressed_block(
711 block,
712 sequences,
713 &mut rep_offsets,
714 is_last,
715 output,
716 workspace,
717 strategy::use_custom_sequence_tables(¶ms, input.len()),
718 )?;
719 }
720 }
721 offset = block_end;
722 }
723 }
724 }
725 Strategy::DFast => {
726 if hash_long.len() != long_size {
727 hash_long.resize(long_size, 0);
728 }
729 if has_prefix && input.len() <= MAX_BLOCK_SIZE {
730 dfast::compress_dfast_with_prefix_reuse(
731 input,
732 ¶ms,
733 &rep_offsets,
734 prefix,
735 hash_table,
736 hash_long,
737 sequences,
738 combined,
739 );
740 block_encoder::encode_compressed_block(
741 input,
742 sequences,
743 &mut rep_offsets,
744 true,
745 output,
746 workspace,
747 strategy::use_custom_sequence_tables(¶ms, input.len()),
748 )?;
749 } else if has_prefix {
750 combined.clear();
751 combined.reserve(prefix.len() + input.len());
752 combined.extend_from_slice(prefix);
753 combined.extend_from_slice(input);
754 let plen = prefix.len();
755 dfast::prefill_hash_tables(
756 combined,
757 plen,
758 params.hash_log,
759 params.chain_log,
760 params.min_match,
761 hash_table,
762 hash_long,
763 );
764
765 while offset < input.len() {
766 let chunk_size = (input.len() - offset).min(MAX_BLOCK_SIZE);
767 let is_last = offset + chunk_size >= input.len();
768 dfast::compress_dfast_block(
769 combined,
770 plen + offset,
771 plen + offset + chunk_size,
772 ¶ms,
773 &rep_offsets,
774 hash_table,
775 hash_long,
776 sequences,
777 );
778 block_encoder::encode_compressed_block(
779 &input[offset..offset + chunk_size],
780 sequences,
781 &mut rep_offsets,
782 is_last,
783 output,
784 workspace,
785 strategy::use_custom_sequence_tables(¶ms, input.len()),
786 )?;
787 offset += chunk_size;
788 }
789 } else {
790 hash_table.fill(0);
791 hash_long.fill(0);
792 while offset < input.len() {
793 let chunk_size = (input.len() - offset).min(MAX_BLOCK_SIZE);
794 let block_end = offset + chunk_size;
795 let is_last = block_end >= input.len();
796 let block = &input[offset..block_end];
797
798 if block_looks_incompressible(block) {
799 block_encoder::encode_raw_block(block, is_last, output)?;
800 } else {
801 dfast::compress_dfast_block(
802 input,
803 offset,
804 block_end,
805 ¶ms,
806 &rep_offsets,
807 hash_table,
808 hash_long,
809 sequences,
810 );
811 block_encoder::encode_compressed_block(
812 block,
813 sequences,
814 &mut rep_offsets,
815 is_last,
816 output,
817 workspace,
818 strategy::use_custom_sequence_tables(¶ms, input.len()),
819 )?;
820 }
821 offset = block_end;
822 }
823 }
824 }
825 }
826 }
827
828 if content_checksum {
829 let hash = xxh64(input, 0);
830 let checksum = (hash & 0xFFFF_FFFF) as u32;
831 output.extend_from_slice(&checksum.to_le_bytes());
832 }
833
834 Ok(())
835}