payload.go RawHistoryFolder
1package core
2
3import (
4 "errors"
5 "fmt"
6 "strings"
7 "unicode/utf8"
8)
9
10const (
11 MaxGraphemeBytes = 128
12 MaxHyperlinkBytes = 4096
13 // PayloadRecordLogicalBytes accounts for two fixed-width string descriptors.
14 PayloadRecordLogicalBytes uint64 = 16
15)
16
17var ErrInvalidCellPayload = errors.New("invalid cell payload")
18
19// CellPayload is an immutable semantic exceptional value. Its zero value is
20// empty. Storage support does not imply parsing support for additional terminal
21// protocols; a payload never exposes a page-local handle.
22type CellPayload struct {
23 grapheme string
24 hyperlink string
25}
26
27// NewCellPayload validates bounded UTF-8 strings without terminal controls.
28// It copies nonempty strings so a small value cannot retain an oversized input.
29func NewCellPayload(grapheme, hyperlink string) (CellPayload, error) {
30 if !validPayloadString(grapheme, MaxGraphemeBytes) || !validPayloadString(hyperlink, MaxHyperlinkBytes) {
31 return CellPayload{}, ErrInvalidCellPayload
32 }
33 return CellPayload{grapheme: strings.Clone(grapheme), hyperlink: strings.Clone(hyperlink)}, nil
34}
35
36func validPayloadString(value string, limit int) bool {
37 if len(value) > limit || !utf8.ValidString(value) {
38 return false
39 }
40 for _, r := range value {
41 if r < 0x20 || r >= 0x7f && r <= 0x9f {
42 return false
43 }
44 }
45 return true
46}
47
48func (p CellPayload) Grapheme() string { return p.grapheme }
49func (p CellPayload) Hyperlink() string { return p.hyperlink }
50func (p CellPayload) Empty() bool { return p == CellPayload{} }
51func (p CellPayload) LogicalBytes() uint64 {
52 if p.Empty() {
53 return 0
54 }
55 return PayloadRecordLogicalBytes + uint64(len(p.grapheme)) + uint64(len(p.hyperlink))
56}
57
58type payloadSlot struct {
59 value CellPayload
60 refs uint32
61}
62
63// internPayload returns a page-local handle and acquires one reference. Slot
64// zero is the implicit empty payload and requires no dictionary allocation.
65func (f Frame) internPayload(value CellPayload) uint32 {
66 if value.Empty() {
67 return 0
68 }
69 p := f.page
70 if id, ok := p.payloadIndex[value]; ok {
71 p.payloads[id-1].refs++
72 return id
73 }
74 if p.payloadIndex == nil {
75 p.payloadIndex = make(map[CellPayload]uint32)
76 }
77 var id uint32
78 if n := len(p.freePayloads); n > 0 {
79 id = p.freePayloads[n-1]
80 p.freePayloads = p.freePayloads[:n-1]
81 p.payloads[id-1] = payloadSlot{value: value, refs: 1}
82 } else {
83 p.payloads = append(p.payloads, payloadSlot{value: value, refs: 1})
84 id = uint32(len(p.payloads))
85 }
86 p.payloadIndex[value] = id
87 p.payloadBytes += value.LogicalBytes()
88 return id
89}
90
91func (f Frame) releasePayload(id uint32) {
92 if id == 0 {
93 return
94 }
95 p := f.page
96 slot := &p.payloads[id-1]
97 slot.refs--
98 if slot.refs != 0 {
99 return
100 }
101 delete(p.payloadIndex, slot.value)
102 p.payloadBytes -= slot.value.LogicalBytes()
103 *slot = payloadSlot{}
104 p.freePayloads = append(p.freePayloads, id)
105}
106
107func (f Frame) payload(id uint32) CellPayload {
108 if id == 0 {
109 return CellPayload{}
110 }
111 return f.page.payloads[id-1].value
112}
113
114func (f Frame) checkPayloadInvariants() error {
115 p := f.page
116 refs := make([]uint32, len(p.payloads))
117 for _, cell := range p.cells {
118 id := cell.payloadID
119 if id == 0 {
120 continue
121 }
122 if uint64(id) > uint64(len(refs)) {
123 return fmt.Errorf("unresolved payload ID %d", id)
124 }
125 refs[id-1]++
126 }
127 free := make([]bool, len(p.payloads))
128 for _, id := range p.freePayloads {
129 if id == 0 || uint64(id) > uint64(len(free)) || free[id-1] {
130 return fmt.Errorf("invalid free payload ID %d", id)
131 }
132 free[id-1] = true
133 }
134 var total uint64
135 used := 0
136 for i, slot := range p.payloads {
137 if slot.refs != refs[i] {
138 return fmt.Errorf("payload %d reference mismatch", i+1)
139 }
140 if slot.refs == 0 {
141 if !free[i] || !slot.value.Empty() {
142 return fmt.Errorf("unreclaimed payload %d", i+1)
143 }
144 continue
145 }
146 if free[i] || slot.value.Empty() || p.payloadIndex[slot.value] != uint32(i+1) {
147 return fmt.Errorf("invalid payload dictionary entry %d", i+1)
148 }
149 if !validPayloadString(slot.value.grapheme, MaxGraphemeBytes) || !validPayloadString(slot.value.hyperlink, MaxHyperlinkBytes) {
150 return ErrInvalidCellPayload
151 }
152 total += slot.value.LogicalBytes()
153 used++
154 }
155 if total != p.payloadBytes || used != len(p.payloadIndex) {
156 return fmt.Errorf("payload accounting mismatch")
157 }
158 return nil
159}