go-ipp/response.go

339 lines
8.0 KiB
Go

/*
Copyright 2022 Dolysis Consulting Limited
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
For changes see the git log
*/
package ipp
import (
"bytes"
"encoding/binary"
"io"
)
// Attributes is a wrapper for a set of attributes
type Attributes map[string][]Attribute
// Response defines a ipp response
type Response struct {
ProtocolVersionMajor int8
ProtocolVersionMinor int8
StatusCode int16
RequestId int32
OperationAttributes Attributes
PrinterAttributes []Attributes
JobAttributes []Attributes
}
// 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
func (r *Response) CheckForErrors() error {
if r.StatusCode != StatusOk {
err := IPPError{
Status: r.StatusCode,
Message: "no status message returned",
}
if len(r.OperationAttributes["status-message"]) > 0 {
err.Message = r.OperationAttributes["status-message"][0].Value.(string)
}
return err
}
return nil
}
// NewResponse creates a new ipp response
func NewResponse(statusCode int16, reqID int32) *Response {
return &Response{
ProtocolVersionMajor: ProtocolVersionMajor,
ProtocolVersionMinor: ProtocolVersionMinor,
StatusCode: statusCode,
RequestId: reqID,
OperationAttributes: make(Attributes),
PrinterAttributes: make([]Attributes, 0),
JobAttributes: make([]Attributes, 0),
}
}
// Encode encodes the response to a byte slice
func (r *Response) Encode() ([]byte, error) {
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
}
if err := enc.Encode(AttributeCharset, Charset); err != nil {
return nil, err
}
if err := enc.Encode(AttributeNaturalLanguage, CharsetLanguage); err != nil {
return nil, err
}
if len(r.OperationAttributes) > 0 {
for name, attr := range r.OperationAttributes {
if len(attr) == 0 {
continue
}
values := make([]interface{}, len(attr))
for i, v := range attr {
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
}
values := make([]interface{}, len(attr))
for i, v := range attr {
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
}
values := make([]interface{}, len(attr))
for i, v := range attr {
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 err := binary.Write(buf, binary.BigEndian, TagEnd); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// ResponseDecoder reads and decodes a response from a stream
type ResponseDecoder struct {
reader io.Reader
}
// NewResponseDecoder returns a new decoder that reads from r
func NewResponseDecoder(r io.Reader) *ResponseDecoder {
return &ResponseDecoder{
reader: r,
}
}
// Decode decodes a ipp response into a response struct. additional data will be written to an io.Writer if data is not nil
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)
// 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 {
// 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 {
break
}
return nil, err
}
startByte := int8(startByteSlice[0])
// check if attributes are completed
if startByte == TagEnd {
break
}
if startByte == TagOperation {
if len(tempAttributes) > 0 && tag != TagCupsInvalid {
appendAttributeToResponse(resp, tag, tempAttributes)
tempAttributes = make(Attributes)
}
tag = TagOperation
tagSet = true
}
if startByte == TagJob {
if len(tempAttributes) > 0 && tag != TagCupsInvalid {
appendAttributeToResponse(resp, tag, tempAttributes)
tempAttributes = make(Attributes)
}
tag = TagJob
tagSet = true
}
if startByte == TagPrinter {
if len(tempAttributes) > 0 && tag != TagCupsInvalid {
appendAttributeToResponse(resp, tag, tempAttributes)
tempAttributes = make(Attributes)
}
tag = TagPrinter
tagSet = true
}
if tagSet {
if _, err := d.reader.Read(startByteSlice); err != nil {
return nil, err
}
startByte = int8(startByteSlice[0])
}
attrib, err := attribDecoder.Decode(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 {
appendAttributeToResponse(resp, tag, tempAttributes)
}
if data != nil {
if _, err := io.Copy(data, d.reader); err != nil {
return nil, err
}
}
return resp, nil
}
func appendAttributeToResponse(resp *Response, tag int8, attr map[string][]Attribute) {
switch tag {
case TagOperation:
resp.OperationAttributes = attr
case TagPrinter:
resp.PrinterAttributes = append(resp.PrinterAttributes, attr)
case TagJob:
resp.JobAttributes = append(resp.JobAttributes, attr)
}
}