1package kittygraphics
2
3import (
4 "fmt"
5)
6
7// ParseControls parses the comma-separated control header of an APC.
8func ParseControls(header []byte) (Controls, error) { return parseControls(header) }
9
10// ParseHeader is an alias for ParseControls.
11func ParseHeader(header []byte) (Controls, error) { return ParseControls(header) }
12
13func parseControls(header []byte) (Controls, error) {
14 var c Controls
15 if len(header) == 0 {
16 return c, nil
17 }
18 var seen [256]bool
19 start := 0
20 for i := 0; i <= len(header); i++ {
21 if i != len(header) && header[i] != ',' {
22 continue
23 }
24 field := header[start:i]
25 if len(field) < 3 || field[1] != '=' {
26 return Controls{}, fmt.Errorf("%w: control %q", ErrInvalidCommand, field)
27 }
28 key := field[0]
29 if seen[key] {
30 return Controls{}, fmt.Errorf("%w: %c", ErrDuplicateControl, key)
31 }
32 seen[key] = true
33 value := field[2:]
34 if err := parseControl(&c, key, value); err != nil {
35 return Controls{}, err
36 }
37 start = i + 1
38 }
39 if c.HasAction && !c.Action.Valid() {
40 return Controls{}, fmt.Errorf("%w: %q", ErrUnknownAction, c.Action)
41 }
42 if c.HasImageID && c.HasImageNumber {
43 return Controls{}, fmt.Errorf("%w: image id and image number are mutually exclusive", ErrInvalidCommand)
44 }
45 if c.HasQuiet && c.Quiet > QuietAll {
46 return Controls{}, fmt.Errorf("%w: quiet=%d", ErrInvalidCommand, c.Quiet)
47 }
48 if c.HasMore && c.More > 1 {
49 return Controls{}, fmt.Errorf("%w: m=%d", ErrInvalidCommand, c.More)
50 }
51 if c.HasCursor && c.Cursor > 1 {
52 return Controls{}, fmt.Errorf("%w: C=%d", ErrInvalidCommand, c.Cursor)
53 }
54 return c, nil
55}
56
57func parseControl(c *Controls, key byte, value []byte) error {
58 if len(value) == 0 {
59 return fmt.Errorf("%w: empty %c value", ErrInvalidInteger, key)
60 }
61 switch ControlKey(key) {
62 case ControlAction:
63 if len(value) != 1 {
64 return fmt.Errorf("%w: action %q", ErrUnknownAction, value)
65 }
66 c.Action, c.HasAction = Action(value[0]), true
67 case ControlCompression:
68 if len(value) != 1 {
69 return fmt.Errorf("%w: o=%q", ErrInvalidCommand, value)
70 }
71 // Preserve unknown one-byte algorithms so Session can return ENOTSUP.
72 c.Compression, c.HasCompression = Compression(value[0]), true
73 case ControlFormat:
74 n, err := parseUint(value)
75 if err != nil {
76 return fieldError(key, err)
77 }
78 f := Format(n)
79 if !f.Valid() {
80 return fmt.Errorf("%w: f=%d", ErrInvalidCommand, n)
81 }
82 c.Format, c.HasFormat = f, true
83 case ControlImageID:
84 return setID(&c.ImageID, &c.HasImageID, key, value)
85 case ControlImageNumber:
86 return setID(&c.ImageNumber, &c.HasImageNumber, key, value)
87 case ControlMore:
88 return setUint(&c.More, &c.HasMore, key, value)
89 case ControlQuiet:
90 n, err := parseUint(value)
91 if err != nil {
92 return fieldError(key, err)
93 }
94 if n > uint64(QuietAll) {
95 return fmt.Errorf("%w: quiet=%d", ErrInvalidCommand, n)
96 }
97 c.Quiet, c.HasQuiet = Quiet(n), true
98 case ControlWidth:
99 return setUint(&c.Width, &c.HasWidth, key, value)
100 case ControlHeight:
101 return setUint(&c.Height, &c.HasHeight, key, value)
102 case ControlColumns:
103 return setUint(&c.Columns, &c.HasColumns, key, value)
104 case ControlRows:
105 return setUint(&c.Rows, &c.HasRows, key, value)
106 case ControlX:
107 return setUint(&c.X, &c.HasX, key, value)
108 case ControlY:
109 return setUint(&c.Y, &c.HasY, key, value)
110 case ControlSourceWidth:
111 return setUint(&c.SourceWidth, &c.HasSourceWidth, key, value)
112 case ControlSourceHeight:
113 return setUint(&c.SourceHeight, &c.HasSourceHeight, key, value)
114 case ControlLayer:
115 return setInt(&c.Layer, &c.HasLayer, key, value)
116 case ControlPlacementID:
117 return setID(&c.PlacementID, &c.HasPlacementID, key, value)
118 case ControlDelete:
119 if len(value) != 1 {
120 return fmt.Errorf("%w: d=%q", ErrInvalidCommand, value)
121 }
122 d := DeleteTarget(value[0])
123 switch d {
124 case DeleteImage, DeleteImageNumber, DeleteCell, DeleteAll, DeleteAllImages, DeleteCellAll:
125 c.Delete, c.HasDelete = d, true
126 default:
127 return fmt.Errorf("%w: d=%q", ErrInvalidCommand, value)
128 }
129 case ControlTransmission:
130 if len(value) != 1 {
131 return fmt.Errorf("%w: t=%q", ErrInvalidCommand, value)
132 }
133 // Preserve unsupported transports long enough for Session to emit the
134 // protocol ENOTSUP response. Rejecting them during header parsing leaves
135 // capability probes such as kitten icat waiting until timeout.
136 c.Transmission, c.HasTransmission = Transmission(value[0]), true
137 case ControlCursor:
138 return setUint(&c.Cursor, &c.HasCursor, key, value)
139 case ControlSourceX:
140 return setUint(&c.SourceX, &c.HasSourceX, key, value)
141 case ControlSourceY:
142 return setUint(&c.SourceY, &c.HasSourceY, key, value)
143 case ControlCellOffsetX:
144 return setInt(&c.CellOffsetX, &c.HasCellOffsetX, key, value)
145 case ControlCellOffsetY:
146 return setInt(&c.CellOffsetY, &c.HasCellOffsetY, key, value)
147 case ControlParent:
148 return setUint(&c.Parent, &c.HasParent, key, value)
149 default:
150 // Kitty reserves unknown controls for forward-compatible extensions.
151 // Commands that depend on an unsupported control are rejected later by
152 // their action-specific validation.
153 return nil
154 }
155 return nil
156}
157
158func fieldError(key byte, err error) error {
159 return fmt.Errorf("%w: %c: %w", ErrInvalidCommand, key, err)
160}
161
162func setID(dst *uint64, present *bool, key byte, value []byte) error {
163 if err := setUint(dst, present, key, value); err != nil {
164 return err
165 }
166 if *dst > uint64(^uint32(0)) {
167 return fieldError(key, ErrIntegerOverflow)
168 }
169 return nil
170}
171
172func setUint(dst *uint64, present *bool, key byte, value []byte) error {
173 n, err := parseUint(value)
174 if err != nil {
175 return fieldError(key, err)
176 }
177 if n > MaxKittyID {
178 return fieldError(key, ErrIntegerOverflow)
179 }
180 *dst = n
181 if present != nil {
182 *present = true
183 }
184 return nil
185}
186
187func setInt(dst *int64, present *bool, key byte, value []byte) error {
188 n, err := parseInt(value)
189 if err != nil {
190 return fieldError(key, err)
191 }
192 if n < -1<<31 || n > 1<<31-1 {
193 return fieldError(key, ErrIntegerOverflow)
194 }
195 *dst = n
196 if present != nil {
197 *present = true
198 }
199 return nil
200}
201
202func parseUint(value []byte) (uint64, error) {
203 if len(value) == 0 {
204 return 0, ErrInvalidInteger
205 }
206 var n uint64
207 for _, b := range value {
208 if b < '0' || b > '9' {
209 return 0, ErrInvalidInteger
210 }
211 digit := uint64(b - '0')
212 if n > (^uint64(0)-digit)/10 {
213 return 0, ErrIntegerOverflow
214 }
215 n = n*10 + digit
216 }
217 return n, nil
218}
219
220func parseInt(value []byte) (int64, error) {
221 if len(value) == 0 {
222 return 0, ErrInvalidInteger
223 }
224 negative := value[0] == '-'
225 if negative {
226 value = value[1:]
227 if len(value) == 0 {
228 return 0, ErrInvalidInteger
229 }
230 }
231 n, err := parseUint(value)
232 if err != nil {
233 return 0, err
234 }
235 if negative {
236 if n > uint64(1)<<63 {
237 return 0, ErrIntegerOverflow
238 }
239 if n == uint64(1)<<63 {
240 return -1 << 63, nil
241 }
242 return -int64(n), nil
243 }
244 if n > uint64(^uint64(0)>>1) {
245 return 0, ErrIntegerOverflow
246 }
247 return int64(n), nil
248}