Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bdecf2f34 |
No files matched your search
+81
-2
@@ -171,9 +171,11 @@ type alertsFile struct {
|
||||
// check refuses the first entry without a field it needs, which would
|
||||
// otherwise be read as something the entry does not say. data is the
|
||||
// file, for a field that may be null or "" but not left out, which the
|
||||
// struct cannot tell apart.
|
||||
// struct cannot tell apart. Its toUTC then converts every time in it to
|
||||
// UTC, since an admin may write a time with an offset.
|
||||
type stateFile interface {
|
||||
check(data []byte) error
|
||||
toUTC()
|
||||
}
|
||||
|
||||
// Load checks that files can be written in Dir, and reads the state files
|
||||
@@ -918,6 +920,80 @@ func missing(i int, field string) error {
|
||||
return fmt.Errorf("entry %d %w %q", i+1, errMissing, field)
|
||||
}
|
||||
|
||||
// toUTC converts every time of each ban to UTC.
|
||||
func (f *bansFile) toUTC() {
|
||||
for i := range f.Bans {
|
||||
ban := &f.Bans[i]
|
||||
convertToUTC(&ban.Start, ban.Expires, ban.Lifted, &ban.Notes.Request.Time)
|
||||
}
|
||||
}
|
||||
|
||||
// toUTC converts every time of each client to UTC.
|
||||
func (f *clientsFile) toUTC() {
|
||||
for i := range f.Clients {
|
||||
client := &f.Clients[i]
|
||||
convertToUTC(&client.Minute.Start, &client.Hour.Start, &client.Day.Start,
|
||||
&client.MinuteBytes.Start, &client.HourBytes.Start,
|
||||
&client.DayBytes.Start, &client.MinuteRefusals.Start,
|
||||
&client.History.FirstSeen, &client.History.LastSeen,
|
||||
&client.History.LookedUp)
|
||||
}
|
||||
}
|
||||
|
||||
// toUTC converts both times of each answer to UTC.
|
||||
func (f *lookupsFile) toUTC() {
|
||||
for i := range f.Lookups {
|
||||
convertToUTC(&f.Lookups[i].Answered, &f.Lookups[i].Used)
|
||||
}
|
||||
}
|
||||
|
||||
// toUTC converts every time of each list, verdict and AbuseIPDB score to
|
||||
// UTC.
|
||||
func (f *reputationFile) toUTC() {
|
||||
for i := range f.Lists {
|
||||
convertToUTC(&f.Lists[i].Tried, &f.Lists[i].Fetched)
|
||||
}
|
||||
|
||||
for i := range f.Verdicts {
|
||||
convertToUTC(&f.Verdicts[i].Fetched)
|
||||
}
|
||||
|
||||
for i := range f.AbuseIPDB.Scores {
|
||||
convertToUTC(&f.AbuseIPDB.Scores[i].Fetched)
|
||||
}
|
||||
}
|
||||
|
||||
// toUTC converts every time of each cooldown, of the hour, of each alert
|
||||
// waiting and of each anomaly counter to UTC.
|
||||
func (f *alertsFile) toUTC() {
|
||||
for i := range f.Cooldowns {
|
||||
convertToUTC(&f.Cooldowns[i].Sent)
|
||||
}
|
||||
|
||||
convertToUTC(&f.Hour.Start)
|
||||
|
||||
for _, waiting := range f.Waiting {
|
||||
for i := range waiting {
|
||||
convertToUTC(&waiting[i].Time)
|
||||
}
|
||||
}
|
||||
|
||||
for i := range f.AnomalyCounters {
|
||||
counter := &f.AnomalyCounters[i]
|
||||
convertToUTC(&counter.Minute.Start, &counter.Hour.Start,
|
||||
&counter.MinuteBytes.Start, &counter.HourBytes.Start)
|
||||
}
|
||||
}
|
||||
|
||||
// convertToUTC converts each of times that is not nil to UTC.
|
||||
func convertToUTC(times ...*time.Time) {
|
||||
for _, at := range times {
|
||||
if at != nil {
|
||||
*at = at.UTC()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// encodeOnePerLine encodes a state file whose entries, under key, are one
|
||||
// to a line, so that grep shows everything about one client.
|
||||
func encodeOnePerLine[E any](key string, entries []E) ([]byte, error) {
|
||||
@@ -955,7 +1031,8 @@ func checkWritable(dir string) error {
|
||||
}
|
||||
|
||||
// parse reads data, what the state file at path holds, into file, a
|
||||
// pointer to that file's struct, and checks its entries.
|
||||
// pointer to that file's struct, checks its entries, and converts every
|
||||
// time in it to UTC.
|
||||
func parse(path string, data []byte, file stateFile) error {
|
||||
// The version is read first, so that a file of another version is
|
||||
// refused for that, and not for an entry this version cannot read.
|
||||
@@ -985,6 +1062,8 @@ func parse(path string, data []byte, file stateFile) error {
|
||||
return fmt.Errorf("%s%s: %w", path, position(data, err), err)
|
||||
}
|
||||
|
||||
file.toUTC()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -1351,6 +1352,62 @@ func TestEditOfEachFileTakenIn(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimesEditedWithAnOffsetWrittenBackInUTC(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := t.TempDir()
|
||||
params := newParams(dir)
|
||||
fill(params)
|
||||
// A lifted ban, so that bans.json holds every time a ban has.
|
||||
params.Ledger.Lift(netip.MustParseAddr("192.0.2.1"), midnight())
|
||||
|
||||
files := load(t, params)
|
||||
|
||||
err := files.WriteAll()
|
||||
if err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
|
||||
// An admin saves each file with every time in it two hours ahead of
|
||||
// UTC, midnight as "2026-10-06T02:00:00+02:00".
|
||||
stamp := regexp.MustCompile(`"\d{4}-\d\d-\d\dT[^"]*Z"`)
|
||||
ahead := time.FixedZone("UTC+2", 2*60*60)
|
||||
written := map[string]string{}
|
||||
|
||||
for _, name := range []string{
|
||||
bansJSON, clientsJSON, lookupsJSON, reputationJSON, alertsJSON,
|
||||
} {
|
||||
written[name] = readFile(t, filepath.Join(dir, name))
|
||||
edited := stamp.ReplaceAllStringFunc(written[name], func(quoted string) string {
|
||||
at, err := time.Parse(time.RFC3339Nano, strings.Trim(quoted, `"`))
|
||||
if err != nil {
|
||||
t.Fatalf("%s holds %s: %v", name, quoted, err)
|
||||
}
|
||||
|
||||
return `"` + at.In(ahead).Format(time.RFC3339Nano) + `"`
|
||||
})
|
||||
|
||||
if !strings.Contains(edited, "+02:00") {
|
||||
t.Fatalf("%s holds no time:\n%s", name, edited)
|
||||
}
|
||||
|
||||
edit(t, dir, name, edited)
|
||||
}
|
||||
|
||||
// Each write takes its edit in first, and writes the same times back in
|
||||
// UTC.
|
||||
err = files.WriteAll()
|
||||
if err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
|
||||
for name, want := range written {
|
||||
if got := readFile(t, filepath.Join(dir, name)); got != want {
|
||||
t.Errorf("%s written back as\n%s\nwant\n%s", name, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOwnWritesAreNotTakenIn(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
// The time zone database, so that the process TestTimesAreInUTCWhateverTZSays
|
||||
// The time zone database, so that the process TestMainRunsDiscriminator
|
||||
// starts finds the zone TZ names on any host.
|
||||
_ "time/tzdata"
|
||||
|
||||
@@ -41,53 +41,7 @@ func TestMain(m *testing.M) {
|
||||
func TestMainRunsDiscriminator(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
binary, err := os.Executable()
|
||||
if err != nil {
|
||||
t.Fatalf("find the test binary: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 3*waitLimit)
|
||||
defer cancel()
|
||||
|
||||
out := &output{}
|
||||
cmd := exec.CommandContext(ctx, binary) //nolint:gosec // this test binary
|
||||
cmd.Stdout = out
|
||||
|
||||
cmd.Env = append(os.Environ(),
|
||||
commandForMain+"="+discriminatorCommand,
|
||||
listenAddr+"="+localhost+":0",
|
||||
stateDir+"="+t.TempDir(),
|
||||
rulesDir+"="+t.TempDir(),
|
||||
lookupSource+"=off")
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("start: %v", err)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
_ = cmd.Process.Kill()
|
||||
_ = cmd.Wait()
|
||||
})
|
||||
|
||||
addr, _ := out.line(t, "msg", "starting")["address"].(string)
|
||||
ask(t, addr, "/", http.StatusOK, discriminator.DispositionAllow)
|
||||
|
||||
err = cmd.Process.Signal(syscall.SIGTERM)
|
||||
if err != nil {
|
||||
t.Fatalf("signal: %v", err)
|
||||
}
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
t.Errorf("exit: %v; output:\n%s", err, out.text())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimesAreInUTCWhateverTZSays(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Kiritimati is 14 hours ahead of UTC.
|
||||
// Kiritimati is 14 hours ahead of UTC; every time sww gives stays in UTC.
|
||||
const zone = "Pacific/Kiritimati"
|
||||
|
||||
_, err := time.LoadLocation(zone)
|
||||
@@ -163,7 +117,8 @@ func TestTimesAreInUTCWhateverTZSays(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// wantUTC checks that value, a time where, is in UTC.
|
||||
// wantUTC checks that value, a time read from the log line or file that
|
||||
// where names, is in UTC.
|
||||
func wantUTC(t *testing.T, where string, value any) {
|
||||
t.Helper()
|
||||
|
||||
|
||||
Reference in new issue
Block a user