Simple roundtrip from ipptool works.

This commit is contained in:
2020-11-01 20:05:53 +01:00
parent 83f35d045c
commit cadcedf43c
16 changed files with 744 additions and 152 deletions

View File

@@ -0,0 +1,5 @@
package ipp
type charsetAttribute struct {
}

View File

@@ -0,0 +1,47 @@
package ipp
import (
"io"
)
type charSetValue struct {
name string
value string
}
func NewCharSetValue(name string, value string) *charSetValue {
c := new(charSetValue)
c.name = name
c.value = value
return c
}
func (c charSetValue) String() string {
return c.name + ":" + c.value
}
func (c *charSetValue) valueTag() tag {
return charsetValueTag
}
func (c *charSetValue) unmarshal(byteStream io.Reader) {
c.name, c.value = unmarshalSingleValue(byteStream)
}
func (c *charSetValue) marshal() []byte {
l := 5 + len(c.name) + len(c.value)
b := make([]byte, l, l)
b[0] = byte(charsetValueTag)
marshalNameValue(c.name, c.value, b[1:])
return b
}
func (c *charSetValue) marshalInto([]byte) int {
return 0
}
func (c *charSetValue) size() int {
l := 1 + 4 // The attribute tag + 2 lengths
l += len(c.name)
l += len(c.value)
return l
}

45
packages/ipp/keyword.go Normal file
View File

@@ -0,0 +1,45 @@
package ipp
import "io"
type keyWord struct {
name string
values []string
}
func newKeyWord() *keyWord {
k := new(keyWord)
return k
}
func (k *keyWord) string() string {
return "a uriValue"
}
func (k *keyWord) valueTag() tag {
return keyWordValueTag
}
func (k *keyWord) unmarshal(byteStream io.Reader) {
}
func (k *keyWord) marshal() []byte {
return []byte{}
}
func (k *keyWord) addValue(v string) {
k.values = append(k.values, v)
}
func (k *keyWord) size() int {
l := 1 + 2 // The value tag (0x44) + name-length field (2 bytes)
l += len(k.name)
l += 2 // value-length field (2 bytes)
l += len(k.values[0])
// Add all additional values
for _, v := range k.values[1:] {
l += 1 + 4 // The value tag (0x44) + 2 length fields (2 bytes)
l += len(v)
}
return l
}

252
packages/ipp/messages.go Normal file
View File

@@ -0,0 +1,252 @@
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))
}

View File

@@ -0,0 +1,43 @@
package ipp
import (
"io"
)
type naturalLanguagage struct {
name string
value string
}
func newNaturalLanguagage(name, value string) *naturalLanguagage {
c := new(naturalLanguagage)
c.name = name
c.value = value
return c
}
func (c naturalLanguagage) String() string {
return c.name + ":" + c.value
}
func (c *naturalLanguagage) valueTag() tag {
return naturalLanguagageValueTag
}
func (c *naturalLanguagage) unmarshal(byteStream io.Reader) {
c.name, c.value = unmarshalSingleValue(byteStream)
}
func (c *naturalLanguagage) marshal() []byte {
l := 5 + len(c.name) + len(c.value)
b := make([]byte, l, l)
b[0] = byte(naturalLanguagageValueTag)
marshalNameValue(c.name, c.value, b[1:])
return b
}
func (c *naturalLanguagage) size() int {
l := 1 + 4 // The attribute tag + 2 lengths
l += len(c.name)
l += len(c.value)
return l
}

View File

@@ -0,0 +1,50 @@
// Code generated by "stringer -type operationId"; DO NOT EDIT.
package ipp
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[PrintJob-2]
_ = x[PrintURI-3]
_ = x[ValidateJob-4]
_ = x[CreateJob-5]
_ = x[SendDocument-6]
_ = x[SendURI-7]
_ = x[CancelJob-8]
_ = x[GetJobAttributes-9]
_ = x[GetJobs-10]
_ = x[GetPrinterAttributes-11]
_ = x[HoldJob-12]
_ = x[ReleaseJob-13]
_ = x[RestartJob-14]
_ = x[PausePrinter-16]
_ = x[ResumePrinter-17]
_ = x[PurgeJobs-18]
}
const (
_operationId_name_0 = "PrintJobPrintURIValidateJobCreateJobSendDocumentSendURICancelJobGetJobAttributesGetJobsGetPrinterAttributesHoldJobReleaseJobRestartJob"
_operationId_name_1 = "PausePrinterResumePrinterPurgeJobs"
)
var (
_operationId_index_0 = [...]uint8{0, 8, 16, 27, 36, 48, 55, 64, 80, 87, 107, 114, 124, 134}
_operationId_index_1 = [...]uint8{0, 12, 25, 34}
)
func (i operationId) String() string {
switch {
case 2 <= i && i <= 14:
i -= 2
return _operationId_name_0[_operationId_index_0[i]:_operationId_index_0[i+1]]
case 16 <= i && i <= 18:
i -= 16
return _operationId_name_1[_operationId_index_1[i]:_operationId_index_1[i+1]]
default:
return "operationId(" + strconv.FormatInt(int64(i), 10) + ")"
}
}

