This repository has been archived by the owner on Dec 12, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 21
/
io.go
218 lines (186 loc) · 5.63 KB
/
io.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package common
import (
"bufio"
"errors"
"fmt"
"io"
"log"
"strings"
)
const (
// MaxLineLen is a maximum length of line in Assuan protocol, including
// space after command and LF.
MaxLineLen = 1000
)
// ReadWriter ties arbitrary io.Reader and io.Writer to get a struct that
// satisfies io.ReadWriter requirements.
type ReadWriter struct {
io.Reader
io.Writer
}
// Pipe is a wrapper for Assuan command stream.
type Pipe struct {
scnr *bufio.Scanner
r io.Reader
w io.Writer
}
// New crreates and initializes Pipe using biderectional stream.
func New(stream io.ReadWriter) Pipe {
p := Pipe{bufio.NewScanner(stream), stream, stream}
p.scnr.Buffer(make([]byte, 0, MaxLineLen), MaxLineLen)
return p
}
// NewPipe crreates and initializes Pipe using 2 streams.
func NewPipe(in io.Reader, out io.Writer) Pipe {
p := Pipe{bufio.NewScanner(in), in, out}
p.scnr.Buffer(make([]byte, 0, MaxLineLen), MaxLineLen)
return p
}
// Close closes Pipe.
func (p *Pipe) Close() error {
// Reserved for future use, no-op now.
return nil
}
// RestrictInputLen controls how lines longer than MaxLineLen should be handled.
// By default they will be discarded and error will be returned. You can disable
// this behavior using RestrictInputLen(false) if implementation you are working
// with violates this restriction.
//
// Note that even with b=false line length will be restricted to
// bufio.MaxScanTokenSize (64 KiB).
//
// This function MUST be called before any I/O, otherwise it will panic.
func (p *Pipe) RestrictInputLen(restrict bool) {
if restrict {
p.scnr.Buffer(make([]byte, 0, MaxLineLen), MaxLineLen)
} else {
p.scnr.Buffer([]byte{}, bufio.MaxScanTokenSize)
}
}
// ReadLine reads raw request/response in following format: command <parameters>
//
// Empty lines and lines starting with # are ignored as specified by protocol.
// Additionally, status information is silently discarded for now.
func (p *Pipe) ReadLine() (cmd string, params string, err error) {
var line string
for {
if ok := p.scnr.Scan(); !ok {
err := p.scnr.Err()
if err == nil {
err = io.EOF
}
return "", "", err
}
line = p.scnr.Text()
// We got something that looks like a message. Let's parse it.
if !strings.HasPrefix(line, "#") && !strings.HasPrefix(line, "S ") && len(strings.TrimSpace(line)) != 0 {
break
}
}
// Part before first whitespace is a command. Everything after first whitespace is parameters.
parts := strings.SplitN(line, " ", 2)
// If there is no parameters... (huh!?)
if len(parts) == 1 {
return strings.ToUpper(parts[0]), "", nil
}
log.Println("<", parts[0])
params, err = UnescapeParameters(parts[1])
if err != nil {
return "", "", err
}
// Command is "normalized" to upper case since peer can send
// commands in any case.
return strings.ToUpper(parts[0]), params, nil
}
// WriteLine writes request/response to pipe.
// Contents of params is escaped according to requirements of Assuan protocol.
func (p *Pipe) WriteLine(cmd string, params string) error {
if len(cmd)+len(params)+2 > MaxLineLen {
log.Println("Refusing to send - command too long")
// 2 is for whitespace after command and LF
return errors.New("command or parameters are too log")
}
log.Println(">", cmd)
var line []byte
if params != "" {
line = []byte(strings.ToUpper(cmd) + " " + EscapeParameters(params) + "\n")
} else {
line = []byte(strings.ToUpper(cmd) + "\n")
}
_, err := p.w.Write(line)
return err
}
func min(a, b int) int {
if a <= b {
return a
}
return b
}
// WriteData sends passed byte slice using one or more D commands.
// Note: Error may occur even after some data is written so it's better
// to just CAN transaction after WriteData error.
func (p *Pipe) WriteData(input []byte) error {
encoded := []byte(EscapeParameters(string(input)))
chunkLen := MaxLineLen - 3 // 3 is for 'D ' and line feed.
for i := 0; i < len(encoded); i += chunkLen {
chunk := encoded[i:min(i+chunkLen, len(encoded))]
chunk = append([]byte{'D', ' '}, chunk...)
chunk = append(chunk, '\n')
if _, err := p.w.Write(chunk); err != nil {
return err
}
}
return nil
}
// WriteDataReader is similar to WriteData but sends data from input Reader
// until EOF.
func (p *Pipe) WriteDataReader(input io.Reader) error {
chunkLen := MaxLineLen - 3 // 3 is for 'D ' and line feed.
buf := make([]byte, chunkLen)
for {
n, err := input.Read(buf)
if errors.Is(err, io.EOF) {
return nil
}
if err != nil {
return err
}
chunk := []byte(EscapeParameters(string(buf[:n])))
chunk = append([]byte{'D', ' '}, chunk...)
chunk = append(chunk, '\n')
if _, err := p.w.Write(chunk); err != nil {
return err
}
}
}
// ReadData reads sequence of D commands and joins data together.
func (p *Pipe) ReadData() (data []byte, err error) {
for {
cmd, chunk, err := p.ReadLine()
if err != nil {
return nil, err
}
if cmd == "END" {
return data, nil
}
if cmd == "CAN" {
return nil, Error{Src: ErrSrcAssuan, Code: ErrUnexpected, SrcName: "assuan", Message: "IPC call has been cancelled"}
}
if cmd != "D" {
return nil, Error{Src: ErrSrcAssuan, Code: ErrUnexpected, SrcName: "assuan", Message: "unexpected IPC command"}
}
unescaped, err := UnescapeParameters(chunk)
if err != nil {
return nil, err
}
data = append(data, []byte(unescaped)...)
}
}
// WriteComment is special case of WriteLine. "Command" is # and text is parameter.
func (p *Pipe) WriteComment(text string) error {
return p.WriteLine("#", text)
}
// WriteError is a special case of WriteLine. It writes command.
func (p *Pipe) WriteError(err Error) error {
return p.WriteLine("ERR", fmt.Sprintf("%d %s <%s>", MakeErrCode(err.Src, err.Code), err.Message, err.SrcName))
}