2022-08-18 01:16:43 +02:00
|
|
|
package maxminddb
|
|
|
|
|
|
|
|
import (
|
|
|
|
"reflect"
|
|
|
|
"runtime"
|
|
|
|
)
|
|
|
|
|
|
|
|
type verifier struct {
|
|
|
|
reader *Reader
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify checks that the database is valid. It validates the search tree,
|
|
|
|
// the data section, and the metadata section. This verifier is stricter than
|
|
|
|
// the specification and may return errors on databases that are readable.
|
|
|
|
func (r *Reader) Verify() error {
|
|
|
|
v := verifier{r}
|
|
|
|
if err := v.verifyMetadata(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err := v.verifyDatabase()
|
|
|
|
runtime.KeepAlive(v.reader)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *verifier) verifyMetadata() error {
|
|
|
|
metadata := v.reader.Metadata
|
|
|
|
|
|
|
|
if metadata.BinaryFormatMajorVersion != 2 {
|
|
|
|
return testError(
|
|
|
|
"binary_format_major_version",
|
|
|
|
2,
|
|
|
|
metadata.BinaryFormatMajorVersion,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if metadata.BinaryFormatMinorVersion != 0 {
|
|
|
|
return testError(
|
|
|
|
"binary_format_minor_version",
|
|
|
|
0,
|
|
|
|
metadata.BinaryFormatMinorVersion,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if metadata.DatabaseType == "" {
|
|
|
|
return testError(
|
|
|
|
"database_type",
|
|
|
|
"non-empty string",
|
|
|
|
metadata.DatabaseType,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(metadata.Description) == 0 {
|
|
|
|
return testError(
|
|
|
|
"description",
|
|
|
|
"non-empty slice",
|
|
|
|
metadata.Description,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if metadata.IPVersion != 4 && metadata.IPVersion != 6 {
|
|
|
|
return testError(
|
|
|
|
"ip_version",
|
|
|
|
"4 or 6",
|
|
|
|
metadata.IPVersion,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if metadata.RecordSize != 24 &&
|
|
|
|
metadata.RecordSize != 28 &&
|
|
|
|
metadata.RecordSize != 32 {
|
|
|
|
return testError(
|
|
|
|
"record_size",
|
|
|
|
"24, 28, or 32",
|
|
|
|
metadata.RecordSize,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if metadata.NodeCount == 0 {
|
|
|
|
return testError(
|
|
|
|
"node_count",
|
|
|
|
"positive integer",
|
|
|
|
metadata.NodeCount,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *verifier) verifyDatabase() error {
|
|
|
|
offsets, err := v.verifySearchTree()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := v.verifyDataSectionSeparator(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return v.verifyDataSection(offsets)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *verifier) verifySearchTree() (map[uint]bool, error) {
|
|
|
|
offsets := make(map[uint]bool)
|
|
|
|
|
|
|
|
it := v.reader.Networks()
|
|
|
|
for it.Next() {
|
|
|
|
offset, err := v.reader.resolveDataPointer(it.lastNode.pointer)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
offsets[uint(offset)] = true
|
|
|
|
}
|
|
|
|
if err := it.Err(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return offsets, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *verifier) verifyDataSectionSeparator() error {
|
|
|
|
separatorStart := v.reader.Metadata.NodeCount * v.reader.Metadata.RecordSize / 4
|
|
|
|
|
|
|
|
separator := v.reader.buffer[separatorStart : separatorStart+dataSectionSeparatorSize]
|
|
|
|
|
|
|
|
for _, b := range separator {
|
|
|
|
if b != 0 {
|
|
|
|
return newInvalidDatabaseError("unexpected byte in data separator: %v", separator)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *verifier) verifyDataSection(offsets map[uint]bool) error {
|
|
|
|
pointerCount := len(offsets)
|
|
|
|
|
|
|
|
decoder := v.reader.decoder
|
|
|
|
|
|
|
|
var offset uint
|
|
|
|
bufferLen := uint(len(decoder.buffer))
|
|
|
|
for offset < bufferLen {
|
2023-07-13 22:34:10 +02:00
|
|
|
var data any
|
2022-08-18 01:16:43 +02:00
|
|
|
rv := reflect.ValueOf(&data)
|
|
|
|
newOffset, err := decoder.decode(offset, rv, 0)
|
|
|
|
if err != nil {
|
|
|
|
return newInvalidDatabaseError(
|
|
|
|
"received decoding error (%v) at offset of %v",
|
|
|
|
err,
|
|
|
|
offset,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if newOffset <= offset {
|
|
|
|
return newInvalidDatabaseError(
|
|
|
|
"data section offset unexpectedly went from %v to %v",
|
|
|
|
offset,
|
|
|
|
newOffset,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pointer := offset
|
|
|
|
|
|
|
|
if _, ok := offsets[pointer]; !ok {
|
|
|
|
return newInvalidDatabaseError(
|
|
|
|
"found data (%v) at %v that the search tree does not point to",
|
|
|
|
data,
|
|
|
|
pointer,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
delete(offsets, pointer)
|
|
|
|
|
|
|
|
offset = newOffset
|
|
|
|
}
|
|
|
|
|
|
|
|
if offset != bufferLen {
|
|
|
|
return newInvalidDatabaseError(
|
|
|
|
"unexpected data at the end of the data section (last offset: %v, end: %v)",
|
|
|
|
offset,
|
|
|
|
bufferLen,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(offsets) != 0 {
|
|
|
|
return newInvalidDatabaseError(
|
|
|
|
"found %v pointers (of %v) in the search tree that we did not see in the data section",
|
|
|
|
len(offsets),
|
|
|
|
pointerCount,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func testError(
|
|
|
|
field string,
|
2023-07-13 22:34:10 +02:00
|
|
|
expected any,
|
|
|
|
actual any,
|
2022-08-18 01:16:43 +02:00
|
|
|
) error {
|
|
|
|
return newInvalidDatabaseError(
|
|
|
|
"%v - Expected: %v Actual: %v",
|
|
|
|
field,
|
|
|
|
expected,
|
|
|
|
actual,
|
|
|
|
)
|
|
|
|
}
|