go-ipp/response.go

341 lines
8.0 KiB
Go
Raw Permalink Normal View History

2022-11-17 11:02:26 +00:00
/*
2022-11-18 09:02:04 +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
}
}