This repository has been archived by the owner on Jan 5, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathresponse.go
125 lines (113 loc) · 3.59 KB
/
response.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// response.go - Generic response handler
//
// To the extent possible under law, Yawning Angel waived all copyright
// and related or neighboring rights to bulb, using the creative
// commons "cc0" public domain dedication. See LICENSE or
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
package bulb
import (
"log"
"net/textproto"
"strconv"
"strings"
)
// Response is a response to a control port command, or an asyncrhonous event.
type Response struct {
// 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.
Err *textproto.Error
// Reply is the text on the EndReplyLine of the response.
Reply string
// 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).
Data []string
// RawLines is all of the lines of a response, without CRLFs.
RawLines []string
}
// IsOk returns true if the response status code indicates success or
// an asynchronous event.
func (r *Response) IsOk() bool {
switch r.Err.Code {
case StatusOk, StatusOkUnneccecary, StatusAsyncEvent:
return true
default:
return false
}
}
// IsAsync returns true if the response is an asyncrhonous event.
func (r *Response) IsAsync() bool {
return r.Err.Code == StatusAsyncEvent
}
// ReadResponse returns the next response object. Calling this
// simultaniously with Read, Request, or StartAsyncReader will lead to
// undefined behavior
func (c *Conn) ReadResponse() (*Response, error) {
var resp *Response
var statusCode int
for {
line, err := c.conn.ReadLine()
if err != nil {
return nil, err
}
if c.debugLog {
log.Printf("S: %s", line)
}
// Parse the line that was just read.
if len(line) < 4 {
return nil, newProtocolError("truncated response: '%s'", line)
}
if code, err := strconv.Atoi(line[0:3]); err != nil {
return nil, newProtocolError("invalid status code: '%s'", line[0:3])
} else if code < 100 {
return nil, newProtocolError("invalid status code: '%s'", line[0:3])
} else if resp == nil {
resp = new(Response)
statusCode = code
} else if code != statusCode {
// The status code should stay fixed for all lines of the
// response, since events can't be interleaved with response
// lines.
return nil, newProtocolError("status code changed: %03d != %03d", code, statusCode)
}
if resp.RawLines == nil {
resp.RawLines = make([]string, 0, 1)
}
if line[3] == ' ' {
// Final line in the response.
resp.Reply = line[4:]
resp.Err = statusCodeToError(statusCode, resp.Reply)
resp.RawLines = append(resp.RawLines, line)
return resp, nil
}
if resp.Data == nil {
resp.Data = make([]string, 0, 1)
}
switch line[3] {
case '-':
// Continuation, keep reading.
resp.Data = append(resp.Data, line[4:])
resp.RawLines = append(resp.RawLines, line)
case '+':
// A "dot-encoded" payload follows.
resp.Data = append(resp.Data, line[4:])
resp.RawLines = append(resp.RawLines, line)
dotBody, err := c.conn.ReadDotBytes()
if err != nil {
return nil, err
}
if c.debugLog {
log.Printf("S: [dot encoded data]")
}
resp.Data = append(resp.Data, strings.TrimRight(string(dotBody), "\n\r"))
dotLines := strings.Split(string(dotBody), "\n")
for _, dotLine := range dotLines[:len(dotLines)-1] {
resp.RawLines = append(resp.RawLines, dotLine)
}
resp.RawLines = append(resp.RawLines, ".")
default:
return nil, newProtocolError("invalid separator: '%c'", line[3])
}
}
}