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  }