package logfile import ( "bytes" "net" "os" "time" "github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/querylog" "github.com/bits-and-blooms/bloom" ) type LogStorage struct { } type LogBlock struct { } type ShapeStorage struct { blocks []ShapeBlock shapeCache map[string][8]byte } func (s *ShapeStorage) FindOrAdd(k []byte) [8]byte { if len(k) <= 8 { o := [8]byte{} for i, v := range k { o[i] = v } return o } return [8]byte{} } type ShapeBlock struct { filter *bloom.BloomFilter fp *os.File } func (b *ShapeBlock) Open(name string) (err error) { _, err = b.fp.Stat() if os.IsNotExist(err) { b.fp, err = os.Create(name) if err != nil { return err } } else if err == nil { b.fp, err = os.OpenFile(name, os.O_RDWR, 0o644) if err != nil { return err } } else { return err } return } var magic = [8]byte{0xfe, 0xed, 0xbe, 0xef, 0x69, 0x00, 0x00, 0x00} const BLOCK_SIZE = 1600 const BLOOM_SIZE_BITS = 500 * 64 const BLOOM_SIZE_BYTES = BLOOM_SIZE_BITS / 8 const BLOOM_SIZE_TOTAL = 8 + 8 + 8 + BLOOM_SIZE_BYTES func (b *ShapeBlock) WriteTo() (err error) { if b.filter == nil { b.filter = bloom.New(BLOOM_SIZE_BITS, 14) } _, err = b.fp.WriteAt(magic[:], 0) bf := new(bytes.Buffer) b.filter.WriteTo(bf) n, err := b.fp.WriteAt(bf.Bytes(), 8) if err != nil { return err } if n != BLOOM_SIZE_TOTAL { panic("invalid bloom header size") } return err } func (b *ShapeBlock) ReadHeader() (err error) { prefix := make([]byte, 8) _, err = b.fp.ReadAt(prefix, 0) if err != nil { return err } bloom := make([]byte, BLOOM_SIZE_TOTAL) _, err = b.fp.ReadAt(bloom, 8) if err != nil { return err } _, err = b.filter.ReadFrom(bytes.NewBuffer(bloom)) if err != nil { return err } return nil } func (b *ShapeBlock) Add(k []byte) int { if uint(len(b.keys)+1) >= b.filter.Cap() { return -1 } if b.Find(k) == -1 { b.filter.Add(k) return len(b.keys) } return -1 } func (b *ShapeBlock) Find(k []byte) [8]byte { if !b.filter.Test(k) { return -1 } for idx, v := range b.keys { if len(k) != len(v) { continue } for i := range v { if v[i] != k[i] { continue } } return idx } return -1 } type StorageEntry struct { Block int64 } // represents a single log entry in storage type LogEntry struct { IP net.IP `json:"IP"` // Client IP Time time.Time `json:"T"` QHost string `json:"QH"` QType string `json:"QT"` QClass string `json:"QC"` ClientID string `json:"CID,omitempty"` ClientProto querylog.ClientProto `json:"CP"` Answer []byte `json:",omitempty"` // sometimes empty answers happen like binerdunt.top or rev2.globalrootservers.net OrigAnswer []byte `json:",omitempty"` Result filtering.Result Elapsed time.Duration Upstream string `json:",omitempty"` // if empty, means it was cached }