master
go 282 lines 8.27 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package topologyv1
4
5 import "fmt"
6
7 func NewTable(rows int, columns []Column, values []ColumnEncoding) (Table, error) {
8 table := Table{
9 Rows: rows,
10 Columns: append([]Column(nil), columns...),
11 Values: append([]ColumnEncoding(nil), values...),
12 }
13 if err := table.Validate(); err != nil {
14 return Table{}, err
15 }
16 return table, nil
17 }
18
19 func MustTable(rows int, columns []Column, values []ColumnEncoding) Table {
20 table, err := NewTable(rows, columns, values)
21 if err != nil {
22 panic(err)
23 }
24 return table
25 }
26
27 func EmptyTable() Table {
28 return Table{
29 Rows: 0,
30 Columns: []Column{},
31 Values: []ColumnEncoding{},
32 }
33 }
34
35 type TableBuilder struct {
36 columns []Column
37 values [][]any
38 rowsErr error
39 }
40
41 func NewTableBuilder(columns ...Column) *TableBuilder {
42 return &TableBuilder{
43 columns: append([]Column(nil), columns...),
44 values: make([][]any, len(columns)),
45 }
46 }
47
48 func (b *TableBuilder) Add(values ...any) int {
49 row := b.Rows()
50 if len(values) != len(b.columns) {
51 b.rowsErr = fmt.Errorf("row has %d values for %d columns", len(values), len(b.columns))
52 return row
53 }
54 for i, value := range values {
55 b.values[i] = append(b.values[i], value)
56 }
57 return row
58 }
59
60 func (b *TableBuilder) Rows() int {
61 if len(b.values) == 0 {
62 return 0
63 }
64 return len(b.values[0])
65 }
66
67 func (b *TableBuilder) Table() (Table, error) {
68 if b.rowsErr != nil {
69 return Table{}, b.rowsErr
70 }
71 if len(b.columns) == 0 {
72 return EmptyTable(), nil
73 }
74 encodings := make([]ColumnEncoding, len(b.values))
75 for i, values := range b.values {
76 encoding := Values(values...)
77 if encoding.Values == nil {
78 encoding.Values = []any{}
79 }
80 encodings[i] = encoding
81 }
82 return NewTable(b.Rows(), b.columns, encodings)
83 }
84
85 func (table Table) Validate() error {
86 if table.Rows < 0 {
87 return fmt.Errorf("rows is negative: %d", table.Rows)
88 }
89 if len(table.Columns) != len(table.Values) {
90 return fmt.Errorf("columns/values length mismatch: %d columns, %d values", len(table.Columns), len(table.Values))
91 }
92
93 seenColumns := make(map[string]struct{}, len(table.Columns))
94 for i, column := range table.Columns {
95 if column.ID == "" {
96 return fmt.Errorf("columns[%d].id is empty", i)
97 }
98 if _, ok := seenColumns[column.ID]; ok {
99 return fmt.Errorf("columns[%d].id duplicates column %q", i, column.ID)
100 }
101 seenColumns[column.ID] = struct{}{}
102 if column.Type == "" {
103 return fmt.Errorf("columns[%d].type is empty", i)
104 }
105 if err := validateColumnContract(i, column); err != nil {
106 return err
107 }
108 if err := validateEncoding(table.Rows, i, column, table.Values[i]); err != nil {
109 return err
110 }
111 }
112
113 return nil
114 }
115
116 func validateColumnContract(columnIndex int, column Column) error {
117 switch column.Type {
118 case "string_ref", "ip_ref", "mac_ref":
119 if column.Dictionary == "" {
120 return fmt.Errorf("columns[%d] type %q requires dictionary", columnIndex, column.Type)
121 }
122 case "bool", "int", "uint", "float", "string", "timestamp", "duration", "ip", "mac", "actor_ref", "link_ref", "evidence_ref", "array", "json":
123 if column.Dictionary != "" {
124 return fmt.Errorf("columns[%d] uses dictionary with non-reference type %q", columnIndex, column.Type)
125 }
126 default:
127 return fmt.Errorf("columns[%d] has unsupported type %q", columnIndex, column.Type)
128 }
129
130 return nil
131 }
132
133 func validateEncoding(rows, columnIndex int, column Column, encoding ColumnEncoding) error {
134 values, err := decodeEncodingValues(rows, columnIndex, encoding)
135 if err != nil {
136 return err
137 }
138 for rowIndex, value := range values {
139 if err := validateEncodedColumnValue(columnIndex, rowIndex, column, value); err != nil {
140 return err
141 }
142 }
143 return nil
144 }
145
146 func decodeEncodingValues(rows, columnIndex int, encoding ColumnEncoding) ([]any, error) {
147 switch value := encoding.(type) {
148 case nil:
149 return nil, fmt.Errorf("values[%d] is nil", columnIndex)
150 case ConstEncoding:
151 if value.Codec != "const" {
152 return nil, fmt.Errorf("values[%d] const encoding has invalid codec %q", columnIndex, value.Codec)
153 }
154 return repeatValue(rows, value.Value), nil
155 case *ConstEncoding:
156 if value == nil {
157 return nil, fmt.Errorf("values[%d] is nil", columnIndex)
158 }
159 if value.Codec != "const" {
160 return nil, fmt.Errorf("values[%d] const encoding has invalid codec %q", columnIndex, value.Codec)
161 }
162 return repeatValue(rows, value.Value), nil
163 case ValuesEncoding:
164 if value.Codec != "values" {
165 return nil, fmt.Errorf("values[%d] values encoding has invalid codec %q", columnIndex, value.Codec)
166 }
167 if len(value.Values) != rows {
168 return nil, fmt.Errorf("values[%d] decoded length mismatch: expected %d, got %d", columnIndex, rows, len(value.Values))
169 }
170 return append([]any(nil), value.Values...), nil
171 case *ValuesEncoding:
172 if value == nil {
173 return nil, fmt.Errorf("values[%d] is nil", columnIndex)
174 }
175 if value.Codec != "values" {
176 return nil, fmt.Errorf("values[%d] values encoding has invalid codec %q", columnIndex, value.Codec)
177 }
178 if len(value.Values) != rows {
179 return nil, fmt.Errorf("values[%d] decoded length mismatch: expected %d, got %d", columnIndex, rows, len(value.Values))
180 }
181 return append([]any(nil), value.Values...), nil
182 case DictEncoding:
183 if value.Codec != "dict" {
184 return nil, fmt.Errorf("values[%d] dict encoding has invalid codec %q", columnIndex, value.Codec)
185 }
186 if err := validateDictEncoding(rows, columnIndex, value.Values, value.Indexes); err != nil {
187 return nil, err
188 }
189 return decodeDictValues(value.Values, value.Indexes), nil
190 case *DictEncoding:
191 if value == nil {
192 return nil, fmt.Errorf("values[%d] is nil", columnIndex)
193 }
194 if value.Codec != "dict" {
195 return nil, fmt.Errorf("values[%d] dict encoding has invalid codec %q", columnIndex, value.Codec)
196 }
197 if err := validateDictEncoding(rows, columnIndex, value.Values, value.Indexes); err != nil {
198 return nil, err
199 }
200 return decodeDictValues(value.Values, value.Indexes), nil
201 default:
202 return nil, fmt.Errorf("values[%d] has unsupported encoding type %T", columnIndex, encoding)
203 }
204 }
205
206 func repeatValue(rows int, value any) []any {
207 values := make([]any, rows)
208 for i := range values {
209 values[i] = value
210 }
211 return values
212 }
213
214 func decodeDictValues(values []any, indexes []int) []any {
215 decoded := make([]any, len(indexes))
216 for i, index := range indexes {
217 decoded[i] = values[index]
218 }
219 return decoded
220 }
221
222 func validateEncodedColumnValue(columnIndex, rowIndex int, column Column, value any) error {
223 if value == nil {
224 if column.Nullable {
225 return nil
226 }
227 return fmt.Errorf("values[%d][%d] is null but column is not nullable", columnIndex, rowIndex)
228 }
229
230 switch column.Type {
231 case "string_ref", "ip_ref", "mac_ref":
232 if n, ok := integerValue(value); !ok || n < 0 {
233 return fmt.Errorf("values[%d][%d] is not a non-negative dictionary reference", columnIndex, rowIndex)
234 }
235 case "actor_ref", "link_ref", "evidence_ref":
236 if n, ok := integerValue(value); !ok || n < 0 {
237 return fmt.Errorf("values[%d][%d] is not a non-negative %s reference", columnIndex, rowIndex, column.Type)
238 }
239 case "array":
240 if _, ok := value.([]any); !ok {
241 return fmt.Errorf("values[%d][%d] is not an array", columnIndex, rowIndex)
242 }
243 case "bool":
244 if _, ok := value.(bool); !ok {
245 return fmt.Errorf("values[%d][%d] is not a bool", columnIndex, rowIndex)
246 }
247 case "int":
248 if _, ok := integerValue(value); !ok {
249 return fmt.Errorf("values[%d][%d] is not an integer", columnIndex, rowIndex)
250 }
251 case "uint":
252 if n, ok := integerValue(value); !ok || n < 0 {
253 return fmt.Errorf("values[%d][%d] is not a non-negative integer", columnIndex, rowIndex)
254 }
255 case "float", "duration":
256 if _, ok := numberValue(value); !ok {
257 return fmt.Errorf("values[%d][%d] is not a number", columnIndex, rowIndex)
258 }
259 case "string", "ip", "mac", "timestamp":
260 if _, ok := value.(string); !ok {
261 return fmt.Errorf("values[%d][%d] is not a string", columnIndex, rowIndex)
262 }
263 case "json":
264 return nil
265 default:
266 return fmt.Errorf("columns[%d] has unsupported type %q", columnIndex, column.Type)
267 }
268
269 return nil
270 }
271
272 func validateDictEncoding(rows, columnIndex int, values []any, indexes []int) error {
273 if len(indexes) != rows {
274 return fmt.Errorf("values[%d] decoded length mismatch: expected %d, got %d", columnIndex, rows, len(indexes))
275 }
276 for i, index := range indexes {
277 if index < 0 || index >= len(values) {
278 return fmt.Errorf("values[%d].indexes[%d] out of bounds: %d", columnIndex, i, index)
279 }
280 }
281 return nil
282 }