//Package ipp provides functonality to handle ipp messages package ipp import ( "bufio" "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 textWithoutLanguageValueTag tag = 0x41 nameWithoutLanguageValueTag tag = 0x42 keyWordValueTag tag = 0x44 uriValueTag tag = 0x45 uriSchemeValueTag tag = 0x46 charsetValueTag tag = 0x47 naturalLanguageValueTag tag = 0x48 mimeMediaTypeValueTag tag = 0x49 memberAttrNameValueTag tag = 0x4a ) // OperationID is 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 printerState int32 // printerstate defenitions const ( Idle printerState = 3 Processing printerState = 4 Stopped printerState = 5 ) type statusCode uint16 // status code defenitions 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) } func unmarshalSingleAttribute(byteStream io.Reader) (string, []byte) { var length uint16 binary.Read(byteStream, binary.BigEndian, &length) attributeName := make([]byte, length) if length > 0 { binary.Read(byteStream, binary.BigEndian, attributeName) } binary.Read(byteStream, binary.BigEndian, &length) attributeValue := make([]byte, length) binary.Read(byteStream, binary.BigEndian, attributeValue) return string(attributeName), attributeValue } func unmarshalSingleValue(byteStream io.Reader) (string, string) { var length uint16 binary.Read(byteStream, binary.BigEndian, &length) attributeName := make([]byte, length) if length > 0 { binary.Read(byteStream, binary.BigEndian, attributeName) } binary.Read(byteStream, binary.BigEndian, &length) attributeValue := make([]byte, length) binary.Read(byteStream, binary.BigEndian, 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)) } type Attribute interface { Name() string valueTag() tag marshal() []byte //size() int } type attributes struct { operation []Attribute printer []Attribute job []Attribute } func (a *attributes) String() string { s := " OperationAttributes" + "\n" for _, a := range a.operation { s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag()) } s = s + " PrinterAttributes" + "\n" for _, a := range a.printer { s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag()) } s = s + " JobAttributes" + "\n" for _, a := range a.job { s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag()) } return s } func (a *attributes) addAttribute(group tag, attr Attribute) { switch group { case operationAttributes: a.operation = append(a.operation, attr) case jobAttributes: a.job = append(a.job, attr) case printerAttributes: a.printer = append(a.printer, attr) default: log.Error("Unknown attribute group") } } func UnMarshalAttributes(bytestream *bufio.Reader) *attributes { a := new(attributes) var t tag err := binary.Read(bytestream, binary.BigEndian, &t) if err != nil { log.Error(err.Error()) } log.Debugf("got tag - %v", t) if t != operationAttributes && t != jobAttributes && t != printerAttributes { log.Errorf("Unknown attribute group tag %v", t) return nil } currentAttributeGroup := t var lastAddValuer AddValuer for { err = binary.Read(bytestream, binary.BigEndian, &t) if err != nil { log.Fatal("End of input before end of attributes tag (%v)", err.Error()) } log.Debugf("Value tag - %v", t) switch t { case endOfAttributes: return a case charsetValueTag: c := NewCharSetValue("", "") c.unmarshal(bytestream) a.addAttribute(currentAttributeGroup, c) log.Debugf("%v %v", c.name, c.value) case booleanValueTag: na := NewBoolean("", false) na.unmarshal(bytestream) a.addAttribute(currentAttributeGroup, na) case uriValueTag: u := NewURIValue("", "") u.unmarshal(bytestream) a.addAttribute(currentAttributeGroup, u) log.Debugf("%v %v", u.name, u.value) case naturalLanguageValueTag: n := NewNaturalLanguage("", "") n.unmarshal(bytestream) a.addAttribute(currentAttributeGroup, n) log.Debugf("%v %v", n.name, n.value) case keyWordValueTag: name, value := unmarshalSingleValue(bytestream) if name == "" { lastAddValuer.addValue(value) } else { k := NewKeyWord(name, value) a.addAttribute(currentAttributeGroup, k) lastAddValuer = k } log.Debugf("%v : %v", name, value) case nameWithoutLanguageValueTag: n := NewNameWithoutLanguage("", "") n.unmarshal(bytestream) a.addAttribute(currentAttributeGroup, n) log.Debugf("%v %v", n.name, n.value) case textWithoutLanguageValueTag: attr := NewtextWithoutLanguage("", "") attr.unmarshal(bytestream) a.addAttribute(currentAttributeGroup, attr) log.Debugf("%v %v", attr.name, attr.value) case mimeMediaTypeValueTag: name, value := unmarshalSingleValue(bytestream) if name == "" { lastAddValuer.addValue(value) } else { m := NewMimeMediaType(name, value) a.addAttribute(currentAttributeGroup, m) lastAddValuer = m } log.Debugf("%v : %v", name, value) case integerValueTag: name, value := unmarshalSingleInteger(bytestream) if name == "" { lastAddValuer.addValue(value) } else { i := NewInteger(name, value) a.addAttribute(currentAttributeGroup, i) lastAddValuer = i } log.Debugf("%v : %v", name, value) case rangeOfIntegerValueTag: name, value := unmarshalSingleRangeOfInteger(bytestream) if name == "" { lastAddValuer.addValue(value) } else { r := NewRangeOfInteger(name, value) a.addAttribute(currentAttributeGroup, r) lastAddValuer = r } log.Debugf("%v : %v", name, value) case enumValueTag: name, value := unmarshalSingleInteger(bytestream) if name == "" { lastAddValuer.addValue(value) } else { e := NewEnum(name, value) a.addAttribute(currentAttributeGroup, e) lastAddValuer = e } log.Debugf("%v : %v", name, value) case begCollectionValueTag: // For now just consume the collection consumeCollection(bytestream) case jobAttributes: log.Debug("Start job attributes") currentAttributeGroup = jobAttributes case printerAttributes: log.Debug("Start printer attributes") currentAttributeGroup = printerAttributes case operationAttributes: log.Debug("Start operation attributes") currentAttributeGroup = operationAttributes case resolutionValueTag: res := NewResolution("", 0, 0) res.unmarshal(bytestream) a.addAttribute(currentAttributeGroup, res) log.Debugf("Resolution %v", res) default: log.Errorf("Unsupported tag %v (%x)", t, uint8(t)) } } }