use crate::error::XdbError;
use crate::{IPV4_SEGMENT_INDEX_BLOCK_SIZE, IPV6_SEGMENT_INDEX_BLOCK_SIZE};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IpVersion {
V4,
V6,
}
#[derive(Debug, Clone, Default)]
pub struct Header {
pub version: u16,
pub cache_type: u16,
pub generation_time: u32,
pub index_base_address: u32,
pub index_end_address: u32,
}
impl Header {
pub fn parse(data: &[u8]) -> Result<Self, XdbError> {
Ok(Header {
version: u16::from_ne_bytes(data[0..2].try_into()?),
cache_type: u16::from_ne_bytes(data[2..4].try_into()?),
generation_time: u32::from_ne_bytes(data[4..8].try_into()?),
index_base_address: u32::from_ne_bytes(data[8..12].try_into()?),
index_end_address: u32::from_ne_bytes(data[12..16].try_into()?),
})
}
pub fn ip_version(&self) -> Result<IpVersion, XdbError> {
let len = self.index_end_address - self.index_base_address + 1;
if len % IPV4_SEGMENT_INDEX_BLOCK_SIZE <= 1 {
Ok(IpVersion::V4)
} else if len % IPV6_SEGMENT_INDEX_BLOCK_SIZE <= 1 {
Ok(IpVersion::V6)
} else {
Err(XdbError::InvalidIPVersion("unknown xdb version".into()))
}
}
}
pub fn detect_version(data: &[u8]) -> Result<IpVersion, XdbError> {
Header::parse(data)?.ip_version()
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::Result;
#[test]
fn test_parse_header() -> Result<()> {
let mut buf = vec![0u8; 256];
buf[0..2].copy_from_slice(&2u16.to_ne_bytes());
let header = Header::parse(&buf)?;
assert_eq!(header.version, 2);
Ok(())
}
#[test]
fn test_detect_version_v4() -> Result<()> {
let path = "./assets/ip2region_v4.xdb";
let data = std::fs::read(path)?;
let version = detect_version(&data)?;
assert_eq!(version, IpVersion::V4);
Ok(())
}
#[test]
fn test_detect_version_v6() -> Result<()> {
let path = "./assets/ip2region_v6.xdb";
let data = std::fs::read(path)?;
let version = detect_version(&data)?;
assert_eq!(version, IpVersion::V6);
Ok(())
}
}