This commit is contained in:
2024-12-25 09:23:31 +01:00
parent 01ca5897db
commit 4e030b794d
306 changed files with 35071 additions and 22 deletions

115
lib/data/encoder/auto.v Normal file
View File

@@ -0,0 +1,115 @@
module encoder
import time
import freeflowuniverse.herolib.ui.console
// example see https://github.com/vlang/v/blob/master/examples/compiletime/reflection.v
pub fn encode[T](obj T) ![]u8 {
mut d := new()
// compile-time `for` loop
// T.fields gives an array of a field metadata type
$for field in T.fields {
// Primitive types
$if field.typ is string {
// $(string_expr) produces an identifier
d.add_string(obj.$(field.name).str())
} $else $if field.typ is int {
d.add_int(int(obj.$(field.name)))
} $else $if field.typ is u8 {
d.add_u8(u8(obj.$(field.name)))
} $else $if field.typ is u16 {
d.add_u16(u16(obj.$(field.name)))
} $else $if field.typ is u32 {
d.add_u32(u32(obj.$(field.name)))
} $else $if field.typ is u64 {
d.add_u64(u64(obj.$(field.name)))
} $else $if field.typ is time.Time {
d.add_time(time.new(obj.$(field.name)))
// Arrays of primitive types
} $else $if field.typ is []string {
// d.add_list_string(obj.$(field.name)) why error??
d.add_list_string(obj.$(field.name)[..])
} $else $if field.typ is []int {
d.add_list_int(obj.$(field.name)[..])
} $else $if field.typ is []u8 {
d.add_list_u8(obj.$(field.name)[..])
} $else $if field.typ is []u16 {
d.add_list_u16(obj.$(field.name)[..])
} $else $if field.typ is []u32 {
d.add_list_u32(obj.$(field.name)[..])
} $else $if field.typ is []u64 {
d.add_list_u64(obj.$(field.name)[..])
// Maps of primitive types
} $else $if field.typ is map[string]string {
d.add_map_string(obj.$(field.name).clone())
} $else $if field.typ is map[string][]u8 {
d.add_map_bytes(obj.$(field.name).clone())
// Structs
} $else $if field.is_struct {
e := encode(obj.$(field.name))!
d.add_list_u8(e)
} $else {
typ_name := typeof(obj.$(field.name)).name
return error("The type `${typ_name}` of field `${field.name}` can't be encoded")
}
}
return d.data
}
pub fn decode[T](data []u8) !T {
mut d := decoder_new(data)
mut result := T{}
// compile-time `for` loop
// T.fields gives an array of a field metadata type
$for field in T.fields {
// console.print_debug(field.name)
// console.print_debug(typeof(result.$(field.name)).name)
// console.print_debug(result.$(field.name))
// Primitive types
$if field.typ is string {
// $(string_expr) produces an identifier
result.$(field.name) = d.get_string()
} $else $if field.typ is int {
result.$(field.name) = d.get_int()
} $else $if field.typ is u8 {
result.$(field.name) = d.get_u8()
} $else $if field.typ is u16 {
result.$(field.name) = d.get_u16()
} $else $if field.typ is u32 {
result.$(field.name) = d.get_u32()
} $else $if field.typ is u64 {
result.$(field.name) = d.get_u64()
} $else $if field.typ is time.Time {
result.$(field.name) = d.get_time()
// Arrays of primitive types
} $else $if field.typ is []string {
result.$(field.name) = d.get_list_string()
} $else $if field.typ is []int {
result.$(field.name) = d.get_list_int()
} $else $if field.typ is []u8 {
result.$(field.name) = d.get_list_u8()
} $else $if field.typ is []u16 {
result.$(field.name) = d.get_list_u16()
} $else $if field.typ is []u32 {
result.$(field.name) = d.get_list_u32()
} $else $if field.typ is []u64 {
result.$(field.name) = d.get_list_u64()
// Maps of primitive types
} $else $if field.typ is map[string]string {
result.$(field.name) = d.get_map_string()
} $else $if field.typ is map[string][]u8 {
result.$(field.name) = d.get_map_bytes()
// Structs
} $else $if field.is_struct {
// TODO handle recursive behavior
} $else {
typ_name := typeof(result.$(field.name)).name
return error("The type `${typ_name}` of field `${field.name}` can't be decoded")
}
}
return result
}
// TODO: complete, the recursive behavior will be little tricky

