]> Cypherpunks.ru repositories - netstring.git/blob - r.go
Unify copyright comment format
[netstring.git] / r.go
1 // netstring -- netstring format serialization library
2 // Copyright (C) 2015-2024 Sergey Matveev <stargrave@stargrave.org>
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, version 3 of the License.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
16 package netstring
17
18 import (
19         "bufio"
20         "errors"
21         "fmt"
22         "io"
23         "strconv"
24 )
25
26 type Reader struct {
27         r    *bufio.Reader
28         left uint64
29         eof  bool
30 }
31
32 // Create new Reader.
33 // Pay attention that bufio.Reader is used to read from it.
34 func NewReader(r io.Reader) *Reader {
35         return &Reader{r: bufio.NewReader(r), eof: true}
36 }
37
38 // Parse netstring prefix. It returns data length.
39 // After this method you have to call either Read() or Discard().
40 // io.EOF is returned when underlying reader has no data anymore.
41 func (r *Reader) Next() (uint64, error) {
42         if !r.eof {
43                 return 0, errors.New("current chunk is unread")
44         }
45         lenRaw, err := r.r.ReadSlice(':')
46         if err != nil {
47                 return 0, fmt.Errorf("netstring header: %w", err)
48         }
49         lenRaw = lenRaw[:len(lenRaw)-1]
50         if len(lenRaw) > 1 && lenRaw[0] == '0' {
51                 return 0, errors.New("netstring header: leading zero")
52         }
53         size, err := strconv.ParseUint(string(lenRaw), 10, 64)
54         if err != nil {
55                 return 0, fmt.Errorf("netstring header: %w", err)
56         }
57         r.left = size
58         r.eof = false
59         if r.left == 0 {
60                 err = r.checkTerminator()
61                 if err == nil {
62                         r.eof = true
63                 }
64         }
65         return size, err
66 }
67
68 func (r *Reader) checkTerminator() error {
69         b, err := r.r.ReadByte()
70         if err != nil {
71                 return fmt.Errorf("netstring terminator: %w", err)
72         }
73         if b != ',' {
74                 return errors.New("netstring terminator: not found")
75         }
76         return nil
77 }
78
79 // Read current netstring chunk.
80 // This method must be called after Next().
81 // Terminator check and error raising are performed only at the end.
82 func (r *Reader) Read(buf []byte) (n int, err error) {
83         if r.eof {
84                 return 0, io.EOF
85         }
86         if uint64(len(buf)) > r.left {
87                 buf = buf[:r.left]
88         }
89         n, err = r.r.Read(buf)
90         r.left -= uint64(n)
91         if r.left == 0 {
92                 err = r.checkTerminator()
93                 if err == nil {
94                         r.eof = true
95                 }
96         }
97         return
98 }
99
100 // Discard remaining bytes in the chunk, possibly fully skipping it.
101 // This method must be called after Next().
102 func (r *Reader) Discard() (err error) {
103         _, err = r.r.Discard(int(r.left))
104         if err != nil {
105                 return
106         }
107         err = r.checkTerminator()
108         if err != nil {
109                 return
110         }
111         r.eof = true
112         return
113 }