Compare commits

...

4 Commits

5 changed files with 114 additions and 43 deletions

View File

@@ -14,7 +14,6 @@ go get git.vsu.ru/ai/cgpcli
package main package main
import ( import (
"fmt"
"git.vsu.ru/ai/cgpcli" "git.vsu.ru/ai/cgpcli"
) )

View File

@@ -236,10 +236,11 @@ func (d *decodeState) dictionaryInterface() any {
} }
func (d *decodeState) arrayInterface() any { func (d *decodeState) arrayInterface() any {
count := 1 commas := 0
depth := 1 depth := 1
ts := d.span ts := d.span
data := d.data data := d.data
empty := true
for ts < len(data) { for ts < len(data) {
f := cgpCharTable[data[ts]] f := cgpCharTable[data[ts]]
@@ -252,20 +253,20 @@ func (d *decodeState) arrayInterface() any {
break break
} }
} else if f&isCgpComma != 0 && depth == 1 { } else if f&isCgpComma != 0 && depth == 1 {
count++ commas++
} }
} }
if f&isCgpSpace == 0 {
empty = false
}
ts++ ts++
} }
// Если мы нашли элементы (или хотя бы один), count должен быть корректным. capacity := 0
// Для пустого массива () count останется 0. Для (1) - тоже 0, надо +1 если не пусто. if !empty {
// Но проще: если мы встретили хотя бы один символ до закрытия, это 1 элемент или больше. capacity = commas + 1
if ts > d.span { // Если между ( и ) что-то было
count++
} }
res := make([]any, 0, capacity)
res := make([]any, 0, count)
for d.span < d.len { for d.span < d.len {
d.skipSpaces() d.skipSpaces()
@@ -616,11 +617,13 @@ func parseIPSB(v any) net.IP {
return net.ParseIP(s) return net.ParseIP(s)
} }
var ipsbsaReplacer = strings.NewReplacer("[", "", "]", "")
func parseIPSBSA(s string) []net.IP { func parseIPSBSA(s string) []net.IP {
if s == "" { if s == "" {
return []net.IP{} return []net.IP{}
} }
s = strings.NewReplacer("[", "", "]", "").Replace(s) s = ipsbsaReplacer.Replace(s)
parts := strings.Split(s, ",") parts := strings.Split(s, ",")
res := make([]net.IP, 0, len(parts)) res := make([]net.IP, 0, len(parts))
for _, p := range parts { for _, p := range parts {

View File

@@ -156,3 +156,81 @@ func TestDecodeDataBlock(t *testing.T) {
}) })
} }
} }
// TestArrayCounting проверяет корректность подсчёта элементов в arrayInterface.
func TestArrayCounting(t *testing.T) {
tests := []struct {
name string
input string
want int
}{
{"Empty", "()", 0},
{"One", "(a)", 1},
{"Two", "(a,b)", 2},
{"Three", "(a,b,c)", 3},
{"Nested", "(a,(b,c),d)", 3},
{"NestedDict", "(a,{k=v;},c)", 3},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var got any
if err := Unmarshal([]byte(tt.input), &got); err != nil {
t.Fatalf("Unmarshal() error = %v", err)
}
arr, ok := got.([]any)
if !ok {
if tt.want == 0 {
return
}
t.Fatalf("expected []any, got %T: %v", got, got)
}
if len(arr) != tt.want {
t.Errorf("len=%d, want %d, got %v", len(arr), tt.want, arr)
}
})
}
}
// TestParseCGPSizeQuotes проверяет нужен ли strings.Trim в parseCGPSize.
func TestParseCGPSizeQuotes(t *testing.T) {
tests := []struct {
name string
input string
want any
}{
{"NoQuotes", "10K", int64(10 * 1024)},
{"WithQuotes", `"10K"`, int64(10 * 1024)},
{"Unlimited", "unlimited", int64(-1)},
{"Plain", "1024", int64(1024)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := parseCGPSize(tt.input)
if got != tt.want {
t.Errorf("parseCGPSize(%q) = %v (%T), want %v (%T)", tt.input, got, got, tt.want, tt.want)
}
})
}
}
// TestParseIPSBSA проверяет parseIPSBSA.
func TestParseIPSBSA(t *testing.T) {
tests := []struct {
name string
input string
want int
}{
{"Empty", "", 0},
{"Single", "[192.168.1.1]", 1},
{"Multiple", "[192.168.1.1], [10.0.0.1]", 2},
{"IPv6", "[::1], [fe80::1]", 2},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := parseIPSBSA(tt.input)
if len(got) != tt.want {
t.Errorf("parseIPSBSA(%q) = %d IPs, want %d: %v", tt.input, len(got), tt.want, got)
}
})
}
}

View File

@@ -106,6 +106,16 @@ func isCGPSafeString(s string) bool {
return true return true
} }
var cgpSizeUnits = []struct {
suffix string
value int64
}{
{"T", 1024 * 1024 * 1024 * 1024},
{"G", 1024 * 1024 * 1024},
{"M", 1024 * 1024},
{"K", 1024},
}
// formatCGPSize преобразует байты в формат CGP (1K, 5M, 10G) или "unlimited". // formatCGPSize преобразует байты в формат CGP (1K, 5M, 10G) или "unlimited".
func formatCGPSize(v int64) string { func formatCGPSize(v int64) string {
if v == -1 { if v == -1 {
@@ -114,18 +124,9 @@ func formatCGPSize(v int64) string {
if v == 0 { if v == 0 {
return "0" return "0"
} }
units := []struct { for _, u := range cgpSizeUnits {
suffix string
value int64
}{
{"T", 1024 * 1024 * 1024 * 1024},
{"G", 1024 * 1024 * 1024},
{"M", 1024 * 1024},
{"K", 1024},
}
for _, u := range units {
if v%u.value == 0 { if v%u.value == 0 {
return fmt.Sprintf("%d%s", v/u.value, u.suffix) return strconv.FormatInt(v/u.value, 10) + u.suffix
} }
} }
return strconv.FormatInt(v, 10) return strconv.FormatInt(v, 10)
@@ -193,8 +194,7 @@ func marshal(w encodeWriter, v any, keyName string) (err error) {
case cgpIPSBSA: case cgpIPSBSA:
w.WriteByte('"') w.WriteByte('"')
ips := []net.IP(val) for i, ip := range val {
for i, ip := range ips {
if i > 0 { if i > 0 {
w.WriteString(", ") w.WriteString(", ")
} }

View File

@@ -82,29 +82,20 @@ func (c CGPIP) String() string {
} }
// writeCGP формирует данные для отправки на сервер CGP напрямую в буфер. // writeCGP формирует данные для отправки на сервер CGP напрямую в буфер.
func (c CGPIP) writeCGP(w encodeWriter) (err error) { func (c CGPIP) writeCGP(w encodeWriter) error {
if c.IsZero() { if c.IsZero() {
_, err = w.WriteString("#NULL#") _, err := w.WriteString("#NULL#")
return return err
} }
if _, err = w.WriteString("#I["); err != nil { w.WriteString("#I[")
return w.WriteString(c.IP.String())
} w.WriteByte(']')
if _, err = w.WriteString(c.IP.String()); err != nil {
return
}
if err = w.WriteByte(']'); err != nil {
return
}
if c.Port > 0 { if c.Port > 0 {
if err = w.WriteByte(':'); err != nil { w.WriteByte(':')
return w.WriteString(strconv.Itoa(c.Port))
} }
if _, err = w.WriteString(strconv.Itoa(c.Port)); err != nil {
return return nil
}
}
return
} }