///|
fn required_u16(raw : Value, key : String, offset : Int) -> Int raise MmdbError {
match raw.get(key) {
Some(Unsigned16(value)) => value.reinterpret_as_int()
_ =>
raise MmdbError(
"invalid-metadata",
offset,
"Expected uint16 metadata field: " + key,
)
}
}
///|
/// All untrusted size products are widened before indexing a byte buffer.
pub fn open_bytes(
data : Bytes,
limits? : Limits = Limits::default(),
) -> Reader raise MmdbError {
if limits.max_depth < 1 ||
limits.max_depth > 256 ||
limits.max_values < 1 ||
limits.max_values > 1000000 ||
limits.max_payload_bytes < 1 ||
limits.max_payload_bytes > 67108864 ||
limits.max_file_bytes < 1 ||
limits.max_file_bytes > 536870912 {
raise MmdbError(
"invalid-limits", -1, "Limits exceed the supported reader profile",
)
}
if data.length() > limits.max_file_bytes {
raise MmdbError("file-limit", -1, "Database exceeds file byte limit")
}
// A JS host may reuse its Uint8Array after opening. Keep a stable snapshot.
let data = Bytes::makei(data.length(), i => data[i])
let marker = b"\xab\xcd\xefMaxMind.com"
let lower = (data.length() - 131072).max(0)
let mut found = -1
for i = data.length() - marker.length(); i >= lower; i = i - 1 {
let mut matches = true
for j in 0.. n
_ =>
raise MmdbError(
"invalid-metadata", meta_start, "Expected uint32 node_count",
)
}
let record_size = required_u16(raw, "record_size", meta_start)
if record_size != 24 && record_size != 28 && record_size != 32 {
raise MmdbError(
"unsupported-record-size", meta_start, "Only 24, 28 and 32 bit nodes are supported",
)
}
let ip_version = required_u16(raw, "ip_version", meta_start)
if ip_version != 4 && ip_version != 6 {
raise MmdbError("invalid-metadata", meta_start, "ip_version must be 4 or 6")
}
let format_major = required_u16(
raw, "binary_format_major_version", meta_start,
)
let format_minor = required_u16(
raw, "binary_format_minor_version", meta_start,
)
if format_major != 2 {
raise MmdbError(
"unsupported-version", meta_start, "Expected MMDB format major 2",
)
}
let database_type = match raw.get("database_type") {
Some(Text(s)) => s
_ =>
raise MmdbError(
"invalid-metadata", meta_start, "Expected string database_type",
)
}
let build_epoch = match raw.get("build_epoch") {
Some(Unsigned64(n)) => n
_ =>
raise MmdbError(
"invalid-metadata", meta_start, "Expected uint64 build_epoch",
)
}
match raw.get("languages") {
None => ()
Some(List(items)) =>
for item in items {
if !(item is Text(_)) {
raise MmdbError(
"invalid-metadata", meta_start, "languages must contain strings",
)
}
}
_ =>
raise MmdbError(
"invalid-metadata", meta_start, "languages must be an array",
)
}
match raw.get("description") {
None => ()
Some(Object(items)) =>
for item in items {
if !(item.1 is Text(_)) {
raise MmdbError(
"invalid-metadata", meta_start, "description values must be strings",
)
}
}
_ =>
raise MmdbError(
"invalid-metadata", meta_start, "description must be a map",
)
}
let tree_size = node_count.to_uint64() * (record_size / 4).to_uint64()
if tree_size + 16 > found.to_uint64() {
raise MmdbError(
"invalid-layout", found, "Tree and separator overlap metadata",
)
}
let tree_end = tree_size.to_int()
for i in tree_end..<(tree_end + 16) {
if data[i] != 0 {
raise MmdbError(
"invalid-separator", i, "Expected sixteen zero separator bytes",
)
}
}
let meta = {
node_count,
record_size,
ip_version,
database_type,
format_major,
format_minor,
build_epoch,
raw,
}
{ data, meta, tree_end, data_end: found, limits, }
}
///|
fn Reader::branch(self : Reader, node : UInt, bit : Int) -> UInt {
let size = self.meta.record_size
let at = node.reinterpret_as_int() * (size / 4)
let mut value : UInt = 0
if size == 28 {
let nibble = self.data[at + 3].to_uint()
value = if bit == 0 { nibble >> 4 } else { nibble & 15 }
let start = if bit == 0 { at } else { at + 4 }
for i in start..<(start + 3) {
value = (value << 8) | self.data[i].to_uint()
}
} else {
let width = size / 8
let start = at + bit * width
for i in start..<(start + width) {
value = (value << 8) | self.data[i].to_uint()
}
}
value
}
///|
/// Mapped/6to4 aliases follow database edges; no implicit Teredo rewriting.
pub fn Reader::lookup(self : Reader, ip : String) -> Lookup raise MmdbError {
let addr = parse_ip(ip)
if addr.version == 6 && self.meta.ip_version == 4 {
raise MmdbError(
"ip-version-mismatch", -1, "IPv6 lookup against IPv4 database",
)
}
let bits = if self.meta.ip_version == 4 { 32 } else { 128 }
let padding = if addr.version == 4 && bits == 128 { 96 } else { 0 }
let mut node : UInt = 0
let mut depth = 0
while node < self.meta.node_count && depth < bits {
let bit = if depth < padding {
0
} else {
let index = depth - padding
(addr.bytes[index / 8] >> (7 - index % 8)) & 1
}
node = self.branch(node, bit)
depth = depth + 1
}
let prefix_length = (depth - padding).max(0)
if node == self.meta.node_count {
return { value: None, prefix_length, }
}
if node < self.meta.node_count {
raise MmdbError(
"invalid-tree", -1, "Search has no terminal after all address bits",
)
}
let relative = node.to_uint64() - self.meta.node_count.to_uint64()
let offset = self.tree_end.to_uint64() + relative
if relative < 16 || offset >= self.data_end.to_uint64() {
raise MmdbError(
"invalid-tree-pointer", -1, "Search record points outside data",
)
}
let ctx = decoder(self.data, self.tree_end + 16, self.data_end, self.limits)
let (value, _) = decode(ctx, offset.to_int(), 0)
{ value: Some(value), prefix_length, }
}