ip2location_bin_format/header/
schema.rs1use super::HEADER_LEN;
2use crate::{index::INDEX_LEN, record_field::RecordFields};
3
4#[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 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 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 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 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#[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#[derive(Debug, Clone, Copy, Default)]
165pub struct SchemaSubType(pub u8);
166
167#[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}