Files
ippserver/packages/ipp/messages.go

349 lines
9.6 KiB
Go

//Package ipp provides functonality to handle ipp messages
//go:generate stringer -type jobState -type printerState
package ipp
import (
"bufio"
"encoding/binary"
"errors"
"fmt"
"io"
log "github.com/sirupsen/logrus"
)
// References
// https://tools.ietf.org/html/rfc8010
// https://tools.ietf.org/html/rfc8011
// ErrNilAttribute is returned when an attempt to use nil as a attribute
var ErrNilAttribute = errors.New("can not add use nil as attribute")
// 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
)
// jobstate defined in rfc8011 ch 5.3.7
type jobState uint16
const (
pending jobState = 3
pendingHeld jobState = 4
processing jobState = 5
processingStoppped jobState = 6
cancelled jobState = 7
aborted jobState = 8
completed jobState = 9
)
type statusCode uint16
// status code defenitions
const (
SuccessfulOk statusCode = 0x0000
SuccessfulOkIgnoredOrSubstitutedAttributes statusCode = 0x0001
SuccessfulOkConflictingAttributes statusCode = 0x0002
ClientErrorBadRequest statusCode = 0x0400
ServerErrorServiceUnavailable statusCode = 0x0502
)
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
unsupported []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())
}
s = s + " Unsupported" + "\n"
for _, a := range a.unsupported {
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)
case unsupportedAttributes:
a.unsupported = append(a.unsupported, attr)
default:
log.Errorf("Unknown attribute group %v", 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 unsupportedValueTag:
attr := NewUnsupportedValue()
attr.unmarshal(bytestream)
a.addAttribute(currentAttributeGroup, attr)
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 unsupportedAttributes:
log.Debug("Start unsupported attributes")
currentAttributeGroup = unsupportedAttributes
case resolutionValueTag:
res := NewSetOfResolution("")
res.unmarshal(bytestream)
a.addAttribute(currentAttributeGroup, res)
log.Debugf("Resolution %v", res)
default:
log.Errorf("Unsupported tag %v (%x)", t, uint8(t))
}
}
}