go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/llx/primitives.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package llx 5 6 import ( 7 "encoding/binary" 8 "fmt" 9 "math" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/rs/zerolog/log" 15 "go.mondoo.com/cnquery/types" 16 ) 17 18 // UnsetPrimitive is the unset primitive 19 var UnsetPrimitive = &Primitive{Type: string(types.Unset)} 20 21 // NilPrimitive is the empty primitive 22 var NilPrimitive = &Primitive{Type: string(types.Nil)} 23 24 // BoolPrimitive creates a primitive from a boolean value 25 func BoolPrimitive(v bool) *Primitive { 26 return &Primitive{ 27 Type: string(types.Bool), 28 Value: bool2bytes(v), 29 } 30 } 31 32 // MaxIntPrimitive is the largest integer possible 33 var MaxIntPrimitive = &Primitive{ 34 Type: string(types.Int), 35 Value: int2bytes(math.MaxInt64), 36 } 37 38 // MinIntPrimitive is the smallest integer possible 39 var MinIntPrimitive = &Primitive{ 40 Type: string(types.Int), 41 Value: int2bytes(math.MinInt64), 42 } 43 44 // IntPrimitive creates a primitive from an int value 45 func IntPrimitive(v int64) *Primitive { 46 return &Primitive{ 47 Type: string(types.Int), 48 Value: int2bytes(v), 49 } 50 } 51 52 // FloatPrimitive creates a primitive from a float value 53 func FloatPrimitive(v float64) *Primitive { 54 return &Primitive{ 55 Type: string(types.Float), 56 Value: float2bytes(v), 57 } 58 } 59 60 // StringPrimitive creates a primitive from a string value 61 func StringPrimitive(s string) *Primitive { 62 return &Primitive{ 63 Type: string(types.String), 64 Value: []byte(s), 65 } 66 } 67 68 // RegexPrimitive creates a primitive from a regex in string shape 69 func RegexPrimitive(r string) *Primitive { 70 return &Primitive{ 71 Type: string(types.Regex), 72 Value: []byte(r), 73 } 74 } 75 76 // TimePrimitive creates a primitive from a time value 77 func TimePrimitive(t *time.Time) *Primitive { 78 if t == nil { 79 return NilPrimitive 80 } 81 82 seconds := t.Unix() 83 nanos := t.Nanosecond() 84 85 v := make([]byte, 12) 86 binary.LittleEndian.PutUint64(v, uint64(seconds)) 87 binary.LittleEndian.PutUint32(v[8:], uint32(nanos)) 88 89 return &Primitive{ 90 Type: string(types.Time), 91 Value: v, 92 } 93 } 94 95 // NeverFutureTime is an indicator for what we consider infinity when looking at time 96 var NeverFutureTime = time.Unix(1<<63-1, 0) 97 98 // NeverPastTime is an indicator for what we consider negative infinity when looking at time 99 var NeverPastTime = time.Unix(-(1<<63 - 1), 0) 100 101 // NeverFuturePrimitive is the special time primitive for the infinite future time 102 var NeverFuturePrimitive = TimePrimitive(&NeverFutureTime) 103 104 // NeverPastPrimitive is the special time primitive for the infinite future time 105 var NeverPastPrimitive = TimePrimitive(&NeverPastTime) 106 107 // ScorePrimitive creates a primitive with a numeric score 108 func ScorePrimitive(num int32) *Primitive { 109 v, err := scoreVector(num) 110 if err != nil { 111 panic(err.Error()) 112 } 113 114 return &Primitive{ 115 Type: string(types.Score), 116 Value: v, 117 } 118 } 119 120 // CvssScorePrimitive creates a primitive for a CVSS score 121 func CvssScorePrimitive(vector string) *Primitive { 122 b, err := scoreString(vector) 123 if err != nil { 124 panic(err.Error()) 125 } 126 127 return &Primitive{ 128 Type: string(types.Score), 129 Value: b, 130 } 131 } 132 133 // RefPrimitive creates a primitive from an int value 134 func RefPrimitiveV2(v uint64) *Primitive { 135 return &Primitive{ 136 Type: string(types.Ref), 137 Value: int2bytes(int64(v)), 138 } 139 } 140 141 // EmptyPrimitive is the empty value indicator 142 var EmptyPrimitive = &Primitive{Type: string(types.Empty)} 143 144 // ArrayPrimitive creates a primitive from a list of primitives 145 func ArrayPrimitive(v []*Primitive, childType types.Type) *Primitive { 146 return &Primitive{ 147 Type: string(types.Array(childType)), 148 Array: v, 149 } 150 } 151 152 // ArrayPrimitiveT create a primitive from an array of type T 153 func ArrayPrimitiveT[T any](v []T, f func(T) *Primitive, typ types.Type) *Primitive { 154 vt := make([]*Primitive, len(v)) 155 for i := range v { 156 vt[i] = f(v[i]) 157 } 158 return ArrayPrimitive(vt, typ) 159 } 160 161 // MapPrimitive creates a primitive from a map of primitives 162 func MapPrimitive(v map[string]*Primitive, childType types.Type) *Primitive { 163 return &Primitive{ 164 Type: string(types.Map(types.String, childType)), 165 Map: v, 166 } 167 } 168 169 // MapPrimitive creates a primitive from a map of type T 170 func MapPrimitiveT[T any](v map[string]T, f func(T) *Primitive, typ types.Type) *Primitive { 171 vt := make(map[string]*Primitive, len(v)) 172 for i := range v { 173 vt[i] = f(v[i]) 174 } 175 return MapPrimitive(vt, typ) 176 } 177 178 // FunctionPrimitive points to a function in the call stack 179 func FunctionPrimitiveV1(v int32) *Primitive { 180 return &Primitive{ 181 // TODO: function signature 182 Type: string(types.Function(0, nil)), 183 Value: int2bytes(int64(v)), 184 } 185 } 186 187 // FunctionPrimitive points to a function in the call stack 188 func FunctionPrimitive(v uint64) *Primitive { 189 return &Primitive{ 190 // TODO: function signature 191 Type: string(types.Function(0, nil)), 192 Value: int2bytes(int64(v)), 193 } 194 } 195 196 // RangePrimitive creates a range primitive from the given 197 // range data. Use the helper functions to initialize and 198 // combine multiple sets of range data. 199 func RangePrimitive(data RangeData) *Primitive { 200 return &Primitive{ 201 Type: string(types.Range), 202 Value: data, 203 } 204 } 205 206 type RangeData []byte 207 208 const ( 209 // Byte indicators for ranges work like this: 210 // 211 // Byte1: version + mode 212 // xxxx xxxx 213 // VVVV -------> version for the range 214 // MMMM --> 1 = single line 215 // 2 = line range 216 // 3 = line with column range 217 // 4 = line + column range 218 // 219 // Byte2+: length indicators 220 // xxxx xxxx 221 // NNNN -------> length of the first entry (up to 128bit) 222 // MMMM --> length of the second entry (up to 128bit) 223 // note: currently we only support up to 32bit 224 // 225 rangeVersion1 byte = 0x10 226 ) 227 228 func NewRange() RangeData { 229 return []byte{} 230 } 231 232 func (r RangeData) AddLine(line uint32) RangeData { 233 r = append(r, rangeVersion1|0x01) 234 bytes := int2bytes(int64(line)) 235 r = append(r, byte(len(bytes)<<4)) 236 r = append(r, bytes...) 237 return r 238 } 239 240 func (r RangeData) AddLineRange(line1 uint32, line2 uint32) RangeData { 241 r = append(r, rangeVersion1|0x02) 242 bytes1 := int2bytes(int64(line1)) 243 bytes2 := int2bytes(int64(line2)) 244 r = append(r, byte(len(bytes1)<<4)|byte(len(bytes2)&0x0f)) 245 r = append(r, bytes1...) 246 r = append(r, bytes2...) 247 return r 248 } 249 250 func (r RangeData) AddColumnRange(line uint32, column1 uint32, column2 uint32) RangeData { 251 r = append(r, rangeVersion1|0x03) 252 bytes := int2bytes(int64(line)) 253 bytes1 := int2bytes(int64(column1)) 254 bytes2 := int2bytes(int64(column2)) 255 256 r = append(r, byte(len(bytes)<<4)) 257 r = append(r, bytes...) 258 259 r = append(r, byte(len(bytes1)<<4)|byte(len(bytes2)&0xf)) 260 r = append(r, bytes1...) 261 r = append(r, bytes2...) 262 return r 263 } 264 265 func (r RangeData) AddLineColumnRange(line1 uint32, line2 uint32, column1 uint32, column2 uint32) RangeData { 266 r = append(r, rangeVersion1|0x04) 267 bytes1 := int2bytes(int64(line1)) 268 bytes2 := int2bytes(int64(line2)) 269 r = append(r, byte(len(bytes1)<<4)|byte(len(bytes2)&0xf)) 270 r = append(r, bytes1...) 271 r = append(r, bytes2...) 272 273 bytes1 = int2bytes(int64(column1)) 274 bytes2 = int2bytes(int64(column2)) 275 r = append(r, byte(len(bytes1)<<4)|byte(len(bytes2)&0xf)) 276 r = append(r, bytes1...) 277 r = append(r, bytes2...) 278 279 return r 280 } 281 282 func (r RangeData) ExtractNext() ([]uint32, RangeData) { 283 if len(r) == 0 { 284 return nil, nil 285 } 286 287 version := r[0] & 0xf0 288 if version != rangeVersion1 { 289 log.Error().Msg("failed to extract range, version is unsupported") 290 return nil, nil 291 } 292 293 entries := r[0] & 0x0f 294 res := []uint32{} 295 idx := 1 296 switch entries { 297 case 3, 4: 298 l1 := int((r[idx] & 0xf0) >> 4) 299 l2 := int(r[idx] & 0x0f) 300 301 idx++ 302 if l1 != 0 { 303 n := bytes2int(r[idx : idx+l1]) 304 idx += l1 305 res = append(res, uint32(n)) 306 } 307 if l2 != 0 { 308 n := bytes2int(r[idx : idx+l1]) 309 idx += l2 310 res = append(res, uint32(n)) 311 } 312 313 fallthrough 314 315 case 1, 2: 316 l1 := int((r[idx] & 0xf0) >> 4) 317 l2 := int(r[idx] & 0x0f) 318 319 idx++ 320 if l1 != 0 { 321 n := bytes2int(r[idx : idx+l1]) 322 idx += l1 323 res = append(res, uint32(n)) 324 } 325 if l2 != 0 { 326 n := bytes2int(r[idx : idx+l1]) 327 idx += l2 328 res = append(res, uint32(n)) 329 } 330 331 default: 332 log.Error().Msg("failed to extract range, wrong number of entries") 333 return nil, nil 334 } 335 336 return res, r[idx:] 337 } 338 339 func (r RangeData) ExtractAll() [][]uint32 { 340 res := [][]uint32{} 341 for { 342 cur, rest := r.ExtractNext() 343 if len(cur) != 0 { 344 res = append(res, cur) 345 } 346 if len(rest) == 0 { 347 break 348 } 349 r = rest 350 } 351 352 return res 353 } 354 355 func (r RangeData) String() string { 356 var res strings.Builder 357 358 items := r.ExtractAll() 359 for i := range items { 360 x := items[i] 361 switch len(x) { 362 case 1: 363 res.WriteString(strconv.Itoa(int(x[0]))) 364 case 2: 365 res.WriteString(strconv.Itoa(int(x[0]))) 366 res.WriteString("-") 367 res.WriteString(strconv.Itoa(int(x[1]))) 368 case 3: 369 res.WriteString(strconv.Itoa(int(x[0]))) 370 res.WriteString(":") 371 res.WriteString(strconv.Itoa(int(x[1]))) 372 res.WriteString("-") 373 res.WriteString(strconv.Itoa(int(x[2]))) 374 case 4: 375 res.WriteString(strconv.Itoa(int(x[0]))) 376 res.WriteString(":") 377 res.WriteString(strconv.Itoa(int(x[2]))) 378 res.WriteString("-") 379 res.WriteString(strconv.Itoa(int(x[1]))) 380 res.WriteString(":") 381 res.WriteString(strconv.Itoa(int(x[3]))) 382 } 383 384 if i != len(items)-1 { 385 res.WriteString(",") 386 } 387 } 388 389 return res.String() 390 } 391 392 // Ref will return the ref value unless this is not a ref type 393 func (p *Primitive) RefV1() (int32, bool) { 394 typ := types.Type(p.Type) 395 if typ != types.Ref && typ.Underlying() != types.FunctionLike { 396 return 0, false 397 } 398 return int32(bytes2int(p.Value)), true 399 } 400 401 // Ref will return the ref value unless this is not a ref type 402 func (p *Primitive) RefV2() (uint64, bool) { 403 typ := types.Type(p.Type) 404 if typ != types.Ref && typ.Underlying() != types.FunctionLike { 405 return 0, false 406 } 407 return uint64(bytes2int(p.Value)), true 408 } 409 410 // Label returns a printable label for this primitive 411 func (p *Primitive) LabelV1(code *CodeV1) string { 412 switch types.Type(p.Type).Underlying() { 413 case types.Any: 414 return string(p.Value) 415 case types.Ref: 416 return "<ref>" 417 case types.Nil: 418 return "null" 419 case types.Bool: 420 if len(p.Value) == 0 { 421 return "null" 422 } 423 if bytes2bool(p.Value) { 424 return "true" 425 } 426 return "false" 427 case types.Int: 428 if len(p.Value) == 0 { 429 return "null" 430 } 431 data := bytes2int(p.Value) 432 if data == math.MaxInt64 { 433 return "Infinity" 434 } 435 if data == math.MinInt64 { 436 return "-Infinity" 437 } 438 return fmt.Sprintf("%d", data) 439 case types.Float: 440 if len(p.Value) == 0 { 441 return "null" 442 } 443 data := bytes2float(p.Value) 444 if math.IsInf(data, 1) { 445 return "Infinity" 446 } 447 if math.IsInf(data, -1) { 448 return "-Infinity" 449 } 450 return fmt.Sprintf("%f", data) 451 case types.String: 452 if len(p.Value) == 0 { 453 return "null" 454 } 455 return PrettyPrintString(string(p.Value)) 456 case types.Regex: 457 if len(p.Value) == 0 { 458 return "null" 459 } 460 return fmt.Sprintf("/%s/", string(p.Value)) 461 case types.Time: 462 return "<...>" 463 case types.Dict: 464 return "<...>" 465 case types.Score: 466 return ScoreString(p.Value) 467 case types.ArrayLike: 468 if len(p.Array) == 0 { 469 return "[]" 470 } 471 return "[..]" 472 473 case types.MapLike: 474 if len(p.Map) == 0 { 475 return "{}" 476 } 477 return "{..}" 478 479 case types.ResourceLike: 480 return "" 481 482 default: 483 return "" 484 } 485 } 486 487 // Label returns a printable label for this primitive 488 func (p *Primitive) LabelV2(code *CodeV2) string { 489 switch types.Type(p.Type).Underlying() { 490 case types.Any: 491 return string(p.Value) 492 case types.Ref: 493 return "<ref>" 494 case types.Nil: 495 return "null" 496 case types.Bool: 497 if len(p.Value) == 0 { 498 return "null" 499 } 500 if bytes2bool(p.Value) { 501 return "true" 502 } 503 return "false" 504 case types.Int: 505 if len(p.Value) == 0 { 506 return "null" 507 } 508 data := bytes2int(p.Value) 509 if data == math.MaxInt64 { 510 return "Infinity" 511 } 512 if data == math.MinInt64 { 513 return "-Infinity" 514 } 515 return fmt.Sprintf("%d", data) 516 case types.Float: 517 if len(p.Value) == 0 { 518 return "null" 519 } 520 data := bytes2float(p.Value) 521 if math.IsInf(data, 1) { 522 return "Infinity" 523 } 524 if math.IsInf(data, -1) { 525 return "-Infinity" 526 } 527 return fmt.Sprintf("%f", data) 528 case types.String: 529 if len(p.Value) == 0 { 530 return "null" 531 } 532 return PrettyPrintString(string(p.Value)) 533 case types.Regex: 534 if len(p.Value) == 0 { 535 return "null" 536 } 537 return fmt.Sprintf("/%s/", string(p.Value)) 538 case types.Time: 539 return "<...>" 540 case types.Dict: 541 return "<...>" 542 case types.Score: 543 return ScoreString(p.Value) 544 case types.ArrayLike: 545 if len(p.Array) == 0 { 546 return "[]" 547 } 548 return "[..]" 549 550 case types.MapLike: 551 if len(p.Map) == 0 { 552 return "{}" 553 } 554 return "{..}" 555 556 case types.ResourceLike: 557 return "" 558 559 case types.Range: 560 return RangeData(p.Value).String() 561 562 default: 563 return "" 564 } 565 } 566 567 func PrettyPrintString(s string) string { 568 res := fmt.Sprintf("%#v", s) 569 res = strings.ReplaceAll(res, "\\n", "\n") 570 res = strings.ReplaceAll(res, "\\t", "\t") 571 return res 572 } 573 574 // Estimation based on https://golang.org/src/runtime/slice.go 575 const arrayOverhead = 2 * 4 576 577 // Estimation based on https://golang.org/src/runtime/slice.go 578 const mapOverhead = 4 + 1 + 1 + 2 + 4 579 580 // Size returns the approximate size of the primitive in bytes 581 func (p *Primitive) Size() int { 582 typ := types.Type(p.Type) 583 584 if typ.NotSet() { 585 return 0 586 } 587 588 if typ.IsArray() { 589 var res int 590 for i := range p.Array { 591 res += p.Array[i].Size() 592 } 593 594 return res + arrayOverhead 595 } 596 597 if typ.IsMap() { 598 // We are under-estimating the real size of maps in memory, because buckets 599 // actually use more room than the calculation below suggests. However, 600 // for the sake of approximation, it serves well enough. 601 602 var res int 603 for k, v := range p.Map { 604 res += len(k) 605 res += v.Size() 606 } 607 608 return res + mapOverhead 609 } 610 611 return len(p.Value) 612 } 613 614 // IsNil returns true if a primitive is nil. A primitive is nil if it's type is nil, 615 // or if it has no associated value. The exception is the string type. If an empty 616 // bytes field is serialized (for example for an empty string), that field is nil. 617 func (p *Primitive) IsNil() bool { 618 return p == nil || 619 p.Type == string(types.Nil) 620 }