2019-03-10 17:43:47 +00:00
|
|
|
package ipp
|
|
|
|
|
|
|
|
import (
|
2020-03-05 15:29:31 +00:00
|
|
|
"bytes"
|
2019-03-10 17:43:47 +00:00
|
|
|
"encoding/binary"
|
|
|
|
"io"
|
|
|
|
)
|
|
|
|
|
2020-03-27 23:27:56 +00:00
|
|
|
// Attributes is a wrapper for a set of attributes
|
2019-03-10 17:43:47 +00:00
|
|
|
type Attributes map[string][]Attribute
|
|
|
|
|
2020-03-27 23:27:56 +00:00
|
|
|
// Response defines a ipp response
|
2019-03-10 17:43:47 +00:00
|
|
|
type Response struct {
|
2020-03-09 11:58:54 +00:00
|
|
|
ProtocolVersionMajor int8
|
|
|
|
ProtocolVersionMinor int8
|
2019-03-10 17:43:47 +00:00
|
|
|
|
2020-03-09 11:51:46 +00:00
|
|
|
StatusCode int16
|
2019-03-13 21:54:08 +00:00
|
|
|
RequestId int32
|
2019-03-10 17:43:47 +00:00
|
|
|
|
|
|
|
OperationAttributes Attributes
|
2020-03-05 15:29:31 +00:00
|
|
|
PrinterAttributes []Attributes
|
|
|
|
JobAttributes []Attributes
|
2019-03-10 17:43:47 +00:00
|
|
|
}
|
|
|
|
|
2020-03-27 23:27:56 +00:00
|
|
|
// CheckForErrors checks the status code and returns a error if it is not zero. it also returns the status message if provided by the server
|
2019-03-10 17:43:47 +00:00
|
|
|
func (r *Response) CheckForErrors() error {
|
2020-03-27 22:32:12 +00:00
|
|
|
if r.StatusCode != StatusOk {
|
|
|
|
err := IPPError{
|
|
|
|
Status: r.StatusCode,
|
|
|
|
Message: "no status message returned",
|
2019-03-10 17:43:47 +00:00
|
|
|
}
|
|
|
|
|
2020-03-27 22:32:12 +00:00
|
|
|
if len(r.OperationAttributes["status-message"]) > 0 {
|
|
|
|
err.Message = r.OperationAttributes["status-message"][0].Value.(string)
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
2019-03-10 17:43:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-27 23:30:48 +00:00
|
|
|
// NewResponse creates a new ipp response
|
2020-03-09 11:51:46 +00:00
|
|
|
func NewResponse(statusCode int16, reqID int32) *Response {
|
2020-03-05 15:29:31 +00:00
|
|
|
return &Response{
|
|
|
|
ProtocolVersionMajor: ProtocolVersionMajor,
|
|
|
|
ProtocolVersionMinor: ProtocolVersionMinor,
|
|
|
|
StatusCode: statusCode,
|
|
|
|
RequestId: reqID,
|
|
|
|
OperationAttributes: make(Attributes),
|
|
|
|
PrinterAttributes: make([]Attributes, 0),
|
|
|
|
JobAttributes: make([]Attributes, 0),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-27 23:27:56 +00:00
|
|
|
// Encode encodes the response to a byte slice
|
|
|
|
func (r *Response) Encode() ([]byte, error) {
|
2020-03-05 15:29:31 +00:00
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
enc := NewAttributeEncoder(buf)
|
|
|
|
|
|
|
|
if err := binary.Write(buf, binary.BigEndian, r.ProtocolVersionMajor); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := binary.Write(buf, binary.BigEndian, r.ProtocolVersionMinor); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := binary.Write(buf, binary.BigEndian, r.StatusCode); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := binary.Write(buf, binary.BigEndian, r.RequestId); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := binary.Write(buf, binary.BigEndian, int8(TagOperation)); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-03-09 09:36:29 +00:00
|
|
|
if err := enc.Encode(AttributeCharset, Charset); err != nil {
|
2020-03-05 15:29:31 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-03-09 09:36:29 +00:00
|
|
|
if err := enc.Encode(AttributeNaturalLanguage, CharsetLanguage); err != nil {
|
2020-03-05 15:29:31 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(r.OperationAttributes) > 0 {
|
|
|
|
for name, attr := range r.OperationAttributes {
|
|
|
|
if len(attr) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-03-09 10:09:16 +00:00
|
|
|
values := make([]interface{}, len(attr))
|
2020-03-06 11:22:56 +00:00
|
|
|
for i, v := range attr {
|
2020-03-05 15:29:31 +00:00
|
|
|
values[i] = v.Value
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(values) == 1 {
|
|
|
|
if err := enc.Encode(name, values[0]); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if err := enc.Encode(name, values); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(r.PrinterAttributes) > 0 {
|
|
|
|
for _, printerAttr := range r.PrinterAttributes {
|
|
|
|
if err := binary.Write(buf, binary.BigEndian, int8(TagPrinter)); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, attr := range printerAttr {
|
|
|
|
if len(attr) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-03-09 10:09:16 +00:00
|
|
|
values := make([]interface{}, len(attr))
|
2020-03-06 11:22:56 +00:00
|
|
|
for i, v := range attr {
|
2020-03-05 15:29:31 +00:00
|
|
|
values[i] = v.Value
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(values) == 1 {
|
|
|
|
if err := enc.Encode(name, values[0]); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if err := enc.Encode(name, values); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(r.JobAttributes) > 0 {
|
|
|
|
for _, jobAttr := range r.JobAttributes {
|
|
|
|
if err := binary.Write(buf, binary.BigEndian, int8(TagJob)); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, attr := range jobAttr {
|
|
|
|
if len(attr) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-03-09 10:09:16 +00:00
|
|
|
values := make([]interface{}, len(attr))
|
2020-03-06 11:22:56 +00:00
|
|
|
for i, v := range attr {
|
2020-03-05 15:29:31 +00:00
|
|
|
values[i] = v.Value
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(values) == 1 {
|
|
|
|
if err := enc.Encode(name, values[0]); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if err := enc.Encode(name, values); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-27 23:27:56 +00:00
|
|
|
if err := binary.Write(buf, binary.BigEndian, TagEnd); err != nil {
|
2020-03-05 15:29:31 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf.Bytes(), nil
|
|
|
|
}
|
|
|
|
|
2020-03-27 23:27:56 +00:00
|
|
|
// ResponseDecoder reads and decodes a response from a stream
|
2019-03-10 17:43:47 +00:00
|
|
|
type ResponseDecoder struct {
|
|
|
|
reader io.Reader
|
|
|
|
}
|
|
|
|
|
2020-03-27 23:27:56 +00:00
|
|
|
// NewResponseDecoder returns a new decoder that reads from r
|
2019-03-10 17:43:47 +00:00
|
|
|
func NewResponseDecoder(r io.Reader) *ResponseDecoder {
|
|
|
|
return &ResponseDecoder{
|
|
|
|
reader: r,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-27 23:27:56 +00:00
|
|
|
// Decode decodes a ipp response into a response struct. additional data will be written to an io.Writer if data is not nil
|
2019-12-26 15:46:08 +00:00
|
|
|
func (d *ResponseDecoder) Decode(data io.Writer) (*Response, error) {
|
2019-03-10 17:43:47 +00:00
|
|
|
/*
|
2019-03-13 21:54:08 +00:00
|
|
|
1 byte: Protocol Major Version - b
|
|
|
|
1 byte: Protocol Minor Version - b
|
|
|
|
2 byte: Status ID - h
|
|
|
|
4 byte: Request ID - i
|
|
|
|
1 byte: Operation Attribute Byte (\0x01)
|
|
|
|
N times: Attributes
|
|
|
|
1 byte: Attribute End Byte (\0x03)
|
2019-03-10 17:43:47 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
resp := new(Response)
|
|
|
|
|
|
|
|
// wrap the reader so we have more functionality
|
2020-03-05 15:29:31 +00:00
|
|
|
// reader := bufio.NewReader(d.reader)
|
2019-03-10 17:43:47 +00:00
|
|
|
|
2019-03-13 21:54:08 +00:00
|
|
|
if err := binary.Read(d.reader, binary.BigEndian, &resp.ProtocolVersionMajor); err != nil {
|
2019-03-10 17:43:47 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-03-13 21:54:08 +00:00
|
|
|
if err := binary.Read(d.reader, binary.BigEndian, &resp.ProtocolVersionMinor); err != nil {
|
2019-03-10 17:43:47 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-03-13 21:54:08 +00:00
|
|
|
if err := binary.Read(d.reader, binary.BigEndian, &resp.StatusCode); err != nil {
|
2019-03-10 17:43:47 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-03-13 21:54:08 +00:00
|
|
|
if err := binary.Read(d.reader, binary.BigEndian, &resp.RequestId); err != nil {
|
2019-03-10 17:43:47 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-03-13 21:54:08 +00:00
|
|
|
startByteSlice := make([]byte, 1)
|
2019-03-10 17:43:47 +00:00
|
|
|
|
|
|
|
tag := TagCupsInvalid
|
|
|
|
previousAttributeName := ""
|
|
|
|
tempAttributes := make(Attributes)
|
|
|
|
tagSet := false
|
|
|
|
|
2019-03-13 21:54:08 +00:00
|
|
|
attribDecoder := NewAttributeDecoder(d.reader)
|
2019-03-10 17:43:47 +00:00
|
|
|
|
|
|
|
// decode attribute buffer
|
|
|
|
for {
|
2019-03-13 21:54:08 +00:00
|
|
|
if _, err := d.reader.Read(startByteSlice); err != nil {
|
2020-03-06 13:47:37 +00:00
|
|
|
// when we read from a stream, we may get an EOF if we want to read the end tag
|
|
|
|
// all data should be read and we can ignore the error
|
|
|
|
if err == io.EOF {
|
2020-03-27 14:59:54 +00:00
|
|
|
break
|
2020-03-06 13:47:37 +00:00
|
|
|
}
|
2019-03-10 17:43:47 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-03-09 11:47:23 +00:00
|
|
|
startByte := int8(startByteSlice[0])
|
2019-03-13 21:54:08 +00:00
|
|
|
|
2019-03-10 17:43:47 +00:00
|
|
|
// check if attributes are completed
|
2019-08-05 16:42:02 +00:00
|
|
|
if startByte == TagEnd {
|
2019-03-10 17:43:47 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2019-08-05 16:42:02 +00:00
|
|
|
if startByte == TagOperation {
|
2019-03-10 17:43:47 +00:00
|
|
|
if len(tempAttributes) > 0 && tag != TagCupsInvalid {
|
2020-03-05 15:29:31 +00:00
|
|
|
appendAttributeToResponse(resp, tag, tempAttributes)
|
2019-03-10 17:43:47 +00:00
|
|
|
tempAttributes = make(Attributes)
|
|
|
|
}
|
|
|
|
|
|
|
|
tag = TagOperation
|
|
|
|
tagSet = true
|
|
|
|
}
|
|
|
|
|
2019-08-05 16:42:02 +00:00
|
|
|
if startByte == TagJob {
|
2019-03-10 17:43:47 +00:00
|
|
|
if len(tempAttributes) > 0 && tag != TagCupsInvalid {
|
2020-03-05 15:29:31 +00:00
|
|
|
appendAttributeToResponse(resp, tag, tempAttributes)
|
2019-03-10 17:43:47 +00:00
|
|
|
tempAttributes = make(Attributes)
|
|
|
|
}
|
|
|
|
|
|
|
|
tag = TagJob
|
|
|
|
tagSet = true
|
|
|
|
}
|
|
|
|
|
2019-08-05 16:42:02 +00:00
|
|
|
if startByte == TagPrinter {
|
2019-03-10 17:43:47 +00:00
|
|
|
if len(tempAttributes) > 0 && tag != TagCupsInvalid {
|
2020-03-05 15:29:31 +00:00
|
|
|
appendAttributeToResponse(resp, tag, tempAttributes)
|
2019-03-10 17:43:47 +00:00
|
|
|
tempAttributes = make(Attributes)
|
|
|
|
}
|
|
|
|
|
|
|
|
tag = TagPrinter
|
|
|
|
tagSet = true
|
|
|
|
}
|
|
|
|
|
2019-03-13 21:54:08 +00:00
|
|
|
if tagSet {
|
|
|
|
if _, err := d.reader.Read(startByteSlice); err != nil {
|
2019-03-10 17:43:47 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2020-03-09 11:47:23 +00:00
|
|
|
startByte = int8(startByteSlice[0])
|
2019-03-10 17:43:47 +00:00
|
|
|
}
|
|
|
|
|
2020-03-09 11:21:57 +00:00
|
|
|
attrib, err := attribDecoder.Decode(startByte)
|
2019-03-10 17:43:47 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if attrib.Name != "" {
|
|
|
|
tempAttributes[attrib.Name] = append(tempAttributes[attrib.Name], *attrib)
|
|
|
|
previousAttributeName = attrib.Name
|
|
|
|
} else {
|
|
|
|
tempAttributes[previousAttributeName] = append(tempAttributes[previousAttributeName], *attrib)
|
|
|
|
}
|
2019-03-13 21:54:08 +00:00
|
|
|
|
|
|
|
tagSet = false
|
2019-03-10 17:43:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(tempAttributes) > 0 && tag != TagCupsInvalid {
|
2020-03-05 15:29:31 +00:00
|
|
|
appendAttributeToResponse(resp, tag, tempAttributes)
|
2019-03-10 17:43:47 +00:00
|
|
|
}
|
|
|
|
|
2020-03-05 15:29:31 +00:00
|
|
|
if data != nil {
|
|
|
|
if _, err := io.Copy(data, d.reader); err != nil {
|
2019-12-26 15:46:08 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2019-03-10 17:43:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
2020-03-09 11:47:23 +00:00
|
|
|
func appendAttributeToResponse(resp *Response, tag int8, attr map[string][]Attribute) {
|
2019-03-10 17:43:47 +00:00
|
|
|
switch tag {
|
|
|
|
case TagOperation:
|
|
|
|
resp.OperationAttributes = attr
|
|
|
|
case TagPrinter:
|
2020-03-05 15:29:31 +00:00
|
|
|
resp.PrinterAttributes = append(resp.PrinterAttributes, attr)
|
2019-03-10 17:43:47 +00:00
|
|
|
case TagJob:
|
2020-03-05 15:29:31 +00:00
|
|
|
resp.JobAttributes = append(resp.JobAttributes, attr)
|
2019-03-10 17:43:47 +00:00
|
|
|
}
|
|
|
|
}
|