1use alloc::format;
2use alloc::string::{String, ToString};
3use alloc::vec::Vec;
4use core::mem;
5
6use crate::common::MAX_BLOCK_SIZE;
7#[cfg(feature = "hash")]
8use core::hash::Hasher;
9#[cfg(feature = "hash")]
10use twox_hash::XxHash64;
11
12use crate::encoding::levels::compress_block_encoded;
13use crate::encoding::{
14 CompressionLevel, EncoderDictionary, MatchGeneratorDriver, Matcher, block_header::BlockHeader,
15 frame_compressor::CachedDictionaryEntropy, frame_compressor::CompressState,
16 frame_compressor::FseTables, frame_compressor::PreviousFseTable, frame_header::FrameHeader,
17};
18use crate::io::{Error, ErrorKind, Write};
19
20pub struct StreamingEncoder<W: Write, M: Matcher = MatchGeneratorDriver> {
26 drain: Option<W>,
27 compression_level: CompressionLevel,
28 state: CompressState<M>,
29 pending: Vec<u8>,
30 encoded_scratch: Vec<u8>,
31 errored: bool,
32 last_error_kind: Option<ErrorKind>,
33 last_error_message: Option<String>,
34 frame_started: bool,
35 target_block_size: Option<u32>,
39 pledged_content_size: Option<u64>,
40 source_size_hint: Option<u64>,
46 content_size_flag: bool,
52 bytes_consumed: u64,
53 strategy_override: Option<crate::encoding::strategy::StrategyTag>,
59 magicless: bool,
62 content_checksum: bool,
68 dictionary: Option<EncoderDictionary>,
71 dictionary_id_flag: bool,
77 dictionary_entropy_cache: Option<CachedDictionaryEntropy>,
81 #[cfg(feature = "hash")]
82 hasher: XxHash64,
83}
84
85impl<W: Write> StreamingEncoder<W, MatchGeneratorDriver> {
86 pub fn new(drain: W, compression_level: CompressionLevel) -> Self {
91 Self::new_with_matcher(
92 MatchGeneratorDriver::new(MAX_BLOCK_SIZE as usize, 1),
93 drain,
94 compression_level,
95 )
96 }
97
98 pub fn set_parameters(
106 &mut self,
107 params: &crate::encoding::CompressionParameters,
108 ) -> Result<(), Error> {
109 self.ensure_open()?;
110 if self.frame_started {
111 return Err(invalid_input_error(
112 "compression parameters must be set before the first write",
113 ));
114 }
115 self.compression_level = params.level();
116 let overrides = params.overrides();
117 self.strategy_override = overrides.strategy.map(|s| s.tag());
120 self.state.strategy_tag = self.strategy_override.unwrap_or_else(|| {
121 crate::encoding::strategy::StrategyTag::for_compression_level(self.compression_level)
122 });
123 self.state.huf_optimal_search = crate::encoding::frame_compressor::huf_search_enabled(
124 self.state.strategy_tag,
125 self.pledged_content_size.or(self.source_size_hint),
126 );
127 self.state.matcher.set_param_overrides(Some(overrides));
128 Ok(())
129 }
130}
131
132impl<W: Write, M: Matcher> StreamingEncoder<W, M> {
133 pub fn new_with_matcher(matcher: M, drain: W, compression_level: CompressionLevel) -> Self {
138 Self {
139 drain: Some(drain),
140 compression_level,
141 state: CompressState {
142 matcher,
143 last_huff_table: None,
144 huff_table_spare: None,
145 fse_tables: FseTables::new(),
146 block_scratch: crate::encoding::blocks::CompressedBlockScratch::new(),
147 offset_hist: [1, 4, 8],
148 strategy_tag: crate::encoding::strategy::StrategyTag::for_compression_level(
149 compression_level,
150 ),
151 huf_optimal_search: true,
152 },
153 pending: Vec::new(),
154 encoded_scratch: Vec::new(),
155 errored: false,
156 last_error_kind: None,
157 last_error_message: None,
158 frame_started: false,
159 target_block_size: None,
160 pledged_content_size: None,
161 source_size_hint: None,
162 content_size_flag: true,
163 bytes_consumed: 0,
164 strategy_override: None,
165 magicless: false,
166 content_checksum: false,
167 dictionary: None,
168 dictionary_id_flag: true,
169 dictionary_entropy_cache: None,
170 #[cfg(feature = "hash")]
171 hasher: XxHash64::with_seed(0),
172 }
173 }
174
175 pub fn set_target_block_size(&mut self, target: Option<u32>) -> Result<(), Error> {
184 self.ensure_open()?;
185 if self.frame_started {
186 return Err(invalid_input_error(
187 "the block-size target must be set before the first write",
188 ));
189 }
190 self.target_block_size = target.map(|t| {
191 t.clamp(
192 crate::common::MIN_TARGET_BLOCK_SIZE,
193 crate::common::MAX_BLOCK_SIZE,
194 )
195 });
196 Ok(())
197 }
198
199 pub fn set_content_checksum(&mut self, emit: bool) -> Result<(), Error> {
207 self.ensure_open()?;
208 if self.frame_started {
209 return Err(invalid_input_error(
210 "content checksum must be set before the first write",
211 ));
212 }
213 self.content_checksum = emit;
214 Ok(())
215 }
216
217 pub fn set_magicless(&mut self, magicless: bool) -> Result<(), Error> {
225 self.ensure_open()?;
226 if self.frame_started {
227 return Err(invalid_input_error(
228 "magicless format must be set before the first write",
229 ));
230 }
231 self.magicless = magicless;
232 Ok(())
233 }
234
235 pub fn set_pledged_content_size(&mut self, size: u64) -> Result<(), Error> {
246 self.ensure_open()?;
247 if self.frame_started {
248 return Err(invalid_input_error(
249 "pledged content size must be set before the first write",
250 ));
251 }
252 self.pledged_content_size = Some(size);
253 self.state.matcher.set_source_size_hint(size);
256 Ok(())
257 }
258
259 pub fn set_content_size_flag(&mut self, emit: bool) -> Result<(), Error> {
266 self.ensure_open()?;
267 if self.frame_started {
268 return Err(invalid_input_error(
269 "content size flag must be set before the first write",
270 ));
271 }
272 self.content_size_flag = emit;
273 Ok(())
274 }
275
276 pub fn set_source_size_hint(&mut self, size: u64) -> Result<(), Error> {
284 self.ensure_open()?;
285 if self.frame_started {
286 return Err(invalid_input_error(
287 "source size hint must be set before the first write",
288 ));
289 }
290 self.state.matcher.set_source_size_hint(size);
291 self.source_size_hint = Some(size);
296 Ok(())
297 }
298
299 pub fn set_dictionary_from_bytes(&mut self, raw_dictionary: &[u8]) -> Result<(), Error> {
306 let dict = EncoderDictionary::from_bytes(raw_dictionary)
307 .map_err(|err| invalid_input_error(&alloc::format!("invalid dictionary: {err:?}")))?;
308 self.set_encoder_dictionary(dict)
309 }
310
311 pub fn set_dictionary_id_flag(&mut self, emit: bool) -> Result<(), Error> {
316 self.ensure_open()?;
317 if self.frame_started {
318 return Err(invalid_input_error(
319 "dictionary ID flag must be set before the first write",
320 ));
321 }
322 self.dictionary_id_flag = emit;
323 Ok(())
324 }
325
326 pub fn set_encoder_dictionary(&mut self, dict: EncoderDictionary) -> Result<(), Error> {
330 self.ensure_open()?;
331 if self.frame_started {
332 return Err(invalid_input_error(
333 "dictionary must be attached before the first write",
334 ));
335 }
336 let inner = &dict.inner;
337 if inner.id == 0 {
338 return Err(invalid_input_error("dictionary has a zero ID"));
339 }
340 if inner.offset_hist.contains(&0) {
341 return Err(invalid_input_error(
342 "dictionary carries a zero repeat offset",
343 ));
344 }
345 self.dictionary_entropy_cache = Some(CachedDictionaryEntropy::from_dictionary(inner));
346 self.dictionary = Some(dict);
347 Ok(())
348 }
349
350 pub fn get_ref(&self) -> &W {
355 self.drain
356 .as_ref()
357 .expect("streaming encoder drain is present until finish consumes self")
358 }
359
360 pub fn heap_size(&self) -> usize {
369 let mut total = self.state.matcher.heap_size();
370 total += self
371 .state
372 .last_huff_table
373 .as_ref()
374 .map_or(0, |table| table.heap_size());
375 total += self
376 .state
377 .huff_table_spare
378 .as_ref()
379 .map_or(0, |table| table.heap_size());
380 total += self.pending.capacity();
381 total += self.encoded_scratch.capacity();
382 total += self
383 .dictionary
384 .as_ref()
385 .map_or(0, |d| d.inner.dict_content.capacity());
386 total += self
387 .dictionary_entropy_cache
388 .as_ref()
389 .map_or(0, CachedDictionaryEntropy::heap_size);
390 total
391 }
392
393 pub fn get_mut(&mut self) -> &mut W {
401 self.drain
402 .as_mut()
403 .expect("streaming encoder drain is present until finish consumes self")
404 }
405
406 pub fn finish(mut self) -> Result<W, Error> {
411 self.ensure_open()?;
412
413 if let Some(pledged) = self.pledged_content_size
417 && self.bytes_consumed != pledged
418 {
419 return Err(invalid_input_error(
420 "pledged content size does not match bytes consumed",
421 ));
422 }
423
424 self.ensure_frame_started()?;
425
426 if self.pending.is_empty() {
427 self.write_empty_last_block()
428 .map_err(|err| self.fail(err))?;
429 } else {
430 self.emit_pending_block(true)?;
431 }
432
433 let mut drain = self
434 .drain
435 .take()
436 .expect("streaming encoder drain must be present when finishing");
437
438 #[cfg(feature = "hash")]
439 if self.content_checksum {
440 let checksum = self.hasher.finish() as u32;
441 drain
442 .write_all(&checksum.to_le_bytes())
443 .map_err(|err| self.fail(err))?;
444 }
445
446 drain.flush().map_err(|err| self.fail(err))?;
447 Ok(drain)
448 }
449
450 fn ensure_open(&self) -> Result<(), Error> {
451 if self.errored {
452 return Err(self.sticky_error());
453 }
454 Ok(())
455 }
456
457 fn sticky_error(&self) -> Error {
461 match (self.last_error_kind, self.last_error_message.as_deref()) {
462 (Some(kind), Some(message)) => error_with_kind_message(
463 kind,
464 format!(
465 "streaming encoder is in an errored state due to previous {kind:?} failure: {message}"
466 ),
467 ),
468 (Some(kind), None) => error_from_kind(kind),
469 (None, Some(message)) => other_error_owned(format!(
470 "streaming encoder is in an errored state: {message}"
471 )),
472 (None, None) => other_error("streaming encoder is in an errored state"),
473 }
474 }
475
476 fn drain_mut(&mut self) -> Result<&mut W, Error> {
477 self.drain
478 .as_mut()
479 .ok_or_else(|| other_error("streaming encoder has no active drain"))
480 }
481
482 fn ensure_frame_started(&mut self) -> Result<(), Error> {
483 if self.frame_started {
484 return Ok(());
485 }
486
487 self.ensure_level_supported()?;
488 let use_dictionary_state =
496 !matches!(self.compression_level, CompressionLevel::Uncompressed)
497 && self.state.matcher.supports_dictionary_priming()
498 && self.dictionary.is_some();
499 if use_dictionary_state && let Some(dict) = self.dictionary.as_ref() {
502 self.state
503 .matcher
504 .set_dictionary_size_hint(dict.inner.dict_content.len());
505 }
506 self.state.matcher.reset(self.compression_level);
507 self.state.offset_hist = if use_dictionary_state {
510 self.dictionary
511 .as_ref()
512 .map(|dict| dict.inner.offset_hist)
513 .unwrap_or([1, 4, 8])
514 } else {
515 [1, 4, 8]
516 };
517 if use_dictionary_state && let Some(dict) = self.dictionary.as_ref() {
522 let offset_hist = dict.inner.offset_hist;
523 self.state
524 .matcher
525 .prime_with_dictionary(dict.inner.dict_content.as_slice(), offset_hist);
526 }
527 if use_dictionary_state && let Some(cache) = self.dictionary_entropy_cache.as_ref() {
530 self.state.last_huff_table.clone_from(&cache.huff);
531 self.state
532 .fse_tables
533 .ll_previous
534 .clone_from(&cache.ll_previous);
535 self.state
536 .fse_tables
537 .ml_previous
538 .clone_from(&cache.ml_previous);
539 self.state
540 .fse_tables
541 .of_previous
542 .clone_from(&cache.of_previous);
543 let ll_entropy = match cache.ll_previous.as_ref() {
544 Some(PreviousFseTable::Custom(table)) => Some(table.as_ref()),
545 _ => None,
546 };
547 let ml_entropy = match cache.ml_previous.as_ref() {
548 Some(PreviousFseTable::Custom(table)) => Some(table.as_ref()),
549 _ => None,
550 };
551 let of_entropy = match cache.of_previous.as_ref() {
552 Some(PreviousFseTable::Custom(table)) => Some(table.as_ref()),
553 _ => None,
554 };
555 self.state.matcher.seed_dictionary_entropy(
556 self.state.last_huff_table.as_ref(),
557 ll_entropy,
558 ml_entropy,
559 of_entropy,
560 );
561 } else {
562 self.state.last_huff_table = None;
563 self.state.fse_tables.ll_previous = None;
564 self.state.fse_tables.ml_previous = None;
565 self.state.fse_tables.of_previous = None;
566 }
567 self.state.strategy_tag = self.strategy_override.unwrap_or_else(|| {
575 crate::encoding::strategy::StrategyTag::for_compression_level(self.compression_level)
576 });
577 self.state.huf_optimal_search = crate::encoding::frame_compressor::huf_search_enabled(
578 self.state.strategy_tag,
579 self.pledged_content_size.or(self.source_size_hint),
580 );
581 #[cfg(feature = "hash")]
582 {
583 self.hasher = XxHash64::with_seed(0);
584 }
585
586 let window_size = self.state.matcher.window_size();
587 if window_size == 0 {
588 return Err(invalid_input_error(
589 "matcher reported window_size == 0, which is invalid",
590 ));
591 }
592
593 let single_segment = self.content_size_flag
602 && !use_dictionary_state
603 && self
604 .pledged_content_size
605 .map(|size| (512..=(1 << 14)).contains(&size) && size <= window_size)
606 .unwrap_or(false);
607
608 let header = FrameHeader {
609 frame_content_size: if self.content_size_flag {
610 self.pledged_content_size
611 } else {
612 None
613 },
614 single_segment,
615 content_checksum: cfg!(feature = "hash") && self.content_checksum,
616 dictionary_id: if use_dictionary_state && self.dictionary_id_flag {
617 self.dictionary.as_ref().map(|dict| dict.inner.id as u64)
618 } else {
619 None
620 },
621 window_size: if single_segment {
622 None
623 } else {
624 Some(window_size)
625 },
626 magicless: self.magicless,
627 };
628 let mut encoded_header = Vec::new();
629 header.serialize(&mut encoded_header);
630 self.drain_mut()
631 .and_then(|drain| drain.write_all(&encoded_header))
632 .map_err(|err| self.fail(err))?;
633
634 self.frame_started = true;
635 Ok(())
636 }
637
638 fn block_capacity(&self) -> usize {
639 let matcher_window = self.state.matcher.window_size() as usize;
640 let ceiling = self
641 .target_block_size
642 .map_or(MAX_BLOCK_SIZE as usize, |t| t as usize);
643 core::cmp::max(1, core::cmp::min(matcher_window, ceiling))
644 }
645
646 fn allocate_pending_space(&mut self, block_capacity: usize) -> Vec<u8> {
647 let mut space = match self.compression_level {
648 CompressionLevel::Fastest
649 | CompressionLevel::Default
650 | CompressionLevel::Better
651 | CompressionLevel::Best
652 | CompressionLevel::Level(_) => self.state.matcher.get_next_space(),
653 CompressionLevel::Uncompressed => Vec::new(),
654 };
655 space.clear();
656 if space.capacity() > block_capacity {
657 space.shrink_to(block_capacity);
658 }
659 if space.capacity() < block_capacity {
660 space.reserve(block_capacity - space.capacity());
661 }
662 space
663 }
664
665 fn emit_full_pending_block(
666 &mut self,
667 block_capacity: usize,
668 consumed: usize,
669 ) -> Option<Result<usize, Error>> {
670 if self.pending.len() != block_capacity {
671 return None;
672 }
673
674 let new_pending = self.allocate_pending_space(block_capacity);
675 let full_block = mem::replace(&mut self.pending, new_pending);
676 if let Err((err, restored_block)) = self.encode_block(full_block, false) {
677 self.pending = restored_block;
678 let err = self.fail(err);
679 if consumed > 0 {
680 return Some(Ok(consumed));
681 }
682 return Some(Err(err));
683 }
684 None
685 }
686
687 fn emit_pending_block(&mut self, last_block: bool) -> Result<(), Error> {
688 let block = mem::take(&mut self.pending);
689 if let Err((err, restored_block)) = self.encode_block(block, last_block) {
690 self.pending = restored_block;
691 return Err(self.fail(err));
692 }
693 if !last_block {
694 let block_capacity = self.block_capacity();
695 self.pending = self.allocate_pending_space(block_capacity);
696 }
697 Ok(())
698 }
699
700 fn ensure_level_supported(&self) -> Result<(), Error> {
704 match self.compression_level {
705 CompressionLevel::Uncompressed
706 | CompressionLevel::Fastest
707 | CompressionLevel::Default
708 | CompressionLevel::Better
709 | CompressionLevel::Best
710 | CompressionLevel::Level(_) => Ok(()),
711 }
712 }
713
714 fn encode_block(
715 &mut self,
716 uncompressed_data: Vec<u8>,
717 last_block: bool,
718 ) -> Result<(), (Error, Vec<u8>)> {
719 let mut raw_block = Some(uncompressed_data);
720 let mut encoded = Vec::new();
721 mem::swap(&mut encoded, &mut self.encoded_scratch);
722 encoded.clear();
723 let needed_capacity = self.block_capacity() + 3;
724 if encoded.capacity() < needed_capacity {
725 encoded.reserve(needed_capacity.saturating_sub(encoded.len()));
726 }
727 let mut moved_into_matcher = false;
728 if raw_block.as_ref().is_some_and(|block| block.is_empty()) {
729 let header = BlockHeader {
730 last_block,
731 block_type: crate::blocks::block::BlockType::Raw,
732 block_size: 0,
733 };
734 header.serialize(&mut encoded);
735 } else {
736 match self.compression_level {
737 CompressionLevel::Uncompressed => {
738 let block = raw_block.as_ref().expect("raw block missing");
739 let header = BlockHeader {
740 last_block,
741 block_type: crate::blocks::block::BlockType::Raw,
742 block_size: block.len() as u32,
743 };
744 header.serialize(&mut encoded);
745 encoded.extend_from_slice(block);
746 }
747 CompressionLevel::Fastest
748 | CompressionLevel::Default
749 | CompressionLevel::Better
750 | CompressionLevel::Best
751 | CompressionLevel::Level(_) => {
752 let block = raw_block.take().expect("raw block missing");
753 debug_assert!(!block.is_empty(), "empty blocks handled above");
754 let dict_active = self.dictionary.is_some()
761 && self.state.matcher.supports_dictionary_priming();
762 compress_block_encoded(
763 &mut self.state,
764 self.compression_level,
765 last_block,
766 block,
767 &mut encoded,
768 dict_active,
769 #[cfg(feature = "lsm")]
772 None,
773 #[cfg(all(feature = "lsm", feature = "hash"))]
774 None,
775 );
776 moved_into_matcher = true;
777 }
778 }
779 }
780
781 if let Err(err) = self.drain_mut().and_then(|drain| drain.write_all(&encoded)) {
782 encoded.clear();
783 mem::swap(&mut encoded, &mut self.encoded_scratch);
784 let restored = if moved_into_matcher {
785 self.state.matcher.get_last_space().to_vec()
786 } else {
787 raw_block.unwrap_or_default()
788 };
789 return Err((err, restored));
790 }
791
792 if moved_into_matcher {
793 #[cfg(feature = "hash")]
794 if self.content_checksum {
795 self.hasher.write(self.state.matcher.get_last_space());
796 }
797 } else {
798 self.hash_block(raw_block.as_deref().unwrap_or(&[]));
799 }
800 encoded.clear();
801 mem::swap(&mut encoded, &mut self.encoded_scratch);
802 Ok(())
803 }
804
805 fn write_empty_last_block(&mut self) -> Result<(), Error> {
806 self.encode_block(Vec::new(), true).map_err(|(err, _)| err)
807 }
808
809 fn fail(&mut self, err: Error) -> Error {
810 self.errored = true;
811 if self.last_error_kind.is_none() {
812 self.last_error_kind = Some(err.kind());
813 }
814 if self.last_error_message.is_none() {
815 self.last_error_message = Some(err.to_string());
816 }
817 err
818 }
819
820 #[cfg(feature = "hash")]
821 fn hash_block(&mut self, uncompressed_data: &[u8]) {
822 if self.content_checksum {
823 self.hasher.write(uncompressed_data);
824 }
825 }
826
827 #[cfg(not(feature = "hash"))]
828 fn hash_block(&mut self, _uncompressed_data: &[u8]) {}
829}
830
831impl<W: Write, M: Matcher> Write for StreamingEncoder<W, M> {
832 fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
833 self.ensure_open()?;
834 if buf.is_empty() {
835 return Ok(0);
836 }
837
838 if let Some(pledged) = self.pledged_content_size
842 && self.bytes_consumed >= pledged
843 {
844 return Err(invalid_input_error(
845 "write would exceed pledged content size",
846 ));
847 }
848
849 self.ensure_frame_started()?;
850
851 let buf = if let Some(pledged) = self.pledged_content_size {
855 let remaining_allowed = pledged
856 .checked_sub(self.bytes_consumed)
857 .ok_or_else(|| invalid_input_error("bytes consumed exceed pledged content size"))?;
858 if remaining_allowed == 0 {
859 return Err(invalid_input_error(
860 "write would exceed pledged content size",
861 ));
862 }
863 let accepted = core::cmp::min(
864 buf.len(),
865 usize::try_from(remaining_allowed).unwrap_or(usize::MAX),
866 );
867 &buf[..accepted]
868 } else {
869 buf
870 };
871
872 let block_capacity = self.block_capacity();
873 if self.pending.capacity() == 0 {
874 self.pending = self.allocate_pending_space(block_capacity);
875 }
876 let mut remaining = buf;
877 let mut consumed = 0usize;
878
879 while !remaining.is_empty() {
880 if let Some(result) = self.emit_full_pending_block(block_capacity, consumed) {
881 return result;
882 }
883
884 let available = block_capacity - self.pending.len();
885 let to_take = core::cmp::min(remaining.len(), available);
886 if to_take == 0 {
887 break;
888 }
889 self.pending.extend_from_slice(&remaining[..to_take]);
890 remaining = &remaining[to_take..];
891 consumed += to_take;
892
893 if let Some(result) = self.emit_full_pending_block(block_capacity, consumed) {
894 if let Ok(n) = &result {
895 self.bytes_consumed += *n as u64;
896 }
897 return result;
898 }
899 }
900 self.bytes_consumed += consumed as u64;
901 Ok(consumed)
902 }
903
904 fn flush(&mut self) -> Result<(), Error> {
905 self.ensure_open()?;
906 if self.pending.is_empty() {
907 return self
908 .drain_mut()
909 .and_then(|drain| drain.flush())
910 .map_err(|err| self.fail(err));
911 }
912 self.ensure_frame_started()?;
913 self.emit_pending_block(false)?;
914 self.drain_mut()
915 .and_then(|drain| drain.flush())
916 .map_err(|err| self.fail(err))
917 }
918}
919
920fn error_from_kind(kind: ErrorKind) -> Error {
921 Error::from(kind)
922}
923
924fn error_with_kind_message(kind: ErrorKind, message: String) -> Error {
925 #[cfg(feature = "std")]
926 {
927 Error::new(kind, message)
928 }
929 #[cfg(not(feature = "std"))]
930 {
931 Error::new(kind, alloc::boxed::Box::new(message))
932 }
933}
934
935fn invalid_input_error(message: &str) -> Error {
936 #[cfg(feature = "std")]
937 {
938 Error::new(ErrorKind::InvalidInput, message)
939 }
940 #[cfg(not(feature = "std"))]
941 {
942 Error::new(
943 ErrorKind::Other,
944 alloc::boxed::Box::new(alloc::string::String::from(message)),
945 )
946 }
947}
948
949fn other_error_owned(message: String) -> Error {
950 #[cfg(feature = "std")]
951 {
952 Error::other(message)
953 }
954 #[cfg(not(feature = "std"))]
955 {
956 Error::new(ErrorKind::Other, alloc::boxed::Box::new(message))
957 }
958}
959
960fn other_error(message: &str) -> Error {
961 #[cfg(feature = "std")]
962 {
963 Error::other(message)
964 }
965 #[cfg(not(feature = "std"))]
966 {
967 Error::new(
968 ErrorKind::Other,
969 alloc::boxed::Box::new(alloc::string::String::from(message)),
970 )
971 }
972}
973
974#[cfg(test)]
975mod tests;