/* 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" ) // Request defines a ipp request type Request struct { ProtocolVersionMajor int8 ProtocolVersionMinor int8 Operation int16 RequestId int32 OperationAttributes map[string]interface{} JobAttributes map[string]interface{} PrinterAttributes map[string]interface{} File io.Reader FileSize int } // NewRequest creates a new ipp request func NewRequest(op int16, reqID int32) *Request { return &Request{ ProtocolVersionMajor: ProtocolVersionMajor, ProtocolVersionMinor: ProtocolVersionMinor, Operation: op, RequestId: reqID, OperationAttributes: make(map[string]interface{}), JobAttributes: make(map[string]interface{}), PrinterAttributes: make(map[string]interface{}), File: nil, FileSize: -1, } } // Encode encodes the request to a byte slice func (r *Request) 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.Operation); 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 attr, value := range r.OperationAttributes { if err := enc.Encode(attr, value); err != nil { return nil, err } } } if len(r.JobAttributes) > 0 { if err := binary.Write(buf, binary.BigEndian, int8(TagJob)); err != nil { return nil, err } for attr, value := range r.JobAttributes { if err := enc.Encode(attr, value); err != nil { return nil, err } } } if len(r.PrinterAttributes) > 0 { if err := binary.Write(buf, binary.BigEndian, int8(TagPrinter)); err != nil { return nil, err } for attr, value := range r.PrinterAttributes { if err := enc.Encode(attr, value); err != nil { return nil, err } } } if err := binary.Write(buf, binary.BigEndian, int8(TagEnd)); err != nil { return nil, err } return buf.Bytes(), nil } // RequestDecoder reads and decodes a request from a stream type RequestDecoder struct { reader io.Reader } // NewRequestDecoder returns a new decoder that reads from r func NewRequestDecoder(r io.Reader) *RequestDecoder { return &RequestDecoder{ reader: r, } } // Decode decodes a ipp request into a request struct. additional data will be written to an io.Writer if data is not nil func (d *RequestDecoder) Decode(data io.Writer) (*Request, error) { req := new(Request) if err := binary.Read(d.reader, binary.BigEndian, &req.ProtocolVersionMajor); err != nil { return nil, err } if err := binary.Read(d.reader, binary.BigEndian, &req.ProtocolVersionMinor); err != nil { return nil, err } if err := binary.Read(d.reader, binary.BigEndian, &req.Operation); err != nil { return nil, err } if err := binary.Read(d.reader, binary.BigEndian, &req.RequestId); err != nil { return nil, err } startByteSlice := make([]byte, 1) tag := TagCupsInvalid previousAttributeName := "" 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 req.OperationAttributes == nil { req.OperationAttributes = make(map[string]interface{}) } tag = TagOperation tagSet = true } if startByte == TagJob { if req.JobAttributes == nil { req.JobAttributes = make(map[string]interface{}) } tag = TagJob tagSet = true } if startByte == TagPrinter { if req.PrinterAttributes == nil { req.PrinterAttributes = make(map[string]interface{}) } 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 != "" { appendAttributeToRequest(req, tag, attrib.Name, attrib.Value) previousAttributeName = attrib.Name } else { appendAttributeToRequest(req, tag, previousAttributeName, attrib.Value) } tagSet = false } if data != nil { if _, err := io.Copy(data, d.reader); err != nil { return nil, err } } return req, nil } func appendAttributeToRequest(req *Request, tag int8, name string, value interface{}) { switch tag { case TagOperation: req.OperationAttributes[name] = value case TagPrinter: req.PrinterAttributes[name] = value case TagJob: req.JobAttributes[name] = value } }