// 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 // for full details. package bulb import ( "log" "net/textproto" "strconv" ) // 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 } // 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 } 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 line[3] == ' ' { // Final line in the response. resp.Reply = line[4:] resp.Err = statusCodeToError(statusCode, resp.Reply) 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:]) case '+': // A "dot-encoded" payload follows. resp.Data = append(resp.Data, line[4:]) 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, string(dotBody)) default: return nil, newProtocolError("invalid separator: '%c'", line[3]) } } }