From 9729e913fa5a2f412ed060a85e44c5ed50a5f94b Mon Sep 17 00:00:00 2001 From: Arthur Pastel Date: Sat, 20 Dec 2025 00:11:23 +0100 Subject: [PATCH 1/3] Allow using read_file to consume pipe mode data --- src/error.rs | 6 ++++++ src/file_reader.rs | 10 +++++++++- src/header.rs | 24 ++++++++++++++++++------ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/error.rs b/src/error.rs index 74e980e..12baee2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -63,6 +63,12 @@ pub enum Error { #[error("The specified size in the perf event header was smaller than the header itself")] InvalidPerfEventSize, + + #[error("Cannot parse non-streaming perf.data file with parse_pipe. Use parse_file instead.")] + FileFormatDetectedInPipeMode, + + #[error("Detected pipe format in file mode")] + PipeFormatDetectedInFileMode, } impl From for Error { diff --git a/src/file_reader.rs b/src/file_reader.rs index 5880760..279ff95 100644 --- a/src/file_reader.rs +++ b/src/file_reader.rs @@ -58,7 +58,15 @@ pub struct PerfFileReader { impl PerfFileReader { pub fn parse_file(mut cursor: C) -> Result { - let header = PerfHeader::parse(&mut cursor)?; + let header = match PerfHeader::parse(&mut cursor) { + Ok(header) => header, + Err(Error::PipeFormatDetectedInFileMode) => { + // Rewind and parse as pipe format instead + cursor.seek(SeekFrom::Start(0))?; + return Self::parse_pipe(cursor); + } + Err(e) => return Err(e), + }; match &header.magic { b"PERFILE2" => { Self::parse_file_impl::(cursor, header, Endianness::LittleEndian) diff --git a/src/header.rs b/src/header.rs index 9000e4d..1849746 100644 --- a/src/header.rs +++ b/src/header.rs @@ -2,6 +2,7 @@ use std::io::Read; use byteorder::{ByteOrder, ReadBytesExt}; +use super::error::Error; use super::features::FeatureSet; use super::section::PerfFileSection; @@ -28,7 +29,7 @@ pub struct PerfHeader { } impl PerfHeader { - pub fn parse(mut reader: R) -> Result { + pub fn parse(mut reader: R) -> Result { let mut magic = [0; 8]; reader.read_exact(&mut magic)?; @@ -39,11 +40,14 @@ impl PerfHeader { } } - fn parse_impl( - mut reader: R, - magic: [u8; 8], - ) -> Result { + fn parse_impl(mut reader: R, magic: [u8; 8]) -> Result { let header_size = reader.read_u64::()?; + + // Detect if this is actually a pipe format instead of file format. + if header_size == std::mem::size_of::() as u64 { + return Err(Error::PipeFormatDetectedInFileMode); + } + let attr_size = reader.read_u64::()?; let attr_section = PerfFileSection::parse::<_, T>(&mut reader)?; let data_section = PerfFileSection::parse::<_, T>(&mut reader)?; @@ -81,7 +85,7 @@ pub struct PerfPipeHeader { } impl PerfPipeHeader { - pub fn parse(mut reader: R) -> Result { + pub fn parse(mut reader: R) -> Result { let mut magic = [0; 8]; reader.read_exact(&mut magic)?; @@ -90,6 +94,14 @@ impl PerfPipeHeader { } else { reader.read_u64::()? }; + + // Detect if this is actually a file format instead of pipe format. + if size > std::mem::size_of::() as u64 + && size == std::mem::size_of::() as u64 + { + return Err(Error::FileFormatDetectedInPipeMode); + } + Ok(Self { magic, size }) } } From f7dd0667acd481c401f0cf62cd05cc70c9b0d00f Mon Sep 17 00:00:00 2001 From: Arthur Pastel Date: Tue, 16 Dec 2025 23:07:26 +0100 Subject: [PATCH 2/3] Support zstd compressed data --- Cargo.toml | 5 + src/constants.rs | 1 + src/decompression.rs | 69 ++++ src/feature_sections.rs | 34 ++ src/file_reader.rs | 96 +++++- src/lib.rs | 4 +- src/perf_file.rs | 14 +- src/record.rs | 2 + tests/compression_e2e.rs | 380 ++++++++++++++++++++++ tests/fixtures/README.md | 29 ++ tests/fixtures/fibo_compressed.pipe.data | Bin 0 -> 108556 bytes tests/fixtures/sleep.data | Bin 0 -> 15120 bytes tests/fixtures/sleep_compressed.data | Bin 0 -> 14620 bytes tests/fixtures/sleep_compressed.pipe.data | Bin 0 -> 31951 bytes 14 files changed, 628 insertions(+), 6 deletions(-) create mode 100644 src/decompression.rs create mode 100644 tests/compression_e2e.rs create mode 100644 tests/fixtures/README.md create mode 100644 tests/fixtures/fibo_compressed.pipe.data create mode 100644 tests/fixtures/sleep.data create mode 100644 tests/fixtures/sleep_compressed.data create mode 100644 tests/fixtures/sleep_compressed.pipe.data diff --git a/Cargo.toml b/Cargo.toml index fdbadf7..a6e2ad2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,10 @@ documentation = "https://docs.rs/linux-perf-data/" repository = "https://github.com/mstange/linux-perf-data/" exclude = ["/.github", "/.vscode", "/tests"] +[features] +default = ["zstd"] +zstd = ["zstd-safe"] + [dependencies] byteorder = "1.4.3" memchr = "2.4.1" @@ -21,6 +25,7 @@ linux-perf-event-reader = "0.10.0" linear-map = "1.2.0" prost = { version = "0.14", default-features = false, features = ["std"] } prost-derive = "0.14" +zstd-safe = { version = "7.2", optional = true } [dev-dependencies] yaxpeax-arch = { version = "0.3", default-features = false } diff --git a/src/constants.rs b/src/constants.rs index 58c9900..0b7534d 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -18,6 +18,7 @@ pub const PERF_RECORD_EVENT_UPDATE: u32 = 78; pub const PERF_RECORD_TIME_CONV: u32 = 79; pub const PERF_RECORD_HEADER_FEATURE: u32 = 80; pub const PERF_RECORD_COMPRESSED: u32 = 81; +pub const PERF_RECORD_COMPRESSED2: u32 = 83; // pub const SIMPLE_PERF_RECORD_TYPE_START: u32 = 32768; diff --git a/src/decompression.rs b/src/decompression.rs new file mode 100644 index 0000000..9b625a9 --- /dev/null +++ b/src/decompression.rs @@ -0,0 +1,69 @@ +use zstd_safe::{DCtx, InBuffer, OutBuffer}; + +/// A zstd decompressor for PERF_RECORD_COMPRESSED records. +pub struct ZstdDecompressor { + dctx: Option>, + /// Buffer for partial perf records that span multiple compressed chunks + partial_record_buffer: Vec, +} + +impl Default for ZstdDecompressor { + fn default() -> Self { + Self::new() + } +} + +impl ZstdDecompressor { + pub fn new() -> Self { + Self { + dctx: None, + partial_record_buffer: Vec::new(), + } + } + + /// Decompress a chunk of zstd data. + pub fn decompress(&mut self, compressed_data: &[u8]) -> Result, std::io::Error> { + let dctx = self.dctx.get_or_insert_with(DCtx::create); + + let mut decompressed = vec![0; compressed_data.len() * 4]; + let mut in_buffer = InBuffer::around(compressed_data); + let mut total_out = 0; + + while in_buffer.pos < in_buffer.src.len() { + let available = decompressed.len() - total_out; + let mut out_buffer = OutBuffer::around(&mut decompressed[total_out..]); + + match dctx.decompress_stream(&mut out_buffer, &mut in_buffer) { + Ok(_) => { + total_out += out_buffer.pos(); + if out_buffer.pos() == available { + decompressed.resize(decompressed.len() + compressed_data.len() * 4, 0); + } + } + Err(code) => { + let error_name = zstd_safe::get_error_name(code); + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("Zstd decompression failed: {}", error_name), + )); + } + } + } + + decompressed.truncate(total_out); + + // Prepend any partial record data from the previous chunk + if !self.partial_record_buffer.is_empty() { + let mut combined = std::mem::take(&mut self.partial_record_buffer); + combined.extend_from_slice(&decompressed); + decompressed = combined; + } + + Ok(decompressed) + } + + /// Save partial record data that spans to the next compressed chunk. + pub fn save_partial_record(&mut self, data: &[u8]) { + self.partial_record_buffer = data.to_vec(); + } +} diff --git a/src/feature_sections.rs b/src/feature_sections.rs index 39080e3..06de827 100644 --- a/src/feature_sections.rs +++ b/src/feature_sections.rs @@ -49,6 +49,40 @@ impl SampleTimeRange { } } +/// Information about compression used in the perf.data file. +#[derive(Debug, Clone, Copy)] +pub struct CompressionInfo { + pub version: u32, + /// Compression algorithm type. 1 = Zstd + pub type_: u32, + /// Compression level (e.g., 1-22 for Zstd) + pub level: u32, + /// Compression ratio achieved + pub ratio: u32, + /// mmap buffer size + pub mmap_len: u32, +} + +impl CompressionInfo { + pub const STRUCT_SIZE: usize = 4 + 4 + 4 + 4 + 4; + pub const ZSTD_TYPE: u32 = 1; + + pub fn parse(mut reader: R) -> Result { + let version = reader.read_u32::()?; + let type_ = reader.read_u32::()?; + let level = reader.read_u32::()?; + let ratio = reader.read_u32::()?; + let mmap_len = reader.read_u32::()?; + Ok(Self { + version, + type_, + level, + ratio, + mmap_len, + }) + } +} + pub struct HeaderString; impl HeaderString { diff --git a/src/file_reader.rs b/src/file_reader.rs index 279ff95..44d57f3 100644 --- a/src/file_reader.rs +++ b/src/file_reader.rs @@ -9,6 +9,9 @@ use linux_perf_event_reader::{ use std::collections::{HashMap, VecDeque}; use std::io::{Cursor, Read, Seek, SeekFrom}; +#[cfg(feature = "zstd")] +use crate::decompression::ZstdDecompressor; + use super::error::{Error, ReadError}; use super::feature_sections::AttributeDescription; use super::features::Feature; @@ -204,6 +207,8 @@ impl PerfFileReader { buffers_for_recycling: VecDeque::new(), current_event_body: Vec::new(), pending_first_record: None, + #[cfg(feature = "zstd")] + zstd_decompressor: ZstdDecompressor::new(), }; Ok(Self { @@ -374,6 +379,8 @@ impl PerfFileReader { buffers_for_recycling: VecDeque::new(), current_event_body: Vec::new(), pending_first_record, + #[cfg(feature = "zstd")] + zstd_decompressor: ZstdDecompressor::new(), }; Ok(Self { @@ -399,6 +406,9 @@ pub struct PerfRecordIter { buffers_for_recycling: VecDeque>, /// For pipe mode: the first non-metadata record that was read during initialization pending_first_record: Option<(PerfEventHeader, Vec)>, + /// Zstd decompressor for handling COMPRESSED records + #[cfg(feature = "zstd")] + zstd_decompressor: ZstdDecompressor, } impl PerfRecordIter { @@ -467,9 +477,9 @@ impl PerfRecordIter { } self.read_offset += u64::from(header.size); - if UserRecordType::try_from(RecordType(header.type_)) - == Some(UserRecordType::PERF_FINISHED_ROUND) - { + let user_record_type = UserRecordType::try_from(RecordType(header.type_)); + + if user_record_type == Some(UserRecordType::PERF_FINISHED_ROUND) { self.sorter.finish_round(); if self.sorter.has_more() { // The sorter is non-empty. We're done. @@ -484,7 +494,6 @@ impl PerfRecordIter { let event_body_len = size - PerfEventHeader::STRUCT_SIZE; let mut buffer = self.buffers_for_recycling.pop_front().unwrap_or_default(); buffer.resize(event_body_len, 0); - // Try to read the event body. For pipe mode, EOF here also means end of stream. match self.reader.read_exact(&mut buffer) { Ok(()) => {} @@ -499,6 +508,28 @@ impl PerfRecordIter { } } + if user_record_type == Some(UserRecordType::PERF_COMPRESSED) { + // PERF_COMPRESSED is the old format, not yet implemented + return Err(Error::IoError(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "PERF_COMPRESSED (type 81) is not supported yet, only PERF_COMPRESSED2 (type 83)", + ))); + } + + if user_record_type == Some(UserRecordType::PERF_COMPRESSED2) { + #[cfg(not(feature = "zstd"))] + { + return Err(Error::IoError(std::io::Error::new(std::io::ErrorKind::Unsupported, + "Compression support is not enabled. Please rebuild with the 'zstd' feature flag.", + ))); + } + #[cfg(feature = "zstd")] + { + self.decompress_and_process_compressed2::(&buffer)?; + continue; + } + } + self.process_record::(header, buffer, offset)?; } @@ -550,7 +581,64 @@ impl PerfRecordIter { attr_index, }; self.sorter.insert_unordered(sort_key, pending_record); + Ok(()) + } + /// Decompresses a PERF_RECORD_COMPRESSED2 record and processes its sub-records. + #[cfg(feature = "zstd")] + fn decompress_and_process_compressed2( + &mut self, + buffer: &[u8], + ) -> Result<(), Error> { + if buffer.len() < 8 { + return Err(ReadError::PerfEventData.into()); + } + let data_size = T::read_u64(&buffer[0..8]) as usize; + if data_size > buffer.len() - 8 { + return Err(ReadError::PerfEventData.into()); + } + let compressed_data = &buffer[8..8 + data_size]; + + let decompressed = self.zstd_decompressor.decompress(compressed_data)?; + + // Parse the decompressed data as a sequence of perf records + let mut cursor = Cursor::new(&decompressed[..]); + let mut offset = 0u64; + + while (cursor.position() as usize) < decompressed.len() { + let header_start = cursor.position() as usize; + // Check if we have enough bytes for a header + let remaining = decompressed.len() - header_start; + if remaining < PerfEventHeader::STRUCT_SIZE { + self.zstd_decompressor + .save_partial_record(&decompressed[header_start..]); + break; + } + + let sub_header = PerfEventHeader::parse::<_, T>(&mut cursor)?; + let sub_size = sub_header.size as usize; + if sub_size < PerfEventHeader::STRUCT_SIZE { + return Err(Error::InvalidPerfEventSize); + } + + let sub_event_body_len = sub_size - PerfEventHeader::STRUCT_SIZE; + // Check if we have enough bytes for the sub-record body + let remaining_after_header = decompressed.len() - cursor.position() as usize; + if sub_event_body_len > remaining_after_header { + self.zstd_decompressor + .save_partial_record(&decompressed[header_start..]); + break; + } + + let mut sub_buffer = self.buffers_for_recycling.pop_front().unwrap_or_default(); + sub_buffer.resize(sub_event_body_len, 0); + cursor + .read_exact(&mut sub_buffer) + .map_err(|_| ReadError::PerfEventData)?; + + self.process_record::(sub_header, sub_buffer, offset)?; + offset += sub_size as u64; + } Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index fc95d4e..61557d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,6 +64,8 @@ mod build_id_event; mod constants; +#[cfg(feature = "zstd")] +mod decompression; mod dso_info; mod dso_key; mod error; @@ -91,7 +93,7 @@ pub use linux_perf_event_reader::Endianness; pub use dso_info::DsoInfo; pub use dso_key::DsoKey; pub use error::{Error, ReadError}; -pub use feature_sections::{AttributeDescription, NrCpus, SampleTimeRange}; +pub use feature_sections::{AttributeDescription, CompressionInfo, NrCpus, SampleTimeRange}; pub use features::{Feature, FeatureSet, FeatureSetIter}; pub use file_reader::{PerfFileReader, PerfRecordIter}; pub use perf_file::PerfFile; diff --git a/src/perf_file.rs b/src/perf_file.rs index fd4625b..0f168d5 100644 --- a/src/perf_file.rs +++ b/src/perf_file.rs @@ -10,7 +10,7 @@ use super::dso_info::DsoInfo; use super::dso_key::DsoKey; use super::error::Error; use super::feature_sections::{ - AttributeDescription, ClockData, NrCpus, PmuMappings, SampleTimeRange, + AttributeDescription, ClockData, CompressionInfo, NrCpus, PmuMappings, SampleTimeRange, }; use super::features::{Feature, FeatureSet}; use super::simpleperf; @@ -213,6 +213,18 @@ impl PerfFile { .transpose() } + /// Information about compression used in the perf.data file + pub fn compression_info(&self) -> Result, Error> { + self.feature_section_data(Feature::COMPRESSED) + .map(|section| { + Ok(match self.endian { + Endianness::LittleEndian => CompressionInfo::parse::<_, LittleEndian>(section), + Endianness::BigEndian => CompressionInfo::parse::<_, BigEndian>(section), + }?) + }) + .transpose() + } + /// The meta info map, if this is a Simpleperf profile. pub fn simpleperf_meta_info(&self) -> Result>, Error> { match self.feature_section_data(Feature::SIMPLEPERF_META_INFO) { diff --git a/src/record.rs b/src/record.rs index 5c3a587..ed4e025 100644 --- a/src/record.rs +++ b/src/record.rs @@ -55,6 +55,7 @@ impl UserRecordType { pub const PERF_TIME_CONV: Self = Self(RecordType(PERF_RECORD_TIME_CONV)); pub const PERF_HEADER_FEATURE: Self = Self(RecordType(PERF_RECORD_HEADER_FEATURE)); pub const PERF_COMPRESSED: Self = Self(RecordType(PERF_RECORD_COMPRESSED)); + pub const PERF_COMPRESSED2: Self = Self(RecordType(PERF_RECORD_COMPRESSED2)); pub const SIMPLEPERF_KERNEL_SYMBOL: Self = Self(RecordType(SIMPLE_PERF_RECORD_KERNEL_SYMBOL)); pub const SIMPLEPERF_DSO: Self = Self(RecordType(SIMPLE_PERF_RECORD_DSO)); @@ -107,6 +108,7 @@ impl std::fmt::Debug for UserRecordType { Self::PERF_TIME_CONV => "PERF_TIME_CONV".fmt(f), Self::PERF_HEADER_FEATURE => "PERF_HEADER_FEATURE".fmt(f), Self::PERF_COMPRESSED => "PERF_COMPRESSED".fmt(f), + Self::PERF_COMPRESSED2 => "PERF_COMPRESSED2".fmt(f), Self::SIMPLEPERF_KERNEL_SYMBOL => "SIMPLEPERF_KERNEL_SYMBOL".fmt(f), Self::SIMPLEPERF_DSO => "SIMPLEPERF_DSO".fmt(f), Self::SIMPLEPERF_SYMBOL => "SIMPLEPERF_SYMBOL".fmt(f), diff --git a/tests/compression_e2e.rs b/tests/compression_e2e.rs new file mode 100644 index 0000000..c9f6317 --- /dev/null +++ b/tests/compression_e2e.rs @@ -0,0 +1,380 @@ +use linux_perf_data::{CompressionInfo, PerfFileReader, PerfFileRecord}; +use std::fs::File; +use std::io::BufReader; + +/// Test that compressed files can be parsed successfully +#[test] +fn test_compressed_file_parsing() { + let file = File::open("tests/fixtures/sleep_compressed.data").unwrap(); + let reader = BufReader::new(file); + + let PerfFileReader { + mut perf_file, + mut record_iter, + } = PerfFileReader::parse_file(reader).unwrap(); + + // Should have compression info + let comp_info = perf_file.compression_info().unwrap(); + assert!( + comp_info.is_some(), + "Compressed file should have compression info" + ); + + // Count records - should be able to read them all + let mut count = 0; + while let Some(_record) = record_iter.next_record(&mut perf_file).unwrap() { + count += 1; + } + + assert!(count > 0, "Should have read some records"); +} + +/// Test that uncompressed files return None for compression_info +#[test] +fn test_uncompressed_file_no_compression_info() { + let file = File::open("tests/fixtures/sleep.data").unwrap(); + let reader = BufReader::new(file); + + let PerfFileReader { + mut perf_file, + mut record_iter, + } = PerfFileReader::parse_file(reader).unwrap(); + + // Should NOT have compression info + let comp_info = perf_file.compression_info().unwrap(); + assert!( + comp_info.is_none(), + "Uncompressed file should not have compression info" + ); + + // But should still be able to read records + let mut count = 0; + while let Some(_record) = record_iter.next_record(&mut perf_file).unwrap() { + count += 1; + } + + assert!(count > 0, "Should have read some records"); +} + +/// Test compression metadata is correct +#[test] +fn test_compression_metadata() { + let file = File::open("tests/fixtures/sleep_compressed.data").unwrap(); + let reader = BufReader::new(file); + + let PerfFileReader { + perf_file, + record_iter: _, + } = PerfFileReader::parse_file(reader).unwrap(); + + let comp_info = perf_file + .compression_info() + .unwrap() + .expect("Compressed file should have compression info"); + + // Should be using Zstd compression + assert_eq!( + comp_info.type_, + CompressionInfo::ZSTD_TYPE, + "Should be using Zstd compression" + ); + + // Compression level should be in valid Zstd range (1-22, or 0 for default) + assert!( + comp_info.level <= 22, + "Compression level should be <= 22, got {}", + comp_info.level + ); + + // Ratio and mmap_len should be non-zero + assert!(comp_info.ratio > 0, "Compression ratio should be non-zero"); + assert!(comp_info.mmap_len > 0, "mmap_len should be non-zero"); +} + +/// Test that compressed and uncompressed files contain equivalent data +#[test] +fn test_compressed_uncompressed_equivalence() { + // Read compressed file + let file = File::open("tests/fixtures/sleep_compressed.data").unwrap(); + let reader = BufReader::new(file); + let PerfFileReader { + mut perf_file, + mut record_iter, + } = PerfFileReader::parse_file(reader).unwrap(); + + // Collect record type debug strings from compressed file + let mut compressed_records = Vec::new(); + while let Some(record) = record_iter.next_record(&mut perf_file).unwrap() { + let record_type_str = match &record { + PerfFileRecord::EventRecord { record, .. } => format!("{:?}", record.record_type), + PerfFileRecord::UserRecord(record) => format!("{:?}", record.record_type), + }; + compressed_records.push(record_type_str); + } + + // Read uncompressed file + let file = File::open("tests/fixtures/sleep.data").unwrap(); + let reader = BufReader::new(file); + let PerfFileReader { + mut perf_file, + mut record_iter, + } = PerfFileReader::parse_file(reader).unwrap(); + + // Collect record type debug strings from uncompressed file + let mut uncompressed_records = Vec::new(); + while let Some(record) = record_iter.next_record(&mut perf_file).unwrap() { + let record_type_str = match &record { + PerfFileRecord::EventRecord { record, .. } => format!("{:?}", record.record_type), + PerfFileRecord::UserRecord(record) => format!("{:?}", record.record_type), + }; + uncompressed_records.push(record_type_str); + } + + // Both files should have records + assert!( + !compressed_records.is_empty(), + "Compressed file should have records" + ); + assert!( + !uncompressed_records.is_empty(), + "Uncompressed file should have records" + ); + + // Note: The test files were generated from different perf record sessions + // (one with -k monotonic, one without), so exact counts won't match. + // This test verifies both files can be parsed and contain the expected + // types of records (e.g., both have SAMPLE, MMAP, etc.) + let compressed_count = compressed_records.len(); + let uncompressed_count = uncompressed_records.len(); + + println!( + "Compressed file: {} records, Uncompressed file: {} records", + compressed_count, uncompressed_count + ); + + // Both should have a reasonable number of records for a 1-second sleep + assert!( + compressed_count >= 10, + "Compressed file should have at least 10 records, got {}", + compressed_count + ); + assert!( + uncompressed_count >= 10, + "Uncompressed file should have at least 10 records, got {}", + uncompressed_count + ); +} + +/// Test that we can read sample records from compressed data +#[test] +fn test_compressed_sample_records() { + let file = File::open("tests/fixtures/sleep_compressed.data").unwrap(); + let reader = BufReader::new(file); + + let PerfFileReader { + mut perf_file, + mut record_iter, + } = PerfFileReader::parse_file(reader).unwrap(); + + let mut sample_count = 0; + let mut total_count = 0; + + // Record type 9 is PERF_RECORD_SAMPLE + const PERF_RECORD_SAMPLE: u32 = 9; + + while let Some(record) = record_iter.next_record(&mut perf_file).unwrap() { + if let PerfFileRecord::EventRecord { record, .. } = record { + if record.record_type.0 == PERF_RECORD_SAMPLE { + sample_count += 1; + } + } + total_count += 1; + } + + assert!(total_count > 0, "Should have processed some records"); + assert!( + sample_count > 0, + "Should have found at least one SAMPLE record" + ); +} + +/// Test that record types are as expected (no COMPRESSED2 or FINISHED_INIT exposed) +#[test] +fn test_no_compressed_records_in_output() { + use linux_perf_data::UserRecordType; + + let file = File::open("tests/fixtures/sleep_compressed.data").unwrap(); + let reader = BufReader::new(file); + + let PerfFileReader { + mut perf_file, + mut record_iter, + } = PerfFileReader::parse_file(reader).unwrap(); + + while let Some(record) = record_iter.next_record(&mut perf_file).unwrap() { + // Check user records - these internal types should never be exposed + if let PerfFileRecord::UserRecord(record) = &record { + assert_ne!( + record.record_type, + UserRecordType::PERF_COMPRESSED2, + "COMPRESSED2 records should be transparent and not exposed" + ); + } + } +} + +/// Test pipe mode with zstd compression +#[test] +fn test_pipe_mode_with_zstd_compression() { + use linux_perf_data::UserRecordType; + + // Read pipe mode compressed file + let file = File::open("tests/fixtures/sleep_compressed.pipe.data").unwrap(); + let reader = BufReader::new(file); + + // Use parse_pipe instead of parse_file + let PerfFileReader { + mut perf_file, + mut record_iter, + } = PerfFileReader::parse_pipe(reader).unwrap(); + + // Should have compression info even in pipe mode + let comp_info = perf_file.compression_info().unwrap(); + assert!( + comp_info.is_some(), + "Pipe mode should support compression info" + ); + + if let Some(info) = comp_info { + assert_eq!( + info.type_, + CompressionInfo::ZSTD_TYPE, + "Should be using Zstd compression" + ); + assert!(info.level <= 22, "Compression level should be valid"); + } + + // Should be able to read all records + let mut total_count = 0; + let mut sample_count = 0; + let mut record_type_counts = std::collections::HashMap::new(); + + // Record type 9 is PERF_RECORD_SAMPLE + const PERF_RECORD_SAMPLE: u32 = 9; + + while let Some(record) = record_iter.next_record(&mut perf_file).unwrap() { + // Verify internal compressed record types are not exposed + if let PerfFileRecord::UserRecord(user_record) = &record { + assert_ne!( + user_record.record_type, + UserRecordType::PERF_COMPRESSED2, + "COMPRESSED2 records should be transparent" + ); + *record_type_counts + .entry(format!("User:{:?}", user_record.record_type)) + .or_insert(0) += 1; + } + + // Count samples + if let PerfFileRecord::EventRecord { record, .. } = &record { + *record_type_counts + .entry(format!("{:?}", record.record_type)) + .or_insert(0) += 1; + if record.record_type.0 == PERF_RECORD_SAMPLE { + sample_count += 1; + } + } + + total_count += 1; + } + + println!( + "Pipe mode zstd: {} total records, {} samples", + total_count, sample_count + ); + println!("Record type counts: {:?}", record_type_counts); + + // Verify we parsed a substantial number of records (streaming decompression working) + assert!( + total_count >= 100, + "Should have read at least 100 records from pipe mode, got {}", + total_count + ); + assert!( + sample_count > 0, + "Should have found sample records in pipe mode" + ); +} + +/// Test that records spanning compressed chunk boundaries are handled correctly. +/// +/// This test uses a fixture where perf records span across COMPRESSED record +/// boundaries, requiring the decompressor to buffer partial records. +#[cfg(feature = "zstd")] +#[test] +fn test_records_spanning_compressed_boundaries() { + let file = File::open("tests/fixtures/fibo_compressed.pipe.data").unwrap(); + let reader = BufReader::new(file); + + let PerfFileReader { + mut perf_file, + mut record_iter, + } = PerfFileReader::parse_pipe(reader).unwrap(); + + const PERF_RECORD_SAMPLE: u32 = 9; + + let mut total_count = 0; + let mut sample_count = 0; + + while let Some(record) = record_iter.next_record(&mut perf_file).unwrap() { + if let PerfFileRecord::EventRecord { record, .. } = &record { + if record.record_type.0 == PERF_RECORD_SAMPLE { + sample_count += 1; + } + } + total_count += 1; + } + + // This file has records that span compressed chunk boundaries. + // Without proper partial record handling, we'd get fewer records or errors. + assert!( + total_count > 1000, + "Expected >1000 records (got {}), partial record handling may be broken", + total_count + ); + assert!( + sample_count > 500, + "Expected >500 samples (got {}), partial record handling may be broken", + sample_count + ); +} + +/// Test feature flag: when zstd is disabled, appropriate error occurs +#[cfg(not(feature = "zstd"))] +#[test] +fn test_zstd_feature_disabled_error() { + let file = File::open("tests/fixtures/sleep_compressed.data").unwrap(); + let reader = BufReader::new(file); + + // Should be able to parse the file header + let result = PerfFileReader::parse_file(reader); + + // But reading compressed records should fail + if let Ok(PerfFileReader { + mut perf_file, + mut record_iter, + }) = result + { + let mut found_error = false; + while let Some(result) = record_iter.next_record(&mut perf_file).transpose() { + if result.is_err() { + found_error = true; + break; + } + } + assert!( + found_error, + "Should get an error when reading compressed data without zstd feature" + ); + } +} diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md new file mode 100644 index 0000000..1cdaf77 --- /dev/null +++ b/tests/fixtures/README.md @@ -0,0 +1,29 @@ +# Test Fixtures + +### File Mode (standard perf.data format) + +- `sleep.data` - Uncompressed perf data file (104-byte header) +- `sleep_compressed.data` - Zstd-compressed perf data file (104-byte header) + +### Pipe Mode (streaming perf.data format) + +- `sleep_compressed.pipe.data` - Zstd-compressed pipe mode data (16-byte header) +- `fibo_compressed.pipe.data` - Zstd-compressed pipe mode data with records spanning compressed chunk boundaries + +## Generation + +These files were generated using the following commands: + +```bash +# File mode - Uncompressed +perf record -o sleep.data -k monotonic sleep 1 + +# File mode - Compressed with zstd +perf record -z -o sleep_compressed.data -k monotonic sleep 1 + +# Pipe mode - Compressed with zstd +perf record -z -o - sleep 1 > sleep_compressed.pipe.data +``` + +All files capture the sleep 1 second workload to enable comparison testing. +Except for `fibo_compressed.pipe.data`, which was generated using a custom workload to create records that span compressed chunk boundaries. diff --git a/tests/fixtures/fibo_compressed.pipe.data b/tests/fixtures/fibo_compressed.pipe.data new file mode 100644 index 0000000000000000000000000000000000000000..20d6e9a3c9231e11f7c86b0fc1c875c85d592a43 GIT binary patch literal 108556 zcmd3P2_RJ87ysBr*^6i)OZKrZtt0yup(w>DbMW!9hgdYu5T#u+>BQ<q<~kcqbgV4s zZkaLx)1<L+y{ZOql`E6hg33OY#6Ac*1qD zQ?H|d(Ab-d!%JJqi0b{h*SjrxVpPz%`A}^A1sN8 z!(pAkrl5!+;}yYT@_#Re#&}VRO$P`H#NacXl7B8kq4DSJY2{F8C`_y99}9srNWmmB zj(}D0^Y{K^xxZzAG!zs_cn=J2EzEpHO7d@a(dSWVV0?%;`ivQ*s5H<7oC4OvZw5Eg z6oqK`dtlcpD=8__WXvE6(Qx|*G-gogZ;FCAT+k%<-(*eu5=i4`%OP0l*|^PIN1!pQ zjB!4-R?x78ZD2SAvtZlr|F$j61JsQIPE0Tck0Y&u-~#EH9{;ax>i=JCX_%i_e*z(R zmH$lH__O@~A2xXy(*i0YkiRJ4R8ml$ku68P{bvB$o+#i{lGplqZ8i~*hO)f&Y;OH| z4@g5rURmkqg;_*E8mjWjGchM7)dJE`lUJU;Aux#o#8Y*7W!2wgP5TlGC<&PBbC`KR zI5g$wrXw(5cn%BpncxA>58*i-JP(8CPVjsODm}3#Fw^`0m9E1$W*b7CF&@E1B>?jS zo)2|)q9{4w-?_F+sIE2aVE+XkOMw&QGYU8{#M$>_!0iZ7PAK4X@x!6Lh&~?PWFRq% zV$OCE~uoL=;(m#0Zd!spX>co4htO}cMmL)=;VSXlW~5*PD+2e={MJz>F9U_;sZTg zJ)CCVRWi`g@eUx&PV5MH>;V)Ihj(%z&yvlQ^*R*rybvAYEVUVd0Q)VFju+9-$7Pn! z1l&$pCjsdYNV7EjK_b8*2%IS3B>Dv6=0XYXKS@5WWMGy%>n9rgb^VWr+ zj4!{^nT;{R{Tn>9F-X8M=vO?mF-mxULuWR|E8lPEXaLt{dhi1`pn#KB?z3551J^%k z=y(ujygL9801nVzLIJ1$9Qz+jfCd5~iU7yw-yKlCI|3Yc!2`BqfGF4{)26{Bf{R)J z4#&sAk?k+Xddh9o^=%w)SQKrDueQU1c$geP5-K#0&>18Kl?5IpSEI?gdH zp_Tz{QWS8y_@TWq?z7#kfM}@BrqNMxfB_0P3FsgvV5t-1L7H8+3(}ZPn+0zBd7yw8 zk>Ky-4Xj4}yg0)MNQ2}Rj7NLTngmr=(iB@3tR9x7R`r15Gen@!kGiP+dw`3OF0`;O z2MYlQSdIrcQNT&|A)=iK{&=zn5s!1i0ZXqQGZHdLV>W#0td;%+YMj@pzUrrCm(+@(SK&<&%eS!AY;HaZVzC+d#0?Qlm~IBsLg%-z=P=I z>VXGLV}LKS0RrOq7jl6_;Y9Lx0amnU0R+UMG>iLaZvkmw{8Z<}5Rk^d;0M@V=Bgj6 zv)N1p@WTfRh)~v5oyhLM?vuL@e&)&w(ol7Eof8s}2F0d#0=B-q$xakI7`Qf*0|gZR z7y1FT+nH25sVs=&pXdiV81I9|lAQ1!#7UJ*;{u4|U+{xS4)pUN&wh;iS2(==2~KD% z7ThF-I`4TkheJ3Y_FaSJ4u^0)?9&Fz9S-4q*w+n~I~>CKun!z8cQ}Oe zVc$4d?r;d_!#;Dc+~E+;hkfZ_xx*ox5Bu1`a)(1WACxBp!j3Y(hNSOY2F|n*=i#Od zrR}|Ne%?5|q8H%7LJB639G%Fxpud>@5Z#aox=IY?6uRXqu!tztA@LqANP-X6AJ~0H zPIk~k0+slo;e77^53C1T(aT3MXsU&FsQ!qF`Vne(DfQQbtDFqMD)l0+wG-|y+M>44X89qO_ z*@FbM5$7|l`f2rW6ZG89vwCir|NbNwq`MD^j0|$ecqkAtQwjj$L(ByK%jbjx?T2xv z$R6VtOeFgtF@6|T6^sJKTA6|)&~s11L$sQUKEQmT(@R1SL18+&2_l-LC*@Q;Mev9Q z;hv_R0Q%|af>ojXq4Glm3F3jMO#VBGA0e9|en1A?&l{#$KgZNHfMRTf`PCcF6aNfW| z4Q4tDItd5D7JB}Adcf0^hc}Ll1WstF2pQx=MgvnE(lmff(u+1Kj+j~F@E``L(SQL~ zu%?X$)(e{cwA>Ejizxk0##DUy&A6`M9{`VMXczjYn@BdVi zo&oI?K%z$ZLzM<-TTE3yjehc+hU$+2kxd-ate>u(l8FQ)a85%jU^&p>C`yBH%&xzn z*H3R(AdUC%_L>^XWO<}RIEp5C0mf#?4;Q=-#tVt}@gYtDVN(5wEU5m0S@qknJP24M zmH--hQ&c~B+z`!!>UW;i^Z)Fxz){r+bUB{_z@+CNpw>U@amy8`6$$>K*8F#C8>*jH z4-4k<{4oDLR4_>31m6D%n1TURHc!IgTnynr%}Ne14gwkj*fc&QcO(#V0$gFHLNf_R z8x@E1oN!R=P4JHs5e;Mb>m&%mL1XU`OCWyCu74o;_QJOvUqPyvKA z6-y`&J_%3#obbT%p)6}-&_pD(P6oVLfp-GjV5WvN2~SArzt9&CZ(u_VNg|^CNH_&A zTrdF*cVK^&)^Garv`ceoN07WoJ{Tn6ghP=T5Yo&O{y2S}q`#=kbI}`EP82(oI=(q! z{9IrGO@V`EoG+r{2$@wo0>?K$S1%7do*GaADAH4#NI`s{sXwOdU+@7-KqEbXKN@N< z2PUedi%pV)(iN!wn7NH>9wZEsfb#&)DG)$#CgG^R2H`-_!JX;&4z&{kh=zzE5xu5t zv6FC!UjG;JfX5jU!Nngo$N$<=C*eq=;+Pvh2(DPD3sb-oNidMVEpko5A$kMiNBx}S zLXiknKNJYC3Gl~O`kQ_$0luoH0_u8WG;H~Yk%VA4!oPF zfWZTvW!@e$ST?23H);C0aw@I?I`&Mr{XlvGtRMfh?ExE~4iB*9^SAAmN$t7(>A&O? zWnCHQ{y|1jflLPvjUMwngYe9*--X*VhZ7o1Mr2ctc3LdZF(Bs(9@<(O=?%D=l_B)6ng=O100A6NE{A$ZFqWiEzS6oM#X`m zMLTW2K>CZuXlpu67;qpCdTJ9Xhz~UNBbp#S5W$>@W14b-^@BjhB7u{t7*p0y(|$vm z=h9y2`*}ba2nxXL0_W`pe1GHb4Tbuslk|e;1%#()f)`+Hn*8_$5BNO{*tCc6&{LgA zK{#mmBHA(+9I#wK`yNmX60o@pE+=_tDZ#z}MAg5IL3n8T|28U~H2UH*;>%y{H{in! z(~#ICKGnBEe45+55F96QWOs0npG-9|J=_3e(`_o7auOb58x_wS_yn#q0n73f^A;SB zfH>{8)!!y)lkkYPLwHcL!UO9O-M%8Qr>NjU@NtF3H8q~xiFp8#if3-)5gi_AN&uju zP9Z^jz}J(!u{b{$AN)-G6lliHdMeJeIq{9U9tJvUBZ0=Jrb6Y}6!Qa`b{Fy*lHct3 z1@%YpIvB(#a0L@w4WpzK0R4jHNdKhtDM+tq_`}ly;UdzPn^AuN+vk9CL*J*NA^~1c zq^CNOnxx<5R2-;@GGJ_)IAHyztS`e#1-wAw1ME~zYXF(V7tzkS@CC+0t2e+VhzIOu zF~$6aX8ahS;&7f5Uw*EKxMMLyaM8>Oj7&peCdtY9&0O^Or~OCq;06XW@D)~SbBA7s zo&wV(Iq|%O@Sx_#A9$U{iAcaWxdg$T%^y9(ARIL9+nI`EcI_M17jWnzps5^z_~HcZ z4d8*VaZSM+kOPzWLwq+E{`@L0aASgs0>VYl^@-FZy(ym)4wyf1)28_B-~fXiGfmH! zNqA82=fW2l4<3glA%SiAo0&K$5 z*E*4!#1Ew|h#&P6Wx&`B`(HZwpn)#{OjA5my`^bSJRcxD|FS)SA0!06k^s1T5&&lr zsHsiSrXW70P1rplR9vVz@d=z~k%%4&-adZ7Y@G5TG(6~!2#w`n&NO`CKm-szn(-Xb zJr}jnNx)x5rTR&JI zKj7ssBGJbW?xN}G|C*X4zkz>(i^3;xK@QlL1PoL#ra<+|QUE-QeK2S;@QuDHbPL1^ z;iKsXih3aV&CVX9?FZm4fP(K=zQE~8`lHkf)sKQpK;fL^7KoivSP%{xyC(+50ejld z2*lD7yRN8l|9qs%g_FY+99KHq+j+&sJN(PCk2!6)U({9y!SD?aRJgB zuw7BwHi@FfKoV4C${#9+ye0vmUYLZ(k`=z$K4rkvkQ z0c&!9974raKPOzj>u)GhqT-oCZ)w_(GdIL1rP<1d%- zH1b06LO5pUw-4#-@9n=Cyk!FSd$e{1-(2*Fs=omf3Jlp?*a=S1$0oI7gaA}O58Z2; z)9qJi`dw=OMq}^M*G!*+aL}w5hzdhE=HmYX+r#wzKh(QGd;z_vrfO0Ypp*2cZ7GCr zc7FDM(;wg~nZO5S;CC;m_~^Mck($J}wiOVrwuv%eY?|Zwg!~|${o-c^ZWB<)P6D{X zFg5EY@vUAQ!sk4z`RT9tsGo@XX_k?^;1QjQkDdz?DF_$M{31pg!j%T*OdQkX8*G=+ z_TSWXDk_g)Toa_}Vh|phegGv4;hEjOF2tvw`%8ddz!ai_>Zj?a+vK785&U#Gr^yd+ zz7D)GL2P%$zqt>=nI;ZcE`M1+AOsM=H3T4-K0X>gl^a6% z2Iep?h5IS0g-XRiS>K>zK22hhzBpS#INE3m0nusl3FMEjAK8!chgv^nf194#L~4@0 zglvZDM@^IgW7DjkI^Dxzu);5#Q}g&!27rXUkSpvD_}jRLITw{Nj{caAv`=_&crcI`M`JxIN$>eNCNN< z0qhtu1)fPb5cUv`+0Dmc9MJx-r%&+23u1rg%Oo824s+4hU;0-P&KG=D4fqidDvCdP z%um8WbcArs&OZMIhaVd7{lLO+b^z|J)5lT29l}vGp&MXqn)(6Z0KZBA_F+H>!cYK~ zwr0$a0VfDY4E&`O2+uTefc=;Y-X8~iGMMZ>5&MZ^`uvFTf^dXD^}~N>hr=63M7jWf zri+8d4sga(`2izE-D9vGBLZhG4+H3|eK{#mSAxi#d`~ZExsl1qCy^f~-n14|} z2C&WGnknAhQ`Ap0zP3^8FQ1jY1I$l-fDgz3zX~=Hn~pkAZC!ZEk;9W;38 z>kJr^?N+nMBGB5eOn@qHVb{#+@3E_?y^ z5rGr>{1TN1ejpB-N2iNUl1tH?>i>;gyj`8JI9ETkn-eIo>B)swPZA)0IMczNp`K{a zeBXu+5B&Whdg3&xNqW*o#Zy14@ebtAgq{FU0H-CuYYF_t#lOB*HQDYcKQR|Q0dWAo zTnV1kim5ng9-S@<@qtE9c@pPZKd=~tB$464WLovpjQ8c#`s?Xn&O|ThVcP2;rdN zOGwK9h%dn0dWtp+KKUfRv{CUm{{y}N`ZB>02oF8giPR*%#GHiq5;IW-jLmHPrR`Te zh#E8aaM)x#ssBJuzu=jHK`;pq&#C_vo{0wa7lR(cL(|_nQ}M*WU%rLpHUoLN_<3O6 za7ZYis;&#Y^e0kK{WSe8B6F_%pzG%*m@rZ2?_!hWqLc;Un4P@}j(c=C%yq0cZPvHa z(bJzsV+H_D;!DiG#X|;uTn`J6iGO*KY!Y7vs5si_xi|y;nBWVoUE%~>p2nx&AUrhs z+LjH;X?Fb!JP!ut6#%@5NEHqmL;rLWsY(1$IuF%P1al^i8SrCbZsq3+?t21X3Ic=a z>B&Y)m7+htZw`M~9kLp^5m3I?&)sY|f2!Zzh3-@Oc^~|}?mzv$tT6?LG(~U0DEysg z%4M2ACX_f2l`c1emy)(a^ejF@@hj8@*a&jch3E>t4(8^BR#U;MXsWBKDXXbzsi zk+6WB4=#EC^_K$pD0G!5w0MAcrou{V9ld>ql*_;Q1LLStg>b9_%FyA^z~XSOnyTuW zI87HdbyX!0hijk{&JFzOg5MCNUZTYT)vSn-VD9J|G$t6_-<$uwKP>Q+BghOTU#BY?5gE5 z3mif~%?IUA)g?JBKm1z|z(p9x01xzD*mU({CjAHaJ95+2kAw#3y{GBon2DYW&j1Iz z0rY;%baBjtAK-7{O_v|!Sjdlmx;SRS5AZjOr;DTPWgx{~_+PYJuoInbyH)BBq2QP< zKV~8q#ToE}YCvXlA`WvnaOMb!nXiE~j?)v%tSB1my)L2PPF!HoDq(h=!M%1aNtitI55A;1EYb{R#tOW z(Nb4aR@QLE%|b80&Ck1C0-p}(EE=Pi#zYG8R@9={)1XZa{I;Ac@LdwzA15f(TdgP| zJHnb0S|Zj`uS=IAe;|x984#I_&`;iIAshg*_Q2_0F8?V3{9Q^1bvY;k8~uz3&A}_@ z+=y?8rH27y(isu zyK|uJ>YBw`&yhwh#Qj(kR7g6acAoQpg@yr`A8ey+;^d)#o{A5q)$jLV?VKHsoiN-S z-6-tw#)v^4_-QR!gfb^icuYD*s|a)c2$ys}RbMx)3sF%vOk{T6cAX zRj4tH*Opyz_pVq}R={31PkU+3s8ovyUrU%H+vSG$QaxA4r5+*ZB~q`MQH{Q~SX7%|1RPZuy2)x0}x9Z|=%k$!_q}MOf{}OM9_P&qc{~exut1+=y57Gvgx9 zWan$2m-gO!AfIi|!a9rAn>vBaD7&GR>|qkd96Jyph(ic>yi}uNb8%>ENz6F%rR>R| z;Z33ZI^XY)Uv^d!8rMFyHb>>O#h#6yAEeu=IGk$S&0ONA)1lp(RVFa5-FlX9?Zd8@ zy~K*cK9e<~{yxmWfi+7d_3~v;R4GTyrdq{O|G7v2hbi))(UkSMm(6 z@8Z70*s(YHWEA`N#F}y49U6N)wN75eZC(EcD{q<~Lp;~+kbXKvsg$vGsn%Gd=Oycydl&M}E2QG`u5C%)C-S~FeJvV;KGnL}G*(tg#^3XvxacOC&6lwD z?~z%6;G7_oj=r zXuNym-&U@6^VEU+r=xywY?I%6U~m6!$r}60P_6o?Hsqy8#$x%^8lO+EmG2ng5+*OM zSH-`TFyZhvVbRP;x=U2$t^B;*u_ke!;_6qIMi?>|EgR)Vayc`lEazpzAp z-?x(2dvoo`o^J=Fx!+jg6y9D-`yz=n7{uR___m?UHt!@yM0C~%sd)whIbT_h5*_7( zEeSn{xANy3?Ft1=qt4vsNN$)P(zL@cv5#4^Ortl=S@%V+SoxFK3vNbc%&E)cO17N5 z`}j?@dUoG^{MT*`ua`ZW*Twe;0*ho&zaza+koqIynYoCojrA(JJfcS~Gu&R>81{Wt zK?N^^`0hje*OTt3BKVK;n&3CKSq8edN3gIgUnacMh!Zgszb)+%ah@&q8}|p3)!qeP zSQn@#F^)4zGA(9`2og+5E{%u_J|#5YSYti2Ky6UZimlO!!d1fPz)dILD_!Q=NV0p5 zNqo*B^E_G5N!?Dk3@IFSSn_#C(>7r)qDGQ5QC(2;sCc@TF!PmHGLAeuyUedy?>rhJ z`WhoG`cn92AZObAUDeI=AP#xlKAGX-@+xp;21+F5+ftuVVCb>>Mf7i$7 zF0W`QT^R3wV{f2-{OME{p$`heo1Q+fN3zH-*|{+4!lTWN!vP-0H-9gx&yP8dej(Ac zkwxH1AIG5-*Pip|9r0DOIoK)Y_PBdi&Lt(MVA|BB)<3x!SSuB}S|bpH$M4ml^RgA_AACePi$9&)ck6YK7fU-1RH& zphL@z2L+{@Fu9~v)x$2*1kL|cS8ApnChdq{m-;%J6dq;hw8vX4qjl$U(P69in6qLR zIq+{E9&F2B&uY`I^MTl)rS^XK#_&sS(3 zI{Jf9zR+uP%uP zj$ZrdvG>~j{V!g57yTeyTypuA`kD*3c2_W@zIcVr|8jY0V9VH3<*KoOldpRFWu!-6 zNF{W7?=?&&uB%^tRx|8Bj`<5!%&e`=EjBi4$}|g?Ea2K$Cvz%3 zrS6V;WDM%SJ)Us!8U>3?y;z6)%i}kMnd*CezbBCX>E5?_n_SyRjf>vAWo!8QxUc)& zWB|hPCkRbnkfe&r0&bw-EY&c?`tr&AqT;$dHNL%K*ab@w_ z1qJ1~g{(FA;tOsB6Q5g>OR0`KSXAqykno(OeZ&xiw@3CWXq99M1i;|tH-Srsro0nP?<9v(9j%!%RpO@_U zlz+!10{f((%~1N2YyrL^^|h|qZvADVnY(riEqb!fS1s&=nf%#}SI*!h`HzGdg!2sZ zO1@)D8GBu+ySAEjL*ybdPiUn>?P7ck11D$f>-DVjRm(T*8@Mu1=U{DEz~Hc}W!Gr)r2DcQONQEAZI;&&+sCbEUjq zFfboAwpJ7D^bUWkDpALLDY8pjs+2*(Vab=z`HkFK>+)BGshwGdDmc5S2f1uBr&Mu( zjbvMBMnWz*&Ou&Wk|B~KTfh4*U+KxC>1+=SdYOxPycNxK@6~4QY(wMkti#yvew4v_YJXl? zzzgG;Mx$F~aYw<<{03zk`GWK6tNeewTz$;yf%g+BE#3WoTa}p(=IGSnFRtCAd1$pD zCrP04#|BLF)t&t|CIzY&PA~!=X|i3QfC=VgtGZrvzImv^Q!r$m*jtc>m$|}|XC0~Q z6I8bQweg9H*q%Cryt>uTT1-ZPsdZNjv()~cW%ZsiDriY_Yr~ns% ztqma~FJ9KKC7Z^G8*(Hul(y)#WO=Ky1o|F{*|GcrO7BWEOSR?21e1IH3*Q-~G*@wM z(Vyl=SjJ_IsFj zVHHacn#VHR*K%|5c(Pq(jQ)_qPF3))zj&`w^28-}7OFy}w{{Yk0Z;0A@Grd~w z2zDD@_IlG!#zP#O-xn>uaoJs{w(O&mOV9o;D{ls#@de>Vx?vl`y1UA^7ww5v%#XUU zGtGD?zzO%D^umTq@&Aop{=)fUf6q$0$b-v^l-ry?4rKA)U%uthinCoW&PjFO+C02v z{YgT~@O*L6?^0zR*22QkW$ITFqdQ+TvhEw((!RqvdD+dB+k4Ii3csy-@$|o5!ABof zUeWquzK!M1&bHbHb3}t4u3`#M88vQ-SzR>UJ#Bas!cOaha;Va={->T^b)A zm9iVh9mUNrzU;e{;X;8zuguyrLuj^6&S%`&0-yW3Z?PUT`0liSS3%(3JT3O~{@3=o z4*Hkw>PT1+hTBBsIc}a;pVT0vyohIyR@wuG#QCqRk8g`Ik2tUhlW>=VE8E!lm`Gey z^r6}iwqoh87!JyRVrZB952ha#2|+cm~(1NvVPuV;SK+8}Hw@(?-j?S&8rt84$~ z+OTC_C!Cs!d7QiYyh@JZ-t@>Nc$&0;N`JrAJu=w$^JlZW&bwITDu>>l->SQ&x%sS( zoy&reZx>FUyYnntoagcDvpM`mJI@PCd_2`xy6=?Oz%Ah?2UWg5xy__oqv$9l;g5 zn`!Gm-)FV8Fzo9-3w!xpX)HRH7nZr`JWR}D=JaG&*;gs2b%v{x)xIcoXx|VAt4DCt z{K7Uy?SP&G$PukO!lkhXPvAGPbu8V=HX=TMSr3o5W11n;xq3qBuvszc(y>@p;qIZQ zRqE1qt_1%I^r7Ao^-%4q2Tiia9cZ?6~jgII@}Pfujl48(HR?Chj`a zCg|{A*1~Fg-x^^i4Hu^t%#TcS^7ki_#$S7`9m?c*zVO-(z22JEnAOQKCg~sTyNYFf z+v2%woW+DZjePgEB%&JF9C;Hig=mXMIPN)f&v<*S+;`;;N*}%~>X~{q^J&E|map%U zbc}D79JW7mRAb4M~@X-t%ONBa&cx&;4{N?2cJ@zbTC3g0Mp*l)h zJj{%@Q9MQ=?rLGE$2>{`k%bEoyB5ah9%(&%^)i#DNcM*;a-%BmmblG6vW1xvS1rX7 zOs;qv?GoGcIVau9sw;)vq-!&Bp=|o1RGaGw`KzLTSfdtyV9wX~luaOyYi3w>KRyx= zCpl`U^y&VFRVpil%$6lyGjH5>E0)vMXlXuk0x|NG9G|3gVQu{Oc_H0BM)CWqOZY5} z|I@8yDmf`DSh9>UZ)~ADi)8E3DuIokw;PL-(}S~e(rvmUx6KRj`T~@9zWOaLazJUp z&8iilL2m`yxoX}lld*nJWVv6`)bXC9LakSFAvQMJ+j60?gx8`IaWX%Y8A=XC_O%;w zrrqE}6*lR|KhwOsO`OH(=__&1PAh$V&xWK!rg^J07aos0yGM$(`oxkv(ZXY&+_Iiy zPes|~%SA5AUyv_X%(OtDxxwBT<4^v<^2u_TFV1~$oV94Jh0M9dybf|!S!tOAtA?Uu zSxZ+$ekhHQ%9UxfoR4*T zC9&y0B>2v+xJU?CbWB!c;pzjj+6PwN7uP&{=7wtNiln>R;%ippDs;9YPhZOEb>$Hs z%CfrdT~W`dgsgkoYAd)}K~Z~Pea%hV>-yM5XLq}od_F7W5Z}~&D7f7}bmUbAgIWF9 zCej`Bg?Tc;Kqnx}wx{yN_*>g6X%Vm5Y@H=)WE~9;=K0@J?#{u)_?E=&XhP#OT4m04 z2VMQ-`?RsAI=ed(S2_LTK%u@9Vm~ZQI@2@%bdYQnQ(%2#F zxy7IG>nq(ZTBQqqTZAYbG#&8@Fj^EMy|Pp+Rq>5^c>D9qdF`8hkp{=K?iaRP71a}} zb6j{}>yh$Pw*@{btjQcH7h_fUXx6uDx68;otn8sHOP0hS%ue`8WydO|*o#hxFw}4Glo%(QUyyJa*l%nkM z`E?b>f~f}TneuW6oJvIv-uc=F=ETO?qPrF{Irxw4e=jK1=h&3V5%OBSs^G}?aN&*> z{>sGNjb&dQ-|?MgF1&v!ICjOidqx3SUp?b4-!l>Jz7}V#bZk6x{_UhM$4(wnO*Q<) zXIARiB`MMHLn5cmX54r&qiU~QFo)Z!qyI@Jdfs|uYLq@|yfHLyEZ&dZwb}NKKplBi zoT<{2-B~AMBEQ6+2Gxql|OD`t`pxUuFR5i)%Fo@vitTY*3wVDWpahu zj2}y8lx#J8EwIu5yotD4134?lGQZn+DWmldIrW7mSC3Z7@ND;2u=;R8YA{=pAk^^L z_5HYXXr!`CQifmJ=MNROo9kK!lIFcke`t65?e>cU;%*Fi@Sm zBz|E27(Vf3u=A3jm!k6gz84Sp?Jzg@xj1Aa-*sqovtEgIbhBlIs@lPlBYC0vtYpjE zkK-TB^Z7wuuH9Vl>PLs&3H(?g=dx(>%S3^=Q$ufe{BZTZaX}#Y_(1Qj&uteS+(LzC^W0&!XAZHoxKg7Hv}hf!#-Q(TrR%d4Y-2=4zjn{6Y;kHqF~1+ddkex-9y~ z;OXqZlnWAtCU4K}SHWI%uG2W=dqjr0jU21o_POX$#pW%iF0k$;6TI!hT9f41Lr0V^ zy^J$VR<+0p&qME39lb4?bI3)qYDts(EkE+xAR*yb4SKfY86`nB>(6YlAIsnHj_3Kc zA(x`?v8*yhC0C}UtXG6{JAKa`es_`IMt=7Q>RkQ{-VerUU&{`0tFjukH1an=`nBfDJtO|HqP^@^P{zOl>-*Ew%#rNc*y5XD=hZ#G7u ziRh;*IyFgOd{>X#O39muRb#QN4cP}TYz?Y6XCpe3p2(PTu|DIuSi$Yn>rEYw{!r?>_G)U?kqOdkpW7tqA$rs91O*OYE86&A4lv*OYmF z+C`2_w$za zA7KGi;;S#;{h)2xQ_yu;Oj|L6Pi51pwqVEVo5vA7qe-Dk`}swF90-*Rcw$qSQW93j zcxKl{r%z$qkN&6jG3;o-5bq`Rd-Dr!)-K(-pGb-zWsClB$376+c|LdDQ||OlR;$om z=Z*(|KWpVSVEeT5ROQoshM32v_PcciVmxaq2XE_oc}ldaf8DR{zmZ|^>osNdPwgXZ zXVlf>286$RB;F*|2luol3Xcrz8gMTVHV%>+Q#0!icIFClvpBLVXkAdrmS~AJOnZg1 zhu0{vy}Y>6Fy%aYS5u#pcpdXI#|=xOi})E!!?DzL(p-i(>VD0x`3T)3s=%_-HU4P6 z@Vcl+Whn7_LpOt_u{id{JvGwyONQ&Biq9-waXdo@xXRH~)wk$9a2eTiBhWR$yeASU z>!tQNGPJ0t#yy&w++X9q;KF>3RjS)`jTa~fgw{npeQyHH^%fU|zK!Mt2u5oP*2?Jv zkp(=e5mx{jcUL100QC~d6pDAd8FN)L)bJ_BObY}B*jK6U2nf%P+~#KRaGUGU{8}Ex z%JjOZQ$H3huyu+9BFZ24gon#*Gzap-b})Z_v08P9hCpyG!q?;jiYazpoWxcpK2cwt zB?i(FStoT2(sGUV#U=ii&*pry>$L8!L9}PZ@W+k{w(6^uFStuC>^@!ldX&VVoow&C zOzbPqfx1)asii&ZGlB)rHe9jZqZm9Q<@G?^KGcNc+Om7+4xKePdePTGaZThq1;v=+ zl_^-l72aa5f!C$(tM7~D_nh<_myhCf99yitv&U+)T7O$-Y{9xiCoVoQL-@Q1n{Tfs zHJ+l~m71TEBL3yjKrhebgLg8X&iA8ib5iA0&v}&-N?&(Gh|-e9-4bxx z;dF@5yS;;4%tve@rE-T7{Xx%~^o&C<*T5KA~f7on0W|R9BjOZrrYU-c7sj zr8k%jH(qYE-F45xDBc~9{vjb5A&xIi9PfOuMJt@p_3)}jN3Hg3Dl zRJ54kkr@S5AxGDYsn%_hzy8&n*qHrZ1Q5=XO|E-B#pXP?km}ig&S@u8SkuDyafnW{ zXB-<1Y8VV!j}!#yvkKo*BXALFv#h%i8E@Tc7M{p`Z~Hy@sBrD2zIA3NWVQl}tb=`x5?W|HIA*@tAvPf%xXkWTUd%I%m|f4|zB* zxy|c*3H@E*{_f0&80@X!jYFgJFXxR~^GD`ozhc%T_~{vh~bm~q@&&m4-la3RTJf-7x%x}%h2dGPc#JjW-i9>_vY}E!80X4=Ui}Rb6)3kSL z$+rwiws1v^2s9|Cdw_-YI?S7ohXZQEC>L+PN8q_uH=p& zcj!pdw|3{-+h2$;JmWaH6Vh`U$K3H-Ue=LKHq z9&;pK+=M-k4sCQ)dUxXpPkmI^P_*nmhtt+vqFM&$aI5z3xS`qAT=>Aop8;5K*xn(d zAgEs^PA+@;{W$y9WYVw+{+_gYckg$`!`l|UX1DB^H`HXmsWH7P%J?$wV(R5|yL*pJ zTH+FMK9U1d{gDO{p?$=jgS|8&HuPPIdiBE=eEH#~#S=Op4vNLWKNc zL?%$cPV~V}k*+r~-b-H`@^~Y|kbvGPlKsLsHeQmKUE~T^Ys)_Ng87f$r+$!4YTFs5 zc5DIAUmM)sxk%$qQ_?0G0p>U*e=((s*Tt(;WedEz&Sn;ygcnkbhP|UxvPM+*)L-5_C5Y2hNpf0MyJ>*fDH0DXtCatg~8~RcgA80JIOEYyd z0BoYhvWSYONt^6)D^nM%r(iyZ>sewJi@O`~bTD#$Pd>56T9`ocAbOjTeTenB{H-EO z)c)IHBx>OJCjB0Pdwm$eOK1PatE|zwnDmBl)&m!}k;-k3ZLx8vsGWEGV3Ct4MkmC3 z`J;Ik?2z1DySD84e$SGPMT0QT{`CO^dy}X#k@cH(d=&8S;|h3wyJj=LFI8;;2#AhIC_4Pi-`_l%rGj-&Baf85;8}O1mB= zqSk*g!?zD}!mgtCUO&_2v@m-H@6%a)-rhGY!tNpl7TgN6U(ixFD2B-%Pd?n(^kp%F z&w&SwBPWO{@4f-37{ZKlnciHyE43j`;E_=L5+zo?dBzMcv`*cj*FhLTBGL5)y z6O+Xl^}>#9GFh?suwOI6Y>UsHx}oJ)4O8W_&m7(k7pOeSWikcf6L^!|uXY3uO{QeTE|b`+EG^ZRg#DgX27}u_~XUQMFul zi6dCwTaUc0tJK3b$RRIowOm?>yQUD14N*|szOE5p80uXT-+w%Z$X3I0HAGdaw1PCfDfNsfYuV`Cp@n`*~9O+sV`vc#_)2S2FIoB1+i>W-=6sz+J6a| zXSuDzYrc@f*dTMum-?1SR)TsT|MksTtX|45;~RvSSvYvi567G*q^-Cok0`#lzRUfg zq`;m}%&lMQ+iX~)RU-v3PNgpPIqSSmmguT!Z#IzIvVfChn{BgIKMWJ4p)qg4m74I7 zd-=?UjRS$DR+aBC&-b~#yzcJgW3$R{0mB1!_Zl1R;|Om2af$4v1B+UX7X`X(IO39~ zoW8HCeXqQp!AK?B(xnfEy}FY9j;!g>j^A`mUZ>B`H`X^>$7G>FbLh>|90}81{?54z zTx(YTaA5fqz_{?BT>7FLc)Ket%Isd1?$+T(+9qFF^)t4A^23-s?>)}HkoZGjQ2n^A zDn|WQwLp`Sr1*b3*BcaFRONSbkX@9t;x6FIba)Brj{Aqg7kc|reWWZ$FRuZZ~u-34BRNMLoOYt z4Z>$q9uV7-kimD|YrpK*adHUN&y8MH{qhj*>Me;H;q|WNZJ%D{Gz4X@zP>W&U>~7N zCuZU9gQUB+FJ_$A)snv%@A6?$e?jHfI0C`Rq_w|NR~F%t_u}pu^OA@k2$lq zK5GST@n#SeEZ?{|oM%asRb}}_Z~HZmL=p_0*+WjZYQB>n-%-^5*`er`cZ`+Zf=90g zxyZb=i-OK9t&!k~eb)FSRI-w9(EDhwUX&Ey*V`y2YsL{xxA>82RpG=#64zcmypk&B z{vP{2Kb{_PS}MdeFP2%EbnFNawaqhudG-Z(_EP-Yjl5I9<# zbOiYz>0apJT*iy|#EW%in~VBOGHO;kF&=hO-~OE-a_|QJI8S8cC%t>2IX)h(#Mqv0 ze-n-@WyHbd!CT%$o)R%{t=DokSW}>CGsw*D*;}`y*em7fEBnyxNA|2a8og4Jzg{+K z?ETZ@QHCWdkL2TzM3d2rq`q|)md>k|ajwVsmCj4hui06oB-q1!r2j~d1x9V$C?HyK z{SINjBWE=p4i*;JnjEhxc0czTN_zS@_2YbI)s<@lIW#`pw)r?z!qv3>Q%i3V z!oEN;lc$h>altoavs;_CebQ*C68URjl33M;8@u~g4?cH&J&!Cz=NE@U$Ux>iLX83O zoY}#1UF%>1=(p(!oS2rugchosB?}KCmhsI4d4A~qFWA3D3n50me+YqyV0K121NS*f zwNme5MmRqfs;mi(@<*{#%RzoI&zY;K{&MnChFg!-6^K(7Zuzx9co_@p7Z;rZ;<#A8dYeBu=}=L~vyr(TU;?smWq zWU=5$a(RW5w=HYKSIboK_qRK~cebBblxpEpT-X{X*B`CGJtllIz9IFl z&V8P~2S;q0cYDbqt`9GGRexlcK(_JDvSZjJ--D7tM<1UgUtQntHFn4k3qO z-#qgsFJgMdf&z}fP3BLdmJHu0&3}SjK+G<9g1sVLd1><{KFvy|r~aPDq9UAz#twI? zzY6<;df1_^lPC4)1v=+TKa-aj>nG9c>roHyC&+Yi@42OWBR6xXDmJNz=K!A*^2UC2hE zbe4nPg@wu(E(;$LYu&7>r}8|+&q>g8{VM(=?i-ln);L$FGx{w&pluYbGVc?E(gsw- z-tr)WBJNdJ-m8m7EkyJkxxP@>pNk_T?EP9L=8$zy9#k?~NQZ0z`haId#>;0fyw-l< z+xf()fwuCA4T&}YIM`@4w z4Xi8pW1=#RMflQC?kK)c0bONw-VPpKo)z;K>s9DB)YLMaR9qCMna?fQ>S$x2_iDSp zaaLtiho02?wOtVg`||D(4{tr|@`>2cs*l#|?A^SGvqI$9!NWa;+s_@9uyr>U7L5Ba)q z)jdsh!{>b0j_{WC6uUpXcF1bpTeqSs6F|*pv)u>7^?ADcgdWMP*}10B=143 zr;PhbdD0q5+4}1UwPpB>f=qFiI#p_Z}zQ$E`rAY^K zoPWG(!n3u4?$?QRHc=izQTOx~eGd<<6lVJ_bzg`rTqUo)tJ;?R!OfS-d&7m*?k~!mhHMavSZn?Dh2JuCl0y{W^ork zDVd+qw}!{7x9#%M9rJF)+99m(h9KVDHIRO|U={1g*a7aXtBBUIZJ!PVB+bjcUjulk zZY#QQb!*n$>hR* z)cvwE-|lrj&wVs#ZRDVrN!X+-d(5RfQ!Zje->YW6vYXnOYuu|Js;z&@Ea-R*>l*#F zi12*%!2^R~_HN0Wo?m?OqO6dQDW3nGj_tX2zv~*r9&yE8;D+pIgF)$S@QE z*RiaN{Ves|4BTuId7sX2Olo+KoBzet*v%kAbX~-&)vCfQOx78y84Y*uNUW;#Nqr*ndfx*BOO{~3V}Tsr^rbGU z?mgfpXSqt0!BAyGTxJ2Qi){$Q#a${LP5c)QN;-?NF*h0MGU=4>Kpa-Cz+JR!=)&_X zlDk^hqjQN=eO8_EVZ~EoT9i8HGITUYU8bf~B9A4rcqNbfh+?gt+^3u87PX=?1B9g* z4BAE&ctg*yGcr~$e9Oe_)(!YJ!v58w)aUL2#sRO4DlcFhsIP3B7zYHXUi*5Ka`5{D zN*ux*(2dtE5y@NU3+SO-tk~T4=4>xqevY{E%fYQ$5h7pPPFp2NmGpecR|yNX4G#NS z9=9!KNYGKgq&QX7G2}2qXwjt`8?T=3h&XeMR2i4bxST z+{hFjIXb#Jp76t`@Pu^k(9rH~SN+c~Z#v@B`j@a8JpNAxHP$g!w>rJI*wE(r;9gSi z+t3X;KVCVA1U?=)dM#yv9?9xiwX90DA6OBe<7w>p_<2^nv79(3k(JK=RtAU1%WkQ1 zU(voJlf`%?#Wcs|>3D5c_O*|ZjIwv@8Wlol*;y4bvsK*7HAB}(MgvJ2LUxgB zgoctzMu{eAXlna^4*GuoeShcmO84w@KF@kT@8>z^wAgOo>$VQyZL9~!2+Nu;NXz^v z(jVG^g^NF^){`Y*z!TI8L zw@52+^fL9>=T!f(qlvNLoEzTLW7J=dm%gx%S>|>iQt1xZz*~~I_bVaW@V<@e{Et+{ z&iVGjd%pbwZn~wGwfq;Z4*Yp4*6!FkxQXiaC#@gv0`@rd@K;!2gx`<$7X2S}&|6ZJ z#g8Yh8dqG&1`m622f9`XRE4X)nm2fOLurq%t=zf(h+N9MuooDe2X%AfPg=<08Gm}$ zynFAOe1E-dVCvws|I4MM zBLx-tJhSaB?pD|1Zq0|r?~&cOdDr;-yvwrR9rLg^o9TI?_4M!-FNrC)P^9e7Ndy!Zb0ulAP68J{Es+*_oqN?)Ac z{&aseb)zqNJ#4ook zTH+7>oR2U0v;C)xYP>A@Mf#uK$IK5!nLk%mYt7ZGvvw5KjC}<9DE}J>yJt_q*R;(x zYjZm#j?T}^GCVVH&bJpmau#suHXPU?EZ-CV=!sF^mv!%g^=5)h&J5Ud!-jy?+eL2!|p1a@Vc_=NPDK>{Jhyy1*Xf>?T*3${a3S9iu=zt+%XF} zsV*y8rLU@;-~T6A{diIT@A)sDlP@jKE>|w!nsg>SmcYpW01vHmcuhM0JHCADy+cOa z`IpAiFYzlZ72aKwb-74&_iJ#SkH=m9FmL)Y{^pCw(FjjA4*zM;JD)(9Gn4d`>5l+Nn4={o|hfDpQ-9NoX zadq@2k7ItLiQ3(pdcv(t?^)lybF58mR{=+0b^|plb>tHxQM~dJ=$qD-*UeG%BV))6 z;yzpBK8rzqK@UyG^0f}#%02}Qj6gPN*-4zGNse$bR{phpeI&Vn*d_US$B2e~|DJ&$ zfikU3Ts!8-NYkkJMl(qHs->p_X#6S@ZCrIWe-GB~D5PFRciMt%Ig#N+4R_eACe ztlO75m!w}G%?Veg-}tgO%s91bLj}{AO|nrN8zDTJKXB_HC)~)W&{ya&Tif&J5pmwZuYd2$7`Z<7kdym!)yJz>Z@qGjBe^|%+h)B!e%sEU z!|jne!tWwyBYPU&G}S+r{uST$;^@wgcJ27DaXYKm&tAAtUA6xpZ|jS3p^`Z5fAnKat)rp$!>JXwCzVF+IVLZ`J9zqNQCJOW#oQ%2;)YuKuy@#PY zW*xDNOJdhLJ@ym|&0ZBde`oTb*4^tOneF4>w39F68Uu`9Fw|gpgF> z6=?Wm=U~aK#65b4*=N$vLst&s_ngjH_GPb`P(vqu>s?mi{WF9^SOdgE!R!u7^KA zh8XF57kv{_6!mAB(Y@X+>&y!Rm*)mgW`(vuCw}e~9RjgaXDmYR=&LW4=TcWU=yb}r zaCbWB%_Al~Us>tz4Gy(uo+z8I=?=A}4(ctF-Xd`2MtaDvW%gGO>r`g$k_1&Gg z4T5IbeS2w1!ncPMPY>NuJY5krk z5P@;X&9_(n=eTya_(c=Oli^KtMvKaO2UU!)s0f#?9@Z}(4?*iOJY3#54@X-aNDYFT zKSRihtuIgS1RM!*0SGghl>?OZHzn-xX5|*U zeKD`R56j1(V0QSn$9}zV?ja%Zp#G!e9WS-K?6uD0GtMSI^emW@89kHk7C(k5bC&p3 z;IKtp!tUJnj;W|+`@Xr#+{iwN7g=l3$m$GMGZeTKII7=In0yl>%=VVxy5;QrFOf@;gGKnnJ!uXfmekau_BrKf~XepT9y` zk4=%wK7++!zY??0d@YifPN|cZPH;RfFP(Qu9=s+Yb&oKgI7&@@KT7_ECq%n)B18=)FP-U%t%kVOqdBCvc^rWX5t zF8(&@{f4k`M~orA=teHLkVsDTNQH8{Pe~fa-+ipUx!7RO!OqInSi?&#AGt;n#k)3n zC?FiDs9jJ>$HSaR`}h7B@X959-AaPY3AfH)4Xpl0U)SdwIAde)U961&{|LJjvE*Hf z2E<*85BPQ|PQ2wRt=p%E-kvKQVoW7`OVv^!;{AxX42A=%Ve<(=`Mjx#K)~evnV&bu zc`s9LQT;%fhamf$yk?`-LEJhcEH>7@61wSA4By2I-pw$#kpW=BP^y^AM#$boewK*^ zhuuTU@X&ZMH0$jF<6Km{*!M}>aW!ugdE}H4NfW13UBC$1Vg=R8YMlOm@!V4_Jnh8RC~L>R znV;Xyv-~q|HgO{Hmpu*AWCz9K`oTluf0fOpvV?X{wF@pyHq{X{bEXP1XiS*ILxgdm zc?zAW@(J>a=BYS~65M)>KxZ^TE_PlL2i{7RVOy~BED{w-R9EYZ)tx0e;V|V(3IG5k+tFccS6s33A@p!;uZDU_oiiuCHVe4mXXCU{#^AkgOY}_ zp9GBm28$2aDvfrLXX^JyS6lAMIK^p0-Eu5f7(@gU2yms|Mg~ZvSnSSo7;wAz*mjBA35BmG!oC`eAXWXq4MUR%L zfSXSlWzQpFjI7Yjr~ZF#JoWGCkdhti0IyRCpy42BQ(uQvz^YO3r}=!Z4+LvfIx@5a z%p?y<1~9lpKb`-Kp&G)%3yJqX@6ksM)~mg{@8TEL3c|bNv^oDQZeweQ7X9^cMZ8Az zZ7Z_qDxM_%6>F#5iw0UEf@=(wt)INE!AVtNX82Td9zmyz5+u%I;gIqcmGyTSYVB&* zNuT)XX)45j?`I0Si{2!d!tqcZuA__)+6*Vjee(a+y2Ur?1Xidy<>R@N^(h}u9*A2Z z#Ee_9a6=LY!H>7`;Ln7P_UIZ#dYtgOfJYnD82`BwZ3M~?Fu=@$=rwS|YjJXQCLk003t{*J`ISBw85I;$4DBIptG zy;@K#_#H%aPdFpqO&5Tb)eo+cfdf6D;eeg+s-Q0IE2@F$qM&r*rQF$k-_^NrWi@8t zp=ft2{@O7TGWEpd&O<#2O=hqz!||cOuXg0%A@w&t#Tr-F#;<<5X{UFK7&vN znI1Ic1ae9_6(azfn676pGZ8>KOf!hPp2KbHXs#^D7*h#6h3H!UeZPkAjky++F@p$M zGCT1QftJ&)pdjI0U5Ed!dSS0*aO7O;`AqHbxi9BQm(L|@+VQOUU>x}8lht+StMK;- z*X)f1oh|8J43c5(P}8w9A)h(p@;^&r(_`p->NI$?3~U!LYjLf!naBnwE22@|Za)PW8M9f-WQ`~!IV06rs;fIuHv z88*52JBgMij$gI0=B_4}DxXGULCsV7IT0Gi9%6bMhXAzkuYONJH2v)CaDhuX+P*cx0*4B-!EZtGlj zT`Cn8eM%C&_p^R(hj**6^gO@D=Hzuc8LwK+Ez{=b17u-cq(&!KAA5tjpDgPwIq^X& zYZCum&AYc5c8A2T#3P?OO2(xgY|L15&vHh&fZA*zH+P@R3~Yl9#9tp&OcrAdj-IZy zK(6Rcp3;)NWNE^{bj@OHj{@z#*HL7mm}aNAF`KOVAdD)xa7X{%X9;*w!s4_V2PtAm z;5SC{f`4-ff84@=Z^H_CoShe$m+XDU(iz4ELkbW!<8tC79P{|@YO#$}+LOyMcDGnu z$52{LkPM_Y(_yD^L5zSwH1%;zN`4XSQJ|V7_Um$tqNV|rFwtQe+6s-rX)L*t1RSYs zE#&nAO7~=j<;i>_6(#P`c`@lo9$&nkf3GtdM&J8_PB=bDk<^hg;ri>CMxzdFf0v** z!HeR@djkYT2+M;{<=@I9C?wpeDn=KVn0v^3B^ZBg8I(T~jcy7KP`2})d@vsTD1v%1 zP##GD_V#j-mWP8wpaJohd!gWk4bVlwFuWP6&BHO^JoT2bq33G|6&;~iri?q*2daT; zF`H>M(BCi?Svf%8M!1U??#svkC<46JLsK(kt2~lHJB3T4Cv~S?vvCx}9>wx`;5RAL zm!C@3Dylk&(>NKv+J9vIbp7SbTGf|_L8J8ODV|S~r-aYiCx^}`uMy(6+~u?1%+7Oy z(RtV)IIlvxw}?dA2z}UObpr~Kbr*(iUwEwkx>Wi}tLdkLM~Wdf=e+w`T`D3DrSmuw zF8N9c_dHtDM8~(X#^b+YR4m?_-nevGsUUq!A!7|kan&wUo0UGsdEB0FYdXzu)D4Xz zWZawQC~o>+d+3827M4O#9uFkU6Y?SfTUv7HF@%T4#AKE6NK%OHN&-=yk3)f|5JcN* zB4&RW1LByp3*1{^R%c5U!4cFC}qnLdBcvxpM^? zsh0iXG?$%Rf#g`Zv`HRLz4=*32t%GW^rLHP+U<6_w0C%e@~z=0?NUwebJxpH+&mXj zt-YhDEBeCY51Pw8hP6%<_T$V9&pqbHDK7Z*fAxuh(OA4q$VULeQ99kiu^wf|vXYc| z@cblRh!29u)6_An;5M3w#(GI9X&JCi1+hEC=xcUcMc;`qk96ZS_S!2G?GEuE&n0}y z;1nzl;=yZx0RZ<8UPDLc6$ZP~#kEFZ*IsqsKHUTijOXcdrQjg~5u9Q_43RLjHFzQ6 z%|9ee=P}YUi0gEDpt6l61|}I0m6Hre24sGU7Y02N1cAzdDCP((88(8Gfg$I*@8LD< z3m8=bzJ~40OO%KB$eXYNKR#_7g0TTpMt!c|L2VoSg&sER+Tv4KB9C{Q{&BQ4d<(OvJF))3)~biS^myv z>l|xq+cI%Eec#K+#lIFOP;k0qE>0=O4+zt04#tnvK6g20R{C&_bGNrLg?$=pk{*@a z?!|cf+Jo8eLm|3YC%JtZC;!rxbK04a|0H%7zqvXth?rJslBzO-g>FYnPk17Zh2dtO zFZ<|mWOP2Ij|;Jw{Q>Qqk3g{ojt= zse%jTsvUQ8S7HXZ9`%%zAc8qFYEX2%d5y@*86oM0Ugy1r`cear^ip$PN-CHsh&>p$ zKmVwtqhnaZ##Cm=;|+5n#vn|`{Dj^2WWT5jCw6Stil1DzTJwkTnxjPv|Y zoCtwEmi*EiUg$p2d5db}DNTBd26nt*R28G?Y`YV}36gmEfF!B398n-0q(K7JCv^R@ z-=!)bo=^2kxJ$b`RtX04piA&bAhqKZn@^b!3-ko+83WUnB#Z8gwEek7OzLUV!3koC zNxXu84-Y|94`tC0!Q%fFdRk@8$9mX=AaBC!AlQHhLRa=Z#+74e=1ove;d+T4r8p65 zhJ&ZG4ENRGTE4f$uf*B0XXn1%v~5YNt1#`<%itBKtk<^K*EPbL5Z}<@PQ33ag~Z1+ z-E0udfw%c>j|}L&JGyzl@Fmp~$39bW2D$EkxWnGzPwh4O!;%|j4mC;K?V9pErgApS z16FJqKc7|}clvkz%N^XZAlZFChDvqlw;%8A-`z;Cds=TJ`HbQgjX%53WzOD=nYqTx zCY8MW)TXKX`c8KkmZ#4BYrk|Dvo!9%2#_$|Ew6`qdcrJQkEhyw+&Oi7ZLEgzN-_^?H@^ynkV!2cP` zZI#^d=dfL(vPI5QeL*ITTf@SnA?ryxlzj<4IN>y(5? zl-rcX6X_jNO+5@me*9%8T*|pSYMk9D_G7wn^7$o^b`ri#wrf*IPj{ZOTJ0tCSOH1( zVj0B~#4EXkKSvKWI}&ysroWnUCOIkSTPoXPt~X!;uIyW`x0f{+z|!CPS*hnK*D!na zqGSGdd{W`)rSZAI2X&Kj$=m%Zh7*OK>R{>=vR1;0xhGG{XRXBIcu-%2dOHY0H5B-5 z5HwN!L^qpiby^Yf>Cw;Su9XzKDgru7M4UwwGb=QI4q;#(oMw_UFN6<;QBNrpC;_w& zcr`G^HAvOG-GPB|Fcc?=1&csNL8QCi{B}IML z-92{P#*E|Li*cx=$)zl6EVP?_YUL!S|Nr!KOg< zypiYiD3vEOuqNU%7^peY_MoVY1`dMo3(^3QXTjhBf`K5qz%m$>2l`bfu(igl<2JI8 zy36eFK`)qi_Sw`*+H4L9adj4ER93Pv28sj`d?njeWYKQ`X#mdw1S3Y@qKE?#Cihfv z;PCGV84Ns;dlJUt2@7Fp$;p6Ngg8!5(i4R_4%g%fDq2CuN;s#qc@3H=txb? zF`O1;AQl-Fh#Z5cnJ*xW3ZmulObW{!(yT%ZGo2Z zY+ZVkaSeE8a18+cEI2UM+DcZ;MroKJl$NTTRzwkGXfh7So6y)MhD972Rzy%(-cisJ zVd1pP%2xwL>pptf=IYGbi9^`e!!Isy{O4XM zMmY7zl)$N8{^BE7d0NDbMAm&JhwBGAO3AK1>J~41SSL|gug1LJ579lGk*#s~Pru-o zy$r7|Gr5-;m-jADEb2aTyfI~`f6b}Ml}<0{FDLr#ekm1eAvH=-vn>O%vDz=Z>{TiU zJ&pb>`>cD+`HE^+d0EUUpHHP($95Dd@w4M5*H(HKeDj}>{p?1+74&?27uO9uRxxn9 ziFviT=ef?&%i7gj3hGL`NG*v6^lhE zn1vO*d?0cOpsI>OYj9;quD|m8DtQeY4{`q@I1^hX23}1K4*NG7WWKNDYDWNaCe?zr ztS}t-OyvXeats>2)WX~%)hDlncsHAoKvJZY`rW{uJkjzcmS|lytaY1gBRB>71d2q% zn@%R)>69~UUo(5r=Vb&H*AezP*07|PZhZSzvJrJ`d9#XnC(Yr;yJtVN1&@b+P7kbF zON+cU^UZvU+C7O?qg|MxB>6lO|75pySRboIne}b9nd@^R6B2F?T&6NAm4A7h1oV@hNjT}GVbIX)X*2uK!S z4{M0b4?#dpP>+SG2lGA#MAU^Xui(XXDz}L2Fm>dJzmJ9Z(0MYFa;V3k3aADa3$j5S znlSWN^pImEbM`sxNm!<05gI1wK`Y@pCE(>#=?QXKWkkW!AafyW25h5g_7cX*B{_~Z zSl0D2AEwKNlAk9X(p8UW4u4vGpy>?@9D|z=%NaOlM^=08d0DxI<04M)4T`{RN9J``u?@EXxM8B zXZ+%Y%t@q3)l0D#?M~`ptqG+V$S~5j4_~T>BUmqFi|AM^>GV>b4z|);y~$UXGt(Tt zamQ?6kUvC5=lPMyYk8>`FUsi?d5E}ZA`v1jfH@8rRWyY~5gii3&&v5kaE4oFM1dQ(${ExTaB0Zpp}wkOeo5#)Xl#!G;%-t0$ zsm$ZZ<1whAd%$8FUFpRsw$m{0R}X6>-YYc#lgX`~IxboPee`j5m}L029Nd3h2LF4q zd3VN-AFj_gugsCv`Eb%`YSly+At6)vxX|n@lZo9_+a|VLNnX(-`?1xEgXB>!T^jVp ziW?*&ds3Jw!)8eN}5lJgOZIac?v%Xd;6Gf@mO2XvX>bX^Sz5fe{G z;6 z<^<*?$(SpdSOt&n>sjtE^!;@=5l>*fv$Pin{$F|YLG`GU08<7J)Q#(idqk?JZCz6X z!{6w83}Gg~JWs{;7^0qnAObDq>F6*6;DWBV+pc9$qeb5Cunkev9;cwQ)c=g6A>eHt z`oa_Iz2n>Lu6<6h_E%3Bj)gbUA4pn|H6Y7P3F+HRxSWf*UJBNB%c3XTt3s?0&xu=? ze9m}g>WcBO-l*njX$#ivrHc!W=w)m&zC|KOW$f9l9ZJpm@!=L$A^QS)-T&TCCFaNy zuFMmLe5ltaIMg~wZVSN}a{ViMV3UhPfCN}9vTD$9l|8}O@Lt*_$E_v@%U~+Hh=vH? zmHJ8)T`5irlE3aPyxI$|e8t^jC+5@pA35Oj5m*?m;qdYSGv@`A-a>kZA~}icDP;tD zol_O~$_=(8g#s8YVB&zEKs60;RHAeQgK1q+ErIh$a^r=p(OeJkR#4JUoQ|ga9BI%C0!?ydLc@q845Ack%0bs5EfL)b&miY z?DF!0yn$f=DHE{c2|{?dS%}$ozsjKgO_!+lCwOOh`osNd^Su_CA_GPAm_q zDSf+k$_&f8!=G_Be%;{f$Gg=(*lxx&G7J>EZu8*3Bx$`+{kY=Fk7eFfT;_qBM!i+7 z=lCS|SjiOAPU!wD^kS@x5FkYoYgv+X{xVekFFQYU4}XLv!Db4Ap#J{i{t=6S(v{x7ai?P09&j=enD{MT8TdQEt~_Y z?zG}L4`T%RC4jg_W+v2Evax)XY?QJgfhe=U`7hCH5R~iAm+APqX*tM77V=zh5;U#( zOQoSs@jrIVJOVu8X)OVQ0?V3{ql6~7ng=QYO@`!$5ar|zF+=8p7Dz`QE;MFh=Iv?C z2I(%`8|0b2gZ7VDx+Z_hOX2%d)wO?=ZS5!NM&i}19^#XtM-;ZvnbRko8Xo^{o%cOo zoK@z6^I&wzN@TClQ~K5@TO+L+@$*^*;|e}O?H*N>f;BA;=FS)o@P2H)^(*63mF%Nl z;?18myxd)RZ!de=z7U(M<`3KIZa6J5VChkVf73vXEEc1#;Dp6|)j=Lx9H?W?ebWnE znG1)VTN|!l5+Zu=f%zq~2bt0ypMR+?*v|j>kyJQpNiOAlkPm5XdVhoByox+g{t&+- zOc-YKc9I=o`9(~QqT5AMD8wVY9*73rCZs=J9t!zpe}(Sj&}?`&Q($oc5RV`dm$>Xn zgOs#7a3p{N;-Vl5j6I6LmLd|MH9`>Qg99HsU9cEHBH4nn4$uDwN?L_sfU`rGhbl|d z_4djU z>bxH#!W6(3qYZI#eQoQ{X>8G>rHk=OdsInmFHFSTOJi>SF&hnj#(>c(jhiGg5ZJGn)r`bh0C`Dx%jOW`tCH@5&U+l zoA~=Hw$H>DJl%0%4ST@$l>&L4oyP3)0AU-MgEt7&KBw{X{y9#ZFYW$@Y0pw#mi~$Q zIi{m|&Vlwn@)(9YmgMo79}?yzZLc$F@~srdU?G3U9t>_r4+e`8y-vVW0>3GU`eKkG zp^g(>|85%xp6KkY^0V(89TP<7gpkPG#;~Thn^#0f;&`ONc7S=ChL$*JRGdi4PQr!3 zcZv{y+3o*m ztG(n33gxAys!&WS>}#jId;U95k7Rf44*ztg`;TAY@tqD%{a1XyWC&P$`kl+Cf|opz zT8|MPbW&4yzg(%9;EAkoy!B9wYeT?ERrfTy7gX%vsVEK-Z=WUCC*e&}o;y5~NhTVM zH1@ZAFx%_7tFw}G+_{ebVzgOEZNlmY(#J0_6~qNM1`sesZPAS$xIM~e$Ai+}69k@a z;lDBH5B>4rtk2P`{rf$Lf8#a>KDZ>``+QJ6ffT5!*D_*>hEXJF^cW6fm^ge4hw$<( zfawr)Q9N-E*1|IMQHULJusRGQgw6{i9>%6lEzdRZC2V++D_p1$p$s>W==7#o*B;TN za;!k~AOX-eA#d_k)``%01?v51-y(TYmI;EGDV5OC(GI+DYN~~Dj}ZWXkmuQCrYAi) zk)(55(?q~ZA&23UkR$%!Bi2OesA)U@!JRw~YLS$!?=GvJ2}}g7re{S8~9@DwV0^=+1tOQT5QDx#f+Gk zAXhTVaE_5P(6u+wmsU`srKuUTnH7f)7?^e(CSGhz>C}51p(Xk6bxmMyF5x_LQ9zl5 zs3-in3_4I?i06*@emqZz?Q^mH_yxLnfzCsVji}+GE5mfnd3=BPC`NBiJ=K&cjLwQ6 zoPAKB65I=$*@oLH!Zn9-#%mh&caf+Qnh- zh^OEdQkVrY1!X1|Ge*EJN+E7yfze1(+cZBGgCx1x!ng&d1_T@=WB6m*;{tvs*b%nK zpzQchK7-=P|1Ws66e>ewuJZ#&l$x5Vc-Dp>$Y5BcN&%_`H)+>N+zK2Kv&aG?Kr%1) zy@6%mLOUW6yZ5I(%DldNOeDlmzX$hydp#%I=_59^Ng-#Gr-M(zr>$PShBW03T~TY- z63V|KDtqf!#7+KosWvPl2~Tf8j^A1%yJO}>yPHkg$O>D93Km22^;_lIWc7gchgWI& z3K~kqPg@-3*Bo$+~3dq>cbIk?Nu{~wz<&P-I#X!(P?7%!8pIexQ)`y58Wa@)Fz0<$2dG%7opmG=v)R~yy!z0qffXm2|Yje zUQ28XIVNPNxA}Sg&5ABsTsAfe>w9+O!m7g92k+KNoA$mOFpjJ^+|kl{TzM=`cIAtl zJl?q^Jf1ZmCU$PRd`0u_dNan#cYI+IL^VG^!hq>tJ!fJAu!{z0EeR=#I`ib-X|;PV z3ra`3Q@0ZDCvPPJo`DaoP7#Aq?uuz=kyw#N31z4yM4lim% ze-oX*>|_YyxF=7mEe0b@PjWmd3|9N8qFFr3DtcYsZnASH55&XpvgHSr0VnkMD8moz zbH=W33rO&hT#(=|^7{)mw!ld`=7)N;^;wfaMHCPKDD9#Fu9fqb@ajY_?7yCi6<=vG zQo;WLufVcEa<=9Io*ls562ydtp{&PU3$?bxbqkXeISg}lxeE29R^RJ!8P~!+S4_Tb z-DqGssN`p%DwW)GL-})c6XEsCJv_dKWAsO?ybqWt-p`(l2cNPL>2$1>ELC#bIR7>R zZg$s(vfXOWUyBnTU&P0bZ7pTdR>o+bd~rasL?Ziax!cUOH5g{0$>IGB!M!pv!h*%A zLjzx~{E&CWPF|1LS`dDiXWDZGV!n%gYTa%^^2$NSjPt+0URCuw`jHV8gIGLq*9`SV zJYqz)?hQV4XX10J^QN_S@#4?8j+*zg*n!;k?vj{*wRPL)J<7%R@4JRiklUO%K7BI% z)aV2M``12~vY2wqADfk~6W*~^Mq?at7Y;JvOg~OR4oKc5#MrCX8$M)2=UAzHi{msZ z@D1I!zsEF8bj{-i504()JYQN~SaSK)5=V=`N97bte%2Ky_Xq6u z;>JT&&~@A!_?mpB*a;faSq{$lqM_{US7i?6)otOU*Xo}ibf48}+#OWSYO#C^H{Guf zy?A-k2Sx$++?u2C)xC^wm)yf^3=o}AT?2Mg{#!MCrl)}gc|tWJNK$6(&*1f9eTV(x z%YR5cy!Ya){9MG^>lV>EBmT7>VZ$t~%qvF4CSNg6etz^-RDmy@f7-`Bp~K|-toy%q zSb{yYWQXQbkfKO1r)rVF<&H1Ynbk zX&^wjORFIy_o(zeNpUhLYC@o{1)<;9if==*?FHwkC1~PGSYk)O9RXw|1*2``ZxM}le=OD`DIL%A92^<=4d57F zI<2KA6$m3qsKGC?cYjM~|3MTiR;9_^CJ@LJSZE;gL+f9LA#^m5m1?pfIR=9wBn;cz zE1H4|Se<1IbLP7pC#CkxcC36aYt>#cPt~cjtploeR)sPKoUiWs&NpoFZM){!vzpOS zoAV#?h3a^p6o<*HliBwBGv*n2&*Tb*t{ZfUg|5>r3TdcvV|&8MJQLhczHhe~!qFw4 zO*7clxR{Juy>pw3)!J{8udu2Uh0?WM^%OLBXQ*S9-;EX7YSDaTBqe^`{>VaHLLJ^l z8o(y;7aiaBuGaO+Cv6v+tmIDIU@$UeSnMl7RJ^;S!GpNGj*gq6Z;7r^k4@QcV4nkm zrdMOBaixx$5%@aDH)w7;Aq;}>St7` zSh)xu>>tJy(MYT7^usRJ8F8l_ds%uNnMzZ9n#(#%uZrEGNYGBz^Y5p!`wK4$Hs`nD zq+VTrWf0y|WpZY$wm~M7XSHkrZu04=+`{uzc4YP-v`nx21flv~y5_ubKXVr->nFCG52N7?^wMwe&CdAJK`!FUq+dKe!gT z{`M{!V!OBV$);Xjwzfx;?ZlqkC+BoF^x4FP7fqQ#E0Yp-M9e-ulltswu38m$@U(-T zMLORHe?h6`gV(&9@u9y59*cE06S*{e-V`X5{a-t+flHS1h&N>DGdatw@JSJiTPi>v zy?xgObZmYGefAiPjv9o)U}>L0TCEur>&^LPFJYUwjhh5TN!T`LqL29W??jhC&>pi* zdxxQ@>pE@=?Vjf#95lUoan&r`w-^1jHpyFe3;Lr3JLa*l6%FUNI}kjtj!w9Jo7&^S zB?W{_*e-PCf+ZIvPsug>)d6H>qCFzBN5rPv8yFBOYgv$&EJG;u-_&4GWW_-^0GQ5=$prKkm@r*IK)5GS^AGTimzRn zT5g9%?j>RHL9o-i@GW))GOC!Zjy+HhSi(%)Mj_;hpycuM$Ko(K=Q$HwIDry&3NOe5fFE20A`AVDqDZ8^ z@a=X2rXUg&@&N<_es!U;hNi8Oi0j~*$AR;Fm?>94RX}_k&=s^)83pw2S=To;0~m6R zGa!K>zy|?Hw?rI+Mgwr1g~IBCoS)we2t)!Ah5+dRgTgE2BoT3Jh}&Ql&L+)8{4U%=yj9D4wT5mF2AR;zZd z-DOo1Z7oE>RXd(=?l^<3PD|X|b++=Lp4ur%`=&PsbEdYPiafIJCbXKS>{>6C`tn5` z!#Ju(>b1Hl&6rAlmK&e`S<8!cXY!e0U!my!JeFO3b;5i0NcVxkU2}FP+%H5wAqcN$ zdcMdJkU1*z@yIOFE;x|3VtwV>M`RXj*1m5U>Ejx{0fD&(?oT%7?-S!ap?&bxYUH|~ zq*kL5*2}fTDHb8l=DtrF7z_1VIpo~BsV7)x{zlHd>Rq%?gLblK)*r6<(VwBNVp+~o znAfEFKYu;vF-6Vpm_Jb8`$MJUGxAtVM20A^GS!>a&1vr4bj*bDYVfzUP@=yiZ*#SY>4 zuvm;OS{Ow0ZZts-l#s}Ea4(|o;aPzFOHB$31*%2OM>x}m&iW>8#$&BX*Ts`A%AX!v#<$vl~2CVQig8F)CeA#K6Hljcm?;qkvaGL+r#i9&XE+?{w^)ebh`&fdA3+S z4mLF4HKr+LiFAzHxy%VNT-@|D;C;`>Ys2oT;9|{8zbWrpL(F&I7p0D4U1A>V($$p& zpKwqUe)FA4sjbqjv))k%82wwHwJHzoE_5M#1$UnTPx9J1;V$O!jOZt{CmfbI$e>-c zyWM2EuG_Cc=Q#b^MG@N31A2YG{Kq8QM=(CFd-L_MnAk}uArfB2WNDthYOHXMTk)_? zqv2_~gweidy|||PW`?_6E9_l`uHFu3Vo!x6#jcV5A*bEE{ca}RD>G!DQRmdt2`ij9{I-*EykrU9!*-zJ_D)XmZNzHVBYGqA>V-PFq!Ys-u0W5SImmiwPwzG|41 zMUH~!pXm$aQIKEz4T8N-eM;I*n>*cvsg@ybacx*`&pjheb1l?+7@tkAR>xw09xWni zGkSY*tf9kW`&(7Lzic#K_v+ij0afPfK##!n{EL9-Upu$J>MhxMo;ai^;&s{GsN0_( z=qkolsGExMvb`nHOWPg~nKM#O*t@$Af+N8?ZYM zw%bC31n6KwGlGSP3^E0@@d=6S7Kxk$eRT9l3~Dg^XxpsQLaY?Vw}a#c%2m=#O2Ggs z00xyW2s97^yS*<13MNP{%Omdq*nZ(jgw(}~7|30a5W~z<;bbx}(-2!=rq*QCA8exm zKLL7~j<#NMY{qybA*i1?Nkp#}ea=A2RcuEmpCD15VBtyubs@fm#7+3WChJmZ-+vk8 zE1(D~Z|Prz1;m?Ss-T<&c%&eKC?%YjAdevH?rnvb-?R>UL&r ze&Zvo^H}E8=O9WXQu0%^!P}1*dN=f=-0Vio6K(%K2khXLhXl6aJ3M`Q$F;q$-tpt^ zRzG>?K~_miHorRVUbyesq2}e6K5?>^kF_18p4f2D5cJ~pzW!#%2@l$RtRK>t!&gXGQzN|iTW0hM3gRu^2-9ktp-i57H zt1(g5iFizU*fkgXrlZXAh>AjgPelsR``hJTx5p$iIxq=dPF^PEUhwLiRvQW0_Ktam ztuaedg&${jA?R?btyrO}8t=z%o6KI0@#8H&+0>bP5YFDaeOvy~C4tO(t0UPXgA8A4 zg_uQ_G~>wrKP`~~wr|N7`y3{-@G+|N+(JTU%(~nlT$L34^youL$^5A=D>(Ip^Bt#U zFJ`AkBy$>z2+^sc#9x)44bNYjGR%E?;QY`J%4*sM+pT?h)!s6ccD7A$oiq1=mqbg88R^y zrz|UbW#S~zSxF?x!b-7+CPEP5!b>)|Se*oiKq7B?=~RDRpaB6FKv)Q&w8eOj)>@D_ zrwCKLoo6jx}Y!%UY6s5A(?MD z1!_h9%^%{{$l@!PB|jLS_x+R4^*yM}FXbjd6goMFM3DrIY0uNn{@UEJ9F9#*d{87O zyDH(ioM&-zVpaA&Ybw?|JVk%Nh1YY#4Aw;DwZ+WW6^yMukt_eW5}*7YpiFi%YqawZ z#dnVhFB8&f4|?Do4M|y-{=96s&s8GPLG_nX;9a!jI)M zCU=yKbgTy5-yVsx*epeB{B^~^we19H(B#+-Nz;!F{2?!XKG~d<#cDVEQH9tRKtrt? z-hA6cWd~15(1%%@)#qg3l#FbfJr4$CK2wc%hi=HVpTUlkp5E_-CodmyKTJ9+O~_Xs z{Q7D8B++{3Sbn~igTGuK-$v7+31)+0P(1|~6Ou$IXN$e`F>$hOtXysjP(%iiDIl`_du`MX6|& zh@?`V%I`cg>V5mX-#>oyPo3wPF`Va|``qWcuj@MBsme|d=8q1(yYF^s{ZlUYZq1vj zd{rs~e9SG=3UA%aF0H0~zt+1?2CuBE%Usw6^F8CkV0jw1K3ys?Bq()Z?Od+x!knXY z>=^Jy(>&idt2};Ok+wce>GPG)OkuS=R6vu^$xq+(Bytygo|;%k#eK5(vMM+*!bcXp zD(iTpi6Anni2_3HV7?r+Lf;uQPZtwP3L9*-V zMb}tC1ab$J=pXzb6J;q9kb8|)>qr4Yf(tb^U~n@fkftQuk<-2@pl|y&4FtwRP&_cC z?OB|0L6JX^#<8j%Fe%M(*;d)v1~{N^0!TJhZpxv9Xlc!4jHcKa(FK4gJulO6wr>&2 zbJ_nUfi!Q_wkfYSzdv}{Wx9r;9j$0aj9jDhDEeG_SiIRDue)Bt-s?}@kjejk`DoO$ zwDoNz#qTMcm;1Vh~_$f-bt7R3nCO%Fe?#aK0Aj8f3TD^@-{tF zDIo`SEro{l&`r-6Nu((SGke7E-%?HlA%&6%IL@3}D`^A>IaM>N+r z2-5y}ku!Jr(#@Af)8&s7OL{r|VZnT}kJ3>K2Qc)`4~q|r7744i+0WPHq~&a?naY#c z>|7BW6vxZY$Ru+dmhI}i7)5#CNY9I4<~A2_T&ua@D;Zmhjtdb6$FuP49f0J8UMxlzeY;ZSd zLuMh%sbNP)1T3#i;9Q_h+!0E$H{NZ!mbCrwfYHI5^k284BV_@9}el~e-Tzn!WZ z7OdQ9v8L%z^a19UB*$kF{U~om#5@Ce{d*q$Vqb>Fae%Mr;WZ+6CneE|^Vya&B0lVT z)XIZi*{UHoXfdT$m#yo$F8M=J4bpJ_6U{qdT%f?fK)CPY5Rjw-*xapZLedv=kyDVD zLoUm~m=mbjXoDBylVq!0J7ixmB=!LAva7C8+5r&C`69AD7sZ63LalbEFF$J>IfXVl z>pT1Fm_pI)LLmR7B;Lt+9p_?LM9E=l-a>6}vrAVO>9Td^hkFmtpZ77sQ4}~PNlKYI zHbdsl$5QOYU1C}?a+-y&-*)3n9?iLvBu-FD8R84pxPkFamfGF;G)o};mqZUOYwx~# z;>2gDwD!cm`^6Qter19w0A4eWuzkyqu)BkdMZ6vgE5-9Aiu;QwV92tDIfC1ROQKRj z9U?I`A~Dz3k-tdCK+;HC2F^JWLce>3;pM2;#>l~=SmZ{4rLngFaB|KC=7|JdAS_4( zDUU48u8^T9|!hsIzG76j^U;(4ciFVOBg}$*1HAiyq!3TdaA_o1?J0w?h z!sz9iJv+%6u6Tl(+i97Yvp)oa>et3o=nQ{uNAu>{ z-YKOdlwI-gd53UruJro1XR4+M#$zwmN^cffTXg`2FRaHdN>M7G?tiM}iJoZO7;b9( z?5n0W_i6DHoc|hkWEAI0|4QPaE>42$XW{$(4k;)!df>jJSRTXmv^~NvMd-&!|E{Dq z$UW$_6q!r~`CRx(Pfb6#aiU~=eNb38ZQgsksmVe9g9`z2`FAv=VIJ{zfH1|>?pC@r_T=eW zoy{cKjL5p@&w37)SxTPvpB)o^f7pG?GfgTf>>_qBy%zMv-}ABrmATSC9sqYIc)U}R z83<4>LgC?=N^xv}N9OxVaSkNNA`lAr>jV^8E2sw}^zCZO?rG!ei$oW^>(ZJ8cn`P~m*+_)eQe!gxM?{rrSYTa~cmkd)#vB)B+!DD>;CZvb7qa}i3}I_1Cn=z||Q z$KrEeW~S`A#W_1sS54kY38O!7qcL44g>#M^zI5{Po#St}@e!{(o5$ZR6DOMFfL-|C z&*`sy%2Lj&Qwf8vP9OBqJG6dEAY9Nln&4jmv$6E|GSCfa3Kp_viuIuzFv>|B2~Zzj zNuK~T#v7mJlL+ORH zuJP@;8^ij_-^iuZiiR^BZ_vBu(aa`=DpwIK&pVU;i(Bl3PFSOP?y9HHsNOZwA&ohD z(Wxip<;$F1*uTi%{SpAJT!zZgP*w6)%>p=qhF!t&*g%~Ix4k`!1bYzhir`U++1sc_#GkWVL3=>XfK&dewJe`JOA}D`VQC zTz2M1_VU)RZ9I-i8?8Y3S@uxl|ePLZ2;n?FermA+??$V-goBY}M}$Y?>i9`bZcR-#_b;Nhkwdh+qG z&@9#(f&kLU_rn>{3Rc~XLLlY=t^owNz-(L{L{oNk2uN4}b@6f)lY%g{NvT-5i!~UM z4bw=`f+&mcm5t|+lCzK-R>*ch3y2?Iz?6!gefJM!I@t8hWp|LY|Ejzx74LbTbTOxq zoL@fe;~XD+x8_Mj2i{J$m22bU>Nd@1UC&tY1WaIPkGo=O$Hx2A4O=FJ`0N=#@)zUux_3q1WM+>C$4*Ai~9 zAWv#IyBofqBs~JeeG~0h^qjv%a>h8mS@m-rWNU4RLyKC*2}&wqI#Tpstm6yiIFm2& zK+*iVqLP0Gr6qQ-kk;I=uk=FDhiubFmHRsq-fZ5s`O5qEUK`FQ+kU;{RbPD}|NPLy zrQWsep=7eyFz>&fe*oHbW#4h01nKb;a>l!|M$SfYa4Onz$>wCO=aL0;&p;emh>hj5bFSLWtS$vb2qZotiegr&k1hR# z<3J+!@QW-ziWVoqPU?R123cBmF*qYhO|agZkC&$}1E^G3B@U3{vhosSFe5*P_O+@+ z2eCX!O|fyZ1zJ5=c`x7v;>p;Tpr-M? zO%`3*wA~$=w?B}HhnNH#^6NuM2VQP;Kk=9vvn>Cd8)cfYq38ACqnj0GH#tk6{m{H` z1ovY47^TwK!ZR|=z)c+gS@(M^rDSXG63m><;#ZV-(RZBi+3CZYiIsC(pKepj=$qg3Mz6X-W&q)o(q6f z5Q=z7sRh|k)|_vBXmf<^Oc~6<_jvigsR~Gq?thpHpj6HPY=tYoSqfuMQs{uCfP!x6 zve^kM)CAzPlejh!C8W3IL$ty}Ksh*qs`($fLDt#0k;?raMCxU~2-VB}S#F)HCYX9E zLIs#Cjtp|;a2MWj9x-hSr*%c*Wf^6J-DUYrR}Q&9=X@CKmO}wL_!(GP{HKP|>TybKDPiK3WIm0tX>Y=wpap72HY;lXKSACSpE#u42j9XS0 zbL)5U|FgzdXJpj)vnTM`=de{aXt0M`7j`^V7YdDS{Lwe$eK@B3VWk#bLO`yt-8G?9 zN?bVO%Ye6l>M~({(*v_w=c{{@np?()cgipp_Pi)VKjNDdP5E_crQiR49)p;hD|qR+ z6x2n&B|hT*h(X0Y(S9ojf75y^2lh5d;Q-JVAxIQj!AnuB+uv`gyj>fhT%N3m^p_HR z%X3+E&!Y}*Gz!!xCp=k^A0P^dv=a&MtkMG_r-j2u6%QQXLWg5H5G@a=&a+Zr2fAqT z4YfRn-w+NcF|e%##NoIy2q1fdqJ1y5ZLUS3T>(RZRjOf0T|d?05Uni2 z0)?1KAp>PM5#R*iCLp!$1(sDH|5z!VO2W~a51;9dBG|7Hz9raZRDr5x zg@FkO2?bPdJSCfAp>4&m^17ZyMFS3I=@_V-q<`JcY&BH9gH+4?<#+#JK5)+B#LMH{#P7&o5Vq{Y_ z9{tabvn|j!5)usoA9*D!%FP zR08%+vh9V)X)7KqYORT>(s#>oPxoKT{_^VGt-3#KmSZxetFLk0KO$`%VVAl_``!KG zjwxKnk+e0BAHI&|F44%!3i9YX=1#aee1@2ETeG{%Q)_iUZC-e5=G5o1yr6X=xjPor zH;f(IUS*m%DT-D2@f7m>*%G~uiYqu?w&yUcXVlg!-D5LsfxD-bq&IX`VY|zNXJfwv zB|lP`!Mzj}z^Yh!dwU=fKO~KQHX)TydPnmf!pXj{nt?e^exTBpU6WU{T-Neht4Q@9>Dv@1CUDWeH-gNaOlnHP6K) zo?2U`+@h9#c+K!8xh)jko^G)e}$-L0vB(Hw76u+;=ucnNOgshk0)6mKahNO0k-5)2RwzplXK9>jOajdo;o z(rIaf3qUTZr&-J}}F&DF* z$Io75Yuf8QdB&()(5vRQH_!YX`wkk6Py_0o-2|zrPw{gX^J^-6IEGEP^-a>C;ge)9 z>?Vo*-&1TG#_Mz{makqT{y6pItZpebH24r~AED0`TX+Whe)Ox-)UIbLF(a2u+r;Ek z{6kf}_l^!Rt{&7T>Mduq-=p#4XIrJ*7aw|GJ-=WR<}Uk^L;ldg&rZifo*i_|%MMApD;;afk<*oZY>)8@LA{`(voWw3g%U*%xYhW-RO z3=v8Y;u`sK;a_#xH~Ag9ujMjUo7Jo;A0 z?wgo{wNj5Z9CLY|nb0;B7Jbi@*mX{>vi1yrkqgi=fY;ya^gQPI$~yf@8hSY9XH7in z>YHNJb}vPkM3O*ZJKOvztEFb9Qn^yZ@}C4nA8}b<8eg`?zF-SR^bKV?M~%i9f39JY zNZZ-)bfZeEUt;hP!mb<@XYec>ptIEli^{+9mo>jhS8bzP>ul$#<-_Heww0A%BvIR} zQkYyP==`@pYoThb>^@ey z1p`~&20vhmj+9@{O-3~+lX}`3u3y}dvNT$o+B6v`zRuFb|RUw7oAWghbqRNNb}XOV;~uBTcl5C!sqmCI=JzB^wSdCk7QA=qNmn7H?HzQ;&djj%7e z?^@5^NYIj%=S*MPh{F#DtIZ~)-Y+{jq3X23pmZ&e28=Cjr#@LVn5)F7JNu7I(Z(y0 zgr{vr@2xRU65;Ypu7RY;4SI2xvjUEPMxi=tgf7!B(61cs_B4EUCIS2Uc&xSPp(z&` z6_56{#2+bYCh{Bi@@E9PpI5J4JJiTmLZ)~5MStW}&ML=vTl=p`l36krxsLVR5_hU^ zRV6=Ofc|Mw7CnM1qC7G9hhET9aVn=(bWZfBl)q5td#JCE^EuriI*!RvB{2fIzHsZJ zQa_&mCW<<7EOqY4bDl@xS^x0%r22hT%-~o@Nl;6_`#gkNUw$0sRWeYGV+m z#fAJWp#65&d2d&b&c@jI{v#E zA`XR$`@|-91`<0();v-ze80L!qdjPg*+?)~g6->? z1zB2rU8uh+s~sH)N@BX_!sr<7J`JY_B>N z2@3oKNtnBOE)n>|BuXI!U}W)UDImbl{wWdpcE@BMhyFbD{w3vMhHTxw zjFw-o!l?($=hth5o!R;+@{yOvl%UKivi{B6-{?fC4ZUAG%8w?fT#79DoZHXcS*w5e zbqFISoZ4ixG)$+Ql%C4pO7ZvNvDL|}`tfX}guh>v7yB+@f8V|f{8@g-FsUT54IQdo zK4q6)R5>kFoZJu?yXPNziSTDr&sZUc#Qgzb2X=R#^Sm?Y3M*aJ+Wo3*@h+9BAv`sQ zYS-IvabK8do@R;9&`ali^e%kimn0*a<1K2xL=wLnW}nsTkb970u8Kt+cY}D}FHFYs zOI#1m3zcA|h39~!`a`|rpxE`-xS{^{xd^Xt4%HvkpJm=zg$*ldP{HImJU!i@0?ad3 zc6TfqDR^+enhLqUGa^peX7NQU3T4uFXS^6ZxvHhA4f~b7Agl1k_p=}pi&}QNZ#l*vjj%1k|V6k zU^-~?L%AO>QTe(aczLB`D^~zB!l&_+uubHzYk@ zTcL(Voan^_;~q!`AqbgPNP0wu9TH|L)sF&U5yJuG<#M2pfxR%IEP|si3AME8VbDg2 z(LmqdixBYGfa-rzDOw@$64U4Wo!({u9u7J3x#gadFUV>aLrE{Xi zY+P?^Fb&lL6w7dEl;ytRkKPmd^71&hoq?lNKU@C&G>&VGoPFZ>oNsOam~}6Qf4Dsp zc!MNVMb9R>6F3e`b$_#>-Ak*h?dnZ4R}JEsl-DNy7}n@iM-^;(I2H1BV)@pBVhAp@ z;;#B?FM4(Pc1OwKPfFBtZvHgRe3w;X?;XwxtJkeQ5a1PjR_X8Y9*%Nfc}`tiP%hqe zu7ht<{jdsZ-?qc*d9J6=I2~3;bNq%fkRD}Wf5=__wDoFzzI#e}P)2wYsYyr;xu6bv znBes%QxcB@#smkjP~-!=3B-_8v?~B01i;(?00a)PB3^EbpuvG%VmJW$7gF`?tm9?~@1Z+^^L-*u_a z9Ed~7J;4dpUt{uciLn(u@;65yySVYLIHenEr&Kk2Po~+ABIjC1y}C! z5Z!IQ(|Td!!92Ync(2oaRLApqTVDUCK@$($i~&K?nK^qyE~ z!+VrEazR5A&O&U`jz07l+n`nEbra4h7%~qwr8x5(*XrU7TXL&Nj#q@eY@X!2z)+m} z_&CsG+w!z^6dHgbQtFHW&kQeVy_C^8+`%?eh498(>0;+lXrsc#*|& zCaZ;Ck`v$Xh@t^~4l$Df4)kEr1alZ!*~pSm_}%YXFN-_Ccmaix@J)XR!a(Tvn@#^G zV$x1ll|)L(;^b{}9f4Mz1{*PRC%`uKEKx4r(lsqhIko4FYkJw}ndsnL>i|#90{KY0 zHPcC*StA=~y3Vwn{@Jf4S%s3l)NCVSe`fYH8O4a9+MsG3A3G(fe8J8i8{KwBOv&WO zglmm#B?K)E^E;d{dVY9!oSHAmrIw;u!o^*>W%Xd!=U6W>{ZA9>Vy6=G(jGt5pik)( zaK=w?ty58dHHcw!w5FL#R7U$9rEGq9<3h-Vk5WnprcK-TkwUr3cX&VE_pJmY-#r&} zicYDCacSUU5<51vVgDXK${695eN8SI@UL_7HpISi?K}@o!w$WVp@2f{g#io&K@oUC zNB&Vduh2G;w{V~B*HkXD-zAPRxblyVj|S4#ggx%R&w3S*m@z;CSY$vVi`_)z5)^`& zUih=hjLSSI6a~O@NP>dHBeg9M5?Fjt@GTxzJFmS`PSlzwVlc|cT8BJmt#we2Oz6El zVh|Axm4KWFd=k7D+-5ODes3tjB~WvZAD_rV6VbrPEs;wP5pp8RaR`Yx5X6*vx={5c z0Dz>xI*Y*s*$+bXWS1h@yi%4xToR7+l)%0okmg4eOK~zRf;U2m178OqMK&Y?CC*@k zcTVw96A_6jD1ESVIAmY@XC%?>sAYrHEz}`T+guo zW@pGn+csaS>?A78Yi7UbmFXj?BNIMRiI~!`#aCh1;(kh_5AP{57!57ITJ6;Qaf@;H zwC<-04{!%!1-IP;pe3j#sP@x$;xNTor>Y=40b)&1x9$9f_3=a_WqfAYe zCI_}94{O^=NB4ZIxe+j%&>b#ZI)BYXdi>;@09?oZvh!amDWO+yEIX|>$rCY_{S^Fl z=&+6YQo96xAxiigKCKtSFsm8XSu>j6D=4t4fAMLVG@9HYI$mlhx%F1~$=Ajo!#JPz z^Q#J2P|;5qqr$^eJvGtFr^udTw+%Au#EztThf5I~vkl*k1(^6hNALIwwcCdvOWNP_ zB?S|+GGES1KusddUHWcsrf_+D0Hz*op3nw1roxvdw9%kW#%2!#;2C6fzz+ctEl5ct z?@`x3MZGotI0` zrSH7EMGRl--dh{1Me`h=GqhF8>=wX07;5?Q%-Rcn6)SqPNVYEWCajS`L%pHZw+BWW zWlrW!T}hOdRy(34BpR=LiH9S$+jk7`o>lDVE~yOd+MAjZjrVt-;>X*{XQ~|i9M+`B z7?CEg-llZiN^n9#oe55sS=jeEYfvT$9(`VZwCIlSt%g24Cq8ev?;5AgOwEqG&8E0` zB{PkkTt6k(JEm>6Xy#O^nSXKJ@#5QBl*(q#ZyfY5H}v*=Pw^t-HSjZ@T5gO(LN*)y zB)d0kd$rBR{->Rh-<@H#enLGTw)%z7xg`2v{M~m;Ynh8#ib9#k_g=lMN%&mwHSCsB zxW3rEJ=sF|#u1Ep;pxoHj5tY8`&(wbGM6)=PQ@QDULNwam8E_ERl!-z*}*s$)Wb<^ zqu+PiGhT49;t`+HZTx)Q{yjWjc@J-OizBdkQC6xh#C{F#L+8)Q= z*=K78gRgYLZ|SJILP0~!xRuJdt~4}3<+8nm{(C+yq28?Ios#^J5Kp4(E~BZZjc8n# zNr&zPBav(EZ@V%C#b!r#DQV=Ub!DukxW0ey<0Orm{Fag_e1s8{W9=BSYzAuBrwE^)iij|(QwMt+i0AkyP zG~38SDkwZ)pMg(5R?pFLg%a>H6ux28 z-0YQ_O7I&C5Zq(7$*x1JMBd!n%OAVWRb|30?3ri1Ua7O}uY0}LMe}rc+YLgKxU0LP z(!b;J6;$cU^IznFbOwx#s5#p)7|Q(RmLx~dXKj_8XYQou6V1t&)Sl>3Gkn3~#;Hdp=p=D{hC0@=g#9Y=&;aoNP)Z@_RP}hhG()*c7zuX3t zu0q?R%^@eLP!#>5=8d@{f~0Uvo|L4_`}1Yx+0Sqm70m-KntJ#9&WcpXX6tO=E6M4d z`ZOFJb>{VzGvMDGt8*SPvT0H-cQVcw@qiy*z(;YgYdhOqc&_)`?ZnCg^vPU`ZTt7z zi6RuO{niFeJ|8&pQ$}}a%Dh)kc0T#+^5V_dxn192?HHd@*%xL+`>Kx`mVzmaf^5G$ zq%cmzId(H^v`KAVt{H>oeN>^r^4D(L&E89J8iaWE2|d}Rc<4n=May0l#%_@V;E$Xp7W$USn%zj?+!$o@SvMR`Z04Vk z8MvZ+4JE64y)s1wS(d=-@A=yYHM1{6MI4YG#`W-}talJ(wnsp}DM&yc*}Z5XDHof` z3MM3SCw%s^x~6W4wM{TYQPwtsCpk&@jl>Ga$FDwBxCr0Xx{kI?h~<3?&eJ2RaUOT~ z>6CDI@W7(G9MhrNX8zVT%k8aDgS&SRGmGh&sPhm6ADH>{wud9Xhjdm}UNKD$-MB^k z6k6_!Y&9lhx-E3=Xv3G1wXTEQc2UC9o*f^)igKdykzIQYy{xL>|DXEGFt&T~-}Mz5 z6t?nr`(dDooI37PXe^2gKtjk~4P?V0wy-b>Nk|T%`%M&KDMg3o;vcV9PYXt~$d6uB zxjwQ3uOC_&D~35rJJ`0XM1X2ONbbWnVXUx<#3CFMx{d0ksX=Yjb8!$Jb5#Ow!;Ah` z|2v?lEAwZD0Byw)qD$94n_k3ocINsUZ~)I1O&q%b_&anW*U<{P`cV6IZ(W@$EiDO< zHqzFCeRPTMr(N!$(Oy1)BR~Pq@Yp|(gZ`!dqnezLW@{aC56O3EZ@8fB7K0>^A z=Yc-JM`oQYC2)2eNzDaLPb7K-w-SU~H7f3O6W?Sdyn9IN$ZR!)e;s?9 z>sxTiP}@I_8=pNevfSGGCRLvQe(N@j-3S^6Y`NuT3)c{Ft-a4}@ z66{z2yUP=imjRP$BtrvgHwCAw@s^HULe6# zjHgPgLWf*45x^_>GMvan69`^j>cABB?|!XEom%PFaUMvBK=@oUG@Bo-4!CO-57mrO z@*`@-ED}3g1J4Q@WaQ5K-YzB-?MyG7#r}1pghf_RFnO{*) z)z9yGYu-c13%Vugx?yuCJ$%03q4g1cSp20ZBGX7{V$x zv$kOwVnWLu&cev132+|(1qk6r{60Sp0g1qYY_J1vL2N#NEa>XMuI(T@A!IuO(lzjl zu{U)8RVKN`PS_CoBFWJNITe>$5RbJWrme%SH<6zyw6P#q*_17K^?eDZUg~{ldVkHH zaj(k#Sf7vSwm*S0F>!Oi*t=Gz?z}@DxicsXZ^ph|Y2U0%BK+@NXz*XHwoyk)wRV5~ z(EQz?RW{QZt5S*z`l3`W=ggvH6{jW+JhyAb(T8<1cVY_gqr-?dM-#1GLoNC8Y_fGGI@xHus)@n7${B1sJ!_NrQuuZ#yTNY%z9p|Ob?cZ z+0`p4S}Md`jj>R6Ny8m9FOYuJ{we7FzFFVFrS`!$O!Zp6PcgpQi(xyTj5gf&zwDHw zy8nAcx2&wemQ_7?;2(IY_Oyxp}2GUf$;DY*{PAC;fC2%@z}u z;F{}jhKz6Y;8j8o&Z4}3XpA*nu6#Saudk`d{otq=Cd8Z<%Twp{=27{kpV{heO!4ho zl=rVI_PA;3wym+T;zPox@A;XTE||Ii{jV3((V4^3Vo%#gaCSU3|L6F~LnW-l4J;2h zx6k7}IF;PSJ_Yzrl5$s9qd7=e_I3(kFa5zc0MLTmTs8QsP2iyWF8Ow0tKe7G< z;;js_&lGz&n{hQ9QB{J&8iCM^YgL9a1G{c2k(6Oiv3PI-mzQKRZHl|P%|1gXVo}yu znj(GY!cya*&A7+jMneO>F=}{~_^xOwwBGt$ZT7MdN^3~(k>#mgdu`$-t-yTolrNfB zoFlf|S3Sn9iTu#K{dEdoN0q@dyDxv0K6vuY5C+gqxmxkK2@_ej-KRMoKxu3Kc2culh z{DOQo6_ovAM`&bPAP!NiO*la7rKG_dFi%D9pkrb_Y<5vsK2BvAB26PW$Ea0t(KV45 z&w~QaQY&R`BpU|L$6?zAqot3SDh`BEUbH~&Mr@ZX~cYGwy+AZk+ zdJX|7=auJB1WM6(2M$%{=aQcq3;}-*RGtHx=nrVJ95@z$j}UW(Sx4(2ort_6N=+LZ z@S?8AL(Qw!x7!G58pF;Xu6+gvk{we;ftB_&06oF{WW`fF%NZDf1tZ`%!|fl3fT{=R zdJu~RED#7F4+P=;LPx6FJlF@AU>%)CSk=(n0=N_}$0s0}Vr(uzOp_xh<a#cgaZy?%k%_jT=*hD1l@ zx|#N`{-nvr&8brlOsr@R8_p7JBYS*9-&WFT#<$%>ve44xcdf+!n3?0>^qhN_dPKT< z$&>qz*g!cIUin*tbBBAP+&o0!$gR`vPWhgBQ%j%G14bpUWN6nrmoG^;?BB<5PvfiQ z%V+^^um4`R{#%FH#7W4|xZ(8m(*({b4?Fa=rAh{@Bw)kK7H$Ay_DanH0gwL-?yaDrc?;v{Z)KiV zD1rzEC*}je5tp6#B-czr_Mn(7DbEXCMzDH=+PSOa#SMI=ucYpDn59)Un$mF7Bw!S` zQ`6gw7_E`XE$+b0)i;8o?>bQs(YPPKN{gfR&gFRpaut8vJgU^rR!m>2D`;16;kMG% z@8V*{jJ|IO;l6Tf#Uv?b8P^{ zIUG3KX_x*qDS&FE-%doO3hvg^eUVi7BOzQN;Nf+RTE~U=D5&Nd#~=E&hIsH+o0itZ6UekDQhP!A=9g4ELcmrbw~8$` z`5WP*`2qcv7z?|=+n>+67h_w8`1RqLoSm;?r58iS*Qdr!R5Y#k>sV}pGq0Nn3>wIp z_aCLr&o!Hi_dS;Hf53^>b}?Qw33$1k@V#Ti)LpsFRmd8y(&7};X}@E%)LtPIZ_r86FV%W1@RIuyfivQ@ z(f^dJLD_&r8yOV9!T=07wpx{a@P&t^s?a|+k?(myT?+$>SV(aT0#zp#;|vU3=J0jJ zDxGQ&`hX!gQsCm|K^7)3ERPnTj1Sy_aWUS4hs_HVE< ztsIP4EI4I>2>k4bTjYSk!;1k@bL!CCB;`^HiV@)zF1drYoM8Tl;&afew@|@p{uIBm zhfhS(uTY02L6b%5d}`*^lqcNW@4i6u{1JrLKi*;X;N_QY8PO!`le_cJOKjIGJrzfD zFx+zaZ2Sd*{Z0?`91k>2@zid^+ng=p2=`Cu8q@_lvCoVe!@b zG^;0uFMs2|=dJwZTcql^q=44O$b4(j8PiQAFE{XMsW!Y&5gI>b(2%#~Sw?m3UX!RR z5e;cp2~^iWn5WLSUwX8BwAC=4FPvGt`_no;x767uvaLo+u{S0&&y?{|e(gT{#(=14 z(BIU!PHXJvl-@6RiUM1i8H}ay9pu;9SxQUvOTXsgJ(zK;q)B#+D`A(*?k&8thQXEL z9qZ;g1*Av><}fMq$MN%C3!6>toX_g`hRa`Co*-9C?xDAFdP5pHS#&w{{*x6aLu+{0nL`2=c}9U_th3;>g{bereoZ z7Yt$#tf~r|t676IP5(qC3QMo8$C*HLNdNgcQh114r@962^PWS}dutf1eH~KJAgG?; zKv`bhly*Kx+3&J<&91T*E?wuqjSYM9tP7&_Ptutgcd~DF8bw_2ZJ8n8@jE}GzTw0L z_AmVR-|?6Kg&#g$!vahDsT~UKE>;$z7b*)OYy-BB9Envx0w6CK%1Ch-c?aS@Kyd)> z18nmF#CrhwAMhWT7hXOBL4)bEB?}(^6Y@-RkvwWh&qor?+`-ZFZfDO%^b%5VWt~08 zl8mt?A$y`gepMwNfd!cRoCmu{3PHMdc9WLWE-F3 z7Sc(B9X+N8_cLkA!~YZj<5r^V~QU;r}u9@ZXa}UWt{Bgfc&t8 z45za)oSFBnS<>>W11J#`5V0dGCOZtV_H4-IDaTtv5*+!(a)T1>QRYaCX7Oi)=)n=I zIJ9j7T`>u{$^kvUOhRuQ??*)aHbi&LaAMNIgswJ@tiHXD6f_X^A95O3tt_FQtUi(Y zM)hu9P<{!;*H2-0ay+d8d+xa>uTX+T!er!a!Umju6NdbC!=AnME9v>a#yt_W zyz=+F!a-J?Im6xlIFN83adQ?u{mj)_6oWzXBp}lRhXeyy0^txuAUAVjq(uT&mG_s) zRvr6ffwX9ra9?!r*TXoP89>A~pU-+qO6;X8?3v=%_+?eRKRkcX zLJ~8{?F@+-@I#Ol)y@d?L~Y}Ji<78@KX;v z%rThupABT-T)fGeh5fNOnh>(-0FN5EWmpm?iigZJs^GS@TZ=7`rWKqcfFHq=(a5DGsZ6#e*LkJ7dxkFq$~N9 zHn?%7a(FRRSHLW<<)w47Fb}RcIsh|q>6CtXaMi3)v}5nO;=SK;F3#e5jT9_HK2a;7 zO5GEmPqjYAQ6H}JC6bAe)qsS=di~vBf9ZAfIFrwbOLAGiH%D<9;l)wOTmgo7aTr-x z0XoIb2esCLp@iI}&kd5vEnUvFGR{kTAuFUwTbFF!Nx|bM5VJWpwU*@%4K{WE0kt+) zl>kpKM;swLUg$EwK8Rja@AxEdyhF-F_Dg(bTZwf559#hGPog$I&6d!-vo)C>1`_;Q z>Rvg^cK*#T1UE)o9)D{Z2Hz!aXx^Y^-_N;my2PF7r&AR9hRH=%3yjT5&8scI^}DAh z+?_Zwud|fAq1#K&{}WY6m%kiJ|FGebWz-&W8A{f?8tIU~`vXEvv%3jegM+Y~r%y+7 z|LQfv;|ptiB_`0oGKe(*fZz=hLM~}zDB^h~Dh&Ro~*@> z?|olpo$>R71Xr7@+T@A81Lb!~p0V>EIT@OkQW19Wo{h35Wyh;m@#FIs!KMm%{oN1B zDAAR9RE|f^-<8n3#WRw&4glau@0ujR`MaN{nj{e}4$CiqB@$r6Sc`o1dUa+sdw(Pu_hZ{FbF;U2zd_7san7i+L ziAI;q0N~J}Y3;{`UwCU5X#e`?QC)C)IYs}Rko6%MOL$nw_`v`^iyza$aQe3&3K`JF z+5M=1NBr?}?iwrc<@IAM(P|&Kp`6n}v)s~1774Ocj>(i52vB`H?dcy35C z--aYW>yb9irq6?R!;+7NatT40O^D1HIa%= zlJ&w^8*%O^C9t=EdF81IKUr78!uk+xNhq;MbY?8htm^|^ z8UqfFSHUO-T{=f~^~`#e8nEs~xkbeDdi^+1MVga@+q#?_L{;*_2vehNJF=QeZuCqV zpA@g_+Vdp+c2#IwRmi3zhDA@#W{9tw7*CQG_}6%XQ6L3lz3lN&IVaN>F69sCEL;t; z_u0`H8E~AA-=i2A*0DZ@$mSI6-9DFq=jaqq z^XKK1X|tG}V3&Y6bF$|c2sA-G|Hfmmo$ifL-yw=N%aUpX`3Zk|8TW;In8_<}VP;0iZJURbd-h z)IC8PSqwNXixrPqxG4L8fCx_H_H?}fOkI8D+%0jNChl!$Tcm9VcSMuo4hVEkV@9Jl zv7l=?)*wt&0DT0G`BEpO->3m6bOD+Yn9(b?`yjdW`Cw7h6N#9A```hM31J}pK8KU# z=nr;yQDaz-0{hx3Ln$LXddmz<^ zZ58evAL8M_#;~h!OaSqMAb=Fo;O#y;o)Ix;!Ilp>pbZ3=Sa1gc762O?{JJo6!tp}@ zo{8ZAM=&9lBY(0S@!xq0eNB}k(BHF<3@cX=M+ALThB(02u_!mRoWeaZb6G|Xa%Pyd zb>Tu=Tgvt-OV6vK@r$sCR;1saTi11hKRzUL7@cZU%QQgY;(HxFKJ9u$8$Q@hRu_Gf zkq$=)k;KZ=)75dMwi}KV?E6S~?a>(O2WPlo4jKHvvaUQHs{MbTS&Xrdee7cmg^@K{ zhGZuSSu@5GO4*W9%-EA;-=Zjqq%4sX#+njQDJ5HFD}_=-^!uEld%xYE`^SBqIcJv6 z`7H1E^M2mX6BR-TWuFf5b{~IxrD8QZM{>J;RL5b@Uo9*84vB(>FG+`jrdrB*Jr_mg zZ4+NKqHbnm^Vs_h|K+Xl)-Wm&SoX$nn!$ahtZ>y6vJ?t%^`{q9HO0&LY zDL?*}D^Z^^wAc_)D{Pgkp^d9MkoXf{ad_cd>fV_D6ejGNl;@wA%-Z0I;H|M_edVbq}`-^E~jA!kBr*kaSPe*Y#tFE~r zxBF9gBoVQfR23hb!*J%kE~q}1t^RtIl3ro{P$drS43YCP+UOH|$d|MClBFTq@rrNx zILc-j_gN=oG#3G1%4m)F`@Z)g=o|Nah6B388uJpV&ztGW&Vf-u<;zHq#&F)sm64VL zeE4tSsehp{tCbR(+yccca}e5eQ5;O zfOrHhx>N7KX43N*KG5A;DkBDf8mnaB{yXK}g56^(Pb*3xT|xWk?FSnGt9j-p8@iz0 zhXEoC}yxe%Md7Mx*8w=%K3>CKnCTuf-y?`GUbOL)R12zkK zElx;_TDrgiyj}j#C)T%WMk-WPjcIYd7J8X?>V^tf+Sh64Fa-iUiuL{m7!f4+7rnYw zp$NVJbke|e4sdooQvf?Y!I`Aeh*bbt8Xzk%v_4sj82od_6K4o&AByevk71E#WAz8S z0~l=Zz5sq5wZDWOC}z!k_OVYo!&!@3K#rysv1A!@>*>91OF&EQQQ+E+^Zucp?CVbU zi|o64>V>_0w?$RKv0ZWaPw!HXv^(zZ1!h(}8Lhp(i{YGEo4A)hq*OC?el-PNEWjaJCN*UNDU><*j@ zP>M(_|Mj!1iwk`xPHndnDw3nRLae@lxmVh5U&}seDc)C9b3G6#uJ+C-ds70WKuglmbKG>kiq((f(tE4PP0Zure!(J zf3*l0_P`N{_Iu9BJvpDZ;sit>mQ{YrS?lch2A=KT^LPN&`X{~+htdVD%EY<(d1WEZ zPK2y@Xsi{hVFyAM0UxafbV-N$?b|khPoBGd&I~>@Cs>7?B=9q1>Y^>d69FpEd?5B9 z_;(DdY?20Hi$6n^d6o+@0QiMyX;*@;4< z>5QHOSdASl0Px5F^Oq2A^w>oTn4q9Y;SzwEv)*&V5FLR-LDR2>!fUr?0R9f+0NQKwr;SB~q}sq%l4^ibXngU!)ZAk^zr7f!6t?WIM_Y zQ=Htf`6u5LSFHCu|91Uh2kyGEd9uB^dvB`Lt#6(dF$sD>7)<2jfl<++OS}@-Uq+P2 z-4}OWnaJ<1sQiW8PfeBjq*ObBL9&)BH-7MGp%vh}=z=KIu%WyqM>f~%DSa%5_@ps) zk(D}2{N$v0>}00SZ+R3g{`K|1IlWPdFC9Odt_|KEbjwzXEk$*QN)U45Xyof#N`Gu8 z4Y~&?m2V3k&UdlSX6l7RnKx<3C>8A0xg7qwoqBxj%IPIqf!pnASu+<)LC)w%vs$mG zQPa3hFJr^*4R}=ri(oi0UMPD7OKi!#Eb?Z9x5yL2)RgvzD93)>X`lDQS*MQMomM|g zCfJaDe~{;V0;qoXu8r-)cC(fQTZ_aSY*{+h^GwPwGj%Xlm-)nH1(aTNCCk~xt=Us4 zl{Ma%S6ayas71FI8ALzsc&VF>GXZA=6h)qShyOMd!WtZL(<~oGh3jsu5>S%(dW&ed z+f`cq8&sgy+=f5-kauF_*OzURR)wkp$JamDvB9Xw=t$V-&(@SV_G@~B+Z9JTe6Pi7 zX&)>4gz~jNCGU`F%)xmtj#eT0>3wTU*6ZKLb`6-nwb2junL9MD@aCipVSl+pB~$Y| z$ww~(nlOQIdzd7iPuozyqgIrCsqXLghKcloch&1XJ3aBCEk_k z=yMh9$My59kUWPTBCh6CA;C8I-}y~OTCBee+3-MjxL9H;p#^~#f=Hwe$Cw-$G&TgV zkI4ZRC_t0{NwYRejPMnc4rQ|O2Nuqa2zraSWtzi>7K9O*p90!11o>I`Nim$Tn%24y zA0hnx?zPAOkeMLZfsP*=+)WK;&$?C+W0Vu(svFUQM~#4o5CKeWknn@Lb~5Jz9^RM< zF~9oQBQ6MFnZ%$Nz{mhI3pO5tKzlRzzX=){T8g)cHxXgmE)7 z_+B)Ru`??VqoDbRjJJ`PEWTo;F2FhjAUBQ&-tvm)VIF~YdMaHHm#B{wO-3`t;m!jd z0jSUb8&s5jC+n851E_6-gMNMWkiP1RtKMP778JH2Eb2Fj>YcNRtv6Pu@u~0Vxk>%; zpQLQZhc9WkA4^_MkDr%^QtJC0f3XN{Lb2M7*P`E$sMl|fOS@eWihE&mhaW$0d^K|* zU5l?>?rLE{_C2M1P28x$r=RxO-qKfnf7S246e}b=-I(@q7YmCfUsOrA*iKxz02O_c`@vN$C8%h)RRdR&s{%sM{yGGCrZm~VS z*S<~!wDlI3`WMqkcYagQP$DQbA-eeBeb;*Lk=VWr$W4o5N;Hqw8y%N=GkRGh{u+w#Cfa4dYEsSBwfAQyYD~ z*MVj^RXZvA|JMr~Vhw%~`~f}sA|{r&Hxe~MYF8kH#K;we(WtE9D+=pfeGD$adb=#x z^x?B{rM~jy-9{hz?IHRZ$Pzx>OK>^V-z0=W!Ujk{J`k3g*iiF3STg#y1qIM?pw^lG z0VpxztRNTtWSocmn5lvu_YEEsV_LGzHs%yPB4+^Kn0dy^r;|<%8Wf!U)?j~j_UM%U z_BtV~IKF?6#lXk*lRTU~i+2t4k^+{LTl=s%;;;6_AHA0BZ8JV%FeWM7__aH6cihOM z1j&o8a|%NooC*BmcJBv9#h4wvs6?hW@yuTbbTC!|OTzvWiq`4N#tQ>5?cCH7(w(^PlpZ~-qB4C~iSi_k;r z7YrKKq5k`Otq?mm=HFWuNCxQTUvp#?Nj~aML?SRt7Zw$n4gsnR13Q5$=5T-xznV1F zqA;5FGj5hI`?bto_{fS7qPM^&0`xyXkOPo@xTBfT_YM&#FHIyuaca!WPz)suz_eif z{U0vP;S%~+SZ!H~mK=Wtm<^C|qmUyqMvANuO?;>g=Fw3DDO?IYG28c~#CMhVgCt*y z3Cp#MDEEgk2UN~oF;o{TdI8C@oba?s9&Jm*W0_~Z*S(YZKFn0ILpfpj4fCnT5gzw> z@kf<+L=8BRI;IRn)N#K=IpZiO>9!+&x&CEZ^VPzNs%}T-wP?+Ss;$E$rJ=6k6K4D3 zjjkI6Av0t8B4>4lZU%<4&27P7(rnVNN0pDy-ai_D>D%5rae|HI3SUm^gfqUJKu5{9^U( z1zveS_CPE6(3+5F9^)elVqh#FkAo-~j&1+cbK!06??Tcs#;x^_N|h-ER2YEiCMN)* zmOD!VBqb+rKsQluM)MlGJqx7EymWHA`|y&FD04%3X%xNmUeP1RehdD~=h-q*Uk$aZ z6qA4EOHj8RKkQmWo7>*62l54<+1}{#;h%>PtZHsfy?v_Bw6+##+htmk9kQnPJscvd zwnsm35o*7qJ$)nR8@aIk5KEq=A?I?5!)Ki(F3K+YNxP-#+UvDJnb=|t;^QKKzx+Ke z?Gc85?jI|#ib-rYx+S*lmo^82-N+zc2`0~%?ao(%V+FwB0{`@NlvYd}K8p(S*fMow zt>-6C=*@k;*6`szpd+kE7aIxF2EhYp35JnChGly4tHD0+nDXOVKw1vCBeKHeA{Y_{ zHx}##pq2myGs=~cFqc!o&xsw7IgQiv=z3T}Cg@<{5iQh6LGxM>w@NvI%-=G5yTijT z^*eqWhnc5VseHcO&4EWY68eswdI@Q3f*jPHruWztRe7g?sovIou$|y7S6=gWf0$?qJKM1GD3^bjA^)d><5Pab-opvST z+gR1#-1J_2-ahznKcO=*L`+iRpWbi?AnKqR3~|=(1}xgN`c-W%URl792Z$~w$cGqu zuK!yL1uFA|m=o_U^?js~NJ0!N0DVDs44lLNxeA1u|17E0>XHI~M$0O&gw%$&Vq!%e zY}<-Vl6k9W5FL$mmZzb43}{8;2#HnA{vn@{nL-W%SNy313xgFox-*Aiy5Psg?I!98!ww3>GI7xPP{X}JD!sHZud9qw{;oa(OX;&@3=?kJU?wh z6__=J>ZNu4i>Ho0khSXdGAtOkav-x^I~?(Y;GYbPc>nu+lM$ykp6?Sjpj%{5D8EkV z;*~3)$d?B@h&WIGjNbAh;wye@Z>I1q>q$6E)g}69Fs~7{L{oP@q+x z(QhWW?vefnXQw7IG;%f~Y#nAEV=7Kb56K<9MM^&1Em9vnY)mXf^BcbVI_I-U$#$H? z(mDmHE)pWrgO=QtWl34c6|O7homCM$6;UG4p5-S!0y;*s7ii?*dx|1fgPd6LGL#D| zw+61Dxror$7^D3B`G`cvbbIn$LvKDgx`KlRQaSc>d1WZU-m(6s*J(Avi|10?r@E&e zki%FWIfW_G^j7+riKlH?Q%Y;5YYhUAsP6gj$v@uc+^v;L{-hyy841bihO0ygJL->N zlU>`?!*}V2C8&|IOC;ef{qJ=x89D{O2>u?RfW?CUaP>2lOzI)_}VN>v%9I z6MXRU^Uo;XL`ZIpd|}EQ6>L&4)Jk5&xks~9ml55CeJoprUyHILtIKX~{+`)&H6_M) z3lifcXBHBF1#Qzxj3m&zCUK>Ut48H!R-||NZ^Dv=K5Jg=hXtXz0)K{Xrr(3aqzz%L_}p$4iw z{v2?I1~w%qO>nzPrP&tbWfTf7K>o{(aWA0{f7sy3^T=}X6cB)Z16^uL9?2rfomha4 zFN=p*&RQ|aJlxK*6E^{Xob?(|wjeWO1r$K=Zm=9custb%QHZ{_1J_YC}?G^^Wg@@|wx z#Wgz+Q4J)s4llddT|TuG_o(}uk9%@CIMr9(BNaaKW(iJo8$2p-<=O9(D8#d-|9}6C z5!xH~?2KrdZJpt0JPRb~?E+;jlc8F!rV<)vg;15VP7y&JOPAXe&D|FP=0?4rx z@cfE{Y~dH|2;mnRqom*HDg(HS4)fX>q}O%&JScv_aTp;jEIs24MTrT6<^-D-gNw`b z6DbOTp35qAKu=aM)=O{#ASQv3mq&2J>L(N1M5Xoh(YXe^2{yw0Us1H&oA+|{T8&6L zi3rt1`A*8%$|Jd#eUAPCTEiM7Q;jEPLRjU?n=Uz&kg66*Iz}Dstp06VYy0gtH#g02 zjVFJ2P9m}=4U6DW@6TH&ylFVHeUp@eUA65Q`9yU#9p|~LJo_eI_q_I+p3=t#DSG^P z+`@Hc6PJ$Xmldb}dgW9Jw9iRBC$3l+Yo<`$G_?`cd#tsri$}2r;Nb5fY<&cX-6lL6=r+gY4kr1Ib>ta>raA$F)uxwrvYgup2qfT#ucLHff3d&+nTXVw{|N|ufXpYT1zY7Z z+J)I=ofT!}^~3AIep4XHm2(M=?i$b#$6TL2F%91`X7DVH+5F}@;5UHiJ}L7Pe<#GW3*3{ zy9DkqY{%eYi+|>MKN$7YV=qR42FcM6>kOyA@4GQlYXkQw;)c*%&Y7fYxsvDFa9BX_ za4OV9CO8yog7`j}bEE48W`w~f>SKI0=y7*B#gOcy>1~O~1Mmy>0Ewse%dt(dF)V07 zb1;m~fNcQ8R*Vi1Af*P%IfOL-=Edwww}EsPsPV!@0e)EO2wV$-yMNHxvLyaZ_7>~{ zWCq~_Xsv~TK5nq>z}PG0m%nQFRbD10dJM%f00FD9;~1JR;ns$xXCH8bX+(;K=MgL# zoy^#aHr7}M^!x9_{ZLXsyoOq5LKpy-DPR>C$|(638Y7t`RRM#%fCpB2k*@YRneDYp zNp-2wLD~Pfn*(!`^{tF6TI~iQaS@zK7|C;i1A%9QC(O?{I7&+x?Ns?NdNm^dQ1+4m zs|j=#H64`0?GdVrwUN2>1^~Vz!zOSXQEXSKH(z6HemNF*&aT006sriH{?4(ZNC%nxj}&kyaniw!n6QJ z32Ec)KpQ$}rm6SsbuJ83uOncFGX(lTy0zYv?`DlhWW%!6*U%tyDm%BxuKHlVIgxQAz@q1?bNMxCG~y`V}f(5G@Zs zcJ77h5389U$iA3;3Pe*8_jd)&BWpX7jow&}AX+kAb*iADy4kQHHt38neldRAPxEX? zK_LZ{W&?N0P`dZA61}!}ezC`YtSHCU&7U#vcPDJWafu|1<0*0ccKoYPLEA_{*O*uA zt9R!w`ce=L-`OpATPY<$u5trMlf=<1x%xtveB9AuAKpqm&|MA-yVjug*iBeEzh(f_ za{2&@t5?PoNZPMXIJR@78{Eb3Jrcikpu?0%pvbwa+HMf{m~B!ra-CF?+G3L#`2m|p|N zB8b!A*S4Qk*p{q2RIwS!N}Q%-ARXWr90@Ist9z#u&z?h~dVz2{z!Km@;7>;nzkn@p zG2AVN04!9rR6wmC51mK~z5-FOQtUC&+diO;3|d$e3Rt`nPg1StO9>Y{A<4O;=4URv zz?O1Swp>fR^+@&HBR`TR%WOj>%I)BZUEY^J`IhxNij}nDLs2c`e&%n6-MxI>ix-`S zueKMglF2Kx&uUc)9(oIjIsBARdO2`=kCR>RrMOy-g`F00+QdU$`ZI^3KMj?+{5o!1 z?)UN+N}(+nrX2pgf7v7THukRtZYUV|8QoE^*uuTcZcHOg=Lx}eC*`XUFfm}%S?CSgl`TkCI(MHm{b7a!OjP^ zuRsh)cgI+cZXv&AkifxI1<;HbSRmG?GK|Zt6VMnJLzFP*@GTw})|V8DB>{ojSdZWh zXyu#pGBH^>Vanw>-y0)~^jU@Cf4rE)a$Fc$9DIx+_0jv-s$ptj(tk@t%0nYNHw3 zlKE4e@JrrxRJz4|{;rVW?sHw;#>_9Cl>7VK45l7uF{e0`sSDc+bD__=7&=fRpL6`W zasYZW4@tMLX*O~>ieA~vqM?O-lgy_nn$z`}JSW-w1{y|XlD|>s`gtt{iuk@qsDxR} zm2o9Dw}{i;i8HOyVl*W9KZsnt8uz(dSv7J}I%M$pCdmnNA0p1`p0A2sakALqS&l1@ zc(T_D#dKc>f)TWQ6I)Jl)O;@nKmUcc@k3 zl-AhMDBMQx+rI?P4csn};Ovvq+&MnLzOk`^s2{F{t>+3YieN+$LSmZwWE$bJ#Zm|$ zt$}2Up)g8_sd_!eodu{CK;kBb6Jma?z=&%C|Jfp_@g)Vd%)sct;BowOJWo88pwNs( zoaOnVE%yX#el{gOQZh!98S>&aaQ)O-e~GQ;=TT4Y)_Xq2!ExVyu(AtV;>(@iAC{GU zj?$ywDGb@kA?L{35y7t+h4675n0%+9KH2kYueLiocM2XW&yCi+ib9<69h@FCBkO!j zn&0GtP6!@J|5hAS?UA$l;Hr*ynw`JHL?_jr3mZc%{iHFLohO&E@Ix!|d^I!1mfLmr z4flt?T$?|j<=;Pe@O?@W)cs9eUh%pip-#70ef~_B*u`!7-#|6eY07C^JxL|e?&1?rw8Gy#G(I}X|A@zF|4Q~`$)NKeFXSgsJXLvsmwc=T@WNU3dd@<@uQ%vK1B&yBmdtStj2Z!I@jlzE}m$~t3jIO`eNg~v3SDF@C_8Qrm zD^H|;2!stWUuH)XRRfObgEtw_!Y?FFj}4fzglTnfr53%~vSV^JA@6g=J)cT1Vtpk~ zck_^fryn8oO2HFB?Be*3k{w#NpkqJ6>#y#i1oB+Ga6K;Z>jCCS*CJ=pGot3qcd@^V zrOK*oUk^Rl8rMlzdaB@yY8DLEaK3ItjUXfyi5hLyI#=HHWg=anJ&<%zKc};YCWzV%9&Surrbxr-JX+grhMrYCd$i^ za~@G=8teKv=vcq7iIdUNc@x{s%t7M?#rPiwG-pJJcI3NfNIixE)q*w@CxvobdHrF? zG-LCOXzdez5(<$q7E>V4j3qq=TixIDw*-~4F@J+aA;T;OJ<{Hbs@P+kkLp&Arq3I& zo%ptVhWC!s2l2)y4ho%IYae!Kvkq_hNnaA2o?;gedVI0`b4CN@e(n9*13xmQ?xt^L zvV@EvL+E0nNquIlc-p>4=aL@oap#T|L9XiSa}i~T^Y?^#b@=Q_P03d2KNB+_8_g?g z36sq4%w(DOEn8`i$hQW#splsNd+xkijwUOo$m^>yW!);@)iEXA73GsiAAUGdyPc9X z4*d=cnx@c`d`of6%AwHRYb}04?#cc9d_Ax1Ovb3j3T28wut55dlj7y13caq6h99=_ z#%!8?^GXQmx9uB?pB|sy>`vn*{!-2DI;}ECc$|KtP0jD@N+V|#5nP-$z3kjJ=?NX7 z6~5-X{rv5OZSU4ceC3_4p5};GZ#99WbblP-`~&U>{!6&%gV!UDcI77|wEVQ2N$H!^ zFplL@Cf$Gj>ewDd*2%*P?ZLvvmO=WWEBD`?K611x&2{AvEsH^I2GLzSjF|&1Q=+yv z&@#ox?&d~IMvLJI;QlJ}D? z!&43H#d2S6>LtZXPnjiBr92{d?Sn-k^>{DbGi}&qaRH^z@7S*+L9z5{D4oj_Ec48{ zZNX78*VGjLs!y&^?J8RAWUK^n=Y&M3!(LJ2FOH+^FQS>X}wwfzkClLC|{(# z5P#Kw)N_7yQ29ud)w$ispQM5htfD`@?C6y^#+_PJ*EqB}f76kC3BGy*7QD0Wlcxwb zM~Rw)>?LB?Gr~XdBO_!efyG2YqpQDH&#x+~Lx%^fr3iI}hiaco?j$a^cBVQGxqoWu zi`s&73)pk$>@cSxK0KK+zv;opVx}eDLuN^|-cXFk`)}lWhx?VpK&TN-C~zKx8z z*L`jXWA34%t+Z{mB~eJ9H&j2H_~tso!%aAnM*sSJV(b%CF0`xi1i@Xe+Lq_(#fFph z)mvDsn+Lu*B0=;?H_zbTAy zyr3&zS+Ut~%tUE$_h<3fskyAh95bj<`LCL#+wSYno_%|I_;fF8#cIzdHs|CTR`byV zzc$TeY@R-t8w-uzP}*qc`OkdSMoMkO*S~-t2?tBE*p4w(`RFDQz+cPLRmBj2s`mYD zsu)HG1|!5-hj9MX=iqCM^_S89QvElLF>}hC7hK@Og9I!7^K+^Qbe)yux+V@>>;VxE zOvnJ-z_AmRk?V(`ZR zZ~^R?aMdVnI8=Nq$8Qj-GwM%F2tX5L6a*RRJlJyoDN9WJQ9=b%#8(&;P(e~H1MB@> zWjSHUl90>}tiq5i9Zsnd)4fXcHf6Bc8gHI{!~*+!RHofympy_T|q4o=3SIHT*1 z5gmH9o`cqndTegNBnBT$&saTL*beD22BZ&s(AI?cSC%XGqmCVO#@=>;(j40_p<&b3jEb zl-3fZt$_$MngT>QKd@ zcylDCjBtNi+%k11z|&&k;^6mkCyj|o8q=j4ok=u8pgk10XjJ8>@=(qtHmVm9UFs6k?bdb6C(2$>%OIhO_6?M(|@y0g&pXXVCO5QkeL=@t~vp9b} z(xB!YoyNz!wTBCf@s4(8t$e}-p5c0{5L`iqYlV#ZF?>a?-fIl~)W?;*OhoSk$p_&> zR{~|I>WLa4zW_C3MqtVK| z(N9fWj1WKQQ2;$jpe7C%Pd7sB^&b$zt6>-*fI@7JTYDLAgZ{rZ>}%*~-W4 zz=M^R7z3EXfHsm0pN3mw|5>$Y$t8N-Wx(SQwDK`b$jC&}>1?6g@PYu9_8vR7&lMD4^ci zx%J=9eGr23y0vR@*AS|!{ym=8Jxmq4EWY=t@7a^}~8+1D8i@@5h&vqxp zwGJIfDoye}uS^3z_G*_U zd#|=e-I;jZS)e~;Y&5ZC?w@Y!2-WB_>mw?9gQYDq^3cmlmd5;eBE5{=o( zYW2n>(YMO$n{~unaXuM?=_jNVs=*7g2FYFN&80prEV}0LW2Ab`m#l>Tcb*!`9)zW1 zXX+PqrXq%orG=-QtX~&+zV}-E#eTP@x8qHU5`t%{b@|h)&T{JO3$$KW4!&TNeutfO z_pr{BOLm9Y29(auM@l+NvhygsFBFLv2ZdZAAS#*%GGQpnxxTZIxI@6*_F<(#G zMxp8ZWMiw1%1JBpE44}oM+az;zni}4N-Vj3dfI+)pM$UO^V9oJoid4J6~f3(kl*ip zJvxS|%T~TQ6%~w>f70}=U7Ty@dAl*Mu)eO{N!G_ac&r36+6XVs=InicT=Y2AMWEL7 zxlBts$m7>-_EdnQ1ggzGzfaX{GF9 z2JU0NGK5EqA2i)kl{~k0qFCe34CVKCr;OFoFhkrE^_n`CZI6htc}$qv^rXS%AZdHA zLn1t;2bpP?O5D!q_+fzHFb#wj^4&0{*q91u#nHRDrDXW_8yKU}7mY#u?cog|`-z1$ zHEAX0+yT4yz6zlo(zbU>Z;9WOy?I7JHqQL{wseQr%QS2HLRfA4%QyGZ=Ij!_`De?N z&a1zZx)I6sLvvHl_Ui*@@{@mEuX=Xqz8~<|6C2tcZ6KGdXnMBK-}eYT7q^H)oLAK_ z)wpqbHhxXJ_{G|g^hEjT=c~I-Ze08E1woOz1*v`qres+%kAC2>NtJ0Qv@XkD8|2PR zl6_POWCm%KvgMCIHjK~RY7&*)Q<2cYKQJ2UGsUb=wPnrusZ8QBi?+!d@~dgPuCR61 zp6AD_cxE)|`u60nZuFD4yQ{rOI1cHNtA%$v_FNlr`4q+D*wpfJt-&N`$$;F~^+MX^ zS5Ne_ui6)By$|n~bl|?erXSu@{33j*ekEdvc<@+?jPWjrbJd`cdw(fqm4v(U_|*9H zCp!Nf;x>seuK8i7i1$eKE%mH&dEfPaDV+5ukfdl`k)`QhvuaO$En21#$?5jlUdL$D z-7`Ir5_}m*MCb9;bbk|M0AB z=S|MYLxuwsHMD!d$@pe(f{zc|t80PXO&8DfWKf>$BF!=9x?E_Ucw{XjR)Dt>DSG;% zlycV3oo+k7<)x+y6?U}w?PTe0k`hW#oxpVD&^DI4?j@A+$tQ2i4(u|NjQY31_A zwd@f{v&v>R&=h{VnGG(QZnUbvJbd_siq|>TPV%F}2T>&o<7de5psgwXDH_nTn6T>=SQxxx8vSKqJ9feR5VofjFPhbkMrdr zinmrw6osybq5t5xU`uEG6#-r@^v}Kt^Kxw>y{7)FlkSfsZ{#~OJkSECBaQVcZZnF3 z;sUU1z4QoBSQyv)1D5TVL4z11q+PQr8gv^0D`N0y;D`a{^{i`OFxZN)re63xm*x>H z0)Zw001pFBLa@pwlt#_bJnffx$pRZFBb}t+qG5%({tg00$x~8%OESmlN{RiF;<`l0z^-6 zI00u|K)IVyte~-$sP;cVz=Z^~l=amK9^iRWBp_*p)lMzIO(**6TP7OtcxY-c8{q7h zcoL2xFEu5$IprE>367pI7$YTHv1@O#cuK&cS`us2JCsY`UPN4ZSoOxWQu^9g+0Q>e zgqfFSHop(~I^?jkx7|83BE^{ON%*jd`Acctv}GDzYica5;1y?{^yJ~>o@AZSE+hK=Go zqap?dt~1TpfAcb|lo;>K$^ezYNCU%fz4sE1Lz9PsBc_1R3=m9%Th;4uy7 zj4s@{Z=jf$d{B%Ff*EuWF`{cfVHli%(%wCr4qX<|V$(uU(^&uc!JyObpGAUprNp{l z3!vFx<6buqClTQ~J62nO8%ImY=HWxk%cYW#+$pb%w+}>)fBn{gu$?pv+bjIe!rDFN z2oFTf&ehi0KS3fz*mhkxI#s_zpeSy@ZV<2f;uYErsvHQ(NPnB>;6sE#*yz?y$d?pgm$Lqu_%O>vh14{Et3~Ql7{nZ4F&?!+-^_G zd?K+8av27rD|C|H0yCca9~Zam+6-2p?wGF9_$Bwz9N!eS+}^h1``VQiA%_G&stE$ zjSOGh&}*@e@U|JAI@D6+=-2&4aK^-WUpr~mNSPaj(3P7%h@(V+AEy7j}htwxYv{9YBQ+uOJ+dU;co4KmV_8xcGO|uE39u$AXO5MRD zn&WgK0V3qXf-LA+)uJrRc@y$#p^o>YQ9PlplH)Fy)Ax75rWMW6!%iwQY?$+RI0FPy zO%3b3?_ay2aU|$V!Q4>{fw{0SS{_-H@AOf9TqpP=ktm&KHV=ZMT{?v06L1=f} z{$=Zu%`o>C{`-6W+M^6N=C7zQw7{PzDX*-1WQ`4pST+ph>P!^f6Uqh1NU&hVA7lz9 zQZW`?_=+q^Jx=Vg^64W8+(;Rf0w1~)lCAgd(3J=-och6??TM?0&AZ`* zmlGC-S^w1aJUgZ!$4NH~^sK@x>Ww1QAC(=rc`gmv>e~!{7N}E!EPCBqh=GwX78HUD zYW`%wJ}x$hwZH{f)qz>D!2<`Y1)Q=H;#Q>2hV!rk{{zHm948Xg*}!M~%fta|66752 znH>poUcl7>WmC8{AEp|B3e$#%EF*tSQz*P%CCJRo4CEC-ulIUqAV{1;#7AmFfJXa3 zbO`WOh=*5#4H7Y&#{a?UXeR)keHRd5K-O!_V-W=QDPm$CNwU)_QeXZddJl;XoVSw+ zshZ=6r7DBdJF}JCJ!}MgilnV-I*;3^fd` zlv0h!+K=1_bjr)wLaKPXLKt(n&=jhNS?xG|XptiAC>PHke8v!iX(4}Mx46qp+ukO2 z?V(`WQ9iYtm4seeaMxCxjnkR=`)lk6+^FP_aR`KD_0iktlYBYO`+pye+`O;({PV$} z=QErpjk-6f2W$3@bradq{MLBm6geV^Y2Smm=+`_#_WLEC^#&K-V&CyJ>y7?LrXea0 z>CF{C-;l!ekVJpN)R%Xfw?J#rZ)JDS2NTcVodq8hW5wNdN&})bOwij8$^r)8 z&oVu0-+WKw_$BoYcdlQw3x!kUfXA3tIrTWyVO~YRZTNeB^&;vw;^7AzP~Zg}ZL8ys zD5{OnJ%ohf=(g8TsVUwt67k7}^giG2iT3^Y2!W>`UmE#ecT&$AFr%Lnc0PSp=!?Y% zOT_O2su9yg=UyRgZ@;R|i0w7|Yklp6SY^v(Zs{$k)$m#G5BI%JqnGgb!C0~4|}{T%}>2@$dd{aGB3M0?ufdz#@D2P^V5k>qwpteFHFDz}!^7IWq6bMHOpeD~*^`@3`O(D6Hu-g9WYsmXAj=zBf+#VxYn)osM+Ay*!|{9;eh zitK}w`!dmoh<=6W38D`ZeT3*&iJl~?6Fo)LAX+0@C)yxt5=C;hD}CVxnb}VL+C{wj zOW$IMF6p(C1hKXdae15HW6J#dBE7uEc42>zcDzA58u3T9 zUb|CuF}W*B4Qs3?@k$!jgV8hn*`hgba(`k)N*s^q4%v?UXsj35rs)BXanAqd#-|%FJ4tE+#f;&ScSEkhq*y?KZm2;9Gi&olxTCIRD5pOpCM$6Lgxc6j8M zXO|!_|Md%!9}O1BtR{EmxsAzP^J6K;{=XXD&!3laU!;mxle_g-Qts(}au8512mM%l z@n4Z$xF6s9qm+C9841X8*G{dJJMql!XuRKT%9$_y%8SQKEB#K|JAad`P)NJ8z2E(` zw6}ibYj2_ek9*`-k-e>29gSR->fF9BU6%FA^-j(co8}7Aw|@V^CG>Na|NI*=e=vgC zp}k5A)ej}C7Geh>NWP}M+>^i3lNYp5vHatd=k@Q29{#1C{4O#m%iluzPtvpyv`{@n z_B>AuQz#b(ZRdz=FR$NC$`=Um+|82z_-;w}lqBVSYn1Z5|146T+jExka(p%IiJtuT zDgQLJuYi*f!X8PqgOvX*15$Z6$t@N%oI9;g{O4~UJo?JT#%m|?_dl@v$%%iwvw-_M z&pp)1Egpz$}am$|MdYewk&=j!gy&%@Xd4I z{n_5X56z5x_Q1(o-`_cNE~>9(`i^OjwG7)17TN*teFAh1KWujVV#5p&DR~f}GC5X; zzuPLsa`D~+_uX~i`2ERn5TiO)t`^J5^tz=H16EGGI?-L4EIkb37zMN4^fS{epnP!=}@*95aF{R14+mWMQ(O z>}q9yxbEMP7ow{Pi1D6;o91dtYm_5~Z`NJEv4+q(EW{|f>j}q_(Na6YsEIP{BD78#6 z`~SLM@I0z7)NM2P+_4U|Eh*u1C*L=CUFZ7)-xK&g!21TTaeR*F^ZF@j0J~a^@@#*e z8YY$FxC)4+VmZ;=CH0u_94-~6<1&?m0Ht!_HWk%a2QkJAW!mZD;zS9=s1(Yn+!iq= z3T4_+A)^ok=u8&M6$RB;1u2u&R}XkLAifqir(j@LS!On+H=X-Pn)IS0F=Zqz+1TlAHUfzz}ao~PH0Ff{!IJ;`D?v;xPPv*)Z?OHaNOZdrlj zoN23>UE{q49MH7&TBtb8(VZUcC2>4OZ-# zoio!Q@%k9Jv*DbfI7y)$|Ao6DwkLF58;Ekqfu?{M`bOPMvHt8L96Xczrf&E(C{PIa zMkyc?PQB*NCz@AWB|Z`K;v}D4nubpcy*Mg|4SXBz#ZftM4EMoNIdpua501*=bfgas zJx2j&xBK7w}=eE8+J-x&wq?92w1Dg^;rc7MZ9=Ws8(Xb@*JWZUt&Ts3C^RZx)~y2LUOs-O(LG^_NQ7m!P#^8!fedU}Ml$ z7>``qTlvoRGkbpei_biX-^5}W`+w+qhTiVjq2<}8ZaSfFB{HG_ES2A!KrIdeQeIHg z;Tx#9ENKEU2GNeda!$iQfH$8ub+;3Gon$}n4yqmPu&%@BewN!%`;{8oF+P$0`zOoO zXDu6k`iF4=$izW_#vtq>O`!*!TCkupBG`^3-!6doRY5>W-LIq?5X2b7c;I|V>mL=B zk7J?z90Vky?-up2316r-T|2q6B1UC)HdRXyBl>1RH_yNdrboZ?Ls^m_KzI=T0rNIV zwO7evI|kw(#)9n{jX<|8@2W~RP=M_i#CUk&oNtBdd)T1b;dI)%(P-d@)N*l8xQJjo nQXWs@^9d|swrQxjJho#H{t@l_3i}&W9|Zyb2honS-%$MzX>dhc literal 0 HcmV?d00001 diff --git a/tests/fixtures/sleep_compressed.data b/tests/fixtures/sleep_compressed.data new file mode 100644 index 0000000000000000000000000000000000000000..ce8d3c47df8b8e5efc2b51391bf21b0aa307ec01 GIT binary patch literal 14620 zcmd5?eQXrR6<>b^yCz^TNsMR}HwviQOlORgkq9&CRRV|20^p8gB2Q*Y9z0SLR z@8UZf;+;LKJG!~IZ{ED$&dhuBxzoRH^Bq0C>)HkdFC0Yr*AjlL!b@nWMnMU=yyqXk zC`l?}yO3LrbPdwANY^2~1L=CC-AH?o-idSr(q5z+k@g|I3u!-6Ap0gfKR(xEW?{V+ zpiDXTC1pakX+0vuwDM9rk6~`F-VK!v$WuF&f>r+LBl#q1KYAxol2f}@B0zznKDBoh z%88Ob*@4VVl;mWe8q_CBaxfbO_Nlx~!Lpf2BlV5nPB?v@G zPX1Dl`b0@iel#29L`hEmbv??7lAQc*4$6s=ocs~pPasNi55HL;yO2M4cAP7C?b9NZ z+8tk4AYMB)2&CuX#RPZ{L`$^?qUwmM6f}w-Hs{$|;qE*gt1$3|Ek~EVFLX!hh37YQ zeKPjS${}IJ=!-`H*EpR3zn@ta@1-qiWcSB|IN?TVbsUcB{- zwWq{Wm+oDi{c7&h=90S?3mrnc@a+>EiCO;luZ& z|FiC$n~v`JRBp#p3zTz1-;k95}M?qp|d}ukG3Ylll95 z>K}Y|Ud^^gV}C!k=fax@)Z1@c`TM>HqrZKqbJ5ppjbFWWu<3y_+fMx8v*&VS z_0h#QRto34FLy~ld~fX=-Cx-BXoH@7;H7_k|B2bRG`2R2RdsD|RxC>X^N_UY6Q}!x z)d$Y){Y>?&7yq{RbE{6haQ>z4w=a*?&wlyUInS;7`E|X2*?M-M;cWK2+0>X?svqlr zSszjKvx(OpPQUQeA76U$>BjGT_bc;SuSAw!TF}d!&+D(XLJM{j>c4skf24%JSi*N= zN22~D;sfE0m+&V`^8XI`K^#yzvEyt-zjzosii?h0jRV>abgR!JA3>Z0O`c%<29Gwh zBE|g8$Zth{1bI{!#*nA+X}pBrhdhnzk0JjUmbWwJ0b?dF>hIz4@4H1WgUB2N;8(hKzB8zYor5)ttzISzDA}G z5dlW@YL)&FT@vRpOyc+`U?^tBvQ@`X)2+}Vt!den{h$FxbjXj!a9-%f!1joFufNkt*`o!r{S+3d1zOW#aJ}%tN){Vfxs{>vKWhW z7Sebg03#k*!J}Hx0gSdtJXT2HcmRwe-ed?FX*9!&Ve4zT%9Qx#|o|}mb zFK9pf7{h>e1T4kk9hI0e3t$J4#gwheLzbx-E@L>t4NOO5QE^<^RV6mbs~jkTaAPr! zHMElphAHK8BKsd4X0xLtE0*SRtr{weahxpYI<=|@2b#xKGaM6cO%cwZmbNS@CA+R_ zk4k<9!|Ea&ZCD@H1~ke4P*_ofW9-iGnO&f{g*l+AODUJLnFU&_dN>xRHwyxcOW;34 zmTjgu>%`RaXC;7>acp3@!%a+6WYIE*)gVYTK04-rJ1lc{QdW-t=Dhypnx@WJ?oi1z zMHXFKR@4CF&jQ5B8ql{S*-o(pjsRP3kwwH&Qs#)Senqc@Gpte^|Gi5P=1hUZv)F($ zvC{JJEI1mbz~NbR%$ov-XK^}z3Y;WM^BWqOg2>`TyLb2nVfo<*M`RIkv<&B_4Aw(= zoPgsG^!J4+GUxT z5y}5qHbZaThO!TFxxF?-749B1ok9w@JnaETMTw)r=}s5 zD~L?1tvwlN0|yvE_itgYThM(#nCljFtsLgM1zk^uxo$z%Tw$(T&~-_e>lSqG4s+dt z&TnwV4dxQ~qci4m%y02zqH}qG84k)6^m{FXCT)II0rKj&E??h;Wff4 zPjdn;LFKU_9ehV*6yZ%kLqW)vyK62)F)ii zlBG;mcQs2_CDm|k&6g2HhQ;$8im4TX$et`GC9!)^g*3kpfKiTeIGVAOH3akKovLJJ zT`TMFlW)1ok#QA?T>{k1G~btNC`a4!;CG@lOB&F0b{&AV3xo6>M5a*=yLhIMoNUS& zJ&vJ}Nm1Zoh#C`Wrhe+lXz37$*0to#&2_K?5JD7k~|-s+&K`*&8r zNDK@FDhXiZ?=eW~ZZ?8R`EUO$%})?n=5qK4>$iShJ1H-eqb&X*kLsqJb|hW1CcR`D z2~du5w1?#m+nUQihn1@wMm8hK=`>ug_6B#Kg9wx(;PNED9)+f> WKkswQZ|wE?AhQ2*lq2Z9s{aAJdH2Eq literal 0 HcmV?d00001 diff --git a/tests/fixtures/sleep_compressed.pipe.data b/tests/fixtures/sleep_compressed.pipe.data new file mode 100644 index 0000000000000000000000000000000000000000..294297a46a688ad469a2fe2a76484c663b52a8ff GIT binary patch literal 31951 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 z Date: Sat, 20 Dec 2025 00:19:41 +0100 Subject: [PATCH 3/3] Add tests between file/pipe mode --- tests/compression_e2e.rs | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/tests/compression_e2e.rs b/tests/compression_e2e.rs index c9f6317..816c297 100644 --- a/tests/compression_e2e.rs +++ b/tests/compression_e2e.rs @@ -1,4 +1,4 @@ -use linux_perf_data::{CompressionInfo, PerfFileReader, PerfFileRecord}; +use linux_perf_data::{CompressionInfo, Error, PerfFileReader, PerfFileRecord}; use std::fs::File; use std::io::BufReader; @@ -378,3 +378,37 @@ fn test_zstd_feature_disabled_error() { ); } } + +/// Test that parse_pipe fails with a clear error when given file format data +#[test] +fn test_parse_pipe_with_file_format_fails() { + let file = File::open("tests/fixtures/sleep.data").unwrap(); + let reader = BufReader::new(file); + + let result = PerfFileReader::parse_pipe(reader); + assert!( + matches!(result, Err(Error::FileFormatDetectedInPipeMode)), + "Expected FileFormatDetectedInPipeMode error" + ); +} + +/// Test that parse_file transparently handles pipe format by falling back to parse_pipe +#[test] +fn test_parse_file_with_pipe_format_falls_back() { + let file = File::open("tests/fixtures/sleep_compressed.pipe.data").unwrap(); + let reader = BufReader::new(file); + + // parse_file should detect pipe format and fall back to parse_pipe + let PerfFileReader { + mut perf_file, + mut record_iter, + } = PerfFileReader::parse_file(reader) + .expect("parse_file should handle pipe format transparently"); + + // Should be able to read records + let mut count = 0; + while let Some(_record) = record_iter.next_record(&mut perf_file).unwrap() { + count += 1; + } + assert!(count > 0, "Should have read some records"); +}