View File

@@ -0,0 +1,155 @@
module encoder
import encoding.binary as bin
import freeflowuniverse.herolib.data.ourtime
import time
pub struct Decoder {
pub mut:
version u8 = 1 // is important
data []u8
}
pub fn decoder_new(data []u8) Decoder {
mut e := Decoder{}
e.data = data
// e.data = data.reverse()
return e
}
pub fn (mut d Decoder) get_string() string {
n := d.get_u16()
v := d.data[..n]
d.data.delete_many(0, n)
return v.bytestr()
}
pub fn (mut d Decoder) get_int() int {
return int(d.get_u32())
}
pub fn (mut d Decoder) get_bytes() []u8 {
n := int(d.get_u32())
v := d.data[..n]
d.data.delete_many(0, n)
return v
}
// adds u16 length of string in bytes + the bytes
pub fn (mut d Decoder) get_u8() u8 {
// remove first byte, this corresponds to u8, so the data bytestring becomes 1 byte shorter
v := d.data.first()
d.data.delete(0)
return v
}
pub fn (mut d Decoder) get_u16() u16 {
v := d.data[..2]
d.data.delete_many(0, 2)
return bin.little_endian_u16(v)
}
pub fn (mut d Decoder) get_u32() u32 {
v := d.data[..4]
d.data.delete_many(0, 4)
return bin.little_endian_u32(v)
}
pub fn (mut d Decoder) get_u64() u64 {
v := d.data[..8]
d.data.delete_many(0, 8)
return bin.little_endian_u64(v)
}
pub fn (mut d Decoder) get_i64() i64 {
v := d.data[..8]
d.data.delete_many(0, 8)
return u64(bin.little_endian_u64(v))
}
pub fn (mut d Decoder) get_time() time.Time {
nano_time := d.get_i64()
seconds := nano_time / int(1e9)
nano_seconds := int(nano_time % int(1e9))
return time.unix_nanosecond(seconds, nano_seconds)
}
pub fn (mut d Decoder) get_ourtime() ourtime.OurTime {
return ourtime.OurTime{
unixt: d.get_i64()
}
}
pub fn (mut d Decoder) get_list_string() []string {
n := d.get_u16()
mut v := []string{len: int(n)}
for i in 0 .. n {
v[i] = d.get_string()
}
return v
}
pub fn (mut d Decoder) get_list_int() []int {
n := d.get_u16()
mut v := []int{len: int(n)}
for i in 0 .. n {
v[i] = d.get_int()
}
return v
}
pub fn (mut d Decoder) get_list_u8() []u8 {
n := d.get_u16()
v := d.data[..n]
d.data.delete_many(0, n)
return v
}
pub fn (mut d Decoder) get_list_u16() []u16 {
n := d.get_u16()
mut v := []u16{len: int(n)}
for i in 0 .. n {
v[i] = d.get_u16()
}
return v
}
pub fn (mut d Decoder) get_list_u32() []u32 {
n := d.get_u16()
mut v := []u32{len: int(n)}
for i in 0 .. n {
v[i] = d.get_u32()
}
return v
}
pub fn (mut d Decoder) get_list_u64() []u64 {
n := d.get_u16()
mut v := []u64{len: int(n)}
for i in 0 .. n {
v[i] = d.get_u64()
}
return v
}
pub fn (mut d Decoder) get_map_string() map[string]string {
n := d.get_u16()
mut v := map[string]string{}
for _ in 0 .. n {
key := d.get_string()
val := d.get_string()
v[key] = val
}
return v
}
pub fn (mut d Decoder) get_map_bytes() map[string][]u8 {
n := d.get_u16()
mut v := map[string][]u8{}
for _ in 0 .. n {
key := d.get_string()
val := d.get_bytes()
v[key] = val
}
return v
}

View File

