1#![deny(
11 nonstandard_style,
12 rust_2018_idioms,
13 rust_2021_compatibility,
14 future_incompatible,
15 rustdoc::all,
16 rustdoc::missing_crate_level_docs,
17 missing_docs,
18 unsafe_code,
19 unused,
20 unused_import_braces,
21 unused_lifetimes,
22 unused_qualifications,
23 variant_size_differences,
24 warnings,
25 clippy::all,
26 clippy::cargo,
27 clippy::pedantic,
28 clippy::allow_attributes_without_reason,
29 clippy::as_underscore,
30 clippy::branches_sharing_code,
31 clippy::clone_on_ref_ptr,
32 clippy::cognitive_complexity,
33 clippy::create_dir,
34 clippy::dbg_macro,
35 clippy::debug_assert_with_mut_call,
36 clippy::decimal_literal_representation,
37 clippy::default_union_representation,
38 clippy::derive_partial_eq_without_eq,
39 clippy::empty_drop,
40 clippy::empty_line_after_outer_attr,
41 clippy::empty_structs_with_brackets,
42 clippy::equatable_if_let,
43 clippy::exhaustive_enums,
44 clippy::exit,
45 clippy::filetype_is_file,
46 clippy::float_cmp_const,
47 clippy::fn_to_numeric_cast_any,
48 clippy::format_push_string,
49 clippy::future_not_send,
50 clippy::get_unwrap,
51 clippy::if_then_some_else_none,
52 clippy::imprecise_flops,
53 clippy::iter_on_empty_collections,
54 clippy::iter_on_single_items,
55 clippy::iter_with_drain,
56 clippy::large_include_file,
57 clippy::let_underscore_must_use,
58 clippy::lossy_float_literal,
59 clippy::mem_forget,
60 clippy::missing_const_for_fn,
61 clippy::mixed_read_write_in_expression,
62 clippy::multiple_inherent_impl,
63 clippy::mutex_atomic,
64 clippy::mutex_integer,
65 clippy::needless_collect,
66 clippy::non_send_fields_in_send_ty,
67 clippy::nonstandard_macro_braces,
68 clippy::option_if_let_else,
69 clippy::or_fun_call,
70 clippy::panic,
71 clippy::path_buf_push_overwrite,
72 clippy::pattern_type_mismatch,
73 clippy::print_stderr,
74 clippy::print_stdout,
75 clippy::rc_buffer,
76 clippy::rc_mutex,
77 clippy::redundant_pub_crate,
78 clippy::rest_pat_in_fully_bound_structs,
79 clippy::same_name_method,
80 clippy::self_named_module_files,
81 clippy::significant_drop_in_scrutinee,
82 clippy::str_to_string,
83 clippy::string_add,
84 clippy::string_lit_as_bytes,
85 clippy::string_slice,
86 clippy::string_to_string,
87 clippy::suboptimal_flops,
88 clippy::suspicious_operation_groupings,
89 clippy::todo,
90 clippy::trailing_empty_array,
91 clippy::trait_duplication_in_bounds,
92 clippy::transmute_undefined_repr,
93 clippy::trivial_regex,
94 clippy::try_err,
95 clippy::type_repetition_in_bounds,
96 clippy::undocumented_unsafe_blocks,
97 clippy::unimplemented,
98 clippy::unnecessary_self_imports,
99 clippy::unneeded_field_pattern,
100 clippy::unseparated_literal_suffix,
101 clippy::unused_peekable,
102 clippy::unused_rounding,
103 clippy::unwrap_used,
104 clippy::use_debug,
105 clippy::use_self,
106 clippy::useless_let_if_seq,
107 clippy::verbose_file_reads
108)]
109#![allow(
110 clippy::non_ascii_literal,
112 clippy::module_name_repetitions,
115 clippy::unreadable_literal,
117 reason = "allow some exceptions"
118)]
119
120mod cht;
123mod error;
124mod header;
125
126use either::Either;
127use h3o::CellIndex;
128use header::Header;
129use std::io::{self, Write};
130
131pub use error::DecodingError;
132
133pub fn compress<W: Write>(
167 writer: &mut W,
168 cells: impl IntoIterator<Item = CellIndex>,
169) -> Result<(), io::Error> {
170 Header::cht_v1().write(writer)?;
171 cht::encode(writer, cells)
172}
173
174pub fn decompress(
195 bytes: &[u8],
196) -> impl Iterator<Item = Result<CellIndex, DecodingError>> + '_ {
197 Header::from_bytes(bytes).map_or_else(
198 |err| Either::Left(std::iter::once(Err(err))),
199 |header| match header {
200 Header::ChtV1 => Either::Right(cht::decode(&bytes[header.len()..])),
201 },
202 )
203}
204
205#[cfg(test)]
208mod tests {
209 use super::*;
210 use std::io::Cursor;
211
212 #[test]
213 fn rountrip() {
214 let cells = vec![
215 CellIndex::try_from(0x8b184584a21efff).expect("valid cell"),
216 CellIndex::try_from(0x8b184584a246fff).expect("valid cell"),
217 CellIndex::try_from(0x8b184584a2a8fff).expect("valid cell"),
218 CellIndex::try_from(0x8b184584a2cbfff).expect("valid cell"),
219 CellIndex::try_from(0x8b184584a329fff).expect("valid cell"),
220 CellIndex::try_from(0x8b184584a366fff).expect("valid cell"),
221 CellIndex::try_from(0x8b184584a389fff).expect("valid cell"),
222 ];
223
224 let mut buffer = Cursor::new(vec![]);
226 compress(&mut buffer, cells.clone()).expect("compress");
227 let bytes = buffer.into_inner();
228
229 let result = decompress(bytes.as_slice())
231 .collect::<Result<Vec<_>, _>>()
232 .expect("valid input");
233 assert_eq!(result, cells);
234 }
235
236 #[test]
237 fn bad_header() {
238 let bytes = [0x81, 0x23];
239
240 let error = decompress(bytes.as_slice())
241 .collect::<Result<Vec<_>, _>>()
242 .expect_err("invalid input");
243 assert!(matches!(error, DecodingError::InvalidHeader(_)));
244 }
245}