diff --git a/backend/types/view_type.go b/backend/types/view_type.go index 301f42cb..9b5ac90a 100644 --- a/backend/types/view_type.go +++ b/backend/types/view_type.go @@ -7,6 +7,7 @@ const FORMAT_YAML = "YAML" const FORMAT_XML = "XML" const FORMAT_HEX = "Hex" const FORMAT_BINARY = "Binary" +const FORMAT_BITSET = "BitSet" const DECODE_NONE = "None" const DECODE_BASE64 = "Base64" diff --git a/backend/utils/convert/bitset_convert.go b/backend/utils/convert/bitset_convert.go new file mode 100644 index 00000000..47f5bba3 --- /dev/null +++ b/backend/utils/convert/bitset_convert.go @@ -0,0 +1,101 @@ +package convutil + +import ( + "fmt" + "math" + "strconv" + "strings" +) + +type BitSetConvert struct{} + +func (BitSetConvert) Enable() bool { + return true +} + +func (BitSetConvert) Encode(str string) (string, bool) { + var result strings.Builder + + str = strings.ReplaceAll(str, "\r\n", "\n") // CRLF → LF + str = strings.ReplaceAll(str, "\r", "\n") // CR → LF + + lines := strings.Split(str, "\n") + bytes := EncodeToRedisBitset(lines) + result.Write(bytes) + + return result.String(), true +} + +// EncodeToRedisBitset encodes a list of strings with integers (positions) into a Redis bitset byte array. +// The bit at position 'n' will be set to 1 if n is in the input array. +// The resulting byte slice can be stored in Redis using SET command. +func EncodeToRedisBitset(numbers []string) []byte { + if len(numbers) == 0 { + return []byte{} + } + + // Find the maximum number to determine the required bit length and convert strings to numbers + maxNum := uint64(0) + var validNumbers []uint64 + for _, s := range numbers { + if s == "" { + continue + } + num, err := strconv.ParseUint(s, 10, 64) + if err != nil || num < 0 || num > math.MaxUint32 { + fmt.Printf("Warning: skipping invalid number '%s': %v\n", s, err) + continue + } + validNumbers = append(validNumbers, num) + if num > maxNum { + maxNum = num + } + } + + if len(validNumbers) == 0 { + return []byte{} + } + + // Calculate required byte length (8 bits per byte) + byteLen := ((maxNum + 7) / 8) + 1 + + // Initialize byte array + bitset := make([]byte, byteLen) + + // Set bits for each number + for _, num := range validNumbers { + byteIndex := num / 8 + if byteIndex < byteLen { + bitIndex := uint(num % 8) + // Set the bit (big-endian bit order within byte) + bitset[byteIndex] |= 1 << (7 - bitIndex) + } + } + + return bitset +} + +func (BitSetConvert) Decode(str string) (string, bool) { + bitset := getBitSet([]byte(str)) + + var binBuilder strings.Builder + for key, value := range bitset { + if value { + binBuilder.WriteString(fmt.Sprintf("%v\n", key)) + //binBuilder.WriteString(fmt.Sprintf("Bit %v = %v \n", key, value)) + } + } + + return binBuilder.String(), true +} + +func getBitSet(redisResponse []byte) []bool { + bitset := make([]bool, len(redisResponse)*8) + for i := range redisResponse { + for j := 7; j >= 0; j-- { + bit_n := uint(i*8 + (7 - j)) + bitset[bit_n] = (redisResponse[i] & (1 << uint(j))) > 0 + } + } + return bitset +} diff --git a/backend/utils/convert/convert.go b/backend/utils/convert/convert.go index 09703968..ad4bdc45 100644 --- a/backend/utils/convert/convert.go +++ b/backend/utils/convert/convert.go @@ -20,6 +20,7 @@ var ( xmlConv XmlConvert base64Conv Base64Convert binaryConv BinaryConvert + bitSetConv BitSetConvert hexConv HexConvert gzipConv GZipConvert deflateConv DeflateConvert @@ -38,6 +39,7 @@ var BuildInFormatters = map[string]DataConvert{ types.FORMAT_XML: xmlConv, types.FORMAT_HEX: hexConv, types.FORMAT_BINARY: binaryConv, + types.FORMAT_BITSET: bitSetConv, } var BuildInDecoders = map[string]DataConvert{ diff --git a/frontend/src/consts/value_view_type.js b/frontend/src/consts/value_view_type.js index bab24c11..2616cec1 100644 --- a/frontend/src/consts/value_view_type.js +++ b/frontend/src/consts/value_view_type.js @@ -10,6 +10,7 @@ export const formatTypes = { XML: 'XML', HEX: 'Hex', BINARY: 'Binary', + BITSET: 'BitSet', } /**