@@ -0,0 +1,176 @@
module encoder
import time
import encoding.binary as bin
import freeflowuniverse.herolib.data.ourtime
const kb = 1024
pub struct Encoder {
pub mut:
data []u8
// datatypes []DataType
}
// enum DataType{
// string
// int
// bytes
// u8
// u16
// u32
// u64
// time
// list_string
// list_int
// list_u8
// list_u16
// list_u32
// list_u64
// map_string
// map_bytes
// }
pub fn new() Encoder {
mut e := Encoder{}
return e
}
// adds u16 length of string in bytes + the bytes
pub fn (mut b Encoder) add_string(data string) {
if data.len > 64 * kb {
panic('string cannot be bigger than 64kb')
}
b.add_u16(u16(data.len))
b.data << data.bytes()
}
// Please note that unlike C and Go, int is always a 32 bit integer.
// We borrow the add_u32() function to handle the encoding of a 32 bit type
pub fn (mut b Encoder) add_int(data int) {
b.add_u32(u32(data))
}
// add bytes or bytestring
pub fn (mut b Encoder) add_bytes(data []u8) {
b.add_u32(u32(data.len))
b.data << data
}
pub fn (mut b Encoder) add_u8(data u8) {
b.data << data
}
pub fn (mut b Encoder) add_u16(data u16) {
mut d := []u8{len: 2}
bin.little_endian_put_u16(mut d, data)
b.data << d
}
pub fn (mut b Encoder) add_u32(data u32) {
mut d := []u8{len: 4}
bin.little_endian_put_u32(mut d, data)
b.data << d
}
pub fn (mut b Encoder) add_u64(data u64) {
mut d := []u8{len: 8}
bin.little_endian_put_u64(mut d, data)
b.data << d
}
pub fn (mut b Encoder) add_i64(data i64) {
mut d := []u8{len: 8}
bin.little_endian_put_u64(mut d, u64(data))
b.data << d
}
pub fn (mut b Encoder) add_time(data time.Time) {
b.add_u64(u64(data.unix_nano())) // add as epoch time
}
pub fn (mut b Encoder) add_ourtime(data ourtime.OurTime) {
b.add_i64(data.unixt)
}
pub fn (mut b Encoder) add_list_string(data []string) {
if data.len > 64 * kb {
panic('list cannot have more than 64kb items.')
}
b.add_u16(u16(data.len))
for item in data {
b.add_string(item)
}
}
pub fn (mut b Encoder) add_list_int(data []int) {
if data.len > 64 * kb {
panic('list cannot have more than 64kb items.')
}
b.add_u16(u16(data.len)) // how many items in list
for item in data {
b.add_int(item)
}
}
pub fn (mut b Encoder) add_list_u8(data []u8) {
if data.len > 64 * kb {
panic('list cannot have more than 64kb items.')
}
b.add_u16(u16(data.len)) // how many items in list
b.data << data
}
pub fn (mut b Encoder) add_list_u16(data []u16) {
if data.len > 64 * kb {
panic('list cannot have more than 64kb items.')
}
b.add_u16(u16(data.len)) // how many items in list
for item in data {
b.add_u16(item)
}
}
pub fn (mut b Encoder) add_list_u32(data []u32) {
if data.len > 64 * kb {
panic('list cannot have more than 64kb items.')
}
b.add_u16(u16(data.len)) // how many items in list
for item in data {
b.add_u32(item)
}
}
pub fn (mut b Encoder) add_list_u64(data []u64) {
if data.len > 64 * kb {
panic('list cannot have more than 64kb items.')
}
b.add_u16(u16(data.len)) // how many items in list
for item in data {
b.add_u64(item)
}
}
// when complicated hash e.g. map of other object need to serialize each sub object
pub fn (mut b Encoder) add_map_string(data map[string]string) {
if data.len > 64 * kb {
panic('map cannot have more than 64kb items.')
}
b.add_u16(u16(data.len)) // max nr of items in the map
for key, val in data {
b.add_string(key)
b.add_string(val)
}
}
// when complicated hash e.g. map of other object need to serialize each sub object
pub fn (mut b Encoder) add_map_bytes(data map[string][]u8) {
if data.len > 64 * kb {
panic('map cannot have more than 64kb items.')
}
b.add_u16(u16(data.len)) // max nr of items in the map
for key, val in data {
b.add_string(key)
b.add_bytes(val)
}
}

View File

