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
![]() |
|||
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 |