bine/control/response.go

107 lines
3.0 KiB
Go
Raw Normal View History

2018-05-10 18:11:18 +00:00
package control
import (
"net/textproto"
"strconv"
"strings"
)
2018-05-14 19:47:05 +00:00
// Response is a response to a control port command or an asynchronous event.
2018-05-10 18:11:18 +00:00
type Response struct {
2018-05-14 20:36:29 +00:00
// Err is the status code and string representation associated with a
// response. Responses that have completed successfully will also have Err
// set to indicate such.
2018-05-10 18:11:18 +00:00
Err *textproto.Error
// Reply is the text on the EndReplyLine of the response.
Reply string
2018-05-14 20:36:29 +00:00
// Data is the MidReplyLines/DataReplyLines of the response. Dot encoded
// data is "decoded" and presented as a single string (terminal ".CRLF"
// removed, all intervening CRs stripped).
2018-05-10 18:11:18 +00:00
Data []string
// RawLines is all of the lines of a response, without CRLFs.
RawLines []string
}
2018-05-14 20:36:29 +00:00
// IsOk returns true if the response status code indicates success or an
// asynchronous event.
2018-05-10 18:11:18 +00:00
func (r *Response) IsOk() bool {
switch r.Err.Code {
2018-05-11 04:08:20 +00:00
case StatusOk, StatusOkUnnecessary, StatusAsyncEvent:
2018-05-10 18:11:18 +00:00
return true
default:
return false
}
}
2018-05-14 20:36:29 +00:00
// DataWithReply returns a combination of Data and Reply to give a full set of
// the lines of the response.
2018-05-11 04:08:20 +00:00
func (r *Response) DataWithReply() []string {
ret := make([]string, len(r.Data)+1)
copy(ret, r.Data)
ret[len(ret)-1] = r.Reply
return ret
}
2018-05-14 20:36:29 +00:00
// IsAsync returns true if the response is an asynchronous event.
2018-05-10 18:11:18 +00:00
func (r *Response) IsAsync() bool {
return r.Err.Code == StatusAsyncEvent
}
2018-05-10 23:05:43 +00:00
// ReadResponse returns the next response object.
2018-05-10 18:11:18 +00:00
func (c *Conn) ReadResponse() (*Response, error) {
var resp *Response
var statusCode int
for {
line, err := c.conn.ReadLine()
if err != nil {
return nil, err
}
2018-05-10 21:29:16 +00:00
c.debugf("Read line: %v", line)
2018-05-10 18:11:18 +00:00
// Parse the line that was just read.
if len(line) < 4 {
2018-05-10 23:05:43 +00:00
return nil, c.protoErr("Truncated response: %v", line)
2018-05-10 18:11:18 +00:00
}
2018-05-10 23:05:43 +00:00
if code, err := strconv.Atoi(line[0:3]); err != nil || code < 100 {
return nil, c.protoErr("Invalid status code: %v", line[0:3])
2018-05-10 18:11:18 +00:00
} else if resp == nil {
2018-05-10 23:05:43 +00:00
resp = &Response{}
2018-05-10 18:11:18 +00:00
statusCode = code
} else if code != statusCode {
2018-05-10 23:05:43 +00:00
// The status code should stay fixed for all lines of the response, since events can't be interleaved with
// response lines.
return nil, c.protoErr("Status code changed: %v != %v", code, statusCode)
2018-05-10 18:11:18 +00:00
}
2018-05-10 23:05:43 +00:00
resp.RawLines = append(resp.RawLines, line)
switch line[3] {
case ' ':
2018-05-10 18:11:18 +00:00
// Final line in the response.
resp.Reply = line[4:]
resp.Err = statusCodeToError(statusCode, resp.Reply)
return resp, nil
case '-':
// Continuation, keep reading.
resp.Data = append(resp.Data, line[4:])
case '+':
// A "dot-encoded" payload follows.
dotBody, err := c.conn.ReadDotBytes()
if err != nil {
return nil, err
}
2018-05-11 17:34:33 +00:00
dotBodyStr := strings.TrimRight(string(dotBody), "\n\r")
2018-05-12 05:41:36 +00:00
// c.debugf("Read dot body:\n---\n%v\n---", dotBodyStr)
resp.Data = append(resp.Data, line[4:]+"\r\n"+dotBodyStr)
2018-05-11 17:34:33 +00:00
dotLines := strings.Split(dotBodyStr, "\n")
2018-05-10 18:11:18 +00:00
for _, dotLine := range dotLines[:len(dotLines)-1] {
resp.RawLines = append(resp.RawLines, dotLine)
}
resp.RawLines = append(resp.RawLines, ".")
default:
2018-05-10 23:05:43 +00:00
return nil, c.protoErr("Invalid separator: '%v'", line[3])
2018-05-10 18:11:18 +00:00
}
}
}