91
packages/ipp/response.go Normal file
View File

@@ -0,0 +1,91 @@
package ipp
import "encoding/binary"
type ippResponseHeader struct {
versionNumber versionNumber
statusCode statusCode
requestId uint32
}
func (h *ippResponseHeader) marshal() []byte {
a := make([]byte, 8, 8)
binary.BigEndian.PutUint16(a[0:2], uint16(h.versionNumber))
binary.BigEndian.PutUint16(a[2:4], uint16(h.statusCode))
binary.BigEndian.PutUint32(a[4:8], h.requestId)
return a
}
type Response struct {
header ippResponseHeader
operationAttributes []Attribute
jobAttributes []Attribute
printerAttributes []Attribute
}
func NewResponse(code statusCode, requestId uint32) *Response {
r := new(Response)
r.header.versionNumber = 0x0101
r.header.requestId = requestId
r.header.statusCode = code
r.operationAttributes = make([]Attribute, 0)
r.printerAttributes = make([]Attribute, 0)
r.jobAttributes = make([]Attribute, 0)
return r
}
// func (r *Response) length() int {
// l := 8 + 1
// if len(r.jobAttributes) > 0 {
// l += 1 //
// for _, e := range r.jobAttributes {
// l += e.length()
// }
// }
// for _, e := range r.operationAttributes {
// l += e.length()
// }
// for _, e := range r.printerAttributes {
// l += e.length()
// }
// }
func (r *Response) Marshal() []byte {
a := make([]byte, 0, 20)
a = append(a, r.header.marshal()...)
if len(r.operationAttributes) > 0 {
a = append(a, byte(operationAttributes))
for _, e := range r.operationAttributes {
a = append(a, e.marshal()...)
}
}
if len(r.jobAttributes) > 0 {
a = append(a, byte(jobAttributes))
for _, e := range r.jobAttributes {
a = append(a, e.marshal()...)
}
}
if len(r.printerAttributes) > 0 {
a = append(a, byte(printerAttributes))
for _, e := range r.printerAttributes {
a = append(a, e.marshal()...)
}
}
a = append(a, byte(endOfAttributes))
return a
}
func (r *Response) AddPrinterAttribute(a Attribute) {
r.printerAttributes = append(r.printerAttributes, a)
}
func (r *Response) AddOperatonAttribute(a Attribute) {
r.operationAttributes = append(r.operationAttributes, a)
}
func (r *Response) AddJobAttribute(a Attribute) {
r.jobAttributes = append(r.jobAttributes, a)
}

View File

@@ -0,0 +1,29 @@
package ipp
import (
"fmt"
"testing"
)
func TestMarshalResponseHeader(T *testing.T) {
var h ippResponseHeader
h.versionNumber = 0x0101
h.statusCode = SuccessfulOk
h.requestId = 0xdeadbeef
b := h.marshal()
fmt.Printf("% x\n", b)
}
func TestMarshalCompleteResponse(T *testing.T) {
r := NewResponse(SuccessfulOk, 0xdeadbeef)
a := NewCharSetValue("attributes-charset", "UTF-8")
r.AddPrinterAttribute(a)
b := r.Marshal()
fmt.Printf("% x\n", b)
}

View File

