2-add-keyword-support (#6)
More development. More types. Fixed attribute groups in requests. Started on client. Saving data to file. More types. Printing from chromeos works a little bit. More types. Spelling corrections. WIP: Fix keyword handling Move request to a separate file and add test. Co-authored-by: Henrik Sölver <henrik.solver@gmail.com> Reviewed-on: #6 Co-Authored-By: henrik <henrik.solver@gmail.com> Co-Committed-By: henrik <henrik.solver@gmail.com>
This commit was merged in pull request #6.
This commit is contained in:
52
packages/ipp/enum.go
Normal file
52
packages/ipp/enum.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package ipp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type enum struct {
|
||||||
|
name string
|
||||||
|
value int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEnum(name string, value int32) *enum {
|
||||||
|
e := new(enum)
|
||||||
|
e.name = name
|
||||||
|
e.value = value
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e enum) String() string {
|
||||||
|
return e.name + ":" + fmt.Sprint(e.value)
|
||||||
|
}
|
||||||
|
func (e *enum) valueTag() tag {
|
||||||
|
return enumValueTag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *enum) unmarshal(byteStream io.Reader) {
|
||||||
|
log.Warn("Unmarshal of enum is not implemented yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *enum) marshal() []byte {
|
||||||
|
l := 3 + len(e.name) + 6
|
||||||
|
b := make([]byte, 0, l)
|
||||||
|
buf := bytes.NewBuffer(b)
|
||||||
|
buf.WriteByte(byte(enumValueTag))
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(len(e.name)))
|
||||||
|
buf.WriteString(e.name)
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(4))
|
||||||
|
binary.Write(buf, binary.BigEndian, e.value)
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *enum) size() int {
|
||||||
|
l := 1 + 4 // The attribute tag + 2 lengths
|
||||||
|
l += len(e.name)
|
||||||
|
l += 4
|
||||||
|
return l
|
||||||
|
}
|
||||||
@@ -1,45 +1,33 @@
|
|||||||
package ipp
|
package ipp
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
type keyWord struct {
|
type keyWord struct {
|
||||||
name string
|
sos *setOfStrings
|
||||||
values []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newKeyWord() *keyWord {
|
func NewKeyWord(name string, values ...string) *keyWord {
|
||||||
|
|
||||||
k := new(keyWord)
|
k := new(keyWord)
|
||||||
|
k.sos = NewSetOfStrings(name, keyWordValueTag, values)
|
||||||
return k
|
return k
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *keyWord) string() string {
|
func (k keyWord) String() string {
|
||||||
return "a uriValue"
|
return k.sos.String()
|
||||||
}
|
|
||||||
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 {
|
func (k *keyWord) size() int {
|
||||||
l := 1 + 2 // The value tag (0x44) + name-length field (2 bytes)
|
return k.sos.size()
|
||||||
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
|
|
||||||
|
func (k *keyWord) valueTag() tag {
|
||||||
|
return k.sos.valueTag()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k *keyWord) marshal() []byte {
|
||||||
|
return k.sos.marshal()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *keyWord) addValue(v string) {
|
||||||
|
k.sos.AddValue(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
36
packages/ipp/keyword_test.go
Normal file
36
packages/ipp/keyword_test.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package ipp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// func TestUnmarshalSimpleKeyword(T *testing.T) {
|
||||||
|
// testdata := []byte{
|
||||||
|
// 0x00, 0x14,
|
||||||
|
// 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x2d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73,
|
||||||
|
// 0x00, 0x16,
|
||||||
|
// 0x70, 0x72, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x2d, 0x6d, 0x61, 0x6b, 0x65, 0x2d, 0x61, 0x6e, 0x64, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x6c,
|
||||||
|
// }
|
||||||
|
// buf := bytes.NewBuffer(testdata)
|
||||||
|
// var k keyWord
|
||||||
|
// k.unmarshal(buf)
|
||||||
|
// assert.Equal(T, k.name, "requested-attributes")
|
||||||
|
// assert.Equal(T, k.values[0], "printer-make-and-model")
|
||||||
|
// }
|
||||||
|
|
||||||
|
func TestMarshalSimpleKeyword(T *testing.T) {
|
||||||
|
testdata := []byte{
|
||||||
|
0x44, 0x00, 0x14,
|
||||||
|
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x2d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73,
|
||||||
|
0x00, 0x16,
|
||||||
|
0x70, 0x72, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x2d, 0x6d, 0x61, 0x6b, 0x65, 0x2d, 0x61, 0x6e, 0x64, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x6c,
|
||||||
|
}
|
||||||
|
|
||||||
|
k := NewKeyWord("requested-attributes")
|
||||||
|
k.addValue("printer-make-and-model")
|
||||||
|
m := k.marshal()
|
||||||
|
assert.Equal(T, testdata, m, "Should be equal")
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,8 +4,6 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// References
|
// References
|
||||||
@@ -45,37 +43,45 @@ const (
|
|||||||
endCollectionValueTag tag = 0x37
|
endCollectionValueTag tag = 0x37
|
||||||
|
|
||||||
// Character string values
|
// Character string values
|
||||||
textWithoutLanguagageValueTag tag = 0x41
|
textWithoutLanguageValueTag tag = 0x41
|
||||||
nameWithoutLanguagageValueTag tag = 0x42
|
nameWithoutLanguageValueTag tag = 0x42
|
||||||
keyWordValueTag tag = 0x44
|
keyWordValueTag tag = 0x44
|
||||||
uriValueTag tag = 0x45
|
uriValueTag tag = 0x45
|
||||||
uriSchemeValueTag tag = 0x46
|
uriSchemeValueTag tag = 0x46
|
||||||
charsetValueTag tag = 0x47
|
charsetValueTag tag = 0x47
|
||||||
naturalLanguagageValueTag tag = 0x48
|
naturalLanguageValueTag tag = 0x48
|
||||||
mimeMediaTypeValueTag tag = 0x49
|
mimeMediaTypeValueTag tag = 0x49
|
||||||
memberAttrNameValueTag tag = 0x4a
|
memberAttrNameValueTag tag = 0x4a
|
||||||
)
|
)
|
||||||
|
|
||||||
// Operation-id, defined in rfc8011
|
// Operation-id, defined in rfc8011
|
||||||
type operationId uint16
|
type OperationId uint16
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PrintJob operationId = 0x0002
|
PrintJob OperationId = 0x0002
|
||||||
PrintURI operationId = 0x0003
|
PrintURI OperationId = 0x0003
|
||||||
ValidateJob operationId = 0x0004
|
ValidateJob OperationId = 0x0004
|
||||||
CreateJob operationId = 0x0005
|
CreateJob OperationId = 0x0005
|
||||||
SendDocument operationId = 0x0006
|
SendDocument OperationId = 0x0006
|
||||||
SendURI operationId = 0x0007
|
SendURI OperationId = 0x0007
|
||||||
CancelJob operationId = 0x0008
|
CancelJob OperationId = 0x0008
|
||||||
GetJobAttributes operationId = 0x0009
|
GetJobAttributes OperationId = 0x0009
|
||||||
GetJobs operationId = 0x000a
|
GetJobs OperationId = 0x000a
|
||||||
GetPrinterAttributes operationId = 0x000b
|
GetPrinterAttributes OperationId = 0x000b
|
||||||
HoldJob operationId = 0x000c
|
HoldJob OperationId = 0x000c
|
||||||
ReleaseJob operationId = 0x000d
|
ReleaseJob OperationId = 0x000d
|
||||||
RestartJob operationId = 0x000e
|
RestartJob OperationId = 0x000e
|
||||||
PausePrinter operationId = 0x0010
|
PausePrinter OperationId = 0x0010
|
||||||
ResumePrinter operationId = 0x0011
|
ResumePrinter OperationId = 0x0011
|
||||||
PurgeJobs operationId = 0x0012
|
PurgeJobs OperationId = 0x0012
|
||||||
|
)
|
||||||
|
|
||||||
|
type printerState int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
Idle printerState = 3
|
||||||
|
Processing printerState = 4
|
||||||
|
Stopped printerState = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
type statusCode uint16
|
type statusCode uint16
|
||||||
@@ -92,151 +98,16 @@ func (v versionNumber) String() string {
|
|||||||
return fmt.Sprintf("%x.%x", vn&0xff00>>8, vn&0x00ff)
|
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) {
|
func unmarshalSingleValue(byteStream io.Reader) (string, string) {
|
||||||
var length uint16
|
var length uint16
|
||||||
binary.Read(byteStream, binary.BigEndian, &length)
|
binary.Read(byteStream, binary.BigEndian, &length)
|
||||||
//log.Infof("Length %v", length)
|
|
||||||
attributeName := make([]byte, length)
|
attributeName := make([]byte, length)
|
||||||
if length > 0 {
|
if length > 0 {
|
||||||
binary.Read(byteStream, binary.BigEndian, attributeName)
|
binary.Read(byteStream, binary.BigEndian, attributeName)
|
||||||
//log.Infof("Valuename %v", string(attributeName))
|
|
||||||
}
|
}
|
||||||
binary.Read(byteStream, binary.BigEndian, &length)
|
binary.Read(byteStream, binary.BigEndian, &length)
|
||||||
//log.Infof("Length %v", length)
|
|
||||||
attributeValue := make([]byte, length)
|
attributeValue := make([]byte, length)
|
||||||
binary.Read(byteStream, binary.BigEndian, attributeValue)
|
binary.Read(byteStream, binary.BigEndian, attributeValue)
|
||||||
//log.Infof("Value %v", string(attributeValue))
|
|
||||||
return string(attributeName), string(attributeValue)
|
return string(attributeName), string(attributeValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
32
packages/ipp/mimemediatype.go
Normal file
32
packages/ipp/mimemediatype.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package ipp
|
||||||
|
|
||||||
|
type mimeMediaType struct {
|
||||||
|
sos *setOfStrings
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMimeMediaType(name string, values ...string) *mimeMediaType {
|
||||||
|
|
||||||
|
m := new(mimeMediaType)
|
||||||
|
m.sos = NewSetOfStrings(name, mimeMediaTypeValueTag, values)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mimeMediaType) String() string {
|
||||||
|
return m.sos.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mimeMediaType) size() int {
|
||||||
|
return m.sos.size()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mimeMediaType) valueTag() tag {
|
||||||
|
return m.sos.valueTag()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mimeMediaType) marshal() []byte {
|
||||||
|
return m.sos.marshal()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mimeMediaType) addValue(v string) {
|
||||||
|
m.sos.AddValue(v)
|
||||||
|
}
|
||||||
45
packages/ipp/namewithoutlanguage.go
Normal file
45
packages/ipp/namewithoutlanguage.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package ipp
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
type NameWithoutLanguage struct {
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNameWithoutLanguage(name, value string) *NameWithoutLanguage {
|
||||||
|
c := new(NameWithoutLanguage)
|
||||||
|
c.name = name
|
||||||
|
c.value = value
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c NameWithoutLanguage) String() string {
|
||||||
|
return c.name + ":" + c.value
|
||||||
|
}
|
||||||
|
func (c *NameWithoutLanguage) valueTag() tag {
|
||||||
|
return nameWithoutLanguageValueTag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *NameWithoutLanguage) unmarshal(byteStream io.Reader) {
|
||||||
|
c.name, c.value = unmarshalSingleValue(byteStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *NameWithoutLanguage) marshal() []byte {
|
||||||
|
l := 5 + len(c.name) + len(c.value)
|
||||||
|
b := make([]byte, l, l)
|
||||||
|
b[0] = byte(nameWithoutLanguageValueTag)
|
||||||
|
marshalNameValue(c.name, c.value, b[1:])
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *NameWithoutLanguage) size() int {
|
||||||
|
l := 1 + 4 // The attribute tag + 2 lengths
|
||||||
|
l += len(c.name)
|
||||||
|
l += len(c.value)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n NameWithoutLanguage) Value() string {
|
||||||
|
return n.value
|
||||||
|
}
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
43
packages/ipp/naturallanguage.go
Normal file
43
packages/ipp/naturallanguage.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package ipp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type naturalLanguage struct {
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNaturalLanguage(name, value string) *naturalLanguage {
|
||||||
|
c := new(naturalLanguage)
|
||||||
|
c.name = name
|
||||||
|
c.value = value
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c naturalLanguage) String() string {
|
||||||
|
return c.name + ":" + c.value
|
||||||
|
}
|
||||||
|
func (c *naturalLanguage) valueTag() tag {
|
||||||
|
return naturalLanguageValueTag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naturalLanguage) unmarshal(byteStream io.Reader) {
|
||||||
|
c.name, c.value = unmarshalSingleValue(byteStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naturalLanguage) marshal() []byte {
|
||||||
|
l := 5 + len(c.name) + len(c.value)
|
||||||
|
b := make([]byte, l, l)
|
||||||
|
b[0] = byte(naturalLanguageValueTag)
|
||||||
|
marshalNameValue(c.name, c.value, b[1:])
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naturalLanguage) size() int {
|
||||||
|
l := 1 + 4 // The attribute tag + 2 lengths
|
||||||
|
l += len(c.name)
|
||||||
|
l += len(c.value)
|
||||||
|
return l
|
||||||
|
}
|
||||||
@@ -36,7 +36,7 @@ var (
|
|||||||
_operationId_index_1 = [...]uint8{0, 12, 25, 34}
|
_operationId_index_1 = [...]uint8{0, 12, 25, 34}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (i operationId) String() string {
|
func (i OperationId) String() string {
|
||||||
switch {
|
switch {
|
||||||
case 2 <= i && i <= 14:
|
case 2 <= i && i <= 14:
|
||||||
i -= 2
|
i -= 2
|
||||||
|
|||||||
26
packages/ipp/printerstate_string.go
Normal file
26
packages/ipp/printerstate_string.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Code generated by "stringer -type printerState ."; 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[Idle-3]
|
||||||
|
_ = x[Processing-4]
|
||||||
|
_ = x[Stopped-5]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _printerState_name = "IdlePreocessingStopped"
|
||||||
|
|
||||||
|
var _printerState_index = [...]uint8{0, 4, 15, 22}
|
||||||
|
|
||||||
|
func (i printerState) String() string {
|
||||||
|
i -= 3
|
||||||
|
if i < 0 || i >= printerState(len(_printerState_index)-1) {
|
||||||
|
return "printerState(" + strconv.FormatInt(int64(i+3), 10) + ")"
|
||||||
|
}
|
||||||
|
return _printerState_name[_printerState_index[i]:_printerState_index[i+1]]
|
||||||
|
}
|
||||||
203
packages/ipp/request.go
Normal file
203
packages/ipp/request.go
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
package ipp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ippRequestHeader struct {
|
||||||
|
versionNumber versionNumber
|
||||||
|
operationId OperationId
|
||||||
|
requestId uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ippRequestHeader) unmarshal(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) marshal() []byte {
|
||||||
|
b := make([]byte, 0, 8)
|
||||||
|
buf := bytes.NewBuffer(b)
|
||||||
|
binary.Write(buf, binary.BigEndian, h.versionNumber)
|
||||||
|
binary.Write(buf, binary.BigEndian, h.operationId)
|
||||||
|
binary.Write(buf, binary.BigEndian, h.requestId)
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
marshal() []byte
|
||||||
|
//size() int
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddValuer interface {
|
||||||
|
addValue(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
operationAttributes map[string]Attribute
|
||||||
|
jobAttributes map[string]Attribute
|
||||||
|
printerAttributes map[string]Attribute
|
||||||
|
header ippRequestHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRequest(op OperationId, requestId uint32) *Request {
|
||||||
|
r := new(Request)
|
||||||
|
r.header.operationId = op
|
||||||
|
r.header.requestId = requestId
|
||||||
|
r.header.versionNumber = 0x0200
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
s = s + " PrinterAttributes" + "\n"
|
||||||
|
for _, a := range r.printerAttributes {
|
||||||
|
s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag())
|
||||||
|
}
|
||||||
|
s = s + " JobAttributes" + "\n"
|
||||||
|
for _, a := range r.jobAttributes {
|
||||||
|
s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag())
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) UnMarshal(body io.Reader) {
|
||||||
|
r.header.unmarshal(body)
|
||||||
|
log.Debugf("Header %v", r.header)
|
||||||
|
var tag tag
|
||||||
|
err := binary.Read(body, binary.BigEndian, &tag)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err.Error())
|
||||||
|
}
|
||||||
|
log.Debugf("got tag - %v", tag)
|
||||||
|
var currentAttributeGroup map[string]Attribute
|
||||||
|
switch tag {
|
||||||
|
case operationAttributes:
|
||||||
|
currentAttributeGroup = r.operationAttributes
|
||||||
|
case jobAttributes:
|
||||||
|
currentAttributeGroup = r.jobAttributes
|
||||||
|
case printerAttributes:
|
||||||
|
currentAttributeGroup = r.printerAttributes
|
||||||
|
default:
|
||||||
|
log.Errorf("Unknown tag %v", tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastAddValuer AddValuer
|
||||||
|
for {
|
||||||
|
err = binary.Read(body, binary.BigEndian, &tag)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("End of input before end of attributes tag (%v)", err.Error())
|
||||||
|
}
|
||||||
|
log.Debugf("Value tag - %v", tag)
|
||||||
|
switch tag {
|
||||||
|
case endOfAttributes:
|
||||||
|
return
|
||||||
|
case charsetValueTag:
|
||||||
|
c := NewCharSetValue("", "")
|
||||||
|
c.unmarshal(body)
|
||||||
|
currentAttributeGroup[c.name] = c
|
||||||
|
log.Debugf("%v %v", c.name, c.value)
|
||||||
|
case uriValueTag:
|
||||||
|
u := NewUriValue("", "")
|
||||||
|
u.unmarshal(body)
|
||||||
|
currentAttributeGroup[u.name] = u
|
||||||
|
log.Debugf("%v %v", u.name, u.value)
|
||||||
|
case naturalLanguageValueTag:
|
||||||
|
n := NewNaturalLanguage("", "")
|
||||||
|
n.unmarshal(body)
|
||||||
|
currentAttributeGroup[n.name] = n
|
||||||
|
log.Debugf("%v %v", n.name, n.value)
|
||||||
|
case keyWordValueTag:
|
||||||
|
name, value := unmarshalSingleValue(body)
|
||||||
|
if name == "" {
|
||||||
|
lastAddValuer.addValue(value)
|
||||||
|
} else {
|
||||||
|
k := NewKeyWord(name, value)
|
||||||
|
currentAttributeGroup[name] = k
|
||||||
|
lastAddValuer = k
|
||||||
|
}
|
||||||
|
log.Debugf("%v : %v", name, value)
|
||||||
|
case nameWithoutLanguageValueTag:
|
||||||
|
n := NewNameWithoutLanguage("", "")
|
||||||
|
n.unmarshal(body)
|
||||||
|
currentAttributeGroup[n.name] = n
|
||||||
|
log.Debugf("%v %v", n.name, n.value)
|
||||||
|
case mimeMediaTypeValueTag:
|
||||||
|
name, value := unmarshalSingleValue(body)
|
||||||
|
if name == "" {
|
||||||
|
lastAddValuer.addValue(value)
|
||||||
|
} else {
|
||||||
|
m := NewMimeMediaType(name, value)
|
||||||
|
currentAttributeGroup[name] = m
|
||||||
|
lastAddValuer = m
|
||||||
|
}
|
||||||
|
log.Debugf("%v : %v", name, value)
|
||||||
|
case jobAttributes:
|
||||||
|
log.Debug("Start job attributes")
|
||||||
|
currentAttributeGroup = r.jobAttributes
|
||||||
|
case resolutionValueTag:
|
||||||
|
res := NewResolution("", 0, 0)
|
||||||
|
res.unmarshal(body)
|
||||||
|
currentAttributeGroup[res.name] = res
|
||||||
|
log.Debugf("Resolution %v", res)
|
||||||
|
default:
|
||||||
|
log.Errorf("Unsupported tag %v", tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) RequestId() uint32 {
|
||||||
|
return r.header.requestId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) Operation() OperationId {
|
||||||
|
return r.header.operationId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) GetAttribute(name string) Attribute {
|
||||||
|
return r.operationAttributes[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) Marshal() []byte {
|
||||||
|
// //s = r.size
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.Write(r.header.marshal())
|
||||||
|
if len(r.operationAttributes) > 0 {
|
||||||
|
buf.WriteByte(byte(operationAttributes))
|
||||||
|
for _, e := range r.operationAttributes {
|
||||||
|
buf.Write(e.marshal())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(r.jobAttributes) > 0 {
|
||||||
|
buf.WriteByte(byte(jobAttributes))
|
||||||
|
for _, e := range r.jobAttributes {
|
||||||
|
buf.Write(e.marshal())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(r.printerAttributes) > 0 {
|
||||||
|
buf.WriteByte(byte(printerAttributes))
|
||||||
|
for _, e := range r.printerAttributes {
|
||||||
|
buf.Write(e.marshal())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.WriteByte(byte(endOfAttributes))
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
79
packages/ipp/request_test.go
Normal file
79
packages/ipp/request_test.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package ipp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testRequest = []byte{0x01, 0x01, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x11,
|
||||||
|
0x01,
|
||||||
|
0x47,
|
||||||
|
0x00, 0x12,
|
||||||
|
0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74,
|
||||||
|
0x00, 0x05,
|
||||||
|
0x75, 0x74, 0x66, 0x2d, 0x38,
|
||||||
|
0x48,
|
||||||
|
0x00, 0x1b,
|
||||||
|
0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2d, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x61, 0x6c, 0x2d, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65,
|
||||||
|
0x00, 0x05,
|
||||||
|
0x65, 0x6e, 0x2d, 0x75, 0x73,
|
||||||
|
0x45, // uriValueTag
|
||||||
|
0x00, 0x0b,
|
||||||
|
0x70, 0x72, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x2d, 0x75, 0x72, 0x69,
|
||||||
|
0x00, 0x20,
|
||||||
|
0x69, 0x70, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x30, 0x2e, 0x32, 0x3a, 0x31, 0x32, 0x33, 0x34, 0x2f, 0x69, 0x70, 0x70, 0x2f, 0x70, 0x72, 0x69, 0x6e, 0x74,
|
||||||
|
0x44, // keywordValueTag
|
||||||
|
0x00, 0x14,
|
||||||
|
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x2d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73,
|
||||||
|
0x00, 0x16,
|
||||||
|
0x70, 0x72, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x2d, 0x6d, 0x61, 0x6b, 0x65, 0x2d, 0x61, 0x6e, 0x64, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x6c,
|
||||||
|
0x44, // keywordValueTag
|
||||||
|
0x00, 0x00,
|
||||||
|
0x00, 0x16,
|
||||||
|
0x69, 0x70, 0x70, 0x2d, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2d, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64,
|
||||||
|
0x44, // keywordValueTag
|
||||||
|
0x00, 0x00,
|
||||||
|
0x00, 0x16,
|
||||||
|
0x69, 0x70, 0x70, 0x2d, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2d, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64,
|
||||||
|
0x44, // keywordValueTag
|
||||||
|
0x00, 0x00,
|
||||||
|
0x00, 0x19,
|
||||||
|
0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2d, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2d, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64,
|
||||||
|
0x44,
|
||||||
|
0x00, 0x00,
|
||||||
|
0x00, 0x0d,
|
||||||
|
0x70, 0x72, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x74, 0x61, 0x74, 0x65,
|
||||||
|
0x44,
|
||||||
|
0x00, 0x00,
|
||||||
|
0x00, 0x15,
|
||||||
|
0x70, 0x72, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2d, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x73,
|
||||||
|
0x44,
|
||||||
|
0x00, 0x00,
|
||||||
|
0x00, 0x15,
|
||||||
|
0x70, 0x72, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
||||||
|
0x03}
|
||||||
|
|
||||||
|
func TestUnmarshalRequestPrinterAttributes(T *testing.T) {
|
||||||
|
buf := bytes.NewBuffer(testRequest)
|
||||||
|
|
||||||
|
req := NewRequest(GetPrinterAttributes, 17)
|
||||||
|
|
||||||
|
req.UnMarshal(buf)
|
||||||
|
fmt.Print(req)
|
||||||
|
assert.Equal(T, versionNumber(0x0101), req.header.versionNumber, "Wrong version number")
|
||||||
|
assert.Equal(T, GetPrinterAttributes, req.header.operationId, "Wrong Operation")
|
||||||
|
assert.Equal(T, uint32(17), req.header.requestId, "Wrong request id")
|
||||||
|
assert.Len(T, req.operationAttributes, 4)
|
||||||
|
v := req.operationAttributes["requested-attributes"].(*keyWord).sos.values
|
||||||
|
assert.Len(T, v, 7)
|
||||||
|
assert.Contains(T, v, "printer-make-and-model")
|
||||||
|
assert.Contains(T, v, "ipp-versions-supported")
|
||||||
|
assert.Contains(T, v, "ipp-features-supported")
|
||||||
|
assert.Contains(T, v, "document-format-supported")
|
||||||
|
assert.Contains(T, v, "printer-state-reasons")
|
||||||
|
assert.Contains(T, v, "printer-state-message")
|
||||||
|
assert.Contains(T, v, "printer-state")
|
||||||
|
}
|
||||||
63
packages/ipp/resolution.go
Normal file
63
packages/ipp/resolution.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package ipp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type resolution struct {
|
||||||
|
name string
|
||||||
|
crossFeedResolution int32
|
||||||
|
feedResolution int32
|
||||||
|
units int8
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResolution(name string, xfres int32, fres int32) *resolution {
|
||||||
|
r := new(resolution)
|
||||||
|
r.name = name
|
||||||
|
r.crossFeedResolution = xfres
|
||||||
|
r.feedResolution = fres
|
||||||
|
r.units = 3 // 3 seems to mean dpi (rfc3805)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resolution) unmarshal(byteStream io.Reader) {
|
||||||
|
var length uint16
|
||||||
|
binary.Read(byteStream, binary.BigEndian, &length)
|
||||||
|
attributeName := make([]byte, length)
|
||||||
|
if length > 0 {
|
||||||
|
binary.Read(byteStream, binary.BigEndian, attributeName)
|
||||||
|
}
|
||||||
|
r.name = string(attributeName)
|
||||||
|
binary.Read(byteStream, binary.BigEndian, &length)
|
||||||
|
if length != 9 {
|
||||||
|
panic("Wrong length in resolution")
|
||||||
|
}
|
||||||
|
binary.Read(byteStream, binary.BigEndian, &r.crossFeedResolution)
|
||||||
|
binary.Read(byteStream, binary.BigEndian, &r.feedResolution)
|
||||||
|
binary.Read(byteStream, binary.BigEndian, &r.units)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r resolution) String() string {
|
||||||
|
return fmt.Sprintf("%v:%v,%v,%v", r.name, r.crossFeedResolution, r.feedResolution, r.units)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resolution) marshal() []byte {
|
||||||
|
|
||||||
|
b := make([]byte, 0, 14+len(r.name))
|
||||||
|
buf := bytes.NewBuffer(b)
|
||||||
|
buf.WriteByte(byte(resolutionValueTag))
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(len(r.name)))
|
||||||
|
buf.WriteString(r.name)
|
||||||
|
binary.Write(buf, binary.BigEndian, uint16(9))
|
||||||
|
binary.Write(buf, binary.BigEndian, int32(r.crossFeedResolution))
|
||||||
|
binary.Write(buf, binary.BigEndian, int32(r.feedResolution))
|
||||||
|
buf.WriteByte(byte(r.units))
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resolution) valueTag() tag {
|
||||||
|
return resolutionValueTag
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
package ipp
|
package ipp
|
||||||
|
|
||||||
import "encoding/binary"
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
type ippResponseHeader struct {
|
type ippResponseHeader struct {
|
||||||
versionNumber versionNumber
|
versionNumber versionNumber
|
||||||
@@ -8,6 +11,10 @@ type ippResponseHeader struct {
|
|||||||
requestId uint32
|
requestId uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h ippResponseHeader) String() string {
|
||||||
|
return fmt.Sprintf("Version number: %v Status code: %v Request Id: %v", h.versionNumber, h.statusCode, h.requestId)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *ippResponseHeader) marshal() []byte {
|
func (h *ippResponseHeader) marshal() []byte {
|
||||||
a := make([]byte, 8, 8)
|
a := make([]byte, 8, 8)
|
||||||
binary.BigEndian.PutUint16(a[0:2], uint16(h.versionNumber))
|
binary.BigEndian.PutUint16(a[0:2], uint16(h.versionNumber))
|
||||||
@@ -18,10 +25,10 @@ func (h *ippResponseHeader) marshal() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
header ippResponseHeader
|
|
||||||
operationAttributes []Attribute
|
operationAttributes []Attribute
|
||||||
jobAttributes []Attribute
|
jobAttributes []Attribute
|
||||||
printerAttributes []Attribute
|
printerAttributes []Attribute
|
||||||
|
header ippResponseHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResponse(code statusCode, requestId uint32) *Response {
|
func NewResponse(code statusCode, requestId uint32) *Response {
|
||||||
@@ -52,6 +59,23 @@ func NewResponse(code statusCode, requestId uint32) *Response {
|
|||||||
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
func (r Response) String() string {
|
||||||
|
|
||||||
|
s := r.header.String() + "\n" + " OperationAttributes" + "\n"
|
||||||
|
for _, a := range r.operationAttributes {
|
||||||
|
s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag())
|
||||||
|
}
|
||||||
|
s = s + " PrinterAttributes" + "\n"
|
||||||
|
for _, a := range r.printerAttributes {
|
||||||
|
s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag())
|
||||||
|
}
|
||||||
|
s = s + " JobAttributes" + "\n"
|
||||||
|
for _, a := range r.jobAttributes {
|
||||||
|
s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag())
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Response) Marshal() []byte {
|
func (r *Response) Marshal() []byte {
|
||||||
a := make([]byte, 0, 20)
|
a := make([]byte, 0, 20)
|
||||||
a = append(a, r.header.marshal()...)
|
a = append(a, r.header.marshal()...)
|
||||||
|
|||||||
88
packages/ipp/setofstrings.go
Normal file
88
packages/ipp/setofstrings.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package ipp
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
|
type setOfStrings struct {
|
||||||
|
name string
|
||||||
|
values []string
|
||||||
|
vTag tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSetOfStrings(name string, t tag, values []string) *setOfStrings {
|
||||||
|
s := new(setOfStrings)
|
||||||
|
s.name = name
|
||||||
|
s.vTag = t
|
||||||
|
s.values = values //make([]string, 0)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s setOfStrings) String() string {
|
||||||
|
r := s.name + " :"
|
||||||
|
for _, v := range s.values {
|
||||||
|
r = r + " " + v
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
func (s *setOfStrings) valueTag() tag {
|
||||||
|
return s.vTag
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (k *keyWord) unmarshal(byteStream io.Reader) {
|
||||||
|
// if len(k.values) == 0 {
|
||||||
|
// var v string
|
||||||
|
// k.name, v = unmarshalSingleValue(byteStream)
|
||||||
|
// k.values = append(k.values, v)
|
||||||
|
// } else {
|
||||||
|
// var v string
|
||||||
|
// _, v = unmarshalSingleValue(byteStream)
|
||||||
|
// k.values = append(k.values, v)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (s *setOfStrings) marshal() []byte {
|
||||||
|
l := 5 + len(s.name) + len(s.values[0])
|
||||||
|
for i := range s.values[1:] {
|
||||||
|
l += 5 + len(s.values[i+1])
|
||||||
|
}
|
||||||
|
res := make([]byte, l, l)
|
||||||
|
p := 0
|
||||||
|
res[p] = byte(s.vTag)
|
||||||
|
p += 1
|
||||||
|
binary.BigEndian.PutUint16(res[p:p+2], uint16(len(s.name)))
|
||||||
|
p += 2
|
||||||
|
copy(res[p:], []byte(s.name))
|
||||||
|
p += len(s.name)
|
||||||
|
binary.BigEndian.PutUint16(res[p:p+2], uint16(len(s.values[0])))
|
||||||
|
p += 2
|
||||||
|
copy(res[p:], []byte(s.values[0]))
|
||||||
|
p += len(s.values[0])
|
||||||
|
for i := range s.values[1:] {
|
||||||
|
res[p] = byte(s.vTag)
|
||||||
|
p += 1
|
||||||
|
binary.BigEndian.PutUint16(res[p:p+2], uint16(0))
|
||||||
|
p = p + 2
|
||||||
|
binary.BigEndian.PutUint16(res[p:p+2], uint16(len(s.values[i+1])))
|
||||||
|
p = p + 2
|
||||||
|
copy(res[p:], []byte(s.values[i+1]))
|
||||||
|
p += len(s.values[i+1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *setOfStrings) AddValue(v string) {
|
||||||
|
s.values = append(s.values, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *setOfStrings) size() int {
|
||||||
|
l := 1 + 2 // The value tag (0x44) + name-length field (2 bytes)
|
||||||
|
l += len(s.name)
|
||||||
|
l += 2 // value-length field (2 bytes)
|
||||||
|
l += len(s.values[0])
|
||||||
|
// Add all additional values
|
||||||
|
for _, v := range s.values[1:] {
|
||||||
|
l += 1 + 4 // The value tag (0x44) + 2 length fields (2 bytes)
|
||||||
|
l += len(v)
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
@@ -28,13 +28,13 @@ func _() {
|
|||||||
_ = x[textWithLanguageValueTag-53]
|
_ = x[textWithLanguageValueTag-53]
|
||||||
_ = x[nameWithLanguageValueTag-54]
|
_ = x[nameWithLanguageValueTag-54]
|
||||||
_ = x[endCollectionValueTag-55]
|
_ = x[endCollectionValueTag-55]
|
||||||
_ = x[textWithoutLanguagageValueTag-65]
|
_ = x[textWithoutLanguageValueTag-65]
|
||||||
_ = x[nameWithoutLanguagageValueTag-66]
|
_ = x[nameWithoutLanguageValueTag-66]
|
||||||
_ = x[keyWordValueTag-68]
|
_ = x[keyWordValueTag-68]
|
||||||
_ = x[uriValueTag-69]
|
_ = x[uriValueTag-69]
|
||||||
_ = x[uriSchemeValueTag-70]
|
_ = x[uriSchemeValueTag-70]
|
||||||
_ = x[charsetValueTag-71]
|
_ = x[charsetValueTag-71]
|
||||||
_ = x[naturalLanguagageValueTag-72]
|
_ = x[naturalLanguageValueTag-72]
|
||||||
_ = x[mimeMediaTypeValueTag-73]
|
_ = x[mimeMediaTypeValueTag-73]
|
||||||
_ = x[memberAttrNameValueTag-74]
|
_ = x[memberAttrNameValueTag-74]
|
||||||
}
|
}
|
||||||
|
|||||||
41
packages/ipp/textWithoutLanguage.go
Normal file
41
packages/ipp/textWithoutLanguage.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package ipp
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
type textWithoutLanguage struct {
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewtextWithoutLanguage(name, value string) *textWithoutLanguage {
|
||||||
|
c := new(textWithoutLanguage)
|
||||||
|
c.name = name
|
||||||
|
c.value = value
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c textWithoutLanguage) String() string {
|
||||||
|
return c.name + ":" + c.value
|
||||||
|
}
|
||||||
|
func (c *textWithoutLanguage) valueTag() tag {
|
||||||
|
return textWithoutLanguageValueTag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *textWithoutLanguage) unmarshal(byteStream io.Reader) {
|
||||||
|
c.name, c.value = unmarshalSingleValue(byteStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *textWithoutLanguage) marshal() []byte {
|
||||||
|
l := 5 + len(c.name) + len(c.value)
|
||||||
|
b := make([]byte, l, l)
|
||||||
|
b[0] = byte(textWithoutLanguageValueTag)
|
||||||
|
marshalNameValue(c.name, c.value, b[1:])
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *textWithoutLanguage) size() int {
|
||||||
|
l := 1 + 4 // The attribute tag + 2 lengths
|
||||||
|
l += len(c.name)
|
||||||
|
l += len(c.value)
|
||||||
|
return l
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ type uriValue struct {
|
|||||||
value string
|
value string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newUriValue(name, value string) *uriValue {
|
func NewUriValue(name, value string) *uriValue {
|
||||||
u := new(uriValue)
|
u := new(uriValue)
|
||||||
u.name = name
|
u.name = name
|
||||||
u.value = value
|
u.value = value
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func Run(ctx context.Context) {
|
|||||||
log.Fatalf("EntryGroupNew() failed: %v", err)
|
log.Fatalf("EntryGroupNew() failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hostname, err := a.GetHostName()
|
//hostname, err := a.GetHostName()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("GetHostName() failed: %v", err)
|
log.Fatalf("GetHostName() failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,8 @@ func Run(ctx context.Context) {
|
|||||||
txt = append(txt, []byte("product=coola-skrivaren"))
|
txt = append(txt, []byte("product=coola-skrivaren"))
|
||||||
txt = append(txt, []byte("Color=T"))
|
txt = append(txt, []byte("Color=T"))
|
||||||
txt = append(txt, []byte("rp=ipp/print"))
|
txt = append(txt, []byte("rp=ipp/print"))
|
||||||
err = eg.AddService(avahi.InterfaceUnspec, avahi.ProtoUnspec, 0, hostname, "_ipp._tcp", "local", fqdn, 1234, txt)
|
txt = append(txt, []byte("ty=ChroBro 001"))
|
||||||
|
err = eg.AddService(avahi.InterfaceUnspec, avahi.ProtoUnspec, 0, "ChroBro 1000", "_ipp._tcp", "local", fqdn, 1234, txt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("AddService() failed: %v", err)
|
log.Fatalf("AddService() failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
8
server/handlegetjobs.go
Normal file
8
server/handlegetjobs.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "ippserver/packages/ipp"
|
||||||
|
|
||||||
|
func handleGetJobs(r *ipp.Request) *ipp.Response {
|
||||||
|
response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestId())
|
||||||
|
return response
|
||||||
|
}
|
||||||
28
server/handlegetprinterattributes.go
Normal file
28
server/handlegetprinterattributes.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "ippserver/packages/ipp"
|
||||||
|
|
||||||
|
func handleGetPrinterAttributes(r *ipp.Request) *ipp.Response {
|
||||||
|
response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestId())
|
||||||
|
var a ipp.Attribute
|
||||||
|
a = ipp.NewCharSetValue("attributes-charset", "utf-8")
|
||||||
|
response.AddOperatonAttribute(a)
|
||||||
|
a = ipp.NewNaturalLanguage("attributes-natural-language", "en")
|
||||||
|
response.AddOperatonAttribute(a)
|
||||||
|
a = ipp.NewUriValue("printer-uri", "ipp://drpork:1234/ipp/print")
|
||||||
|
response.AddOperatonAttribute(a)
|
||||||
|
a = ipp.NewtextWithoutLanguage("printer-make-and-model", "ChroBro 001")
|
||||||
|
response.AddOperatonAttribute(a)
|
||||||
|
a = ipp.NewEnum("printer-state", int32(ipp.Idle))
|
||||||
|
response.AddOperatonAttribute(a)
|
||||||
|
a = ipp.NewKeyWord("ipp-versions-supported", "1.0", "1.1", "2.0")
|
||||||
|
response.AddOperatonAttribute(a)
|
||||||
|
response.AddOperatonAttribute(ipp.NewKeyWord("ipp-features-supported", "wfds-print-1.0"))
|
||||||
|
response.AddOperatonAttribute(ipp.NewMimeMediaType("document-format-supported", "image/pwg-raster"))
|
||||||
|
response.AddOperatonAttribute(ipp.NewKeyWord("media-supported", "iso_a4_210x297mm"))
|
||||||
|
response.AddOperatonAttribute(ipp.NewKeyWord("sides-supported", "one-sided", "two-sided-long-edge", "two-sided-short-edge"))
|
||||||
|
response.AddOperatonAttribute(ipp.NewKeyWord("print-color-mode-supported", "auto", "color", "monochrome"))
|
||||||
|
response.AddOperatonAttribute(ipp.NewResolution("printer-resolution-default", 600, 600))
|
||||||
|
response.AddOperatonAttribute(ipp.NewBoolean("printer-is-accepting-jobs", true))
|
||||||
|
return response
|
||||||
|
}
|
||||||
22
server/handleprintjob.go
Normal file
22
server/handleprintjob.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"ippserver/packages/ipp"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handlePrintJob(r *ipp.Request, byteStream io.Reader) *ipp.Response {
|
||||||
|
|
||||||
|
a := r.GetAttribute("job-name")
|
||||||
|
//a.(nameWithoutLanguage).Value
|
||||||
|
f, err := os.Create(a.(*ipp.NameWithoutLanguage).Value())
|
||||||
|
if err != nil {
|
||||||
|
panic("fail")
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
io.Copy(f, byteStream)
|
||||||
|
response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestId())
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"ippserver/packages/ipp"
|
"ippserver/packages/ipp"
|
||||||
"ippserver/packages/mdnsserver"
|
"ippserver/packages/mdnsserver"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -15,6 +14,7 @@ func main() {
|
|||||||
customFormatter.TimestampFormat = "2006-01-02 15:04:05"
|
customFormatter.TimestampFormat = "2006-01-02 15:04:05"
|
||||||
log.SetFormatter(customFormatter)
|
log.SetFormatter(customFormatter)
|
||||||
customFormatter.FullTimestamp = true
|
customFormatter.FullTimestamp = true
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -35,18 +35,35 @@ func handle(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "Unsupported method", http.StatusMethodNotAllowed)
|
http.Error(w, "Unsupported method", http.StatusMethodNotAllowed)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
log.Info(r.Header)
|
||||||
//body := make([]byte, r.ContentLength)
|
//body := make([]byte, r.ContentLength)
|
||||||
//io.ReadFull(r.Body, body)
|
//io.ReadFull(r.Body, body)
|
||||||
//log.Infof("Body %x", body)
|
//log.Infof("Body %x", body)
|
||||||
|
|
||||||
request := ipp.NewRequest()
|
request := ipp.NewRequest(0, 0)
|
||||||
|
//rdata := make([]byte, r.ContentLength)
|
||||||
|
//io.ReadFull(r.Body, rdata)
|
||||||
|
//log.Printf("Data %x", rdata)
|
||||||
|
//fmt.Printf("data % #0x", rdata)
|
||||||
|
//buf := bytes.NewBuffer(rdata)
|
||||||
request.UnMarshal(r.Body)
|
request.UnMarshal(r.Body)
|
||||||
fmt.Printf("%v", request)
|
log.Infof("Request: \n%v\n", request)
|
||||||
response := ipp.NewResponse(ipp.SuccessfulOk, request.RequestId())
|
var response *ipp.Response
|
||||||
a := ipp.NewCharSetValue("attributes-charset", "utf-8")
|
switch request.Operation() {
|
||||||
response.AddOperatonAttribute(a)
|
case ipp.GetPrinterAttributes:
|
||||||
|
response = handleGetPrinterAttributes(request)
|
||||||
|
case ipp.PrintJob:
|
||||||
|
response = handlePrintJob(request, r.Body)
|
||||||
|
case ipp.GetJobs:
|
||||||
|
response = handleGetJobs(request)
|
||||||
|
default:
|
||||||
|
response = ipp.NewResponse(ipp.ClientErrorBadRequest, request.RequestId())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Response:\n%v\n", response)
|
||||||
data := response.Marshal()
|
data := response.Marshal()
|
||||||
log.Infof("% x", data)
|
|
||||||
|
//log.Debugf("% x", data)
|
||||||
w.Write(data)
|
w.Write(data)
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user