maxminddb/within.rs
1//! Network iteration types.
2
3use std::cmp::Ordering;
4use std::net::IpAddr;
5
6use crate::decoder;
7use crate::error::MaxMindDbError;
8use crate::reader::Reader;
9use crate::result::{LookupResult, LookupSource, NetworkKind};
10
11/// Options for network iteration.
12///
13/// Controls which networks are yielded when iterating over the database
14/// with [`Reader::within()`] or [`Reader::networks()`].
15///
16/// # Example
17///
18/// ```
19/// use maxminddb::WithinOptions;
20///
21/// // Default options (skip aliases, skip networks without data, include empty values)
22/// let opts = WithinOptions::default();
23///
24/// // Include aliased networks (IPv4 networks via IPv6 aliases)
25/// let opts = WithinOptions::default().include_aliased_networks();
26///
27/// // Skip empty values and include networks without data
28/// let opts = WithinOptions::default()
29/// .skip_empty_values()
30/// .include_networks_without_data();
31/// ```
32#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
33pub struct WithinOptions {
34 /// Include IPv4 networks multiple times when accessed via IPv6 aliases.
35 include_aliased_networks: bool,
36 /// Include networks that have no associated data record.
37 include_networks_without_data: bool,
38 /// Skip networks whose data is an empty map or empty array.
39 skip_empty_values: bool,
40}
41
42impl WithinOptions {
43 /// Include IPv4 networks multiple times when accessed via IPv6 aliases.
44 ///
45 /// In IPv6 databases, IPv4 networks are stored at `::0/96`. However, the
46 /// same data is accessible through several IPv6 prefixes (e.g.,
47 /// `::ffff:0:0/96` for IPv4-mapped IPv6). By default, these aliases are
48 /// skipped to avoid yielding the same network multiple times.
49 ///
50 /// When enabled, the iterator will yield these aliased networks.
51 #[must_use]
52 pub fn include_aliased_networks(mut self) -> Self {
53 self.include_aliased_networks = true;
54 self
55 }
56
57 /// Include networks that have no associated data record.
58 ///
59 /// Some tree nodes point to "no data" (the node_count sentinel). By default
60 /// these are skipped. When enabled, these networks are yielded and
61 /// [`LookupResult::has_data()`] returns `false` for them.
62 #[must_use]
63 pub fn include_networks_without_data(mut self) -> Self {
64 self.include_networks_without_data = true;
65 self
66 }
67
68 /// Skip networks whose data is an empty map or empty array.
69 ///
70 /// Some databases store empty maps `{}` or empty arrays `[]` for records
71 /// without meaningful data. This option filters them out.
72 #[must_use]
73 pub fn skip_empty_values(mut self) -> Self {
74 self.skip_empty_values = true;
75 self
76 }
77}
78
79#[derive(Debug)]
80pub(crate) struct WithinNode {
81 pub(crate) node: usize,
82 pub(crate) ip_int: IpInt,
83 pub(crate) prefix_len: usize,
84}
85
86/// Iterator over IP networks within a CIDR range.
87///
88/// Created by [`Reader::within()`](crate::Reader::within) or
89/// [`Reader::networks()`](crate::Reader::networks). Yields
90/// [`LookupResult`] for each network in the database that falls
91/// within the specified range.
92///
93/// Networks are yielded in depth-first order through the search tree.
94/// Use [`LookupResult::decode()`](crate::LookupResult::decode) to
95/// deserialize the data for each result.
96///
97/// # Example
98///
99/// ```
100/// use maxminddb::{Reader, WithinOptions, geoip2};
101///
102/// let reader = Reader::open_readfile("test-data/test-data/GeoIP2-City-Test.mmdb").unwrap();
103/// for result in reader.within("89.160.20.0/24".parse().unwrap(), Default::default()).unwrap() {
104/// let lookup = result.unwrap();
105/// if let Some(city) = lookup.decode::<geoip2::City>().unwrap() {
106/// println!("{}: {:?}", lookup.network().unwrap(), city.city.names.english);
107/// }
108/// }
109/// ```
110#[derive(Debug)]
111pub struct Within<'de, S: AsRef<[u8]>> {
112 pub(crate) reader: &'de Reader<S>,
113 pub(crate) node_count: usize,
114 pub(crate) stack: Vec<WithinNode>,
115 pub(crate) options: WithinOptions,
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub(crate) enum IpInt {
120 V4(u32),
121 V6(u128),
122}
123
124impl IpInt {
125 pub(crate) fn new(ip_addr: IpAddr) -> Self {
126 match ip_addr {
127 IpAddr::V4(v4) => IpInt::V4(v4.into()),
128 IpAddr::V6(v6) => IpInt::V6(v6.into()),
129 }
130 }
131
132 #[inline(always)]
133 pub(crate) fn get_bit(&self, index: usize) -> bool {
134 match self {
135 IpInt::V4(ip) => (ip >> (31 - index)) & 1 == 1,
136 IpInt::V6(ip) => (ip >> (127 - index)) & 1 == 1,
137 }
138 }
139
140 #[inline(always)]
141 pub(crate) fn set_bit(&mut self, index: usize) {
142 match self {
143 IpInt::V4(ip) => *ip |= 1 << (31 - index),
144 IpInt::V6(ip) => *ip |= 1 << (127 - index),
145 }
146 }
147
148 pub(crate) fn bit_count(&self) -> usize {
149 match self {
150 IpInt::V4(_) => 32,
151 IpInt::V6(_) => 128,
152 }
153 }
154
155 pub(crate) fn is_ipv4_in_ipv6(&self) -> bool {
156 match self {
157 IpInt::V4(_) => false,
158 IpInt::V6(ip) => *ip <= 0xFFFFFFFF,
159 }
160 }
161}
162
163impl<'de, S: AsRef<[u8]>> Iterator for Within<'de, S> {
164 type Item = Result<LookupResult<'de, S>, MaxMindDbError>;
165
166 fn next(&mut self) -> Option<Self::Item> {
167 while let Some(current) = self.stack.pop() {
168 let bit_count = current.ip_int.bit_count();
169
170 // Skip networks that are aliases for the IPv4 network (unless option is set)
171 if !self.options.include_aliased_networks
172 && self.reader.ipv4_start != 0
173 && current.node == self.reader.ipv4_start
174 && bit_count == 128
175 && !current.ip_int.is_ipv4_in_ipv6()
176 {
177 continue;
178 }
179
180 match current.node.cmp(&self.node_count) {
181 Ordering::Greater => {
182 // This is a data node, emit it and we're done (until the following next call)
183 let ip_addr = ip_int_to_addr(¤t.ip_int);
184
185 // Resolve the pointer to a data offset
186 let data_offset = match self.reader.resolve_data_pointer(current.node) {
187 Ok(offset) => offset,
188 Err(e) => return Some(Err(e)),
189 };
190
191 // Check if we should skip empty values
192 if self.options.skip_empty_values {
193 match self.is_empty_value_at(data_offset) {
194 Ok(true) => continue, // Skip empty value
195 Ok(false) => {} // Not empty, proceed
196 Err(e) => return Some(Err(e)),
197 }
198 }
199
200 let network_kind = match current.ip_int {
201 IpInt::V4(_) => NetworkKind::V4,
202 IpInt::V6(_)
203 if current.ip_int.is_ipv4_in_ipv6()
204 && self.reader.has_ipv4_subtree()
205 && current.prefix_len >= self.reader.ipv4_start_bit_depth =>
206 {
207 NetworkKind::V4InV6Subtree
208 }
209 IpInt::V6(_) => NetworkKind::V6,
210 };
211
212 return Some(Ok(LookupResult::new_found(
213 self.reader,
214 data_offset,
215 current.prefix_len as u8,
216 ip_addr,
217 LookupSource::Iter,
218 network_kind,
219 )));
220 }
221 Ordering::Equal => {
222 // Dead end (no data) - include if option is set
223 if self.options.include_networks_without_data {
224 let ip_addr = ip_int_to_addr(¤t.ip_int);
225 let network_kind = match current.ip_int {
226 IpInt::V4(_) => NetworkKind::V4,
227 IpInt::V6(_)
228 if current.ip_int.is_ipv4_in_ipv6()
229 && self.reader.has_ipv4_subtree()
230 && current.prefix_len >= self.reader.ipv4_start_bit_depth =>
231 {
232 NetworkKind::V4InV6Subtree
233 }
234 IpInt::V6(_) => NetworkKind::V6,
235 };
236 return Some(Ok(LookupResult::new_not_found(
237 self.reader,
238 current.prefix_len as u8,
239 ip_addr,
240 LookupSource::Iter,
241 network_kind,
242 )));
243 }
244 // Otherwise skip (current behavior)
245 }
246 Ordering::Less => {
247 // In order traversal of our children
248 // right/1-bit
249 let mut right_ip_int = current.ip_int;
250
251 if current.prefix_len < bit_count {
252 right_ip_int.set_bit(current.prefix_len);
253 }
254
255 self.push_child(current.node, 1, right_ip_int, current.prefix_len + 1);
256 // left/0-bit
257 self.push_child(current.node, 0, current.ip_int, current.prefix_len + 1);
258 }
259 }
260 }
261 None
262 }
263}
264
265impl<'de, S: AsRef<[u8]>> Within<'de, S> {
266 fn push_child(
267 &mut self,
268 parent_node: usize,
269 direction: usize,
270 ip_int: IpInt,
271 prefix_len: usize,
272 ) {
273 let node = self.reader.read_node(parent_node, direction);
274 self.stack.push(WithinNode {
275 node,
276 ip_int,
277 prefix_len,
278 });
279 }
280
281 /// Check if the value at the given data offset is an empty map or array.
282 fn is_empty_value_at(&self, data_offset: usize) -> Result<bool, MaxMindDbError> {
283 let buf = &self.reader.buf.as_ref()[self.reader.pointer_base..];
284 let mut dec = decoder::Decoder::new(buf, data_offset);
285 let (size, type_num) = dec.peek_type()?;
286 match type_num {
287 decoder::TYPE_MAP | decoder::TYPE_ARRAY => Ok(size == 0),
288 _ => Ok(false), // Non-container types are never "empty"
289 }
290 }
291}
292
293/// Convert IpInt to IpAddr
294pub(crate) fn ip_int_to_addr(ip_int: &IpInt) -> IpAddr {
295 match ip_int {
296 IpInt::V4(ip) => IpAddr::V4((*ip).into()),
297 IpInt::V6(ip) => {
298 // Check if this is an IPv4-mapped IPv6 address
299 if *ip <= 0xFFFFFFFF {
300 IpAddr::V4((*ip as u32).into())
301 } else {
302 IpAddr::V6((*ip).into())
303 }
304 }
305 }
306}