@@ -0,0 +1,85 @@
// Code generated by "stringer -type tag"; DO NOT EDIT.
package ipp
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[beginAttribute-0]
_ = x[operationAttributes-1]
_ = x[jobAttributes-2]
_ = x[endOfAttributes-3]
_ = x[printerAttributes-4]
_ = x[unsupportedAttributes-5]
_ = x[unsupportedValueTag-16]
_ = x[unknownValueTag-18]
_ = x[noValueValueTag-19]
_ = x[integerValueTag-33]
_ = x[booleanValueTag-34]
_ = x[enumValueTag-35]
_ = x[octetStringValueTag-48]
_ = x[dateTimeValueTag-49]
_ = x[resolutionValueTag-50]
_ = x[rangeOfIntegerValueTag-51]
_ = x[begCollectionValueTag-52]
_ = x[textWithLanguageValueTag-53]
_ = x[nameWithLanguageValueTag-54]
_ = x[endCollectionValueTag-55]
_ = x[textWithoutLanguagageValueTag-65]
_ = x[nameWithoutLanguagageValueTag-66]
_ = x[keyWordValueTag-68]
_ = x[uriValueTag-69]
_ = x[uriSchemeValueTag-70]
_ = x[charsetValueTag-71]
_ = x[naturalLanguagageValueTag-72]
_ = x[mimeMediaTypeValueTag-73]
_ = x[memberAttrNameValueTag-74]
}
const (
_tag_name_0 = "beginAttributeoperationAttributesjobAttributesendOfAttributesprinterAttributesunsupportedAttributes"
_tag_name_1 = "unsupportedValueTag"
_tag_name_2 = "unknownValueTagnoValueValueTag"
_tag_name_3 = "integerValueTagbooleanValueTagenumValueTag"
_tag_name_4 = "octetStringValueTagdateTimeValueTagresolutionValueTagrangeOfIntegerValueTagbegCollectionValueTagtextWithLanguageValueTagnameWithLanguageValueTagendCollectionValueTag"
_tag_name_5 = "textWithoutLanguagageValueTagnameWithoutLanguagageValueTag"
_tag_name_6 = "keywordValueTaguriValueTaguriSchemeValueTagcharsetValueTagnaturalLanguagageValueTagmimeMediaTypeValueTagmemberAttrNameValueTag"
)
var (
_tag_index_0 = [...]uint8{0, 14, 33, 46, 61, 78, 99}
_tag_index_2 = [...]uint8{0, 15, 30}
_tag_index_3 = [...]uint8{0, 15, 30, 42}
_tag_index_4 = [...]uint8{0, 19, 35, 53, 75, 96, 120, 144, 165}
_tag_index_5 = [...]uint8{0, 29, 58}
_tag_index_6 = [...]uint8{0, 15, 26, 43, 58, 83, 104, 126}
)
func (i tag) String() string {
switch {
case i <= 5:
return _tag_name_0[_tag_index_0[i]:_tag_index_0[i+1]]
case i == 16:
return _tag_name_1
case 18 <= i && i <= 19:
i -= 18
return _tag_name_2[_tag_index_2[i]:_tag_index_2[i+1]]
case 33 <= i && i <= 35:
i -= 33
return _tag_name_3[_tag_index_3[i]:_tag_index_3[i+1]]
case 48 <= i && i <= 55:
i -= 48
return _tag_name_4[_tag_index_4[i]:_tag_index_4[i+1]]
case 65 <= i && i <= 66:
i -= 65
return _tag_name_5[_tag_index_5[i]:_tag_index_5[i+1]]
case 68 <= i && i <= 74:
i -= 68
return _tag_name_6[_tag_index_6[i]:_tag_index_6[i+1]]
default:
return "tag(" + strconv.FormatInt(int64(i), 10) + ")"
}
}

42
packages/ipp/urivalue.go Normal file
View File

@@ -0,0 +1,42 @@
package ipp
import (
"io"
)
type uriValue struct {
name string
value string
}
func newUriValue(name, value string) *uriValue {
u := new(uriValue)
u.name = name
u.value = value
return u
}
func (u uriValue) String() string {
return u.name + ":" + u.value
}
func (u *uriValue) valueTag() tag {
return uriValueTag
}
func (u *uriValue) unmarshal(byteStream io.Reader) {
u.name, u.value = unmarshalSingleValue(byteStream)
}
func (u *uriValue) marshal() []byte {
res := make([]byte, u.size())
res[0] = byte(uriValueTag)
marshalNameValue(u.name, u.value, res[1:])
return res
}
func (u *uriValue) size() int {
l := 1 + 4 // The attribute tag + 2 lengths
l += len(u.name)
l += len(u.value)
return l
}

View File

@@ -0,0 +1,17 @@
package ipp
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestMarshalUriValue(T *testing.T) {
var u uriValue
u.name = "foo"
u.value = "bar"
b := u.marshal()
assert.Len(T, b, 11)
fmt.Printf("% x\n", b)
}