1
18
19 package stats
20
21 import (
22 "crypto/sha256"
23 "encoding/csv"
24 "encoding/hex"
25 "fmt"
26 "math"
27 "math/rand"
28 "os"
29 "sort"
30 "strconv"
31 )
32
33
34 type payloadCurveRange struct {
35 from, to int32
36 weight float64
37 }
38
39
40
41 func newPayloadCurveRange(line []string) (*payloadCurveRange, error) {
42 if len(line) != 3 {
43 return nil, fmt.Errorf("invalid number of entries in line %v (expected 3)", line)
44 }
45
46 var from, to int64
47 var weight float64
48 var err error
49 if from, err = strconv.ParseInt(line[0], 10, 32); err != nil {
50 return nil, err
51 }
52 if from <= 0 {
53 return nil, fmt.Errorf("line %v: field (%d) must be in (0, %d]", line, from, math.MaxInt32)
54 }
55 if to, err = strconv.ParseInt(line[1], 10, 32); err != nil {
56 return nil, err
57 }
58 if to <= 0 {
59 return nil, fmt.Errorf("line %v: field %d must be in (0, %d]", line, to, math.MaxInt32)
60 }
61 if from > to {
62 return nil, fmt.Errorf("line %v: from (%d) > to (%d)", line, from, to)
63 }
64 if weight, err = strconv.ParseFloat(line[2], 64); err != nil {
65 return nil, err
66 }
67 return &payloadCurveRange{from: int32(from), to: int32(to), weight: weight}, nil
68 }
69
70
71
72 func (pcr *payloadCurveRange) chooseRandom() int {
73 if pcr.from == pcr.to {
74 return int(pcr.from)
75 }
76
77 return int(rand.Int31n(pcr.to-pcr.from+1) + pcr.from)
78 }
79
80
81
82 func sha256file(file string) (string, error) {
83 data, err := os.ReadFile(file)
84 if err != nil {
85 return "", err
86 }
87 sum := sha256.Sum256(data)
88 return hex.EncodeToString(sum[:]), nil
89 }
90
91
92
93
94 type PayloadCurve struct {
95 pcrs []*payloadCurveRange
96
97
98 Sha256 string
99 }
100
101
102
103 func NewPayloadCurve(file string) (*PayloadCurve, error) {
104 f, err := os.Open(file)
105 if err != nil {
106 return nil, err
107 }
108 defer f.Close()
109
110 r := csv.NewReader(f)
111 lines, err := r.ReadAll()
112 if err != nil {
113 return nil, err
114 }
115
116 ret := &PayloadCurve{}
117 var total float64
118 for _, line := range lines {
119 pcr, err := newPayloadCurveRange(line)
120 if err != nil {
121 return nil, err
122 }
123
124 ret.pcrs = append(ret.pcrs, pcr)
125 total += pcr.weight
126 }
127
128 ret.Sha256, err = sha256file(file)
129 if err != nil {
130 return nil, err
131 }
132 for _, pcr := range ret.pcrs {
133 pcr.weight /= total
134 }
135
136 sort.Slice(ret.pcrs, func(i, j int) bool {
137 if ret.pcrs[i].from == ret.pcrs[j].from {
138 return ret.pcrs[i].to < ret.pcrs[j].to
139 }
140 return ret.pcrs[i].from < ret.pcrs[j].from
141 })
142
143 var lastTo int32
144 for _, pcr := range ret.pcrs {
145 if lastTo >= pcr.from {
146 return nil, fmt.Errorf("[%d, %d] overlaps with a different line", pcr.from, pcr.to)
147 }
148 lastTo = pcr.to
149 }
150
151 return ret, nil
152 }
153
154
155
156 func (pc *PayloadCurve) ChooseRandom() int {
157 target := rand.Float64()
158 var seen float64
159 for _, pcr := range pc.pcrs {
160 seen += pcr.weight
161 if seen >= target {
162 return pcr.chooseRandom()
163 }
164 }
165
166
167 return 1
168 }
169
170
171
172 func (pc *PayloadCurve) Hash() string {
173 return pc.Sha256
174 }
175
176
177 func (pc *PayloadCurve) ShortHash() string {
178 return pc.Sha256[:8]
179 }
180
View as plain text