@@ -0,0 +1,285 @@
module encoder
import time
import math
import freeflowuniverse.herolib.ui.console
fn test_string() {
mut e := new()
e.add_string('a')
e.add_string('bc')
assert e.data == [u8(1), 0, 97, 2, 0, 98, 99]
mut d := decoder_new(e.data)
assert d.get_string() == 'a'
assert d.get_string() == 'bc'
}
fn test_int() {
mut e := new()
e.add_int(min_i32)
e.add_int(max_i32)
assert e.data == [u8(0x00), 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0x7f]
mut d := decoder_new(e.data)
assert d.get_int() == min_i32
assert d.get_int() == max_i32
}
fn test_bytes() {
sb := 'abcdef'.bytes()
mut e := new()
e.add_list_u8(sb)
assert e.data == [u8(6), 0, 97, 98, 99, 100, 101, 102]
mut d := decoder_new(e.data)
assert d.get_list_u8() == sb
}
fn test_u8() {
mut e := new()
e.add_u8(min_u8)
e.add_u8(max_u8)
assert e.data == [u8(0x00), 0xff]
mut d := decoder_new(e.data)
assert d.get_u8() == min_u8
assert d.get_u8() == max_u8
}
fn test_u16() {
mut e := new()
e.add_u16(min_u16)
e.add_u16(max_u16)
assert e.data == [u8(0x00), 0x00, 0xff, 0xff]
mut d := decoder_new(e.data)
assert d.get_u16() == min_u16
assert d.get_u16() == max_u16
}
fn test_u32() {
mut e := new()
e.add_u32(min_u32)
e.add_u32(max_u32)
assert e.data == [u8(0x00), 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff]
mut d := decoder_new(e.data)
assert d.get_u32() == min_u32
assert d.get_u32() == max_u32
}
fn test_u64() {
mut e := new()
e.add_u64(min_u64)
e.add_u64(max_u64)
assert e.data == [u8(0x00), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff]
mut d := decoder_new(e.data)
assert d.get_u64() == min_u64
assert d.get_u64() == max_u64
}
fn test_time() {
mut e := new()
t := time.now()
e.add_time(t)
mut d := decoder_new(e.data)
assert d.get_time() == t
}
fn test_list_string() {
list := ['a', 'bc', 'def']
mut e := new()
e.add_list_string(list)
assert e.data == [u8(3), 0, 1, 0, 97, 2, 0, 98, 99, 3, 0, 100, 101, 102]
mut d := decoder_new(e.data)
assert d.get_list_string() == list
}
fn test_list_int() {
list := [0x872fea95, 0, 0xfdf2e68f]
mut e := new()
e.add_list_int(list)
assert e.data == [u8(3), 0, 0x95, 0xea, 0x2f, 0x87, 0, 0, 0, 0, 0x8f, 0xe6, 0xf2, 0xfd]
mut d := decoder_new(e.data)
assert d.get_list_int() == list
}
fn test_list_u8() {
list := [u8(153), 0, 22]
mut e := new()
e.add_list_u8(list)
assert e.data == [u8(3), 0, 153, 0, 22]
mut d := decoder_new(e.data)
assert d.get_list_u8() == list
}
fn test_list_u16() {
list := [u16(0x8725), 0, 0xfdff]
mut e := new()
e.add_list_u16(list)
assert e.data == [u8(3), 0, 0x25, 0x87, 0, 0, 0xff, 0xfd]
mut d := decoder_new(e.data)
assert d.get_list_u16() == list
}
fn test_list_u32() {
list := [u32(0x872fea95), 0, 0xfdf2e68f]
mut e := new()
e.add_list_u32(list)
assert e.data == [u8(3), 0, 0x95, 0xea, 0x2f, 0x87, 0, 0, 0, 0, 0x8f, 0xe6, 0xf2, 0xfd]
mut d := decoder_new(e.data)
assert d.get_list_u32() == list
}
fn test_map_string() {
mp := {
'1': 'a'
'2': 'bc'
}
mut e := new()
e.add_map_string(mp)
assert e.data == [u8(2), 0, 1, 0, 49, 1, 0, 97, 1, 0, 50, 2, 0, 98, 99]
mut d := decoder_new(e.data)
assert d.get_map_string() == mp
}
fn test_map_bytes() {
mp := {
'1': 'a'.bytes()
'2': 'bc'.bytes()
}
mut e := new()
e.add_map_bytes(mp)
assert e.data == [u8(2), 0, 1, 0, 49, 1, 0, 0, 0, 97, 1, 0, 50, 2, 0, 0, 0, 98, 99]
mut d := decoder_new(e.data)
assert d.get_map_bytes() == mp
}
struct StructType[T] {
mut:
val T
}
fn get_empty_struct_input[T]() StructType[T] {
return StructType[T]{}
}
fn get_struct_input[T](val T) StructType[T] {
return StructType[T]{
val: val
}
}
fn encode_decode_struct[T](input StructType[T]) bool {
data := encode(input) or {
console.print_debug('Failed to encode, error: ${err}')
return false
}
output := decode[StructType[T]](data) or {
console.print_debug('Failed to decode, error: ${err}')
return false
}
return input == output
}
fn test_struct() {
// string
assert encode_decode_struct(get_empty_struct_input[string]())
assert encode_decode_struct(get_struct_input(''))
assert encode_decode_struct(get_struct_input('a'))
// int
assert encode_decode_struct(get_empty_struct_input[int]())
assert encode_decode_struct(get_struct_input(-1))
// u8
assert encode_decode_struct(get_empty_struct_input[u8]())
assert encode_decode_struct(get_struct_input(u8(2)))
// u16
assert encode_decode_struct(get_empty_struct_input[u16]())
assert encode_decode_struct(get_struct_input(u16(3)))
// u32
assert encode_decode_struct(get_empty_struct_input[u32]())
assert encode_decode_struct(get_struct_input(u32(4)))
// u64
assert encode_decode_struct(get_empty_struct_input[u64]())
assert encode_decode_struct(get_struct_input(u64(5)))
// time.Time
// assert encode_decode_struct[time.Time](get_empty_struct_input[time.Time]()) // get error here
assert encode_decode_struct[time.Time](get_struct_input[time.Time](time.now()))
// string array
assert encode_decode_struct(get_empty_struct_input[[]string]())
assert encode_decode_struct(get_struct_input([]string{}))
assert encode_decode_struct(get_struct_input(['']))
assert encode_decode_struct(get_struct_input(['a']))
// int array
assert encode_decode_struct(get_empty_struct_input[[]int]())
assert encode_decode_struct(get_struct_input([]int{}))
assert encode_decode_struct(get_struct_input([-1]))
// u8 array
assert encode_decode_struct(get_empty_struct_input[[]u8]())
assert encode_decode_struct(get_struct_input([]u8{}))
assert encode_decode_struct(get_struct_input([u8(2)]))
// u16 array
assert encode_decode_struct(get_empty_struct_input[[]u16]())
assert encode_decode_struct(get_struct_input([]u16{}))
assert encode_decode_struct(get_struct_input([u16(3)]))
// u32 array
assert encode_decode_struct(get_empty_struct_input[[]u32]())
assert encode_decode_struct(get_struct_input([]u32{}))
assert encode_decode_struct(get_struct_input([u32(4)]))
// u64 array
assert encode_decode_struct(get_empty_struct_input[[]u64]())
assert encode_decode_struct(get_struct_input([]u64{}))
assert encode_decode_struct(get_struct_input([u64(5)]))
// string map
assert encode_decode_struct(get_empty_struct_input[map[string]string]())
assert encode_decode_struct(get_struct_input(map[string]string{}))
assert encode_decode_struct(get_struct_input({
'1': 'a'
}))
// bytes map
assert encode_decode_struct(get_empty_struct_input[map[string][]u8]())
assert encode_decode_struct(get_struct_input(map[string][]u8{}))
assert encode_decode_struct(get_struct_input({
'1': 'a'.bytes()
}))
// struct
assert encode_decode_struct(get_empty_struct_input[StructType[int]]())
assert encode_decode_struct(get_struct_input(StructType[int]{}))
// assert encode_decode_struct(get_struct_input(StructType[int]{
// val: int(1)
// })) // decode not implemented
}

