Skip to main content

sudachi/dic/
header.rs

1/*
2 * Copyright (c) 2021-2024 Works Applications Co., Ltd.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use nom::{bytes::complete::take, number::complete::le_u64};
18use std::io::Write;
19use std::time::{Duration, SystemTime};
20use thiserror::Error;
21
22use super::read::SudachiNomResult;
23use crate::error::{SudachiError, SudachiResult};
24
25/// Sudachi error
26#[derive(Error, Debug, Eq, PartialEq)]
27#[non_exhaustive]
28pub enum HeaderError {
29    #[error("Invalid header version")]
30    InvalidVersion,
31
32    #[error("Invalid system dictionary version")]
33    InvalidSystemDictVersion,
34
35    #[error("Invalid user dictionary version")]
36    InvalidUserDictVersion,
37
38    #[error("Unable to parse")]
39    CannotParse,
40}
41
42/// Header version
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub enum HeaderVersion {
45    SystemDict(SystemDictVersion),
46    UserDict(UserDictVersion),
47}
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub enum SystemDictVersion {
50    // we cannot set value since value can be larger than isize
51    Version1,
52    Version2,
53}
54
55impl HeaderVersion {
56    pub fn to_u64(&self) -> u64 {
57        #[allow(unreachable_patterns)]
58        match self {
59            HeaderVersion::SystemDict(SystemDictVersion::Version1) => {
60                HeaderVersion::SYSTEM_DICT_VERSION_1
61            }
62            HeaderVersion::SystemDict(SystemDictVersion::Version2) => {
63                HeaderVersion::SYSTEM_DICT_VERSION_2
64            }
65            HeaderVersion::UserDict(UserDictVersion::Version1) => {
66                HeaderVersion::USER_DICT_VERSION_1
67            }
68            HeaderVersion::UserDict(UserDictVersion::Version2) => {
69                HeaderVersion::USER_DICT_VERSION_2
70            }
71            HeaderVersion::UserDict(UserDictVersion::Version3) => {
72                HeaderVersion::USER_DICT_VERSION_3
73            }
74            _ => panic!("unknown version {:?}", self),
75        }
76    }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub enum UserDictVersion {
81    Version1,
82    Version2,
83    Version3,
84}
85impl HeaderVersion {
86    /// the first version of system dictionaries
87    const SYSTEM_DICT_VERSION_1: u64 = 0x7366d3f18bd111e7;
88    /// the second version of system dictionaries
89    const SYSTEM_DICT_VERSION_2: u64 = 0xce9f011a92394434;
90    /// the first version of user dictionaries
91    const USER_DICT_VERSION_1: u64 = 0xa50f31188bd211e7;
92    /// the second version of user dictionaries
93    const USER_DICT_VERSION_2: u64 = 0x9fdeb5a90168d868;
94    /// the third version of user dictionaries
95    const USER_DICT_VERSION_3: u64 = 0xca9811756ff64fb0;
96
97    pub fn from_u64(v: u64) -> Option<Self> {
98        match v {
99            HeaderVersion::SYSTEM_DICT_VERSION_1 => {
100                Some(Self::SystemDict(SystemDictVersion::Version1))
101            }
102            HeaderVersion::SYSTEM_DICT_VERSION_2 => {
103                Some(Self::SystemDict(SystemDictVersion::Version2))
104            }
105            HeaderVersion::USER_DICT_VERSION_1 => Some(Self::UserDict(UserDictVersion::Version1)),
106            HeaderVersion::USER_DICT_VERSION_2 => Some(Self::UserDict(UserDictVersion::Version2)),
107            HeaderVersion::USER_DICT_VERSION_3 => Some(Self::UserDict(UserDictVersion::Version3)),
108            _ => None,
109        }
110    }
111}
112
113/// Dictionary header
114///
115/// Contains version, create_time, and description
116#[derive(Debug, Clone, Eq, PartialEq)]
117pub struct Header {
118    pub version: HeaderVersion,
119    pub create_time: u64,
120    pub description: String,
121}
122
123impl Default for Header {
124    fn default() -> Self {
125        Self::new()
126    }
127}
128
129impl Header {
130    const DESCRIPTION_SIZE: usize = 256;
131    pub const STORAGE_SIZE: usize = 8 + 8 + Header::DESCRIPTION_SIZE;
132
133    /// Creates new system dictionary header
134    /// Its version field should be modified to create user dictionary header
135    pub fn new() -> Self {
136        let unix_time = SystemTime::now()
137            .duration_since(SystemTime::UNIX_EPOCH)
138            .expect("unix time error");
139
140        Self {
141            version: HeaderVersion::SystemDict(SystemDictVersion::Version2),
142            create_time: unix_time.as_secs(),
143            description: String::new(),
144        }
145    }
146
147    /// Set creation time
148    pub fn set_time(&mut self, time: SystemTime) -> SystemTime {
149        let unix_time = time
150            .duration_since(SystemTime::UNIX_EPOCH)
151            .expect("unix time error");
152
153        let old_unix_secs = std::mem::replace(&mut self.create_time, unix_time.as_secs());
154
155        SystemTime::UNIX_EPOCH + Duration::from_secs(old_unix_secs)
156    }
157
158    /// Creates a new header from a dictionary bytes
159    pub fn parse(bytes: &[u8]) -> Result<Header, HeaderError> {
160        let (_rest, (version, create_time, description)) =
161            header_parser(bytes).map_err(|_| HeaderError::CannotParse)?;
162
163        let version = HeaderVersion::from_u64(version).ok_or(HeaderError::InvalidVersion)?;
164
165        Ok(Header {
166            version,
167            create_time,
168            description,
169        })
170    }
171
172    /// Returns if this header version has grammar
173    pub fn has_grammar(&self) -> bool {
174        matches!(
175            self.version,
176            HeaderVersion::SystemDict(_)
177                | HeaderVersion::UserDict(UserDictVersion::Version2)
178                | HeaderVersion::UserDict(UserDictVersion::Version3)
179        )
180    }
181
182    /// Returns if this header version has synonym group ids
183    pub fn has_synonym_group_ids(&self) -> bool {
184        matches!(
185            self.version,
186            HeaderVersion::SystemDict(SystemDictVersion::Version2)
187                | HeaderVersion::UserDict(UserDictVersion::Version3)
188        )
189    }
190
191    pub fn write_to<W: Write>(&self, w: &mut W) -> SudachiResult<usize> {
192        if self.description.len() > Header::DESCRIPTION_SIZE {
193            return Err(SudachiError::InvalidDataFormat(
194                Header::DESCRIPTION_SIZE,
195                self.description.clone(),
196            ));
197        }
198
199        w.write_all(&self.version.to_u64().to_le_bytes())?;
200        w.write_all(&self.create_time.to_le_bytes())?;
201        w.write_all(self.description.as_bytes())?;
202        for _ in 0..Header::DESCRIPTION_SIZE - self.description.len() {
203            w.write_all(&[0])?;
204        }
205        Ok(Header::STORAGE_SIZE)
206    }
207}
208
209/// Create String from UTF-8 bytes up to NUL byte or end of slice (whichever is first)
210fn nul_terminated_str_from_slice(buf: &[u8]) -> String {
211    let str_bytes: &[u8] = if let Some(nul_idx) = buf.iter().position(|b| *b == 0) {
212        &buf[..nul_idx]
213    } else {
214        buf
215    };
216    String::from_utf8_lossy(str_bytes).to_string()
217}
218
219fn description_parser(input: &[u8]) -> SudachiNomResult<&[u8], String> {
220    let (rest, description_bytes) = take(Header::DESCRIPTION_SIZE)(input)?;
221    Ok((rest, nul_terminated_str_from_slice(description_bytes)))
222}
223
224fn header_parser(input: &[u8]) -> SudachiNomResult<&[u8], (u64, u64, String)> {
225    nom::sequence::tuple((le_u64, le_u64, description_parser))(input)
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231
232    fn header_from_parts<T: AsRef<[u8]>>(
233        version: u64,
234        create_time: u64,
235        description: T,
236    ) -> Result<Header, HeaderError> {
237        let mut bytes = Vec::new();
238        bytes.extend(&version.to_le_bytes());
239        bytes.extend(&create_time.to_le_bytes());
240        bytes.extend(description.as_ref());
241
242        Header::parse(&bytes)
243    }
244
245    #[test]
246    fn graceful_failure() {
247        // Too small
248        assert_eq!(Header::parse(&[]), Err(HeaderError::CannotParse));
249
250        assert_eq!(
251            header_from_parts(42, 0, vec![0; Header::DESCRIPTION_SIZE]),
252            Err(HeaderError::InvalidVersion)
253        );
254    }
255
256    #[test]
257    fn simple_header() {
258        let mut description: Vec<u8> = Vec::new();
259        let description_str = "My Description";
260        description.extend(description_str.bytes());
261        description.extend(&vec![0; Header::DESCRIPTION_SIZE]);
262
263        assert_eq!(
264            header_from_parts(HeaderVersion::SYSTEM_DICT_VERSION_1, 1337, &description),
265            Ok(Header {
266                version: HeaderVersion::SystemDict(SystemDictVersion::Version1),
267                description: description_str.to_string(),
268                create_time: 1337,
269            })
270        );
271    }
272
273    #[test]
274    fn write_system() {
275        let header = Header::new();
276        let mut data: Vec<u8> = Vec::new();
277        assert_eq!(header.write_to(&mut data).unwrap(), Header::STORAGE_SIZE);
278        let header2 = Header::parse(&data).unwrap();
279        assert_eq!(header, header2);
280    }
281
282    #[test]
283    fn write_user() {
284        let mut header = Header::new();
285        header.version = HeaderVersion::UserDict(UserDictVersion::Version3);
286        header.description = String::from("some great header");
287        let mut data: Vec<u8> = Vec::new();
288        assert_eq!(header.write_to(&mut data).unwrap(), Header::STORAGE_SIZE);
289        let header2 = Header::parse(&data).unwrap();
290        assert_eq!(header, header2);
291    }
292}