From 5fa44412e5221f8532d7858aefbe5ae179dc52ba Mon Sep 17 00:00:00 2001 From: Dominik Richter Date: Wed, 5 Feb 2025 04:09:00 -0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=B9=20extract=20range=20from=20llx=20(?= =?UTF-8?q?#5167)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Dominik Richter --- llx/primitives.go | 198 --------------------------------------- llx/primitives_test.go | 22 ----- llx/range.go | 208 +++++++++++++++++++++++++++++++++++++++++ llx/range_test.go | 32 +++++++ 4 files changed, 240 insertions(+), 220 deletions(-) create mode 100644 llx/range.go create mode 100644 llx/range_test.go diff --git a/llx/primitives.go b/llx/primitives.go index 2d879ef2be..77f497dd65 100644 --- a/llx/primitives.go +++ b/llx/primitives.go @@ -7,11 +7,9 @@ import ( "encoding/binary" "fmt" "math" - "strconv" "strings" "time" - "github.com/rs/zerolog/log" "go.mondoo.com/cnquery/v11/types" ) @@ -193,202 +191,6 @@ func FunctionPrimitive(v uint64) *Primitive { } } -// RangePrimitive creates a range primitive from the given -// range data. Use the helper functions to initialize and -// combine multiple sets of range data. -func RangePrimitive(data RangeData) *Primitive { - return &Primitive{ - Type: string(types.Range), - Value: data, - } -} - -type RangeData []byte - -const ( - // Byte indicators for ranges work like this: - // - // Byte1: version + mode - // xxxx xxxx - // VVVV -------> version for the range - // MMMM --> 1 = single line - // 2 = line range - // 3 = line with column range - // 4 = line + column range - // - // Byte2+: length indicators - // xxxx xxxx - // NNNN -------> length of the first entry (up to 128bit) - // MMMM --> length of the second entry (up to 128bit) - // note: currently we only support up to 32bit - // - rangeVersion1 byte = 0x10 -) - -func NewRange() RangeData { - return []byte{} -} - -func (r RangeData) AddLine(line uint32) RangeData { - r = append(r, rangeVersion1|0x01) - bytes := int2bytes(int64(line)) - r = append(r, byte(len(bytes)<<4)) - r = append(r, bytes...) - return r -} - -func (r RangeData) AddLineRange(line1 uint32, line2 uint32) RangeData { - r = append(r, rangeVersion1|0x02) - bytes1 := int2bytes(int64(line1)) - bytes2 := int2bytes(int64(line2)) - r = append(r, byte(len(bytes1)<<4)|byte(len(bytes2)&0x0f)) - r = append(r, bytes1...) - r = append(r, bytes2...) - return r -} - -func (r RangeData) AddColumnRange(line uint32, column1 uint32, column2 uint32) RangeData { - r = append(r, rangeVersion1|0x03) - bytes := int2bytes(int64(line)) - bytes1 := int2bytes(int64(column1)) - bytes2 := int2bytes(int64(column2)) - - r = append(r, byte(len(bytes)<<4)) - r = append(r, bytes...) - - r = append(r, byte(len(bytes1)<<4)|byte(len(bytes2)&0xf)) - r = append(r, bytes1...) - r = append(r, bytes2...) - return r -} - -func (r RangeData) AddLineColumnRange(line1 uint32, line2 uint32, column1 uint32, column2 uint32) RangeData { - r = append(r, rangeVersion1|0x04) - bytes1 := int2bytes(int64(line1)) - bytes2 := int2bytes(int64(line2)) - r = append(r, byte(len(bytes1)<<4)|byte(len(bytes2)&0xf)) - r = append(r, bytes1...) - r = append(r, bytes2...) - - bytes1 = int2bytes(int64(column1)) - bytes2 = int2bytes(int64(column2)) - r = append(r, byte(len(bytes1)<<4)|byte(len(bytes2)&0xf)) - r = append(r, bytes1...) - r = append(r, bytes2...) - - return r -} - -func (r RangeData) ExtractNext() ([]uint32, RangeData) { - if len(r) == 0 { - return nil, nil - } - - version := r[0] & 0xf0 - if version != rangeVersion1 { - log.Error().Msg("failed to extract range, version is unsupported") - return nil, nil - } - - entries := r[0] & 0x0f - res := []uint32{} - idx := 1 - switch entries { - case 3, 4: - l1 := int((r[idx] & 0xf0) >> 4) - l2 := int(r[idx] & 0x0f) - - idx++ - if l1 != 0 { - n := bytes2int(r[idx : idx+l1]) - idx += l1 - res = append(res, uint32(n)) - } - if l2 != 0 { - n := bytes2int(r[idx : idx+l1]) - idx += l2 - res = append(res, uint32(n)) - } - - fallthrough - - case 1, 2: - l1 := int((r[idx] & 0xf0) >> 4) - l2 := int(r[idx] & 0x0f) - - idx++ - if l1 != 0 { - n := bytes2int(r[idx : idx+l1]) - idx += l1 - res = append(res, uint32(n)) - } - if l2 != 0 { - n := bytes2int(r[idx : idx+l1]) - idx += l2 - res = append(res, uint32(n)) - } - - default: - log.Error().Msg("failed to extract range, wrong number of entries") - return nil, nil - } - - return res, r[idx:] -} - -func (r RangeData) ExtractAll() [][]uint32 { - res := [][]uint32{} - for { - cur, rest := r.ExtractNext() - if len(cur) != 0 { - res = append(res, cur) - } - if len(rest) == 0 { - break - } - r = rest - } - - return res -} - -func (r RangeData) String() string { - var res strings.Builder - - items := r.ExtractAll() - for i := range items { - x := items[i] - switch len(x) { - case 1: - res.WriteString(strconv.Itoa(int(x[0]))) - case 2: - res.WriteString(strconv.Itoa(int(x[0]))) - res.WriteString("-") - res.WriteString(strconv.Itoa(int(x[1]))) - case 3: - res.WriteString(strconv.Itoa(int(x[0]))) - res.WriteString(":") - res.WriteString(strconv.Itoa(int(x[1]))) - res.WriteString("-") - res.WriteString(strconv.Itoa(int(x[2]))) - case 4: - res.WriteString(strconv.Itoa(int(x[0]))) - res.WriteString(":") - res.WriteString(strconv.Itoa(int(x[2]))) - res.WriteString("-") - res.WriteString(strconv.Itoa(int(x[1]))) - res.WriteString(":") - res.WriteString(strconv.Itoa(int(x[3]))) - } - - if i != len(items)-1 { - res.WriteString(",") - } - } - - return res.String() -} - // Ref will return the ref value unless this is not a ref type func (p *Primitive) RefV1() (int32, bool) { typ := types.Type(p.Type) diff --git a/llx/primitives_test.go b/llx/primitives_test.go index 43141d4a55..93e0a97f4e 100644 --- a/llx/primitives_test.go +++ b/llx/primitives_test.go @@ -109,25 +109,3 @@ func TestPrimitiveNil(t *testing.T) { assert.False(t, p.IsNil()) }) } - -func TestRange(t *testing.T) { - t.Run("single line", func(t *testing.T) { - r := RangePrimitive(NewRange().AddLine(12)) - assert.Equal(t, "12", r.LabelV2(nil)) - }) - - t.Run("line range", func(t *testing.T) { - r := RangePrimitive(NewRange().AddLineRange(12, 18)) - assert.Equal(t, "12-18", r.LabelV2(nil)) - }) - - t.Run("column range", func(t *testing.T) { - r := RangePrimitive(NewRange().AddColumnRange(12, 1, 28)) - assert.Equal(t, "12:1-28", r.LabelV2(nil)) - }) - - t.Run("line and column range", func(t *testing.T) { - r := RangePrimitive(NewRange().AddLineColumnRange(12, 18, 1, 28)) - assert.Equal(t, "12:1-18:28", r.LabelV2(nil)) - }) -} diff --git a/llx/range.go b/llx/range.go new file mode 100644 index 0000000000..8284dff8c4 --- /dev/null +++ b/llx/range.go @@ -0,0 +1,208 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package llx + +import ( + "strconv" + "strings" + + "github.com/rs/zerolog/log" + "go.mondoo.com/cnquery/v11/types" +) + +// RangePrimitive creates a range primitive from the given +// range data. Use the helper functions to initialize and +// combine multiple sets of range data. +func RangePrimitive(data RangeData) *Primitive { + return &Primitive{ + Type: string(types.Range), + Value: data, + } +} + +type RangeData []byte + +const ( + // Byte indicators for ranges work like this: + // + // Byte1: version + mode + // xxxx xxxx + // VVVV -------> version for the range + // MMMM --> 1 = single line + // 2 = line range + // 3 = line with column range + // 4 = line + column range + // + // Byte2+: length indicators + // xxxx xxxx + // NNNN -------> length of the first entry (up to 128bit) + // MMMM --> length of the second entry (up to 128bit) + // note: currently we only support up to 32bit + // + rangeVersion1 byte = 0x10 +) + +func NewRange() RangeData { + return []byte{} +} + +func (r RangeData) AddLine(line uint32) RangeData { + r = append(r, rangeVersion1|0x01) + bytes := int2bytes(int64(line)) + r = append(r, byte(len(bytes)<<4)) + r = append(r, bytes...) + return r +} + +func (r RangeData) AddLineRange(line1 uint32, line2 uint32) RangeData { + r = append(r, rangeVersion1|0x02) + bytes1 := int2bytes(int64(line1)) + bytes2 := int2bytes(int64(line2)) + r = append(r, byte(len(bytes1)<<4)|byte(len(bytes2)&0x0f)) + r = append(r, bytes1...) + r = append(r, bytes2...) + return r +} + +func (r RangeData) AddColumnRange(line uint32, column1 uint32, column2 uint32) RangeData { + r = append(r, rangeVersion1|0x03) + bytes := int2bytes(int64(line)) + bytes1 := int2bytes(int64(column1)) + bytes2 := int2bytes(int64(column2)) + + r = append(r, byte(len(bytes)<<4)) + r = append(r, bytes...) + + r = append(r, byte(len(bytes1)<<4)|byte(len(bytes2)&0xf)) + r = append(r, bytes1...) + r = append(r, bytes2...) + return r +} + +func (r RangeData) AddLineColumnRange(line1 uint32, line2 uint32, column1 uint32, column2 uint32) RangeData { + r = append(r, rangeVersion1|0x04) + bytes1 := int2bytes(int64(line1)) + bytes2 := int2bytes(int64(line2)) + r = append(r, byte(len(bytes1)<<4)|byte(len(bytes2)&0xf)) + r = append(r, bytes1...) + r = append(r, bytes2...) + + bytes1 = int2bytes(int64(column1)) + bytes2 = int2bytes(int64(column2)) + r = append(r, byte(len(bytes1)<<4)|byte(len(bytes2)&0xf)) + r = append(r, bytes1...) + r = append(r, bytes2...) + + return r +} + +func (r RangeData) ExtractNext() ([]uint32, RangeData) { + if len(r) == 0 { + return nil, nil + } + + version := r[0] & 0xf0 + if version != rangeVersion1 { + log.Error().Msg("failed to extract range, version is unsupported") + return nil, nil + } + + entries := r[0] & 0x0f + res := []uint32{} + idx := 1 + switch entries { + case 3, 4: + l1 := int((r[idx] & 0xf0) >> 4) + l2 := int(r[idx] & 0x0f) + + idx++ + if l1 != 0 { + n := bytes2int(r[idx : idx+l1]) + idx += l1 + res = append(res, uint32(n)) + } + if l2 != 0 { + n := bytes2int(r[idx : idx+l1]) + idx += l2 + res = append(res, uint32(n)) + } + + fallthrough + + case 1, 2: + l1 := int((r[idx] & 0xf0) >> 4) + l2 := int(r[idx] & 0x0f) + + idx++ + if l1 != 0 { + n := bytes2int(r[idx : idx+l1]) + idx += l1 + res = append(res, uint32(n)) + } + if l2 != 0 { + n := bytes2int(r[idx : idx+l1]) + idx += l2 + res = append(res, uint32(n)) + } + + default: + log.Error().Msg("failed to extract range, wrong number of entries") + return nil, nil + } + + return res, r[idx:] +} + +func (r RangeData) ExtractAll() [][]uint32 { + res := [][]uint32{} + for { + cur, rest := r.ExtractNext() + if len(cur) != 0 { + res = append(res, cur) + } + if len(rest) == 0 { + break + } + r = rest + } + + return res +} + +func (r RangeData) String() string { + var res strings.Builder + + items := r.ExtractAll() + for i := range items { + x := items[i] + switch len(x) { + case 1: + res.WriteString(strconv.Itoa(int(x[0]))) + case 2: + res.WriteString(strconv.Itoa(int(x[0]))) + res.WriteString("-") + res.WriteString(strconv.Itoa(int(x[1]))) + case 3: + res.WriteString(strconv.Itoa(int(x[0]))) + res.WriteString(":") + res.WriteString(strconv.Itoa(int(x[1]))) + res.WriteString("-") + res.WriteString(strconv.Itoa(int(x[2]))) + case 4: + res.WriteString(strconv.Itoa(int(x[0]))) + res.WriteString(":") + res.WriteString(strconv.Itoa(int(x[2]))) + res.WriteString("-") + res.WriteString(strconv.Itoa(int(x[1]))) + res.WriteString(":") + res.WriteString(strconv.Itoa(int(x[3]))) + } + + if i != len(items)-1 { + res.WriteString(",") + } + } + + return res.String() +} diff --git a/llx/range_test.go b/llx/range_test.go new file mode 100644 index 0000000000..3fb657e6b6 --- /dev/null +++ b/llx/range_test.go @@ -0,0 +1,32 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package llx + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRange(t *testing.T) { + t.Run("single line", func(t *testing.T) { + r := RangePrimitive(NewRange().AddLine(12)) + assert.Equal(t, "12", r.LabelV2(nil)) + }) + + t.Run("line range", func(t *testing.T) { + r := RangePrimitive(NewRange().AddLineRange(12, 18)) + assert.Equal(t, "12-18", r.LabelV2(nil)) + }) + + t.Run("column range", func(t *testing.T) { + r := RangePrimitive(NewRange().AddColumnRange(12, 1, 28)) + assert.Equal(t, "12:1-28", r.LabelV2(nil)) + }) + + t.Run("line and column range", func(t *testing.T) { + r := RangePrimitive(NewRange().AddLineColumnRange(12, 18, 1, 28)) + assert.Equal(t, "12:1-18:28", r.LabelV2(nil)) + }) +}