Test Failed
Push — master ( 569253...ad9af8 )
by Vyacheslav
01:38
created
Severity
1
package goagi
2
3
import (
4
	"bufio"
5
	"strings"
6
	"time"
7
)
8
9
// ErrAGI goagi error
10
var ErrAGI = newError("AGI session")
11
12
const rwDefaultTimeout = time.Second * 1
13
14
// Reader interface for AGI object. Can be net.Conn, os.File or crafted
15
type Reader interface {
16
	Read(b []byte) (int, error)
17
}
18
19
// Writer interface for AGI object. Can be net.Conn, os.File or crafted
20
type Writer interface {
21
	SetWriteDeadline(t time.Time) error
22
	Write(b []byte) (int, error)
23
}
24
25
/*
26
Debugger for AGI instance. Any interface that provides Printf method.
27
Usage example:
28
```go
29
	dbg := logger.New(os.Stdout, "myagi:", log.Lmicroseconds)
30
	r, w := net.Pipe()
31
	agi, err := goagi.New(r, w, dbg)
32
```
33
It should be used only for debugging as it give lots of output.
34
*/
35
type Debugger interface {
36
	Printf(format string, v ...interface{})
37
}
38
39
// AGI object
40
type AGI struct {
41
	env      map[string]string
42
	arg      []string
43
	reader   Reader
44
	writer   Writer
45
	isHUP    bool
46
	debugger Debugger
47
	wrtout   time.Duration
48
}
49
50
const (
51
	codeUnknown int = 0
52
	codeEarly       = 100
53
	codeSucc        = 200
54
	codeE503        = 503
55
	codeE510        = 510
56
	codeE511        = 511
57
	codeE520        = 520
58
)
59
60
var codeMap = map[string]int{
61
	"100 ": codeEarly,
62
	"200 ": codeSucc,
63
	"503 ": codeE503,
64
	"510 ": codeE510,
65
	"511 ": codeE511,
66
	"520 ": codeE520,
67
}
68
69
/*
70
New creates and returns AGI object.
71
Can be used to create agi and fastagi sessions.
72
Example for agi:
73
```go
74
	import (
75
		"github.com/staskobzar/goagi"
76
		"os"
77
	)
78
79
	agi, err := goagi.New(os.Stdin, os.Stdout, nil)
80
	if err != nil {
81
		panic(err)
82
	}
83
	agi.Verbose("Hello World!")
84
```
85
86
Fast agi example:
87
```go
88
	ln, err := net.Listen("tcp", "127.0.0.1:4573")
89
	if err != nil {
90
		panic(err)
91
	}
92
	for {
93
		conn, err := ln.Accept()
94
		if err != nil {
95
			panic(err)
96
		}
97
		go func(conn net.Conn) {
98
			agi, err := goagi.New(conn, conn, nil)
99
			if err != nil {
100
				panic(err)
101
			}
102
			agi.Verbose("Hello World!")
103
		}(conn)
104
	}
105
```
106
*/
107
func New(r Reader, w Writer, dbg Debugger) (*AGI, error) {
108
	agi := &AGI{
109
		reader:   r,
110
		writer:   w,
111
		debugger: dbg,
112
		wrtout:   rwDefaultTimeout,
113
	}
114
	agi.dbg("[>] New AGI")
115
	sessData, err := agi.sessionInit()
116
	if err != nil {
117
		return nil, ErrAGI.Msg("Failed to read setup: %s", err)
118
	}
119
	agi.sessionSetup(sessData)
120
	return agi, nil
121
}
122
123
// Env returns AGI environment variable by key
124
func (agi *AGI) Env(key string) string {
125
	agi.dbg("[>] Env for %q", key)
126
	val, ok := agi.env[key]
127
	if ok {
128
		return val
129
	}
130
	return ""
131
}
132
133
// EnvArgs returns list of environment arguments
134
func (agi *AGI) EnvArgs() []string {
135
	agi.dbg("[>] EnvArgs")
136
	return agi.arg
137
}
138
139
func (agi *AGI) sessionInit() ([]string, error) {
140
	agi.dbg("[>] sessionInit")
141
	buf := bufio.NewReader(agi.reader)
142
	data := make([]string, 0)
143
144
	for {
145
		line, err := buf.ReadString('\n')
146
		if err != nil {
147
			return nil, err
148
		}
149
		if line == "\n" {
150
			break
151
		}
152
		agi.dbg(" [v] read line: %q", line)
153
		data = append(data, line[:len(line)-1])
154
	}
155
	return data, nil
156
}
157
158
func (agi *AGI) dbg(pattern string, vargs ...interface{}) {
159
	if agi.debugger != nil {
160
		pattern += "\n"
161
		agi.debugger.Printf(pattern, vargs...)
0 ignored issues
show
can't check non-constant format in call to Printf
Loading history...
162
	}
163
}
164
165
// low level response from device and response as string, matched response
166
// code, true if channel reported as hangup and error.
167
func (agi *AGI) read() (resp string, code int, err error) {
168
	agi.dbg("[>] readResponse")
169
	buf := bufio.NewReader(agi.reader)
170
	var builder strings.Builder
171
	moreInputExpected := false
172
173
	for {
174
		line, fail := buf.ReadString('\n')
175
		if fail != nil {
176
			err = fail
177
			return
178
		}
179
180
		agi.dbg(" [v] got line: %q", line)
181
182
		builder.WriteString(line)
183
		resp = builder.String()
184
		if codeMatch, ok := matchCode(line); ok {
185
			code = codeMatch
186
			return
187
		}
188
189
		if matchPrefix(line, "520-") {
190
			moreInputExpected = true
191
		}
192
193
		if matchPrefix(line, "HANGUP") {
194
			agi.isHUP = true
195
			builder.Reset()
196
			continue
197
		}
198
199
		if !moreInputExpected {
200
			err = ErrAGI.Msg("Invalid input while reading response: %q", resp)
201
			return
202
		}
203
	}
204
}
205
206
func matchPrefix(line, pattern string) bool {
207
	if len(line) < len(pattern) {
208
		return false
209
	}
210
	return line[:len(pattern)] == pattern
211
}
212
213
func matchCode(data string) (int, bool) {
214
	if len(data) < 4 {
215
		return 0, false
216
	}
217
	if codeMatch, ok := codeMap[data[:4]]; ok {
218
		return codeMatch, true
219
	}
220
	return 0, false
221
}
222
223
func (agi *AGI) write(command []byte) error {
224
	agi.dbg("[>] readResponse")
225
	if agi.wrtout > 0 {
226
		tout := time.Now().Add(agi.wrtout)
227
		agi.dbg(" [v] set write timeout at: %s", tout)
228
229
		if err := agi.writer.SetWriteDeadline(tout); err != nil {
230
			return err
231
		}
232
	}
233
234
	agi.dbg(" [v] writing command: %q", string(command))
235
236
	_, err := agi.writer.Write(command)
237
	if err != nil {
238
		return err
239
	}
240
	agi.dbg(" [v] write successfully done")
241
	return nil
242
}
243
244
// write command, read and parse response
245
func (agi *AGI) execute(cmd string) (Response, error) {
246
	agi.dbg("[>] execute cmd: %q", cmd)
247
	if err := agi.write([]byte(cmd)); err != nil {
248
		return nil, err
249
	}
250
251
	resp, code, err := agi.read()
252
	if err != nil {
253
		return nil, err
254
	}
255
256
	return agi.parseResponse(resp, code)
257
}
258