-
Notifications
You must be signed in to change notification settings - Fork 106
Expand file tree
/
Copy pathverifier.go
More file actions
178 lines (156 loc) · 4.16 KB
/
Copy pathverifier.go
File metadata and controls
178 lines (156 loc) · 4.16 KB
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package maxminddb
import (
"runtime"
"unicode/utf8"
"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 !utf8.ValidString(metadata.DatabaseType) {
return mmdberrors.NewInvalidDatabaseError("database_type contains invalid UTF-8")
}
for language, description := range metadata.Description {
if !utf8.ValidString(language) || !utf8.ValidString(description) {
return mmdberrors.NewInvalidDatabaseError("description contains invalid UTF-8")
}
}
for _, language := range metadata.Languages {
if !utf8.ValidString(language) {
return mmdberrors.NewInvalidDatabaseError("languages contains invalid UTF-8")
}
}
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 := searchTreeSizeBytes(
v.reader.Metadata.NodeCount,
v.reader.Metadata.RecordSize,
)
separatorEnd := separatorStart + dataSectionSeparatorSize
if separatorEnd < separatorStart || separatorEnd > uint(len(v.reader.buffer)) {
return mmdberrors.NewInvalidDatabaseError(
"unexpected end of database while reading data section separator",
)
}
separator := v.reader.buffer[separatorStart:separatorEnd]
var zeroSeparator [16]byte
if [16]byte(separator) != zeroSeparator {
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,
)
}