go-ipp/response.go
Fabian d0d4987b76 - changed the get jobs method to fetch jobs from printer, classes or both
- the response decoder now need a writer for additional response data, for example if you fetch documents from the server
2019-12-26 16:46:08 +01:00

182 lines
3.8 KiB
Go

package ipp
import (
"encoding/binary"
"errors"
"fmt"
"io"
)
type Attributes map[string][]Attribute
type Response struct {
ProtocolVersionMajor uint8
ProtocolVersionMinor uint8
StatusCode uint16
RequestId int32
OperationAttributes Attributes
Printers []Attributes
Jobs []Attributes
data io.Writer
}
func (r *Response) CheckForErrors() error {
if r.StatusCode != 0 {
if len(r.OperationAttributes["status-message"]) == 0 {
return fmt.Errorf("ipp server return error code %d but no status message", r.StatusCode)
}
return errors.New(r.OperationAttributes["status-message"][0].Value.(string))
}
return nil
}
type ResponseDecoder struct {
reader io.Reader
}
func NewResponseDecoder(r io.Reader) *ResponseDecoder {
return &ResponseDecoder{
reader: r,
}
}
func (d *ResponseDecoder) Decode(data io.Writer) (*Response, error) {
/*
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)
*/
resp := new(Response)
resp.data = data
// wrap the reader so we have more functionality
//reader := bufio.NewReader(d.reader)
if err := binary.Read(d.reader, binary.BigEndian, &resp.ProtocolVersionMajor); err != nil {
return nil, err
}
if err := binary.Read(d.reader, binary.BigEndian, &resp.ProtocolVersionMinor); err != nil {
return nil, err
}
if err := binary.Read(d.reader, binary.BigEndian, &resp.StatusCode); err != nil {
return nil, err
}
if err := binary.Read(d.reader, binary.BigEndian, &resp.RequestId); err != nil {
return nil, err
}
startByteSlice := make([]byte, 1)
tag := TagCupsInvalid
previousAttributeName := ""
tempAttributes := make(Attributes)
tagSet := false
attribDecoder := NewAttributeDecoder(d.reader)
// decode attribute buffer
for {
if _, err := d.reader.Read(startByteSlice); err != nil {
return nil, err
}
startByte := startByteSlice[0]
// check if attributes are completed
if startByte == TagEnd {
break
}
if startByte == TagOperation {
if len(tempAttributes) > 0 && tag != TagCupsInvalid {
appendAttribute(resp, tag, tempAttributes)
tempAttributes = make(Attributes)
}
tag = TagOperation
tagSet = true
}
if startByte == TagJob {
if len(tempAttributes) > 0 && tag != TagCupsInvalid {
appendAttribute(resp, tag, tempAttributes)
tempAttributes = make(Attributes)
}
tag = TagJob
tagSet = true
}
if startByte == TagPrinter {
if len(tempAttributes) > 0 && tag != TagCupsInvalid {
appendAttribute(resp, tag, tempAttributes)
tempAttributes = make(Attributes)
}
tag = TagPrinter
tagSet = true
}
if tagSet {
if _, err := d.reader.Read(startByteSlice); err != nil {
return nil, err
}
startByte = startByteSlice[0]
}
attrib, err := attribDecoder.Decode(Tag(startByte))
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)
}
tagSet = false
}
if len(tempAttributes) > 0 && tag != TagCupsInvalid {
appendAttribute(resp, tag, tempAttributes)
}
if resp.data != nil {
if _, err := io.Copy(resp.data, d.reader); err != nil {
return nil, err
}
}
if resp.StatusCode != 0 {
return resp, errors.New(resp.OperationAttributes["status-message"][0].Value.(string))
}
return resp, nil
}
func appendAttribute(resp *Response, tag Tag, attr map[string][]Attribute) {
switch tag {
case TagOperation:
resp.OperationAttributes = attr
case TagPrinter:
resp.Printers = append(resp.Printers, attr)
case TagJob:
resp.Jobs = append(resp.Jobs, attr)
}
}