File: verifier.go

package info (click to toggle)
golang-github-oschwald-maxminddb-golang-v2 2.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,132 kB
  • sloc: perl: 557; makefile: 3
file content (156 lines) | stat: -rw-r--r-- 3,442 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package maxminddb

import (
	"runtime"

	"github.com/oschwald/maxminddb-golang/v2/internal/mmdberrors"
)

type verifier struct {
	reader *Reader
}

// Verify performs comprehensive validation of the MaxMind DB file.
//
// This method validates:
//   - Metadata section: format versions, required fields, and value constraints
//   - Search tree: traverses all networks to verify tree structure integrity
//   - Data section separator: validates the 16-byte separator between tree and data
//   - Data section: verifies all data records referenced by the search tree
//
// The verifier is stricter than the MaxMind DB specification and may return
// errors on some databases that are still readable by normal operations.
// This method is useful for:
//   - Validating database files after download or generation
//   - Debugging database corruption issues
//   - Ensuring database integrity in critical applications
//
// Note: Verification traverses the entire database and may be slow on large files.
// The method is thread-safe and can be called on an active Reader.
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 map",
			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.reader.decoder.VerifyDataSection(offsets)
}

func (v *verifier) verifySearchTree() (map[uint]bool, error) {
	offsets := make(map[uint]bool)

	for result := range v.reader.Networks() {
		if err := result.Err(); err != nil {
			return nil, err
		}
		offsets[result.offset] = true
	}
	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 mmdberrors.NewInvalidDatabaseError(
				"unexpected byte in data separator: %v",
				separator,
			)
		}
	}
	return nil
}

func testError(
	field string,
	expected any,
	actual any,
) error {
	return mmdberrors.NewInvalidDatabaseError(
		"%v - Expected: %v Actual: %v",
		field,
		expected,
		actual,
	)
}