package ipp import ( "encoding/binary" "fmt" "io" log "github.com/sirupsen/logrus" ) // References // https://tools.ietf.org/html/rfc8010 // https://tools.ietf.org/html/rfc8011 // Defined value tags // from rfc8010 type tag uint8 const ( // attribute group tags beginAttribute tag = 0x00 operationAttributes tag = 0x01 jobAttributes tag = 0x02 endOfAttributes tag = 0x03 printerAttributes tag = 0x04 unsupportedAttributes tag = 0x05 // Out of band unsupportedValueTag tag = 0x10 unknownValueTag tag = 0x12 noValueValueTag tag = 0x13 // Integer values integerValueTag tag = 0x21 booleanValueTag tag = 0x22 enumValueTag tag = 0x23 // octetString Tags octetStringValueTag tag = 0x30 dateTimeValueTag tag = 0x31 resolutionValueTag tag = 0x32 rangeOfIntegerValueTag tag = 0x33 begCollectionValueTag tag = 0x34 textWithLanguageValueTag tag = 0x35 nameWithLanguageValueTag tag = 0x36 endCollectionValueTag tag = 0x37 // Character string values textWithoutLanguagageValueTag tag = 0x41 nameWithoutLanguagageValueTag tag = 0x42 keyWordValueTag tag = 0x44 uriValueTag tag = 0x45 uriSchemeValueTag tag = 0x46 charsetValueTag tag = 0x47 naturalLanguagageValueTag tag = 0x48 mimeMediaTypeValueTag tag = 0x49 memberAttrNameValueTag tag = 0x4a ) // Operation-id, defined in rfc8011 type operationId uint16 const ( PrintJob operationId = 0x0002 PrintURI operationId = 0x0003 ValidateJob operationId = 0x0004 CreateJob operationId = 0x0005 SendDocument operationId = 0x0006 SendURI operationId = 0x0007 CancelJob operationId = 0x0008 GetJobAttributes operationId = 0x0009 GetJobs operationId = 0x000a GetPrinterAttributes operationId = 0x000b HoldJob operationId = 0x000c ReleaseJob operationId = 0x000d RestartJob operationId = 0x000e PausePrinter operationId = 0x0010 ResumePrinter operationId = 0x0011 PurgeJobs operationId = 0x0012 ) type statusCode uint16 const ( SuccessfulOk statusCode = 0x0000 ClientErrorBadRequest statusCode = 0x0400 ) type versionNumber uint16 func (v versionNumber) String() string { vn := uint16(v) return fmt.Sprintf("%x.%x", vn&0xff00>>8, vn&0x00ff) } type ippRequestHeader struct { versionNumber versionNumber operationId operationId requestId uint32 } func (h *ippRequestHeader) marshal(byteStream io.Reader) { binary.Read(byteStream, binary.BigEndian, &h.versionNumber) binary.Read(byteStream, binary.BigEndian, &h.operationId) binary.Read(byteStream, binary.BigEndian, &h.requestId) } func (h ippRequestHeader) String() string { return fmt.Sprintf("Version number: %v Operation Id: %v Request Id: %v", h.versionNumber, h.operationId, h.requestId) } type Attribute interface { valueTag() tag unmarshal(io.Reader) marshal() []byte size() int } type Request struct { header ippRequestHeader operationAttributes map[string]Attribute jobAttributes map[string]Attribute printerAttributes map[string]Attribute } func NewRequest() *Request { r := new(Request) r.operationAttributes = make(map[string]Attribute) r.jobAttributes = make(map[string]Attribute) r.printerAttributes = make(map[string]Attribute) return r } func (r Request) String() string { s := r.header.String() + "\n" + " OperationAttributes" + "\n" for _, a := range r.operationAttributes { s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag()) } return s } // log.Info(r.Header) // log.Info(r.ContentLength) //body := make([]byte, r.ContentLength) //body.Read(body) func (r *Request) UnMarshal(body io.Reader) { r.header.marshal(body) log.Infof("Header %v", r.header) var tag tag err := binary.Read(body, binary.BigEndian, &tag) if err != nil { log.Error(err.Error()) } log.Infof("got tag - %v", tag) if tag == operationAttributes { nextoperationattr: for tag != endOfAttributes { var lastKeyword *keyWord err = binary.Read(body, binary.BigEndian, &tag) if err != nil { log.Error(err.Error()) } log.Infof("Value tag - %v", tag) switch tag { case endOfAttributes: err = binary.Read(body, binary.BigEndian, &tag) if err == io.EOF { // No more data return } if err != nil { log.Error(err.Error()) } log.Infof("got tag - %v", tag) err = binary.Read(body, binary.BigEndian, &tag) if err != nil { log.Error(err.Error()) } log.Infof("got tag - %v", tag) break nextoperationattr case charsetValueTag: c := NewCharSetValue("", "") c.unmarshal(body) r.operationAttributes[c.name] = c log.Infof("%v %v", c.name, c.value) case uriValueTag: u := newUriValue("", "") u.unmarshal(body) r.operationAttributes[u.name] = u log.Infof("%v %v", u.name, u.value) case naturalLanguagageValueTag: n := newNaturalLanguagage("", "") n.unmarshal(body) r.operationAttributes[n.name] = n log.Infof("%v %v", n.name, n.value) case keyWordValueTag: k := newKeyWord() k.unmarshal(body) r.operationAttributes[k.name] = k if k.name == "" { lastKeyword.addValue(k.values[0]) } else { lastKeyword = k } default: log.Errorf("Unsupported tag %v", tag) } } log.Infof("Value tag %v", tag) } else { log.Error("unexpected tag") // TODO Return something sensible here } } func (r *Request) RequestId() uint32 { return r.header.requestId } func unmarshalSingleValue(byteStream io.Reader) (string, string) { var length uint16 binary.Read(byteStream, binary.BigEndian, &length) //log.Infof("Length %v", length) attributeName := make([]byte, length) if length > 0 { binary.Read(byteStream, binary.BigEndian, attributeName) //log.Infof("Valuename %v", string(attributeName)) } binary.Read(byteStream, binary.BigEndian, &length) //log.Infof("Length %v", length) attributeValue := make([]byte, length) binary.Read(byteStream, binary.BigEndian, attributeValue) //log.Infof("Value %v", string(attributeValue)) return string(attributeName), string(attributeValue) } func marshalNameValue(name, value string, b []byte) { p := 0 binary.BigEndian.PutUint16(b[p:p+2], uint16(len(name))) p += 2 copy(b[p:], []byte(name)) p += len(name) binary.BigEndian.PutUint16(b[p:p+2], uint16(len(value))) p += 2 copy(b[p:], []byte(value)) }