1#![doc(html_root_url = "https://docs.rs/ciff/0.3.1")]
37#![warn(
38 missing_docs,
39 trivial_casts,
40 trivial_numeric_casts,
41 unused_import_braces,
42 unused_qualifications
43)]
44#![warn(clippy::all, clippy::pedantic)]
45#![allow(
46 clippy::module_name_repetitions,
47 clippy::default_trait_access,
48 clippy::cast_possible_wrap,
49 clippy::cast_possible_truncation,
50 clippy::copy_iterator
51)]
52
53use anyhow::{anyhow, Context};
54use indicatif::ProgressIterator;
55use indicatif::{ProgressBar, ProgressStyle};
56use memmap::Mmap;
57use num_traits::ToPrimitive;
58use protobuf::{CodedInputStream, CodedOutputStream};
59use std::borrow::Borrow;
60use std::convert::TryFrom;
61use std::ffi::{OsStr, OsString};
62use std::fmt;
63use std::fs::File;
64use std::io::{self, BufRead, BufReader, BufWriter, Write};
65use std::path::{Path, PathBuf};
66use tempfile::TempDir;
67
68mod proto;
69pub use proto::{DocRecord, Posting, PostingsList};
70
71mod binary_collection;
72pub use binary_collection::{
73 BinaryCollection, BinarySequence, InvalidFormat, RandomAccessBinaryCollection,
74};
75
76mod payload_vector;
77pub use payload_vector::{build_lexicon, PayloadIter, PayloadSlice, PayloadVector};
78
79type Result<T> = anyhow::Result<T>;
80
81const DEFAULT_PROGRESS_TEMPLATE: &str =
82 "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {count}/{total} ({eta})";
83
84#[derive(PartialEq, Clone, Default)]
87struct Header {
88 num_postings_lists: u32,
89 num_documents: u32,
90 protobuf_header: proto::Header,
92}
93
94impl Header {
95 fn from_stream(input: &mut CodedInputStream<'_>) -> Result<Self> {
102 let header = input.read_message::<proto::Header>()?;
103 let num_documents = u32::try_from(header.get_num_docs())
104 .context("Number of documents must be non-negative.")?;
105 let num_postings_lists = u32::try_from(header.get_num_postings_lists())
106 .context("Number of documents must be non-negative.")?;
107 Ok(Self {
108 protobuf_header: header,
109 num_documents,
110 num_postings_lists,
111 })
112 }
113}
114
115impl fmt::Display for Header {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 write!(f, "{}", self.protobuf_header)
118 }
119}
120
121fn pb_style() -> ProgressStyle {
123 ProgressStyle::default_bar()
124 .template(DEFAULT_PROGRESS_TEMPLATE)
125 .progress_chars("=> ")
126}
127
128pub fn encode_u32_sequence<N, S, W>(writer: &mut W, len: u32, sequence: S) -> io::Result<()>
154where
155 N: Borrow<u32>,
156 S: IntoIterator<Item = N>,
157 W: Write,
158{
159 let size: [u8; 4] = len.to_le_bytes();
160 writer.write_all(&size)?;
161 for element in sequence {
162 writer.write_all(&element.borrow().to_le_bytes())?;
163 }
164 Ok(())
165}
166
167fn write_posting_list<DW, FW, TW>(
168 posting_list: &PostingsList,
169 documents: &mut DW,
170 frequencies: &mut FW,
171 terms: &mut TW,
172) -> Result<()>
173where
174 DW: Write,
175 FW: Write,
176 TW: Write,
177{
178 let length = posting_list
179 .get_df()
180 .to_u32()
181 .ok_or_else(|| anyhow!("Cannot cast to u32: {}", posting_list.get_df()))?;
182
183 let postings = posting_list.get_postings();
184
185 encode_u32_sequence(
186 documents,
187 length,
188 postings.iter().scan(0_u32, |prev, p| {
189 *prev += u32::try_from(p.get_docid()).expect("Negative ID");
190 Some(*prev)
191 }),
192 )?;
193
194 encode_u32_sequence(
195 frequencies,
196 length,
197 postings
198 .iter()
199 .map(|p| u32::try_from(p.get_tf()).expect("Negative frequency")),
200 )?;
201
202 writeln!(terms, "{}", posting_list.get_term())?;
203 Ok(())
204}
205
206fn check_lines_sorted<R: BufRead>(reader: R) -> io::Result<bool> {
207 let mut prev = String::from("");
208 for line in reader.lines() {
209 let line = line?;
210 if line < prev {
211 return Ok(false);
212 }
213 prev = line;
214 }
215 Ok(true)
216}
217
218pub fn concat<S1, S2>(path: S1, suffix: S2) -> OsString
223where
224 S1: AsRef<OsStr>,
225 S2: AsRef<OsStr>,
226{
227 let mut path = path.as_ref().to_owned();
228 path.push(suffix);
229 path
230}
231
232#[derive(Debug, Clone, Default)]
234struct PisaIndexPaths {
235 documents: PathBuf,
236 frequencies: PathBuf,
237 sizes: PathBuf,
238}
239
240impl PisaIndexPaths {
241 #[must_use]
242 fn from_base_path<P: AsRef<OsStr>>(path: P) -> Self {
243 Self {
244 documents: PathBuf::from(concat(path.as_ref(), ".docs")),
245 frequencies: PathBuf::from(concat(path.as_ref(), ".freqs")),
246 sizes: PathBuf::from(concat(path.as_ref(), ".sizes")),
247 }
248 }
249}
250
251#[derive(Debug, Clone, Default)]
252struct PisaPaths {
253 index: PisaIndexPaths,
254 terms: PathBuf,
255 titles: PathBuf,
256 termlex: Option<PathBuf>,
257 doclex: Option<PathBuf>,
258}
259
260impl PisaPaths {
261 #[must_use]
262 fn from_base_path<P: AsRef<OsStr>>(path: P) -> Self {
263 Self {
264 index: PisaIndexPaths::from_base_path(&path),
265 terms: PathBuf::from(concat(&path, ".terms")),
266 titles: PathBuf::from(concat(&path, ".documents")),
267 termlex: Some(PathBuf::from(concat(&path, ".termlex"))),
268 doclex: Some(PathBuf::from(concat(&path, ".doclex"))),
269 }
270 }
271}
272
273fn reorder_postings(path: &Path, order: &[usize], skip_first: bool) -> Result<()> {
274 let temp = TempDir::new()?;
275 let tmp_path = temp.path().join("coll");
276 std::fs::rename(path, &tmp_path)?;
277 let mmap = unsafe { Mmap::map(&File::open(tmp_path)?)? };
278 let coll = RandomAccessBinaryCollection::try_from(mmap.as_ref())?;
279 let mut writer = BufWriter::new(File::create(path)?);
280 if skip_first {
281 let order: Vec<_> = std::iter::once(0)
282 .chain(order.iter().map(|&i| i + 1))
283 .collect();
284 binary_collection::reorder(&coll, &order, &mut writer)?;
285 } else {
286 binary_collection::reorder(&coll, order, &mut writer)?;
287 }
288 writer.flush()?;
289 Ok(())
290}
291
292fn reorder_pisa_index(paths: &PisaPaths) -> Result<()> {
293 let terms = BufReader::new(File::open(&paths.terms)?)
294 .lines()
295 .collect::<io::Result<Vec<_>>>()?;
296 let mut order: Vec<_> = (0..terms.len()).collect();
297 order.sort_by_key(|&i| &terms[i]);
298 reorder_postings(&paths.index.documents, &order, true)?;
299 reorder_postings(&paths.index.frequencies, &order, false)?;
300 let mut term_writer = BufWriter::new(File::create(&paths.terms)?);
301 for index in order {
302 writeln!(&mut term_writer, "{}", terms[index])?;
303 }
304 Ok(())
305}
306
307#[derive(Debug, Default, Clone)]
309pub struct CiffToPisa {
310 input: Option<PathBuf>,
311 documents_path: Option<PathBuf>,
312 frequencies_path: Option<PathBuf>,
313 sizes_path: Option<PathBuf>,
314 terms_path: Option<PathBuf>,
315 titles_path: Option<PathBuf>,
316 termlex_path: Option<PathBuf>,
317 doclex_path: Option<PathBuf>,
318}
319
320impl CiffToPisa {
321 pub fn input_path<P: Into<PathBuf>>(&mut self, path: P) -> &mut Self {
323 self.input = Some(path.into());
324 self
325 }
326
327 pub fn output_paths<P: AsRef<OsStr>>(&mut self, base_path: P) -> &mut Self {
338 let paths = PisaPaths::from_base_path(base_path);
339 self.documents_path = Some(paths.index.documents);
340 self.frequencies_path = Some(paths.index.frequencies);
341 self.sizes_path = Some(paths.index.sizes);
342 self.terms_path = Some(paths.terms);
343 self.titles_path = Some(paths.titles);
344 self.termlex_path = paths.termlex;
345 self.doclex_path = paths.doclex;
346 self
347 }
348
349 pub fn skip_lexicons(&mut self) -> &mut Self {
351 self.termlex_path = None;
352 self.doclex_path = None;
353 self
354 }
355
356 pub fn convert(&self) -> Result<()> {
365 let input = self
366 .input
367 .as_ref()
368 .ok_or_else(|| anyhow!("input path undefined"))?;
369 let index_output = PisaIndexPaths {
370 documents: self
371 .documents_path
372 .clone()
373 .ok_or_else(|| anyhow!("document postings path undefined"))?,
374 frequencies: self
375 .frequencies_path
376 .clone()
377 .ok_or_else(|| anyhow!("frequency postings path undefined"))?,
378 sizes: self
379 .sizes_path
380 .clone()
381 .ok_or_else(|| anyhow!("document sizes path undefined"))?,
382 };
383 let output = PisaPaths {
384 index: index_output,
385 terms: self
386 .terms_path
387 .clone()
388 .ok_or_else(|| anyhow!("terms path undefined"))?,
389 titles: self
390 .titles_path
391 .clone()
392 .ok_or_else(|| anyhow!("terms path undefined"))?,
393 termlex: self.termlex_path.clone(),
394 doclex: self.doclex_path.clone(),
395 };
396 convert_to_pisa(input, &output)
397 }
398}
399
400#[deprecated = "use CiffToPisa instead"]
411pub fn ciff_to_pisa(input: &Path, output: &Path, generate_lexicons: bool) -> Result<()> {
412 let mut converter = CiffToPisa::default();
413 converter.input_path(input).output_paths(output);
414 if !generate_lexicons {
415 converter.skip_lexicons();
416 }
417 converter.convert()
418}
419
420fn convert_to_pisa(input: &Path, output: &PisaPaths) -> Result<()> {
421 println!("{:?}", output);
422 let mut ciff_reader =
423 File::open(input).with_context(|| format!("Unable to open {}", input.display()))?;
424 let mut input = CodedInputStream::new(&mut ciff_reader);
425 let mut documents = BufWriter::new(File::create(&output.index.documents)?);
426 let mut frequencies = BufWriter::new(File::create(&output.index.frequencies)?);
427 let mut terms = BufWriter::new(File::create(&output.terms)?);
428
429 let header = Header::from_stream(&mut input)?;
430 println!("{}", header);
431
432 eprintln!("Processing postings");
433 encode_u32_sequence(&mut documents, 1, [header.num_documents].iter())?;
434 let progress = ProgressBar::new(u64::try_from(header.num_postings_lists)?);
435 progress.set_style(pb_style());
436 progress.set_draw_delta(10);
437 for _ in 0..header.num_postings_lists {
438 write_posting_list(
439 &input.read_message::<PostingsList>()?,
440 &mut documents,
441 &mut frequencies,
442 &mut terms,
443 )?;
444 progress.inc(1);
445 }
446 progress.finish();
447
448 documents.flush()?;
449 frequencies.flush()?;
450 terms.flush()?;
451
452 eprintln!("Processing document lengths");
453 let mut sizes = BufWriter::new(File::create(&output.index.sizes)?);
454 let mut trecids = BufWriter::new(File::create(&output.titles)?);
455
456 let progress = ProgressBar::new(u64::from(header.num_documents));
457 progress.set_style(pb_style());
458 progress.set_draw_delta(u64::from(header.num_documents) / 100);
459 sizes.write_all(&header.num_documents.to_le_bytes())?;
460 sizes.flush()?;
461
462 for docs_seen in 0..header.num_documents {
463 let doc_record = input.read_message::<DocRecord>()?;
464
465 let docid: u32 = doc_record
466 .get_docid()
467 .to_u32()
468 .ok_or_else(|| anyhow!("Cannot cast docid to u32: {}", doc_record.get_docid()))?;
469
470 let trecid = doc_record.get_collection_docid();
471 let length: u32 = doc_record.get_doclength().to_u32().ok_or_else(|| {
472 anyhow!(
473 "Cannot cast doc length to u32: {}",
474 doc_record.get_doclength()
475 )
476 })?;
477
478 if docid != docs_seen {
479 anyhow::bail!("Document sizes must come in order");
480 }
481
482 sizes.write_all(&length.to_le_bytes())?;
483 writeln!(trecids, "{}", trecid)?;
484 progress.inc(1);
485 }
486 trecids.flush()?;
487 progress.finish();
488
489 if !check_lines_sorted(BufReader::new(File::open(&output.terms)?))? {
490 reorder_pisa_index(output)?;
491 }
492
493 eprintln!("Generating the document and term lexicons...");
494 drop(trecids);
495 if let Some(termlex) = output.termlex.as_ref() {
496 build_lexicon(&output.terms, termlex)?;
497 }
498 if let Some(doclex) = output.doclex.as_ref() {
499 build_lexicon(&output.titles, doclex)?;
500 }
501
502 Ok(())
503}
504
505fn read_document_count(
506 documents: &mut BinaryCollection,
507) -> std::result::Result<u32, InvalidFormat> {
508 let invalid = || InvalidFormat::new("Unable to read document count");
509 documents
510 .next()
511 .ok_or_else(invalid)??
512 .get(0)
513 .ok_or_else(invalid)
514}
515
516fn header(documents_bytes: &[u8], sizes_bytes: &[u8], description: &str) -> Result<proto::Header> {
517 let mut num_postings_lists = 0;
518
519 eprintln!("Collecting posting lists statistics");
520 let progress = ProgressBar::new(documents_bytes.len() as u64);
521 progress.set_style(pb_style());
522 progress.set_draw_delta(100_000);
523 let mut collection = BinaryCollection::try_from(documents_bytes)?;
524 let num_documents = read_document_count(&mut collection)?;
525 for sequence in collection {
526 num_postings_lists += 1;
527 let sequence = sequence?;
528 progress.inc((sequence.bytes().len() + 4) as u64);
529 }
530 progress.finish();
531
532 eprintln!("Computing average document length");
533 let progress = ProgressBar::new(u64::from(num_documents));
534 progress.set_style(pb_style());
535 let doclen_sum: i64 = sizes(sizes_bytes)?
536 .iter()
537 .map(i64::from)
538 .progress_with(progress)
539 .sum();
540
541 let mut header = proto::Header::default();
542 header.set_version(1);
543 header.set_description(description.into());
544 header.set_num_postings_lists(num_postings_lists);
545 header.set_total_postings_lists(num_postings_lists);
546 header.set_total_terms_in_collection(doclen_sum);
547 header.set_num_docs(num_documents as i32);
548 header.set_total_docs(num_documents as i32);
549 #[allow(clippy::cast_precision_loss)]
550 header.set_average_doclength(doclen_sum as f64 / f64::from(num_documents));
551 Ok(header)
552}
553
554fn sizes(memory: &[u8]) -> std::result::Result<BinarySequence<'_>, InvalidFormat> {
555 BinaryCollection::try_from(memory)?
556 .next()
557 .ok_or_else(|| InvalidFormat::new("sizes collection is empty"))?
558}
559
560fn write_sizes(sizes_mmap: &Mmap, titles_file: &File, out: &mut CodedOutputStream) -> Result<()> {
561 let titles = BufReader::new(titles_file);
562 for ((docid, size), title) in sizes(sizes_mmap)?.iter().enumerate().zip(titles.lines()) {
563 let mut document = DocRecord::default();
564 document.set_docid(docid as i32);
565 document.set_collection_docid(title?);
566 document.set_doclength(size as i32);
567 out.write_message_no_tag(&document)?;
568 }
569 Ok(())
570}
571
572fn write_postings(
573 documents_mmap: &Mmap,
574 frequencies_mmap: &Mmap,
575 terms_file: &File,
576 out: &mut CodedOutputStream,
577) -> Result<()> {
578 let mut documents = BinaryCollection::try_from(&documents_mmap[..])?;
579 let num_documents = u64::from(read_document_count(&mut documents)?);
580 let frequencies = BinaryCollection::try_from(&frequencies_mmap[..])?;
581 let terms = BufReader::new(terms_file);
582
583 eprintln!("Writing postings");
584 let progress = ProgressBar::new(num_documents);
585 progress.set_style(pb_style());
586 progress.set_draw_delta(num_documents / 100);
587 for ((term_documents, term_frequencies), term) in documents
588 .zip(frequencies)
589 .zip(terms.lines())
590 .progress_with(progress)
591 {
592 let mut posting_list = PostingsList::default();
593 posting_list.set_term(term?);
594 let mut count = 0;
595 let mut sum = 0;
596 let mut last_doc = 0;
597 for (docid, frequency) in term_documents?.iter().zip(term_frequencies?.iter()) {
598 let mut posting = Posting::default();
599 posting.set_docid(docid as i32 - last_doc);
600 posting.set_tf(frequency as i32);
601 posting_list.postings.push(posting);
602 count += 1;
603 sum += i64::from(frequency);
604 last_doc = docid as i32;
605 }
606 posting_list.set_df(count);
607 posting_list.set_cf(sum);
608 out.write_message_no_tag(&posting_list)?;
609 }
610 Ok(())
611}
612
613#[derive(Debug, Default, Clone)]
615pub struct PisaToCiff {
616 documents_path: Option<PathBuf>,
617 frequencies_path: Option<PathBuf>,
618 sizes_path: Option<PathBuf>,
619 terms_path: Option<PathBuf>,
620 titles_path: Option<PathBuf>,
621 output_path: Option<PathBuf>,
622 description: String,
623}
624
625impl PisaToCiff {
626 pub fn description<S: Into<String>>(&mut self, description: S) -> &mut Self {
628 self.description = description.into();
629 self
630 }
631
632 pub fn pisa_paths<P: AsRef<OsStr>>(&mut self, base_path: P) -> &mut Self {
641 let paths = PisaPaths::from_base_path(base_path);
642 self.documents_path = Some(paths.index.documents);
643 self.frequencies_path = Some(paths.index.frequencies);
644 self.sizes_path = Some(paths.index.sizes);
645 self.terms_path = Some(paths.terms);
646 self.titles_path = Some(paths.titles);
647 self
648 }
649
650 pub fn index_paths<P: AsRef<OsStr>>(&mut self, base_path: P) -> &mut Self {
655 let PisaIndexPaths {
656 documents,
657 frequencies,
658 sizes,
659 } = PisaIndexPaths::from_base_path(base_path);
660 self.documents_path = Some(documents);
661 self.frequencies_path = Some(frequencies);
662 self.sizes_path = Some(sizes);
663 self
664 }
665
666 pub fn terms_path<P: Into<PathBuf>>(&mut self, path: P) -> &mut Self {
668 self.terms_path = Some(path.into());
669 self
670 }
671
672 pub fn titles_path<P: Into<PathBuf>>(&mut self, path: P) -> &mut Self {
674 self.titles_path = Some(path.into());
675 self
676 }
677
678 pub fn output_path<P: Into<PathBuf>>(&mut self, path: P) -> &mut Self {
680 self.output_path = Some(path.into());
681 self
682 }
683
684 pub fn convert(&self) -> Result<()> {
693 pisa_to_ciff_from_paths(
694 self.documents_path
695 .as_ref()
696 .ok_or_else(|| anyhow!("undefined document postings path"))?,
697 self.frequencies_path
698 .as_ref()
699 .ok_or_else(|| anyhow!("undefined frequency postings path"))?,
700 self.sizes_path
701 .as_ref()
702 .ok_or_else(|| anyhow!("undefined document sizes path"))?,
703 self.terms_path
704 .as_ref()
705 .ok_or_else(|| anyhow!("undefined terms path"))?,
706 self.titles_path
707 .as_ref()
708 .ok_or_else(|| anyhow!("undefined titles path"))?,
709 self.output_path
710 .as_ref()
711 .ok_or_else(|| anyhow!("undefined output path"))?,
712 &self.description,
713 )
714 }
715}
716
717#[deprecated = "use PisaToCiff instead"]
726pub fn pisa_to_ciff(
727 collection_input: &Path,
728 terms_input: &Path,
729 titles_input: &Path,
730 output: &Path,
731 description: &str,
732) -> Result<()> {
733 PisaToCiff::default()
734 .description(description)
735 .index_paths(collection_input)
736 .terms_path(terms_input)
737 .titles_path(titles_input)
738 .output_path(output)
739 .convert()
740}
741
742fn pisa_to_ciff_from_paths(
743 documents_path: &Path,
744 frequencies_path: &Path,
745 sizes_path: &Path,
746 terms_path: &Path,
747 titles_path: &Path,
748 output: &Path,
749 description: &str,
750) -> Result<()> {
751 let documents_file = File::open(documents_path)?;
752 let frequencies_file = File::open(frequencies_path)?;
753 let sizes_file = File::open(sizes_path)?;
754 let terms_file = File::open(terms_path)?;
755 let titles_file = File::open(titles_path)?;
756
757 let documents_mmap = unsafe { Mmap::map(&documents_file)? };
758 let frequencies_mmap = unsafe { Mmap::map(&frequencies_file)? };
759 let sizes_mmap = unsafe { Mmap::map(&sizes_file)? };
760
761 let mut writer = BufWriter::new(File::create(output)?);
762 let mut out = CodedOutputStream::new(&mut writer);
763
764 let header = header(&documents_mmap[..], &sizes_mmap[..], description)?;
765 out.write_message_no_tag(&header)?;
766
767 write_postings(&documents_mmap, &frequencies_mmap, &terms_file, &mut out)?;
768 write_sizes(&sizes_mmap, &titles_file, &mut out)?;
769
770 out.flush()?;
771
772 Ok(())
773}
774
775#[cfg(test)]
776mod test {
777 use super::*;
778
779 #[test]
780 fn test_size_sequence() {
781 let empty_memory = Vec::<u8>::new();
782 let sizes = sizes(&empty_memory);
783 assert!(sizes.is_err());
784 assert_eq!(
785 "Invalid binary collection format: sizes collection is empty",
786 &format!("{}", sizes.err().unwrap())
787 );
788
789 let valid_memory: Vec<u8> = [
790 5_u32.to_le_bytes(),
791 1_u32.to_le_bytes(),
792 2_u32.to_le_bytes(),
793 3_u32.to_le_bytes(),
794 4_u32.to_le_bytes(),
795 5_u32.to_le_bytes(),
796 ]
797 .iter()
798 .flatten()
799 .copied()
800 .collect();
801 let sizes = super::sizes(&valid_memory);
802 assert!(sizes.is_ok());
803 assert_eq!(
804 sizes.unwrap().iter().collect::<Vec<u32>>(),
805 vec![1_u32, 2, 3, 4, 5]
806 );
807 }
808
809 fn header_to_buf(header: &proto::Header) -> Result<Vec<u8>> {
810 let mut buffer = Vec::<u8>::new();
811 let mut out = CodedOutputStream::vec(&mut buffer);
812 out.write_message_no_tag(header)?;
813 out.flush()?;
814 Ok(buffer)
815 }
816
817 #[test]
818 fn test_read_default_header() -> Result<()> {
819 let mut proto_header = proto::Header::default();
820 proto_header.set_num_docs(17);
821 proto_header.set_num_postings_lists(1234);
822
823 let buffer = header_to_buf(&proto_header)?;
824
825 let mut input = CodedInputStream::from_bytes(&buffer);
826 let header = Header::from_stream(&mut input)?;
827 assert_eq!(header.protobuf_header, proto_header);
828 assert_eq!(header.num_documents, 17);
829 assert_eq!(header.num_postings_lists, 1234);
830 Ok(())
831 }
832
833 #[test]
834 fn test_read_negative_num_documents() -> Result<()> {
835 let mut proto_header = proto::Header::default();
836 proto_header.set_num_docs(-17);
837
838 let buffer = header_to_buf(&proto_header)?;
839
840 let mut input = CodedInputStream::from_bytes(&buffer);
841 assert!(Header::from_stream(&mut input).is_err());
842 Ok(())
843 }
844
845 #[test]
846 fn test_read_negative_num_posting_lists() -> Result<()> {
847 let mut proto_header = proto::Header::default();
848 proto_header.set_num_postings_lists(-1234);
849
850 let buffer = header_to_buf(&proto_header)?;
851
852 let mut input = CodedInputStream::from_bytes(&buffer);
853 assert!(Header::from_stream(&mut input).is_err());
854 Ok(())
855 }
856}