Test Failed
Push — master ( 2acb92...dffabb )
by Vyacheslav
01:53
created

goagi.*AGI.read   B

Complexity

Conditions 7

Size

Total Lines 35
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 25
nop 0
dl 0
loc 35
rs 7.8799
c 0
b 0
f 0
1
package goagi
2
3
import (
4
	"bufio"
5
	"strings"
6
)
7
8
// ErrAGI goagi error
9
var ErrAGI = newError("AGI session")
10
11
// Reader interface for AGI object. Can be net.Conn, os.File or crafted
12
type Reader interface {
13
	Read(b []byte) (int, error)
14
}
15
16
// Writer interface for AGI object. Can be net.Conn, os.File or crafted
17
type Writer interface {
18
	Write(b []byte) (int, error)
19
}
20
21
// Debugger for AGI instance. Any interface that provides Printf method.
22
// It should be used only for debugging as it give lots of output.
23
type Debugger interface {
24
	Printf(format string, v ...interface{})
25
}
26
27
// AGI object
28
type AGI struct {
29
	env      map[string]string
30
	arg      []string
31
	reader   Reader
32
	writer   Writer
33
	isHUP    bool
34
	debugger Debugger
35
}
36
37
const (
38
	codeUnknown int = 0
39
	codeEarly       = 100
40
	codeSucc        = 200
41
	codeE503        = 503
42
	codeE510        = 510
43
	codeE511        = 511
44
	codeE520        = 520
45
)
46
47
var codeMap = map[string]int{
48
	"100 ": codeEarly,
49
	"200 ": codeSucc,
50
	"503 ": codeE503,
51
	"510 ": codeE510,
52
	"511 ": codeE511,
53
	"520 ": codeE520,
54
}
55
56
/*
57
New creates and returns AGI object.
58
Can be used to create agi and fastagi sessions.
59
60
Parameters:
61
62
- Reader that implements Read method
63
64
- Writer that implements Write method
65
66
- Debugger that allows to deep library debugging. Nil for production.
67
68
*/
69
func New(r Reader, w Writer, dbg Debugger) (*AGI, error) {
70
	agi := &AGI{
71
		reader:   r,
72
		writer:   w,
73
		debugger: dbg,
74
	}
75
	agi.dbg("[>] New AGI")
76
	sessData, err := agi.sessionInit()
77
	if err != nil {
78
		return nil, ErrAGI.Msg("Failed to read setup: %s", err)
79
	}
80
	agi.sessionSetup(sessData)
81
	return agi, nil
82
}
83
84
// Env returns AGI environment variable by key
85
func (agi *AGI) Env(key string) string {
86
	agi.dbg("[>] Env for %q", key)
87
	val, ok := agi.env[key]
88
	if ok {
89
		return val
90
	}
91
	return ""
92
}
93
94
// EnvArgs returns list of environment arguments
95
func (agi *AGI) EnvArgs() []string {
96
	agi.dbg("[>] EnvArgs")
97
	return agi.arg
98
}
99
100
func (agi *AGI) sessionInit() ([]string, error) {
101
	agi.dbg("[>] sessionInit")
102
	buf := bufio.NewReader(agi.reader)
103
	data := make([]string, 0)
104
105
	for {
106
		line, err := buf.ReadString('\n')
107
		if err != nil {
108
			return nil, err
109
		}
110
		if line == "\n" {
111
			break
112
		}
113
		agi.dbg(" [v] read line: %q", line)
114
		data = append(data, line[:len(line)-1])
115
	}
116
	return data, nil
117
}
118
119
func (agi *AGI) dbg(pattern string, vargs ...interface{}) {
120
	if agi.debugger != nil {
121
		pattern += "\n"
122
		agi.debugger.Printf(pattern, vargs...)
0 ignored issues
show
introduced by
can't check non-constant format in call to Printf
Loading history...
123
	}
124
}
125
126
// low level response from device and response as string, matched response
127
// code, true if channel reported as hangup and error.
128
func (agi *AGI) read() (resp string, code int, err error) {
129
	agi.dbg("[>] readResponse")
130
	buf := bufio.NewReader(agi.reader)
131
	var builder strings.Builder
132
	moreInputExpected := false
133
134
	for {
135
		line, fail := buf.ReadString('\n')
136
		if fail != nil {
137
			err = fail
138
			return
139
		}
140
141
		agi.dbg(" [v] got line: %q", line)
142
143
		builder.WriteString(line)
144
		resp = builder.String()
145
		if codeMatch, ok := matchCode(line); ok {
146
			code = codeMatch
147
			return
148
		}
149
150
		if matchPrefix(line, "520-") {
151
			moreInputExpected = true
152
		}
153
154
		if matchPrefix(line, "HANGUP") {
155
			agi.isHUP = true
156
			builder.Reset()
157
			continue
158
		}
159
160
		if !moreInputExpected {
161
			err = ErrAGI.Msg("Invalid input while reading response: %q", resp)
162
			return
163
		}
164
	}
165
}
166
167
func matchPrefix(line, pattern string) bool {
168
	if len(line) < len(pattern) {
169
		return false
170
	}
171
	return line[:len(pattern)] == pattern
172
}
173
174
func matchCode(data string) (int, bool) {
175
	if len(data) < 4 {
176
		return 0, false
177
	}
178
	if codeMatch, ok := codeMap[data[:4]]; ok {
179
		return codeMatch, true
180
	}
181
	return 0, false
182
}
183
184
func (agi *AGI) write(command []byte) error {
185
	agi.dbg("[>] readResponse")
186
187
	agi.dbg(" [v] writing command: %q", string(command))
188
189
	_, err := agi.writer.Write(command)
190
	if err != nil {
191
		return err
192
	}
193
	agi.dbg(" [v] write successfully done")
194
	return nil
195
}
196
197
// write command, read and parse response
198
func (agi *AGI) execute(cmd string) (Response, error) {
199
	agi.dbg("[>] execute cmd: %q", cmd)
200
	if err := agi.write([]byte(cmd)); err != nil {
201
		return nil, err
202
	}
203
204
	resp, code, err := agi.read()
205
	if err != nil {
206
		return nil, err
207
	}
208
209
	return agi.parseResponse(resp, code)
210
}
211