Skip to content

Commit

Permalink
Implement netconf chunk parsing without regex
Browse files Browse the repository at this point in the history
  • Loading branch information
netixx committed Apr 18, 2024
1 parent 3f655cb commit 88dd2ca
Showing 1 changed file with 52 additions and 29 deletions.
81 changes: 52 additions & 29 deletions response/netconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package response

import (
"bytes"
"errors"
"fmt"
"regexp"
"strconv"
Expand All @@ -16,10 +17,12 @@ const (
v1Dot1 = "1.1"
v1Dot0Delim = "]]>]]>"
xmlHeader = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
v1Dot1MaxChunkSize = 4294967295 // from https://datatracker.ietf.org/doc/html/rfc6242#section-4.2
)

var v1Dot1MaxChunkSizeLen = len(strconv.Itoa(v1Dot1MaxChunkSize))

type netconfPatterns struct {
v1dot1Chunk *regexp.Regexp
rpcErrors *regexp.Regexp

Check failure on line 26 in response/netconf.go

View workflow job for this annotation

GitHub Actions / unit-test (ubuntu-latest, 1.21)

File is not `gofumpt`-ed (gofumpt)

Check failure on line 26 in response/netconf.go

View workflow job for this annotation

GitHub Actions / unit-test (ubuntu-latest, 1.22)

File is not `gofumpt`-ed (gofumpt)
}

Expand All @@ -31,7 +34,6 @@ var (
func getNetconfPatterns() *netconfPatterns {
netconfPatternsInstanceOnce.Do(func() {
netconfPatternsInstance = &netconfPatterns{
v1dot1Chunk: regexp.MustCompile(`(?ms)(\d+)\n(.*?)^#`), //nolint:gocritic
rpcErrors: regexp.MustCompile(`(?s)<rpc-errors?>(.*)</rpc-errors?>`),

Check failure on line 37 in response/netconf.go

View workflow job for this annotation

GitHub Actions / unit-test (ubuntu-latest, 1.21)

File is not `gofumpt`-ed (gofumpt)

Check failure on line 37 in response/netconf.go

View workflow job for this annotation

GitHub Actions / unit-test (ubuntu-latest, 1.22)

File is not `gofumpt`-ed (gofumpt)
}
})
Expand Down Expand Up @@ -120,42 +122,63 @@ func (r *NetconfResponse) record1dot0() {
r.Result = string(bytes.TrimSpace(b))
}

func (r *NetconfResponse) validateChunk(i int, b []byte) {
// does this need more ... "massaging" like scrapli?
// chunk regex matches the newline before the chunk size or end of message delimiter, so we
// subtract one for that newline char
if len(b)-1 != i {
errMsg := fmt.Sprintf("return element lengh invalid, expted: %d, got %d for element: %s\n",
i,
len(b)-1,
b)

Check failure on line 125 in response/netconf.go

View workflow job for this annotation

GitHub Actions / unit-test (ubuntu-latest, 1.21)

File is not `gofumpt`-ed (gofumpt)

Check failure on line 125 in response/netconf.go

View workflow job for this annotation

GitHub Actions / unit-test (ubuntu-latest, 1.22)

File is not `gofumpt`-ed (gofumpt)
func (r *NetconfResponse) record1dot1() {

Check failure on line 127 in response/netconf.go

View workflow job for this annotation

GitHub Actions / unit-test (ubuntu-latest, 1.21)

File is not `gofumpt`-ed (gofumpt)

Check failure on line 127 in response/netconf.go

View workflow job for this annotation

GitHub Actions / unit-test (ubuntu-latest, 1.22)

File is not `gofumpt`-ed (gofumpt)
joined, err := r.record1dot1Chunks(r.RawResult)
if err != nil {
r.Failed = &OperationError{
Input: string(r.Input),
Output: r.Result,
ErrorString: errMsg,
ErrorString: err.Error(),
}
}
}

func (r *NetconfResponse) record1dot1() {
patterns := getNetconfPatterns()

chunkSections := patterns.v1dot1Chunk.FindAllSubmatch(r.RawResult, -1)

var joined []byte
joined = bytes.TrimPrefix(joined, []byte(xmlHeader))

for _, chunkSection := range chunkSections {
chunk := chunkSection[2]
r.Result = string(bytes.TrimSpace(joined))
}

size, _ := strconv.Atoi(string(chunkSection[1]))

Check failure on line 142 in response/netconf.go

View workflow job for this annotation

GitHub Actions / unit-test (ubuntu-latest, 1.21)

File is not `gofumpt`-ed (gofumpt)

Check failure on line 142 in response/netconf.go

View workflow job for this annotation

GitHub Actions / unit-test (ubuntu-latest, 1.22)

File is not `gofumpt`-ed (gofumpt)
r.validateChunk(size, chunk)
func (r *NetconfResponse) record1dot1Chunks(d []byte) ([]byte, error) {
cursor := 0

joined = append(joined, chunk[:len(chunk)-1]...)
joined := []byte{}
for cursor < len(d) {
// allow for some amount of newlines
if d[cursor] == byte('\n') {
cursor++
continue
}
if d[cursor] != byte('#') {
return nil, fmt.Errorf("unable to parse netconf response: chunk marker missing, got '%s'", string(d[cursor]))

Check failure on line 154 in response/netconf.go

View workflow job for this annotation

GitHub Actions / unit-test (ubuntu-latest, 1.21)

File is not `gofumpt`-ed (gofumpt)

Check failure on line 154 in response/netconf.go

View workflow job for this annotation

GitHub Actions / unit-test (ubuntu-latest, 1.22)

File is not `gofumpt`-ed (gofumpt)
}
cursor ++

Check failure on line 156 in response/netconf.go

View workflow job for this annotation

GitHub Actions / unit-test (ubuntu-latest, 1.21)

File is not `gofumpt`-ed (gofumpt)

Check failure on line 156 in response/netconf.go

View workflow job for this annotation

GitHub Actions / unit-test (ubuntu-latest, 1.22)

File is not `gofumpt`-ed (gofumpt)
// found prompt
if d[cursor] == byte('#') {
return joined, nil
}
// look for end of chunk size
// allow to match end with \n char
chunkSizeLen := 0
for ;chunkSizeLen < v1Dot1MaxChunkSizeLen+1; chunkSizeLen ++ {

Check failure on line 164 in response/netconf.go

View workflow job for this annotation

GitHub Actions / unit-test (ubuntu-latest, 1.21)

File is not `gofumpt`-ed (gofumpt)

Check failure on line 164 in response/netconf.go

View workflow job for this annotation

GitHub Actions / unit-test (ubuntu-latest, 1.22)

File is not `gofumpt`-ed (gofumpt)
if cursor + chunkSizeLen >= len(d) {
return nil, errors.New("unable to parse netconf response: chunk size not found before end of data")
}
if d[cursor + chunkSizeLen] == byte('\n') {
break
}
}
chunkSizeStr := string(d[cursor:cursor+chunkSizeLen])
cursor += chunkSizeLen+1
chunkSize, err := strconv.Atoi(chunkSizeStr)
if err != nil {
return nil, fmt.Errorf("unable to parse chunk size '%s': %s", chunkSizeStr, err)
}
joined = append(joined, d[cursor:cursor+chunkSize]...)
// last new line of block is not counted
// since it's considered a delimiter for next chunk
cursor += chunkSize + 1
}

joined = bytes.TrimPrefix(joined, []byte(xmlHeader))

r.Result = string(bytes.TrimSpace(joined))
}
return joined, nil
}

0 comments on commit 88dd2ca

Please sign in to comment.