go-ipp/response.go

339 lines
8 KiB
Go
Raw Normal View History

2022-11-17 11:02:26 +00:00
/*
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
*/
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 {
if r.StatusCode != StatusOk {
err := IPPError{
Status: r.StatusCode,
Message: "no status message returned",
2019-03-10 17:43:47 +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
}
if err := enc.Encode(AttributeCharset, Charset); err != nil {
2020-03-05 15:29:31 +00:00
return nil, err
}
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
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 {
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
if startByte == TagEnd {
2019-03-10 17:43:47 +00:00
break
}
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
}
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
}
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 {
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
}
}