Test Failed
Push — master ( d4cdd3...569253 )
by Vyacheslav
03:17 queued 01:50
created

goagi.*AGI.Env   A

Complexity

Conditions 2

Size

Total Lines 7
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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