Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}