ip2location_bin_format/header/
schema.rs

1use super::HEADER_LEN;
2use crate::{index::INDEX_LEN, record_field::RecordFields};
3
4//
5#[non_exhaustive]
6#[derive(Debug, Clone, Copy, Default)]
7pub struct Schema {
8    pub sub_type: SchemaSubType,
9    pub num_record_fields: u8,
10    pub date: (u8, u8, u8),
11    pub v4_records_count: u32,
12    pub v4_records_position_start: u32,
13    pub v6_records_count: u32,
14    pub v6_records_position_start: u32,
15    pub v4_index_position_start: u32,
16    pub v6_index_position_start: u32,
17    pub r#type: SchemaType,
18    // TODO, what's meaning?
19    pub license_code: u8,
20    pub total_size: u32,
21}
22
23impl Schema {
24    pub fn record_fields(&self) -> Option<RecordFields> {
25        RecordFields::try_from((self.r#type, self.sub_type)).ok()
26    }
27
28    pub fn has_v6(&self) -> bool {
29        self.v6_records_count > 0
30    }
31
32    pub fn v4_index_seek_from_start(&self) -> u64 {
33        self.v4_index_position_start as u64 - 1
34    }
35
36    pub fn v6_index_seek_from_start(&self) -> Option<u64> {
37        if self.has_v6() {
38            Some(self.v6_index_position_start as u64 - 1)
39        } else {
40            None
41        }
42    }
43
44    pub fn v4_records_seek_from_start(&self) -> u64 {
45        self.v4_records_position_start as u64 - 1
46    }
47
48    pub fn v6_records_seek_from_start(&self) -> Option<u64> {
49        if self.has_v6() {
50            Some(self.v6_records_position_start as u64 - 1)
51        } else {
52            None
53        }
54    }
55}
56
57impl Schema {
58    pub fn verify(&self) -> Result<(), VerifyError> {
59        //
60        let record_fields = self
61            .record_fields()
62            .ok_or(VerifyError::SubTypeInvalid(self.sub_type))?;
63
64        if record_fields.len() != self.num_record_fields as usize {
65            return Err(VerifyError::NumRecordFieldsMismatch(self.num_record_fields));
66        }
67
68        //
69        if !self.has_v6() {
70            if self.v6_index_position_start != 1 {
71                return Err(VerifyError::Other(
72                    "v6_index_position_start should eq 1 when v6_records_count is 0".into(),
73                ));
74            }
75
76            if self.v6_records_position_start != 1 {
77                return Err(VerifyError::Other(
78                    "v6_records_position_start should eq 1 when v6_records_count is 0".into(),
79                ));
80            }
81        }
82
83        //
84        let mut cur_position: u32 = 0;
85        cur_position += HEADER_LEN + 1;
86
87        if self.v4_index_position_start != cur_position {
88            return Err(VerifyError::XPositionStartInvalid(
89                format!(
90                    "v4_index_position_start mismatch {} {}",
91                    self.v4_index_position_start, cur_position
92                )
93                .into(),
94            ));
95        }
96        cur_position += INDEX_LEN;
97
98        if self.has_v6() {
99            if self.v6_index_position_start != cur_position {
100                return Err(VerifyError::XPositionStartInvalid(
101                    format!(
102                        "v6_index_position_start mismatch {} {}",
103                        self.v6_index_position_start, cur_position
104                    )
105                    .into(),
106                ));
107            }
108            cur_position += INDEX_LEN;
109        }
110
111        if self.v4_records_position_start != cur_position {
112            return Err(VerifyError::XPositionStartInvalid(
113                format!(
114                    "v4_records_position_start mismatch {} {}",
115                    self.v4_records_position_start, cur_position
116                )
117                .into(),
118            ));
119        }
120        cur_position += record_fields.records_bytes_len_for_ipv4(self.v4_records_count);
121
122        if self.has_v6() {
123            if self.v6_records_position_start != cur_position {
124                return Err(VerifyError::XPositionStartInvalid(
125                    format!(
126                        "v6_records_position_start mismatch {} {}",
127                        self.v6_records_position_start, cur_position
128                    )
129                    .into(),
130                ));
131            }
132            cur_position += record_fields.records_bytes_len_for_ipv6(self.v6_records_count);
133        }
134
135        if cur_position >= self.total_size {
136            return Err(VerifyError::TotalSizeTooSmall(self.total_size));
137        }
138
139        Ok(())
140    }
141}
142
143//
144#[derive(Debug)]
145pub enum VerifyError {
146    SubTypeInvalid(SchemaSubType),
147    NumRecordFieldsMismatch(u8),
148    XPositionStartInvalid(Box<str>),
149    TotalSizeTooSmall(u32),
150    Other(Box<str>),
151}
152
153impl core::fmt::Display for VerifyError {
154    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
155        write!(f, "{self:?}")
156    }
157}
158
159impl std::error::Error for VerifyError {}
160
161//
162//
163//
164#[derive(Debug, Clone, Copy, Default)]
165pub struct SchemaSubType(pub u8);
166
167//
168//
169//
170#[repr(u8)]
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub enum SchemaType {
173    None,
174    IP2Location,
175    IP2Proxy,
176}
177
178impl Default for SchemaType {
179    fn default() -> Self {
180        Self::None
181    }
182}
183
184impl TryFrom<u8> for SchemaType {
185    type Error = ();
186
187    fn try_from(value: u8) -> Result<Self, Self::Error> {
188        match value {
189            1 => Ok(Self::IP2Location),
190            2 => Ok(Self::IP2Proxy),
191            _ => Err(()),
192        }
193    }
194}
195
196impl SchemaType {
197    pub fn is_ip2location(&self) -> bool {
198        matches!(self, Self::IP2Location | Self::None)
199    }
200
201    pub fn is_ip2proxy(&self) -> bool {
202        matches!(self, Self::IP2Proxy)
203    }
204}