#![deny(trivial_casts, trivial_numeric_casts, unused_import_braces)]
use std::collections::BTreeMap;
use std::fmt::{self, Display, Formatter};
use std::io;
use std::net::IpAddr;
use std::path::Path;
use serde::{de, Deserialize};
#[cfg(feature = "mmap")]
use memmap::Mmap;
#[cfg(feature = "mmap")]
use memmap::MmapOptions;
#[cfg(feature = "mmap")]
use std::fs::File;
#[derive(Debug, PartialEq)]
pub enum MaxMindDBError {
AddressNotFoundError(String),
InvalidDatabaseError(String),
IoError(String),
MapError(String),
DecodingError(String),
}
impl From<io::Error> for MaxMindDBError {
fn from(err: io::Error) -> MaxMindDBError {
MaxMindDBError::IoError(err.to_string())
}
}
impl Display for MaxMindDBError {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
match self {
MaxMindDBError::AddressNotFoundError(msg) => {
write!(fmt, "AddressNotFoundError: {}", msg)?
}
MaxMindDBError::InvalidDatabaseError(msg) => {
write!(fmt, "InvalidDatabaseError: {}", msg)?
}
MaxMindDBError::IoError(msg) => write!(fmt, "IoError: {}", msg)?,
MaxMindDBError::MapError(msg) => write!(fmt, "MapError: {}", msg)?,
MaxMindDBError::DecodingError(msg) => write!(fmt, "DecodingError: {}", msg)?,
}
Ok(())
}
}
impl std::error::Error for MaxMindDBError {}
impl de::Error for MaxMindDBError {
fn custom<T: Display>(msg: T) -> Self {
MaxMindDBError::DecodingError(format!("{}", msg))
}
}
#[derive(Deserialize, Debug)]
pub struct Metadata {
pub binary_format_major_version: u16,
pub binary_format_minor_version: u16,
pub build_epoch: u64,
pub database_type: String,
pub description: BTreeMap<String, String>,
pub ip_version: u16,
pub languages: Vec<String>,
pub node_count: u32,
pub record_size: u16,
}
pub struct Reader<S: AsRef<[u8]>> {
buf: S,
pub metadata: Metadata,
ipv4_start: usize,
pointer_base: usize,
}
#[cfg(feature = "mmap")]
impl<'de> Reader<Mmap> {
pub fn open_mmap<P: AsRef<Path>>(database: P) -> Result<Reader<Mmap>, MaxMindDBError> {
let file_read = File::open(database)?;
let mmap = unsafe { MmapOptions::new().map(&file_read) }?;
Reader::from_source(mmap)
}
}
impl<'de> Reader<Vec<u8>> {
pub fn open_readfile<P: AsRef<Path>>(database: P) -> Result<Reader<Vec<u8>>, MaxMindDBError> {
use std::fs;
let buf: Vec<u8> = fs::read(&database)?;
Reader::from_source(buf)
}
}
impl<'de, S: AsRef<[u8]>> Reader<S> {
pub fn from_source(buf: S) -> Result<Reader<S>, MaxMindDBError> {
let data_section_separator_size = 16;
let metadata_start = find_metadata_start(buf.as_ref())?;
let mut type_decoder = decoder::Decoder::new(buf.as_ref(), metadata_start);
let metadata = Metadata::deserialize(&mut type_decoder)?;
let search_tree_size = (metadata.node_count as usize) * (metadata.record_size as usize) / 4;
let mut reader = Reader {
buf,
pointer_base: search_tree_size + data_section_separator_size,
metadata,
ipv4_start: 0,
};
reader.ipv4_start = reader.find_ipv4_start()?;
Ok(reader)
}
pub fn lookup<T>(&'de self, address: IpAddr) -> Result<T, MaxMindDBError>
where
T: Deserialize<'de>,
{
let ip_bytes = ip_to_bytes(address);
let pointer = self.find_address_in_tree(&ip_bytes)?;
if pointer == 0 {
return Err(MaxMindDBError::AddressNotFoundError(
"Address not found in database".to_owned(),
));
}
let rec = self.resolve_data_pointer(pointer)?;
let mut decoder = decoder::Decoder::new(&self.buf.as_ref()[self.pointer_base..], rec);
T::deserialize(&mut decoder)
}
fn find_address_in_tree(&self, ip_address: &[u8]) -> Result<usize, MaxMindDBError> {
let bit_count = ip_address.len() * 8;
let mut node = self.start_node(bit_count)?;
let node_count = self.metadata.node_count as usize;
for i in 0..bit_count {
if node >= node_count {
break;
}
let bit = 1 & (ip_address[i >> 3] >> (7 - (i % 8)));
node = self.read_node(node, bit as usize)?;
}
match node_count {
n if n == node => Ok(0),
n if node > n => Ok(node),
_ => Err(MaxMindDBError::InvalidDatabaseError(
"invalid node in search tree".to_owned(),
)),
}
}
fn start_node(&self, length: usize) -> Result<usize, MaxMindDBError> {
if length == 128 {
Ok(0)
} else {
Ok(self.ipv4_start)
}
}
fn find_ipv4_start(&self) -> Result<usize, MaxMindDBError> {
if self.metadata.ip_version != 6 {
return Ok(0);
}
let mut node: usize = 0usize;
for _ in 0u8..96 {
if node >= self.metadata.node_count as usize {
break;
}
node = self.read_node(node, 0)?;
}
Ok(node)
}
fn read_node(&self, node_number: usize, index: usize) -> Result<usize, MaxMindDBError> {
let buf = self.buf.as_ref();
let base_offset = node_number * (self.metadata.record_size as usize) / 4;
let val = match self.metadata.record_size {
24 => {
let offset = base_offset + index * 3;
to_usize(0, &buf[offset..offset + 3])
}
28 => {
let mut middle = buf[base_offset + 3];
if index != 0 {
middle &= 0x0F
} else {
middle = (0xF0 & middle) >> 4
}
let offset = base_offset + index * 4;
to_usize(middle, &buf[offset..offset + 3])
}
32 => {
let offset = base_offset + index * 4;
to_usize(0, &buf[offset..offset + 4])
}
s => {
return Err(MaxMindDBError::InvalidDatabaseError(format!(
"unknown record size: \
{:?}",
s
)))
}
};
Ok(val)
}
fn resolve_data_pointer(&self, pointer: usize) -> Result<usize, MaxMindDBError> {
let resolved = pointer - (self.metadata.node_count as usize) - 16;
if resolved > self.buf.as_ref().len() {
return Err(MaxMindDBError::InvalidDatabaseError(
"the MaxMind DB file's search tree \
is corrupt"
.to_owned(),
));
}
Ok(resolved)
}
}
fn to_usize(base: u8, bytes: &[u8]) -> usize {
bytes
.iter()
.fold(base as usize, |acc, &b| (acc << 8) | b as usize)
}
fn ip_to_bytes(address: IpAddr) -> Vec<u8> {
match address {
IpAddr::V4(a) => a.octets().to_vec(),
IpAddr::V6(a) => a.octets().to_vec(),
}
}
fn find_metadata_start(buf: &[u8]) -> Result<usize, MaxMindDBError> {
let metadata_start_marker: [u8; 14] = [
0x6d, 0x6f, 0x63, 0x2e, 0x64, 0x6e, 0x69, 0x4d, 0x78, 0x61, 0x4d, 0xEF, 0xCD, 0xAB,
];
let marker_length = metadata_start_marker.len();
for start_position in marker_length..buf.len() - 1 {
let mut not_found = false;
for (offset, marker_byte) in metadata_start_marker.iter().enumerate() {
let file_byte = buf[buf.len() - start_position - offset - 1];
if file_byte != *marker_byte {
not_found = true;
break;
}
}
if !not_found {
return Ok(buf.len() - start_position);
}
}
Err(MaxMindDBError::InvalidDatabaseError(
"Could not find MaxMind DB metadata in file.".to_owned(),
))
}
mod decoder;
pub mod geoip2;
#[cfg(test)]
mod reader_test;
#[cfg(test)]
mod tests {
use super::MaxMindDBError;
#[test]
fn test_error_display() {
assert_eq!(
format!(
"{}",
MaxMindDBError::AddressNotFoundError("something went wrong".to_owned())
),
"AddressNotFoundError: something went wrong".to_owned(),
);
assert_eq!(
format!(
"{}",
MaxMindDBError::InvalidDatabaseError("something went wrong".to_owned())
),
"InvalidDatabaseError: something went wrong".to_owned(),
);
assert_eq!(
format!(
"{}",
MaxMindDBError::IoError("something went wrong".to_owned())
),
"IoError: something went wrong".to_owned(),
);
assert_eq!(
format!(
"{}",
MaxMindDBError::MapError("something went wrong".to_owned())
),
"MapError: something went wrong".to_owned(),
);
assert_eq!(
format!(
"{}",
MaxMindDBError::DecodingError("something went wrong".to_owned())
),
"DecodingError: something went wrong".to_owned(),
);
}
}