Passed
Push — master ( ad0bc7...31cf29 )
by Vyacheslav
01:25
created

goagi.*AGI.IsHungup   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 0
dl 0
loc 2
rs 10
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
// IsHungup returns true if AGI channel recieved HANGUP signal
101
func (agi *AGI) IsHungup() bool {
102
	return agi.isHUP
103
}
104
105
func (agi *AGI) sessionInit() ([]string, error) {
106
	agi.dbg("[>] sessionInit")
107
	buf := bufio.NewReader(agi.reader)
108
	data := make([]string, 0)
109
110
	for {
111
		line, err := buf.ReadString('\n')
112
		if err != nil {
113
			return nil, err
114
		}
115
		if line == "\n" {
116
			break
117
		}
118
		agi.dbg(" [v] read line: %q", line)
119
		data = append(data, line[:len(line)-1])
120
	}
121
	return data, nil
122
}
123
124
func (agi *AGI) dbg(pattern string, vargs ...interface{}) {
125
	if agi.debugger != nil {
126
		pattern += "\n"
127
		agi.debugger.Printf(pattern, vargs...)
128
	}
129
}
130
131
// low level response from device and response as string, matched response
132
// code, true if channel reported as hangup and error.
133
func (agi *AGI) read() (resp string, code int, err error) {
134
	agi.dbg("[>] readResponse")
135
	buf := bufio.NewReader(agi.reader)
136
	var builder strings.Builder
137
	moreInputExpected := false
138
139
	for {
140
		line, fail := buf.ReadString('\n')
141
		if fail != nil {
142
			err = fail
143
			return
144
		}
145
146
		agi.dbg(" [v] got line: %q", line)
147
148
		builder.WriteString(line)
149
		resp = builder.String()
150
		if codeMatch, ok := matchCode(line); ok {
151
			code = codeMatch
152
			return
153
		}
154
155
		if matchPrefix(line, "520-") {
156
			moreInputExpected = true
157
		}
158
159
		if matchPrefix(line, "HANGUP") {
160
			agi.isHUP = true
161
			builder.Reset()
162
			continue
163
		}
164
165
		if !moreInputExpected {
166
			err = ErrAGI.Msg("Invalid input while reading response: %q", resp)
167
			return
168
		}
169
	}
170
}
171
172
func matchPrefix(line, pattern string) bool {
173
	if len(line) < len(pattern) {
174
		return false
175
	}
176
	return line[:len(pattern)] == pattern
177
}
178
179
func matchCode(data string) (int, bool) {
180
	if len(data) < 4 {
181
		return 0, false
182
	}
183
	if codeMatch, ok := codeMap[data[:4]]; ok {
184
		return codeMatch, true
185
	}
186
	return 0, false
187
}
188
189
func (agi *AGI) write(command []byte) error {
190
	agi.dbg("[>] readResponse")
191
192
	agi.dbg(" [v] writing command: %q", string(command))
193
194
	_, err := agi.writer.Write(command)
195
	if err != nil {
196
		return err
197
	}
198
	agi.dbg(" [v] write successfully done")
199
	return nil
200
}
201
202
// write command, read and parse response
203
func (agi *AGI) execute(cmd string) (Response, error) {
204
	agi.dbg("[>] execute cmd: %q", cmd)
205
	if err := agi.write([]byte(cmd)); err != nil {
206
		return nil, err
207
	}
208
209
	resp, code, err := agi.read()
210
	if err != nil {
211
		return nil, err
212
	}
213
214
	return agi.parseResponse(resp, code)
215
}
216