247
lib/data/encoder/readme.md Normal file
View File

@@ -0,0 +1,247 @@
# V Binary Encoder/Decoder
A high-performance binary encoder/decoder module for V that provides efficient serialization and deserialization of data structures. The encoder supports automatic encoding/decoding of structs using V's compile-time reflection capabilities.
## Features
- Automatic struct encoding/decoding using compile-time reflection
- Support for primitive types, arrays, maps, and nested structs
- Compact binary format with length prefixing
- Size limits to prevent memory issues (64KB for strings/lists)
- Comprehensive error handling
- Built-in versioning support
## Format
The binary format starts with a version byte (currently v1), followed by the encoded data:
```
[version_byte][encoded_data...]
```
## Supported Types
### Primitive Types
- `string`
- `int` (32-bit)
- `u8`
- `u16`
- `u32`
- `u64`
- `time.Time`
### Arrays
- `[]string`
- `[]int`
- `[]u8`
- `[]u16`
- `[]u32`
- `[]u64`
### Maps
- `map[string]string`
- `map[string][]u8`
### Structs
- Nested struct support with automatic encoding/decoding
## Usage
### Basic Encoding
```v
import freeflowuniverse.herolib.data.encoder
// Create a new encoder
mut e := encoder.new()
// Add primitive values
e.add_string('hello')
e.add_int(42)
e.add_u8(255)
e.add_u16(65535)
e.add_u32(4294967295)
e.add_u64(18446744073709551615)
// Add arrays
e.add_list_string(['one', 'two', 'three'])
e.add_list_int([1, 2, 3])
// Add maps
e.add_map_string({
'key1': 'value1'
'key2': 'value2'
})
// Get encoded bytes
encoded := e.data
```
### Basic Decoding
```v
// Create decoder from bytes
mut d := encoder.decoder_new(encoded)
// Read values in same order as encoded
str := d.get_string()
num := d.get_int()
byte := d.get_u8()
u16_val := d.get_u16()
u32_val := d.get_u32()
u64_val := d.get_u64()
// Read arrays
strings := d.get_list_string()
ints := d.get_list_int()
// Read maps
str_map := d.get_map_string()
```
### Automatic Struct Encoding/Decoding
```v
struct Person {
name string
age int
tags []string
meta map[string]string
}
// Create struct instance
person := Person{
name: 'John'
age: 30
tags: ['developer', 'v']
meta: {
'location': 'NYC'
'role': 'engineer'
}
}
// Encode struct
encoded := encoder.encode(person)!
// Decode back to struct
decoded := encoder.decode[Person](encoded)!
```
## Example
Here's a complete example showing how to encode nested structs:
```v
import freeflowuniverse.herolib.data.encoder
// Define some nested structs
struct Address {
street string
number int
country string
}
struct Person {
name string
age int
addresses []Address // nested array of structs
metadata map[string]string
}
// Example usage
fn main() {
// Create test data
mut person := Person{
name: 'John Doe'
age: 30
addresses: [
Address{
street: 'Main St'
number: 123
country: 'USA'
},
Address{
street: 'Side St'
number: 456
country: 'Canada'
}
]
metadata: {
'id': 'abc123'
'type': 'customer'
}
}
// Encode the data
mut e := encoder.new()
// Add version byte (v1)
e.add_u8(1)
// Encode the Person struct
e.add_string(person.name)
e.add_int(person.age)
// Encode the addresses array
e.add_u16(u16(person.addresses.len)) // number of addresses
for addr in person.addresses {
e.add_string(addr.street)
e.add_int(addr.number)
e.add_string(addr.country)
}
// Encode the metadata map
e.add_map_string(person.metadata)
// The binary data is now in e.data
encoded := e.data
// Later, when decoding, first byte tells us the version
version := encoded[0]
assert version == 1
}
```
## Binary Format Details
For the example above, the binary layout would be:
```
[1] // version byte (v1)
[len][John Doe] // name (u16 length + bytes)
[30] // age (int/u32)
[2] // number of addresses (u16)
[len][Main St] // address 1 street
[123] // address 1 number
[len][USA] // address 1 country
[len][Side St] // address 2 street
[456] // address 2 number
[len][Canada] // address 2 country
[2] // number of metadata entries (u16)
[len][id] // key 1
[len][abc123] // value 1
[len][type] // key 2
[len][customer] // value 2
```
## Implementation Details
### Binary Format
The encoded data follows this format:
1. For strings:
- u16 length prefix
- raw string bytes
2. For arrays:
- u16 length prefix
- encoded elements
3. For maps:
- u16 count of entries
- encoded key-value pairs