1use nom::number::complete::le_u64;
18use std::fmt::Display;
19use std::time::Duration;
20use thiserror::Error;
21
22use super::header::HeaderVersion;
23use super::read::{
24 error::SudachiNomResult,
25 utf8_string::utf8_string,
26 varint::{varint32, varint64},
27};
28use crate::error::SudachiResult;
29
30static MAGIC_BYTES: &[u8] = b"SudachiBinaryDic";
31
32#[derive(Error, Debug, Eq, PartialEq)]
34#[non_exhaustive]
35pub enum DescriptionError {
36 #[error("Unable to parse")]
37 CannotParse,
38
39 #[error("Invalid magic bytes")]
40 InvalidMagicBytes,
41
42 #[error("V0 version")]
43 V0Version,
44
45 #[error("Invalid header version {0}")]
46 InvalidVersion(u64),
47
48 #[error("Dictionary part not found: {0}")]
49 DictionaryPartNotFound(String),
50
51 #[error("Dictionary part out of range: {0}..{1}")]
52 DictionaryPartOutOfRange(usize, usize),
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum Block {
58 ConnectionMatrix,
62 POSTable,
64 TRIEIndex,
67 WordPointers,
69 Entries,
71 Strings,
73 ReferenceIdTable,
75}
76
77impl Block {
78 fn as_string_representation(&self) -> &str {
81 match self {
82 Block::ConnectionMatrix => "ConnMatrix",
83 Block::POSTable => "POS",
84 Block::TRIEIndex => "TrieIndex",
85 Block::WordPointers => "WordPointers",
86 Block::Entries => "Entries",
87 Block::Strings => "Strings",
88 Block::ReferenceIdTable => "ReferenceIdTable",
89 }
90 }
91}
92
93impl Display for Block {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 write!(f, "{}", self.as_string_representation())
96 }
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct BlockInfo {
102 name: String,
103 start: u64,
104 size: u64,
105}
106
107impl BlockInfo {
108 pub fn parse(buf: &[u8]) -> SudachiNomResult<&[u8], Self> {
110 let (rest, (name, start, size)) =
111 nom::sequence::tuple((utf8_string, varint64, varint64))(buf)?;
112 Ok((rest, Self { name, start, size }))
113 }
114
115 pub fn name(&self) -> &str {
116 &self.name
117 }
118
119 pub fn start(&self) -> u64 {
120 self.start
121 }
122
123 pub fn size(&self) -> u64 {
124 self.size
125 }
126
127 pub fn end(&self) -> u64 {
128 self.start + self.size
129 }
130}
131
132#[derive(Debug, Clone, PartialEq, Eq)]
134pub struct Description {
135 creation_time: Duration,
136 comment: String,
137 signature: String,
138 reference: String,
139 blocks: Vec<BlockInfo>,
140 flags: u64,
141 num_total_entries: u32,
142 num_indexed_entries: u32,
143}
144
145impl Description {
146 pub fn load(buf: &[u8]) -> SudachiResult<Self> {
147 Self::check_v0_format(buf)?;
148
149 let rest = Self::check_magic(buf)?;
150 let (rest, version) = le_u64(rest)?;
151 if version == 1 {
152 Self::load_v1(rest)
153 } else {
154 Err(DescriptionError::InvalidVersion(version).into())
155 }
156 }
157
158 fn check_v0_format(buf: &[u8]) -> SudachiResult<()> {
162 let (_rest, version) = le_u64(buf)?;
163 let v0_version = HeaderVersion::from_u64(version);
164 match v0_version {
165 Some(_) => Err(DescriptionError::V0Version.into()),
166 None => Ok(()),
167 }
168 }
169
170 fn check_magic(buf: &[u8]) -> SudachiResult<&[u8]> {
174 let (rest, first_bytes) = nom::bytes::complete::take(MAGIC_BYTES.len())(buf)?;
175 match MAGIC_BYTES
176 .iter()
177 .zip(first_bytes.iter())
178 .position(|(a, b)| a != b)
179 {
180 Some(_) => Err(DescriptionError::InvalidMagicBytes.into()),
181 None => Ok(rest),
182 }
183 }
184
185 fn load_v1(buf: &[u8]) -> SudachiResult<Self> {
191 let (
192 _rest,
193 (
194 creation_time_secs,
195 flags,
196 comment,
197 signature,
198 reference,
199 num_indexed_entries,
200 num_total_entries,
201 blocks,
202 ),
203 ) = nom::sequence::tuple((
204 le_u64,
205 le_u64,
206 utf8_string,
207 utf8_string,
208 utf8_string,
209 varint32,
210 varint32,
211 nom::multi::length_count(varint32, BlockInfo::parse),
212 ))(buf)?;
213
214 Ok(Self {
215 creation_time: Duration::from_secs(creation_time_secs),
216 comment,
217 signature,
218 reference,
219 blocks,
220 flags,
221 num_total_entries,
222 num_indexed_entries,
223 })
224 }
225
226 pub fn creation_time(&self) -> Duration {
227 self.creation_time
228 }
229
230 pub fn comment(&self) -> &str {
231 &self.comment
232 }
233
234 pub fn signature(&self) -> &str {
235 &self.signature
236 }
237
238 pub fn reference(&self) -> &str {
239 &self.reference
240 }
241
242 pub fn is_system_dictionary(&self) -> bool {
243 self.reference.is_empty()
244 }
245
246 pub fn is_user_dictionary(&self) -> bool {
247 !self.reference.is_empty()
248 }
249
250 pub fn blocks(&self) -> &[BlockInfo] {
251 &self.blocks
252 }
253
254 pub fn slice_or_none<'a>(
255 &self,
256 buf: &'a [u8],
257 block: Block,
258 ) -> SudachiResult<Option<&'a [u8]>> {
259 let block_name = block.as_string_representation();
260 match self.blocks.iter().find(|block| block.name() == block_name) {
261 Some(block) => {
262 let start = block.start() as usize;
263 let end = block.end() as usize;
264 if buf.len() < end {
265 return Err(DescriptionError::DictionaryPartOutOfRange(start, end).into());
266 }
267 Ok(Some(&buf[start..end]))
268 }
269 None => Ok(None),
270 }
271 }
272
273 pub fn slice<'a>(&self, buf: &'a [u8], block: Block) -> SudachiResult<&'a [u8]> {
274 self.slice_or_none(buf, block)?
275 .ok_or_else(|| DescriptionError::DictionaryPartNotFound(block.to_string()).into())
276 }
277
278 pub fn is_runtime_costs(&self) -> bool {
279 self.flags & 0x1 != 0
280 }
281
282 pub fn num_total_entries(&self) -> u32 {
283 self.num_total_entries
284 }
285
286 pub fn num_indexed_entries(&self) -> u32 {
287 self.num_indexed_entries
288 }
289}