goagi.*AGI.read   B
last analyzed

Complexity

Conditions 7

Size

Total Lines 36
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 26
nop 0
dl 0
loc 36
rs 7.856
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
func New(r Reader, w Writer, dbg Debugger) (*AGI, error) {
69
	agi := &AGI{
70
		reader:   r,
71
		writer:   w,
72
		debugger: dbg,
73
	}
74
	agi.dbg("[>] New AGI")
75
	sessData, err := agi.sessionInit()
76
	if err != nil {
77
		return nil, ErrAGI.Msg("Failed to read setup: %s", err)
78
	}
79
	agi.sessionSetup(sessData)
80
	return agi, nil
81
}
82
83
func (agi *AGI) Close() {
84
	agi.env = nil
85
	agi.arg = nil
86
}
87
88
// Env returns AGI environment variable by key
89
func (agi *AGI) Env(key string) string {
90
	agi.dbg("[>] Env for %q", key)
91
	val, ok := agi.env[key]
92
	if ok {
93
		return val
94
	}
95
	return ""
96
}
97
98
// EnvArgs returns list of environment arguments
99
func (agi *AGI) EnvArgs() []string {
100
	agi.dbg("[>] EnvArgs")
101
	return agi.arg
102
}
103
104
// IsHungup returns true if AGI channel received HANGUP signal
105
func (agi *AGI) IsHungup() bool {
106
	return agi.isHUP
107
}
108
109
func (agi *AGI) sessionInit() ([]string, error) {
110
	agi.dbg("[>] sessionInit")
111
	buf := bufio.NewReader(agi.reader)
112
	data := make([]string, 0)
113
114
	for {
115
		line, err := buf.ReadString('\n')
116
		if err != nil {
117
			return nil, err
118
		}
119
		if line == "\n" {
120
			break
121
		}
122
		agi.dbg(" [v] read line: %q", line)
123
		data = append(data, line[:len(line)-1])
124
	}
125
	return data, nil
126
}
127
128
func (agi *AGI) dbg(pattern string, vargs ...interface{}) {
129
	if agi.debugger != nil {
130
		pattern += "\n"
131
		agi.debugger.Printf(pattern, vargs...)
132
	}
133
}
134
135
// low level response from device and response as string, matched response
136
// code, true if channel reported as hangup and error.
137
func (agi *AGI) read() (resp string, code int, err error) {
138
	agi.dbg("[>] readResponse")
139
	buf := bufio.NewReader(agi.reader)
140
	var builder strings.Builder
141
	moreInputExpected := false
142
143
	for {
144
		line, fail := buf.ReadString('\n')
145
		if fail != nil {
146
			err = fail
147
			return
148
		}
149
150
		agi.dbg(" [v] got line: %q", line)
151
152
		builder.WriteString(line)
153
		resp = builder.String()
154
		if codeMatch, ok := matchCode(line); ok {
155
			code = codeMatch
156
			builder.Reset()
157
			return
158
		}
159
160
		if matchPrefix(line, "520-") {
161
			moreInputExpected = true
162
		}
163
164
		if matchPrefix(line, "HANGUP") {
165
			agi.isHUP = true
166
			builder.Reset()
167
			continue
168
		}
169
170
		if !moreInputExpected {
171
			err = ErrAGI.Msg("Invalid input while reading response: %q", resp)
172
			return
173
		}
174
	}
175
}
176
177
func matchPrefix(line, pattern string) bool {
178
	if len(line) < len(pattern) {
179
		return false
180
	}
181
	return line[:len(pattern)] == pattern
182
}
183
184
func matchCode(data string) (int, bool) {
185
	if len(data) < 4 {
186
		return 0, false
187
	}
188
	if codeMatch, ok := codeMap[data[:4]]; ok {
189
		return codeMatch, true
190
	}
191
	return 0, false
192
}
193
194
func (agi *AGI) write(command []byte) error {
195
	agi.dbg("[>] readResponse")
196
197
	agi.dbg(" [v] writing command: %q", string(command))
198
199
	_, err := agi.writer.Write(command)
200
	if err != nil {
201
		return err
202
	}
203
	agi.dbg(" [v] write successfully done")
204
	return nil
205
}
206
207
// write command, read and parse response
208
func (agi *AGI) execute(cmd string) (Response, error) {
209
	agi.dbg("[>] execute cmd: %q", cmd)
210
	if err := agi.write([]byte(cmd)); err != nil {
211
		return nil, err
212
	}
213
214
	resp, code, err := agi.read()
215
	if err != nil {
216
		return nil, err
217
	}
218
219
	return agi.parseResponse(resp, code)
220
}
221