definition.gno
5.58 Kb · 216 lines
1package definition
2
3import (
4 "chain/runtime/unsafe"
5 "net/url"
6 "sort"
7 "strings"
8
9 "gno.land/p/akkadia/beta/accesscontrol"
10 "gno.land/p/nt/bptree/v0"
11 "gno.land/r/akkadia/beta/admin"
12)
13
14type Field struct {
15 Key string
16 Value string
17}
18
19var definitions *bptree.BPTree
20
21func init() {
22 definitions = bptree.NewBPTree32()
23}
24
25// DeleteAll removes all definitions so an administrator can reload the catalog.
26func DeleteAll(cur realm) {
27 assertCanWrite(0, cur)
28 definitions = bptree.NewBPTree32()
29}
30
31// SetDefinitions accepts URL-escaped keys and query-encoded field objects as paired
32// comma-separated strings, so MsgCall can pass a batch without realm DTOs.
33func SetDefinitions(cur realm, keys, values string) {
34 assertCanWrite(0, cur)
35 if len(keys)+len(values) > 131071 {
36 panic("call bytes exceeds 131071")
37 }
38 if keys == "" && values == "" {
39 assertBatchCount(0)
40 }
41 if keys == "" || values == "" {
42 panic("keys and values count mismatch")
43 }
44 type entry struct {
45 Key string
46 Fields []Field
47 }
48 entries := []entry{}
49 keyStart, keyEnd := 0, 0
50 valueStart, valueEnd := 0, 0
51 for {
52 assertBatchCount(len(entries) + 1)
53 for keyEnd < len(keys) && keys[keyEnd] != ',' {
54 keyEnd++
55 }
56 for valueEnd < len(values) && values[valueEnd] != ',' {
57 valueEnd++
58 }
59 if keyStart == keyEnd || valueStart == valueEnd {
60 panic("empty entry token")
61 }
62 key, err := url.QueryUnescape(keys[keyStart:keyEnd])
63 if err != nil {
64 panic("invalid encoded key")
65 }
66 attributes, err := url.ParseQuery(values[valueStart:valueEnd])
67 if err != nil {
68 panic("invalid encoded fields")
69 }
70 if len(attributes) < 1 || len(attributes) > 32 {
71 panic("field count must be between 1 and 32")
72 }
73 fields := make([]Field, 0, len(attributes))
74 for name, fieldValues := range attributes {
75 if len(fieldValues) != 1 {
76 panic("duplicate field key")
77 }
78 fields = append(fields, Field{name, fieldValues[0]})
79 }
80 entries = append(entries, entry{key, fields})
81 keyDone := keyEnd >= len(keys)
82 valueDone := valueEnd >= len(values)
83 if keyDone != valueDone {
84 panic("keys and values count mismatch")
85 }
86 if keyDone {
87 break
88 }
89 keyEnd++
90 valueEnd++
91 keyStart, valueStart = keyEnd, valueEnd
92 }
93 // Prepare every final record before writing any state.
94 prepared := make([][]Field, len(entries))
95 batchBytes := 0
96 for i, entry := range entries {
97 assertIdentifier(entry.Key, "key", 128)
98 batchBytes += len(entry.Key) + assertFields(entry.Fields)
99 if batchBytes > 65536 {
100 panic("batch bytes exceeds 65536")
101 }
102 for j := 0; j < i; j++ {
103 if entries[j].Key == entry.Key {
104 panic("duplicate entry")
105 }
106 }
107 fields, _ := getFields(entry.Key)
108 for _, update := range entry.Fields {
109 found := false
110 for j := range fields {
111 if fields[j].Key == update.Key {
112 fields[j].Value = update.Value
113 found = true
114 break
115 }
116 }
117 if !found {
118 fields = append(fields, update)
119 }
120 }
121 assertFields(fields)
122 // At most 32 fields; insertion sort avoids another persistent index.
123 for j := 1; j < len(fields); j++ {
124 for k := j; k > 0 && fields[k].Key < fields[k-1].Key; k-- {
125 fields[k], fields[k-1] = fields[k-1], fields[k]
126 }
127 }
128 prepared[i] = fields
129 }
130 for i, entry := range entries {
131 definitions.Set(entry.Key, prepared[i])
132 }
133}
134
135// Get returns ordered field records. An empty list means the key is absent;
136// stored definitions always contain at least one field.
137func Get(key string) []map[string]string {
138 fields, _ := getFields(key)
139 result := make([]map[string]string, 0, len(fields))
140 for _, field := range fields {
141 result = append(result, map[string]string{"key": field.Key, "value": field.Value})
142 }
143 return result
144}
145
146func getFields(key string) (fields []Field, found bool) {
147 if !definitions.Has(key) {
148 return nil, false
149 }
150 stored := definitions.Get(key).([]Field)
151 fields = make([]Field, len(stored))
152 copy(fields, stored)
153 return fields, true
154}
155
156// GetField includes "value" even when it is empty. An empty map means absent.
157func GetField(key, fieldKey string) map[string]string {
158 fields, _ := getFields(key)
159 for _, field := range fields {
160 if field.Key == fieldKey {
161 return map[string]string{"value": field.Value}
162 }
163 }
164 return map[string]string{}
165}
166
167func GetKeyCount() int { return definitions.Size() }
168
169func ListKeys(page, count int) []string {
170 assertPage(page, count)
171 result := []string{}
172 // Compare before multiplication so an extreme page cannot overflow.
173 if definitions.Size() == 0 || page-1 > (definitions.Size()-1)/count {
174 return result
175 }
176 // Sort a query-local view so existing records keep their original storage keys.
177 ordered := make([]string, 0, definitions.Size())
178 definitions.Iterate("", "", func(key string, _ any) bool {
179 numeric := true
180 for _, r := range key {
181 if r < '0' || r > '9' {
182 numeric = false
183 break
184 }
185 }
186 if numeric {
187 // Keys are at most 128 bytes. Padding avoids integer overflow; the
188 // original key breaks ties such as "01" and "1" without merging them.
189 ordered = append(ordered, "0"+strings.Repeat("0", 128-len(key))+key+":"+key)
190 } else {
191 ordered = append(ordered, "1:"+key)
192 }
193 return false
194 })
195 sort.Strings(ordered)
196 start := (page - 1) * count
197 end := start + count
198 if end > len(ordered) {
199 end = len(ordered)
200 }
201 for _, key := range ordered[start:end] {
202 result = append(result, key[strings.IndexByte(key, ':')+1:])
203 }
204 return result
205}
206
207func assertCanWrite(_ int, rlm realm) {
208 accesscontrol.AssertIsAdmin(0, rlm, admin.IsAdmin)
209 // MsgRun has the signer address but an ephemeral /e/<address>/run path.
210 if !rlm.Previous().IsUser() {
211 panic("direct user call or run required")
212 }
213 if len(unsafe.OriginSend()) != 0 {
214 panic("attached coins not allowed")
215 }
216}