]> Sergey Matveev's repositories - btrtrc.git/blob - iplist/packed.go
152c680a18590ff9cc7abac251b5743cdd482b05
[btrtrc.git] / iplist / packed.go
1 package iplist
2
3 import (
4         "encoding/binary"
5         "io"
6         "net"
7 )
8
9 // The packed format is an 8 byte integer of the number of ranges. Then 20
10 // bytes per range, consisting of 4 byte packed IP being the lower bound IP of
11 // the range, then 4 bytes of the upper, inclusive bound, 8 bytes for the
12 // offset of the description from the end of the packed ranges, and 4 bytes
13 // for the length of the description. After these packed ranges, are the
14 // concatenated descriptions.
15
16 const (
17         packedRangesOffset = 8
18         packedRangeLen     = 20
19 )
20
21 func (me *IPList) WritePacked(w io.Writer) (err error) {
22         descOffsets := make(map[string]int64, len(me.ranges))
23         descs := make([]string, 0, len(me.ranges))
24         var nextOffset int64
25         // This is a little monadic, no?
26         write := func(b []byte, expectedLen int) {
27                 if err != nil {
28                         return
29                 }
30                 var n int
31                 n, err = w.Write(b)
32                 if err != nil {
33                         return
34                 }
35                 if n != expectedLen {
36                         panic(n)
37                 }
38         }
39         var b [8]byte
40         binary.LittleEndian.PutUint64(b[:], uint64(len(me.ranges)))
41         write(b[:], 8)
42         for _, r := range me.ranges {
43                 write(r.First.To4(), 4)
44                 write(r.Last.To4(), 4)
45                 descOff, ok := descOffsets[r.Description]
46                 if !ok {
47                         descOff = nextOffset
48                         descOffsets[r.Description] = descOff
49                         descs = append(descs, r.Description)
50                         nextOffset += int64(len(r.Description))
51                 }
52                 binary.LittleEndian.PutUint64(b[:], uint64(descOff))
53                 write(b[:], 8)
54                 binary.LittleEndian.PutUint32(b[:], uint32(len(r.Description)))
55                 write(b[:4], 4)
56         }
57         for _, d := range descs {
58                 write([]byte(d), len(d))
59         }
60         return
61 }
62
63 func NewFromPacked(b []byte) PackedIPList {
64         return PackedIPList(b)
65 }
66
67 type PackedIPList []byte
68
69 var _ Ranger = PackedIPList{}
70
71 func (me PackedIPList) len() int {
72         return int(binary.LittleEndian.Uint64(me[:8]))
73 }
74
75 func (me PackedIPList) NumRanges() int {
76         return me.len()
77 }
78
79 func (me PackedIPList) getRange(i int) (ret Range) {
80         rOff := packedRangesOffset + packedRangeLen*i
81         first := me[rOff : rOff+4]
82         last := me[rOff+4 : rOff+8]
83         descOff := int(binary.LittleEndian.Uint64(me[rOff+8:]))
84         descLen := int(binary.LittleEndian.Uint32(me[rOff+16:]))
85         descOff += packedRangesOffset + packedRangeLen*me.len()
86         ret = Range{net.IP(first), net.IP(last), string(me[descOff : descOff+descLen])}
87         return
88 }
89
90 func (me PackedIPList) Lookup(ip net.IP) (r *Range) {
91         ip4 := ip.To4()
92         if ip4 == nil {
93                 // If the IP list was built successfully, then it only contained IPv4
94                 // ranges. Therefore no IPv6 ranges are blocked.
95                 if ip.To16() == nil {
96                         r = &Range{
97                                 Description: "bad IP",
98                         }
99                 }
100                 return
101         }
102         return lookup(me.getRange, me.len(), ip4)
103 }