// netstring -- netstring format serialization library // Copyright (C) 2015-2024 Sergey Matveev // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3 of the License. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . package netstring import ( "bufio" "errors" "fmt" "io" "strconv" ) type Reader struct { r *bufio.Reader left uint64 eof bool } // Create new Reader. // Pay attention that bufio.Reader is used to read from it. func NewReader(r io.Reader) *Reader { return &Reader{r: bufio.NewReader(r), eof: true} } // Parse netstring prefix. It returns data length. // After this method you have to call either Read() or Discard(). // io.EOF is returned when underlying reader has no data anymore. func (r *Reader) Next() (uint64, error) { if !r.eof { return 0, errors.New("current chunk is unread") } lenRaw, err := r.r.ReadSlice(':') if err != nil { return 0, fmt.Errorf("netstring header: %w", err) } lenRaw = lenRaw[:len(lenRaw)-1] if len(lenRaw) > 1 && lenRaw[0] == '0' { return 0, errors.New("netstring header: leading zero") } size, err := strconv.ParseUint(string(lenRaw), 10, 64) if err != nil { return 0, fmt.Errorf("netstring header: %w", err) } r.left = size r.eof = false if r.left == 0 { err = r.checkTerminator() if err == nil { r.eof = true } } return size, err } func (r *Reader) checkTerminator() error { b, err := r.r.ReadByte() if err != nil { return fmt.Errorf("netstring terminator: %w", err) } if b != ',' { return errors.New("netstring terminator: not found") } return nil } // Read current netstring chunk. // This method must be called after Next(). // Terminator check and error raising are performed only at the end. func (r *Reader) Read(buf []byte) (n int, err error) { if r.eof { return 0, io.EOF } if uint64(len(buf)) > r.left { buf = buf[:r.left] } n, err = r.r.Read(buf) r.left -= uint64(n) if r.left == 0 { err = r.checkTerminator() if err == nil { r.eof = true } } return } // Discard remaining bytes in the chunk, possibly fully skipping it. // This method must be called after Next(). func (r *Reader) Discard() (err error) { _, err = r.r.Discard(int(r.left)) if err != nil { return } err = r.checkTerminator() if err != nil { return } r.eof = true return }