From 74c1ce257aec7ea906a8b8a9d3495987066f39e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20S=C3=B6lver?= Date: Sat, 5 Dec 2020 21:25:09 +0100 Subject: [PATCH 1/6] Move request to a separate file and add test. --- packages/ipp/messages.go | 133 ---------------------------------- packages/ipp/request.go | 137 +++++++++++++++++++++++++++++++++++ packages/ipp/request_test.go | 79 ++++++++++++++++++++ 3 files changed, 216 insertions(+), 133 deletions(-) create mode 100644 packages/ipp/request.go create mode 100644 packages/ipp/request_test.go diff --git a/packages/ipp/messages.go b/packages/ipp/messages.go index da6c279..5033df9 100644 --- a/packages/ipp/messages.go +++ b/packages/ipp/messages.go @@ -4,8 +4,6 @@ import ( "encoding/binary" "fmt" "io" - - log "github.com/sirupsen/logrus" ) // References @@ -92,137 +90,6 @@ func (v versionNumber) String() string { 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) diff --git a/packages/ipp/request.go b/packages/ipp/request.go new file mode 100644 index 0000000..a4d014d --- /dev/null +++ b/packages/ipp/request.go @@ -0,0 +1,137 @@ +package ipp + +import ( + "encoding/binary" + "fmt" + "io" + + log "github.com/sirupsen/logrus" +) + +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 +} + +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 { + var lastKeyword *keyWord + nextoperationattr: + for tag != endOfAttributes { + + 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: + name, value := unmarshalSingleValue(body) + if name == "" { + lastKeyword.addValue(value) + } else { + k := newKeyWord() + k.name = name + k.addValue(value) + r.operationAttributes[k.name] = k + 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 +} diff --git a/packages/ipp/request_test.go b/packages/ipp/request_test.go new file mode 100644 index 0000000..7dfafdc --- /dev/null +++ b/packages/ipp/request_test.go @@ -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() + + 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).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") +} -- 2.34.1 From c3c608b28a0c0be003dd8191c4598dbac0d624b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20S=C3=B6lver?= Date: Sat, 5 Dec 2020 21:29:57 +0100 Subject: [PATCH 2/6] WIP: Fix keyword handling --- packages/ipp/keyword.go | 54 ++++++++++++++++++++++++++++++++---- packages/ipp/keyword_test.go | 15 ++++++++++ 2 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 packages/ipp/keyword_test.go diff --git a/packages/ipp/keyword.go b/packages/ipp/keyword.go index 655479a..e61d8b9 100644 --- a/packages/ipp/keyword.go +++ b/packages/ipp/keyword.go @@ -1,6 +1,8 @@ package ipp -import "io" +import ( + "encoding/binary" +) type keyWord struct { name string @@ -9,21 +11,61 @@ type keyWord struct { func newKeyWord() *keyWord { k := new(keyWord) + k.values = make([]string, 0) return k } -func (k *keyWord) string() string { - return "a uriValue" +func (k keyWord) String() string { + r := k.name + " :" + for _, v := range k.values { + r = r + " " + v + } + return r } func (k *keyWord) valueTag() tag { return keyWordValueTag } -func (k *keyWord) unmarshal(byteStream io.Reader) { - -} +// 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 (k *keyWord) marshal() []byte { + l := 5 + len(k.name) + len(k.values[0]) + for i := range k.values[1:] { + l += 5 + len(k.values[i+1]) + } + res := make([]byte, 0, l) + p := 0 + res[p] = byte(keyWordValueTag) + p += 1 + binary.BigEndian.PutUint16(res[p:p+2], uint16(len(k.name))) + p += 2 + copy(res[p:], []byte(k.name)) + p += len(k.name) + binary.BigEndian.PutUint16(res[p:p+2], uint16(len(k.values[0]))) + p += 2 + copy(res[p:], []byte(k.values[0])) + p += len(k.values[0]) + for i := range k.values[1:] { + res[p] = byte(keyWordValueTag) + p += 1 + binary.BigEndian.PutUint16(res[p:p+2], uint16(0)) + p = p + 2 + binary.BigEndian.PutUint16(res[p:p+2], uint16(len(k.values[i+1]))) + p = p + 2 + copy(res[p:], []byte(k.values[i+1])) + p += len(k.values[i+1]) + } + return []byte{} } diff --git a/packages/ipp/keyword_test.go b/packages/ipp/keyword_test.go new file mode 100644 index 0000000..9b944e0 --- /dev/null +++ b/packages/ipp/keyword_test.go @@ -0,0 +1,15 @@ +package ipp + +// 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") +// } -- 2.34.1 From a1261c1b8f19b1bdb1c2c9e668cb9d495c322246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20S=C3=B6lver?= Date: Wed, 23 Dec 2020 21:51:30 +0100 Subject: [PATCH 3/6] Spelling corrections. --- packages/ipp/naturallanguagage.go | 43 ------------------------------- packages/ipp/naturallanguage.go | 43 +++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 43 deletions(-) delete mode 100644 packages/ipp/naturallanguagage.go create mode 100644 packages/ipp/naturallanguage.go diff --git a/packages/ipp/naturallanguagage.go b/packages/ipp/naturallanguagage.go deleted file mode 100644 index 1f27a55..0000000 --- a/packages/ipp/naturallanguagage.go +++ /dev/null @@ -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 -} diff --git a/packages/ipp/naturallanguage.go b/packages/ipp/naturallanguage.go new file mode 100644 index 0000000..43da042 --- /dev/null +++ b/packages/ipp/naturallanguage.go @@ -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 +} -- 2.34.1 From 7ecbae27c242a1d25f23e29f18807ae0bc4baa61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20S=C3=B6lver?= Date: Wed, 23 Dec 2020 23:21:47 +0100 Subject: [PATCH 4/6] More types. --- main.go | 20 +++++++++-- packages/ipp/enum.go | 53 +++++++++++++++++++++++++++++ packages/ipp/keyword.go | 4 +-- packages/ipp/messages.go | 18 +++++----- packages/ipp/request.go | 12 +++---- packages/ipp/response.go | 4 +++ packages/ipp/tag_string.go | 6 ++-- packages/ipp/textWithoutLanguage.go | 41 ++++++++++++++++++++++ packages/ipp/urivalue.go | 2 +- packages/mdnsserver/server.go | 1 + 10 files changed, 138 insertions(+), 23 deletions(-) create mode 100644 packages/ipp/enum.go create mode 100644 packages/ipp/textWithoutLanguage.go diff --git a/main.go b/main.go index eadf1cc..e2160ef 100644 --- a/main.go +++ b/main.go @@ -1,8 +1,10 @@ package main import ( + "bytes" "context" "fmt" + "io" "ippserver/packages/ipp" "ippserver/packages/mdnsserver" "net/http" @@ -40,10 +42,24 @@ func handle(w http.ResponseWriter, r *http.Request) { // log.Infof("Body %x", body) request := ipp.NewRequest() - request.UnMarshal(r.Body) + 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(buf) fmt.Printf("%v", request) response := ipp.NewResponse(ipp.SuccessfulOk, request.RequestId()) - a := ipp.NewCharSetValue("attributes-charset", "utf-8") + 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", uint16(3)) response.AddOperatonAttribute(a) data := response.Marshal() log.Infof("% x", data) diff --git a/packages/ipp/enum.go b/packages/ipp/enum.go new file mode 100644 index 0000000..c1ca650 --- /dev/null +++ b/packages/ipp/enum.go @@ -0,0 +1,53 @@ +package ipp + +import ( + "encoding/binary" + "io" +) + +type enum struct { + name string + value uint16 +} + +func NewEnum(name string, value uint16) *enum { + e := new(enum) + e.name = name + e.value = value + return e +} + +func (e enum) String() string { + return e.name + ":" + string(e.value) +} +func (e *enum) valueTag() tag { + return enumValueTag +} + +func (e *enum) unmarshal(byteStream io.Reader) { + //e.name, e.value = unmarshalSingleValue(byteStream) + +} + +func (e *enum) marshal() []byte { + l := 3 + len(e.name) + 6 + b := make([]byte, l, l) + p := 0 + b[0] = byte(enumValueTag) + p += 1 + binary.BigEndian.PutUint16(b[p:p+2], uint16(len(e.name))) + p += 2 + copy(b[p:], []byte(e.name)) + p += len(e.name) + binary.BigEndian.PutUint16(b[p:p+2], uint16(4)) + p += 2 + binary.BigEndian.PutUint32(b[p:p+4], uint32(e.value)) + return b +} + +func (e *enum) size() int { + l := 1 + 4 // The attribute tag + 2 lengths + l += len(e.name) + l += 4 + return l +} diff --git a/packages/ipp/keyword.go b/packages/ipp/keyword.go index e61d8b9..7c5b0a0 100644 --- a/packages/ipp/keyword.go +++ b/packages/ipp/keyword.go @@ -9,7 +9,7 @@ type keyWord struct { values []string } -func newKeyWord() *keyWord { +func NewKeyWord() *keyWord { k := new(keyWord) k.values = make([]string, 0) return k @@ -69,7 +69,7 @@ func (k *keyWord) marshal() []byte { return []byte{} } -func (k *keyWord) addValue(v string) { +func (k *keyWord) AddValue(v string) { k.values = append(k.values, v) } diff --git a/packages/ipp/messages.go b/packages/ipp/messages.go index 5033df9..243bf01 100644 --- a/packages/ipp/messages.go +++ b/packages/ipp/messages.go @@ -43,15 +43,15 @@ const ( 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 + 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 ) // Operation-id, defined in rfc8011 diff --git a/packages/ipp/request.go b/packages/ipp/request.go index a4d014d..f18f6c9 100644 --- a/packages/ipp/request.go +++ b/packages/ipp/request.go @@ -99,23 +99,23 @@ func (r *Request) UnMarshal(body io.Reader) { r.operationAttributes[c.name] = c log.Infof("%v %v", c.name, c.value) case uriValueTag: - u := newUriValue("", "") + u := NewUriValue("", "") u.unmarshal(body) r.operationAttributes[u.name] = u log.Infof("%v %v", u.name, u.value) - case naturalLanguagageValueTag: - n := newNaturalLanguagage("", "") + case naturalLanguageValueTag: + n := NewNaturalLanguage("", "") n.unmarshal(body) r.operationAttributes[n.name] = n log.Infof("%v %v", n.name, n.value) case keyWordValueTag: name, value := unmarshalSingleValue(body) if name == "" { - lastKeyword.addValue(value) + lastKeyword.AddValue(value) } else { - k := newKeyWord() + k := NewKeyWord() k.name = name - k.addValue(value) + k.AddValue(value) r.operationAttributes[k.name] = k lastKeyword = k } diff --git a/packages/ipp/response.go b/packages/ipp/response.go index e1f36e0..2a86b39 100644 --- a/packages/ipp/response.go +++ b/packages/ipp/response.go @@ -52,6 +52,10 @@ func NewResponse(code statusCode, requestId uint32) *Response { // } +func (r Response) String() string { + return "" +} + func (r *Response) Marshal() []byte { a := make([]byte, 0, 20) a = append(a, r.header.marshal()...) diff --git a/packages/ipp/tag_string.go b/packages/ipp/tag_string.go index 8101cc3..c7c4eb5 100644 --- a/packages/ipp/tag_string.go +++ b/packages/ipp/tag_string.go @@ -28,13 +28,13 @@ func _() { _ = x[textWithLanguageValueTag-53] _ = x[nameWithLanguageValueTag-54] _ = x[endCollectionValueTag-55] - _ = x[textWithoutLanguagageValueTag-65] - _ = x[nameWithoutLanguagageValueTag-66] + _ = x[textWithoutLanguageValueTag-65] + _ = x[nameWithoutLanguageValueTag-66] _ = x[keyWordValueTag-68] _ = x[uriValueTag-69] _ = x[uriSchemeValueTag-70] _ = x[charsetValueTag-71] - _ = x[naturalLanguagageValueTag-72] + _ = x[naturalLanguageValueTag-72] _ = x[mimeMediaTypeValueTag-73] _ = x[memberAttrNameValueTag-74] } diff --git a/packages/ipp/textWithoutLanguage.go b/packages/ipp/textWithoutLanguage.go new file mode 100644 index 0000000..1c53a13 --- /dev/null +++ b/packages/ipp/textWithoutLanguage.go @@ -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 +} diff --git a/packages/ipp/urivalue.go b/packages/ipp/urivalue.go index b570c56..79c2b63 100644 --- a/packages/ipp/urivalue.go +++ b/packages/ipp/urivalue.go @@ -9,7 +9,7 @@ type uriValue struct { value string } -func newUriValue(name, value string) *uriValue { +func NewUriValue(name, value string) *uriValue { u := new(uriValue) u.name = name u.value = value diff --git a/packages/mdnsserver/server.go b/packages/mdnsserver/server.go index db33db6..92f6ae4 100644 --- a/packages/mdnsserver/server.go +++ b/packages/mdnsserver/server.go @@ -39,6 +39,7 @@ func Run(ctx context.Context) { txt = append(txt, []byte("product=coola-skrivaren")) txt = append(txt, []byte("Color=T")) txt = append(txt, []byte("rp=ipp/print")) + txt = append(txt, []byte("ty=ChroBro 001")) err = eg.AddService(avahi.InterfaceUnspec, avahi.ProtoUnspec, 0, hostname, "_ipp._tcp", "local", fqdn, 1234, txt) if err != nil { log.Fatalf("AddService() failed: %v", err) -- 2.34.1 From 8b5f69cb5294049cd46300966b52bb9653b5b785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20S=C3=B6lver?= Date: Fri, 25 Dec 2020 23:25:39 +0100 Subject: [PATCH 5/6] More types. Printing from chromeos works a little bit. --- main.go | 35 +++++++---- packages/ipp/enum.go | 28 ++++----- packages/ipp/keyword.go | 92 ++++++----------------------- packages/ipp/keyword_test.go | 21 +++++++ packages/ipp/messages.go | 8 +++ packages/ipp/mimemediatype.go | 32 ++++++++++ packages/ipp/namewithoutlanguage.go | 41 +++++++++++++ packages/ipp/printerstate_string.go | 26 ++++++++ packages/ipp/request.go | 68 +++++++++++++++------ packages/ipp/request_test.go | 2 +- packages/ipp/resolution.go | 48 +++++++++++++++ packages/ipp/response.go | 24 +++++++- packages/ipp/setofstrings.go | 88 +++++++++++++++++++++++++++ packages/mdnsserver/server.go | 4 +- 14 files changed, 393 insertions(+), 124 deletions(-) create mode 100644 packages/ipp/mimemediatype.go create mode 100644 packages/ipp/namewithoutlanguage.go create mode 100644 packages/ipp/printerstate_string.go create mode 100644 packages/ipp/resolution.go create mode 100644 packages/ipp/setofstrings.go diff --git a/main.go b/main.go index e2160ef..4010e48 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,8 @@ package main import ( - "bytes" "context" "fmt" - "io" "ippserver/packages/ipp" "ippserver/packages/mdnsserver" "net/http" @@ -17,6 +15,7 @@ func main() { customFormatter.TimestampFormat = "2006-01-02 15:04:05" log.SetFormatter(customFormatter) customFormatter.FullTimestamp = true + log.SetLevel(log.DebugLevel) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -37,18 +36,18 @@ func handle(w http.ResponseWriter, r *http.Request) { http.Error(w, "Unsupported method", http.StatusMethodNotAllowed) } - // body := make([]byte, r.ContentLength) - // io.ReadFull(r.Body, body) - // log.Infof("Body %x", body) + //body := make([]byte, r.ContentLength) + //io.ReadFull(r.Body, body) + //log.Infof("Body %x", body) request := ipp.NewRequest() - rdata := make([]byte, r.ContentLength) - io.ReadFull(r.Body, rdata) + //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(buf) - fmt.Printf("%v", request) + //fmt.Printf("data % #0x", rdata) + //buf := bytes.NewBuffer(rdata) + request.UnMarshal(r.Body) + fmt.Printf("Request: \n%v\n", request) response := ipp.NewResponse(ipp.SuccessfulOk, request.RequestId()) var a ipp.Attribute a = ipp.NewCharSetValue("attributes-charset", "utf-8") @@ -59,10 +58,20 @@ func handle(w http.ResponseWriter, r *http.Request) { response.AddOperatonAttribute(a) a = ipp.NewtextWithoutLanguage("printer-make-and-model", "ChroBro 001") response.AddOperatonAttribute(a) - a = ipp.NewEnum("printer-state", uint16(3)) + 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")) + + fmt.Printf("Response:\n%v\n", response) data := response.Marshal() - log.Infof("% x", data) + + log.Debugf("% x", data) w.Write(data) } diff --git a/packages/ipp/enum.go b/packages/ipp/enum.go index c1ca650..89b5309 100644 --- a/packages/ipp/enum.go +++ b/packages/ipp/enum.go @@ -1,16 +1,18 @@ package ipp import ( + "bytes" "encoding/binary" + "fmt" "io" ) type enum struct { name string - value uint16 + value int32 } -func NewEnum(name string, value uint16) *enum { +func NewEnum(name string, value int32) *enum { e := new(enum) e.name = name e.value = value @@ -18,7 +20,7 @@ func NewEnum(name string, value uint16) *enum { } func (e enum) String() string { - return e.name + ":" + string(e.value) + return e.name + ":" + fmt.Sprint(e.value) } func (e *enum) valueTag() tag { return enumValueTag @@ -31,18 +33,14 @@ func (e *enum) unmarshal(byteStream io.Reader) { func (e *enum) marshal() []byte { l := 3 + len(e.name) + 6 - b := make([]byte, l, l) - p := 0 - b[0] = byte(enumValueTag) - p += 1 - binary.BigEndian.PutUint16(b[p:p+2], uint16(len(e.name))) - p += 2 - copy(b[p:], []byte(e.name)) - p += len(e.name) - binary.BigEndian.PutUint16(b[p:p+2], uint16(4)) - p += 2 - binary.BigEndian.PutUint32(b[p:p+4], uint32(e.value)) - return b + 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 { diff --git a/packages/ipp/keyword.go b/packages/ipp/keyword.go index 7c5b0a0..f702cdc 100644 --- a/packages/ipp/keyword.go +++ b/packages/ipp/keyword.go @@ -1,87 +1,33 @@ package ipp -import ( - "encoding/binary" -) - type keyWord struct { - name string - values []string + sos *setOfStrings } -func NewKeyWord() *keyWord { +func NewKeyWord(name string, values ...string) *keyWord { + k := new(keyWord) - k.values = make([]string, 0) + k.sos = NewSetOfStrings(name, keyWordValueTag, values) return k } func (k keyWord) String() string { - r := k.name + " :" - for _, v := range k.values { - r = r + " " + v - } - return r -} -func (k *keyWord) valueTag() tag { - return keyWordValueTag -} - -// 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 (k *keyWord) marshal() []byte { - l := 5 + len(k.name) + len(k.values[0]) - for i := range k.values[1:] { - l += 5 + len(k.values[i+1]) - } - res := make([]byte, 0, l) - p := 0 - res[p] = byte(keyWordValueTag) - p += 1 - binary.BigEndian.PutUint16(res[p:p+2], uint16(len(k.name))) - p += 2 - copy(res[p:], []byte(k.name)) - p += len(k.name) - binary.BigEndian.PutUint16(res[p:p+2], uint16(len(k.values[0]))) - p += 2 - copy(res[p:], []byte(k.values[0])) - p += len(k.values[0]) - for i := range k.values[1:] { - res[p] = byte(keyWordValueTag) - p += 1 - binary.BigEndian.PutUint16(res[p:p+2], uint16(0)) - p = p + 2 - binary.BigEndian.PutUint16(res[p:p+2], uint16(len(k.values[i+1]))) - p = p + 2 - copy(res[p:], []byte(k.values[i+1])) - p += len(k.values[i+1]) - } - - return []byte{} -} - -func (k *keyWord) AddValue(v string) { - k.values = append(k.values, v) + return k.sos.String() } 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 + return k.sos.size() } + +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) +} + diff --git a/packages/ipp/keyword_test.go b/packages/ipp/keyword_test.go index 9b944e0..fb2fbc7 100644 --- a/packages/ipp/keyword_test.go +++ b/packages/ipp/keyword_test.go @@ -1,5 +1,11 @@ package ipp +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + // func TestUnmarshalSimpleKeyword(T *testing.T) { // testdata := []byte{ // 0x00, 0x14, @@ -13,3 +19,18 @@ package ipp // 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") + +} diff --git a/packages/ipp/messages.go b/packages/ipp/messages.go index 243bf01..1aa6a7c 100644 --- a/packages/ipp/messages.go +++ b/packages/ipp/messages.go @@ -76,6 +76,14 @@ const ( PurgeJobs operationId = 0x0012 ) +type printerState int32 + +const ( + Idle printerState = 3 + Preocessing printerState = 4 + Stopped printerState = 5 +) + type statusCode uint16 const ( diff --git a/packages/ipp/mimemediatype.go b/packages/ipp/mimemediatype.go new file mode 100644 index 0000000..1e5c4b6 --- /dev/null +++ b/packages/ipp/mimemediatype.go @@ -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) +} diff --git a/packages/ipp/namewithoutlanguage.go b/packages/ipp/namewithoutlanguage.go new file mode 100644 index 0000000..6888a5f --- /dev/null +++ b/packages/ipp/namewithoutlanguage.go @@ -0,0 +1,41 @@ +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 +} diff --git a/packages/ipp/printerstate_string.go b/packages/ipp/printerstate_string.go new file mode 100644 index 0000000..4f13146 --- /dev/null +++ b/packages/ipp/printerstate_string.go @@ -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[Preocessing-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]] +} diff --git a/packages/ipp/request.go b/packages/ipp/request.go index f18f6c9..42d5a60 100644 --- a/packages/ipp/request.go +++ b/packages/ipp/request.go @@ -26,16 +26,19 @@ func (h ippRequestHeader) String() string { type Attribute interface { valueTag() tag - //unmarshal(io.Reader) marshal() []byte - size() int + //size() int +} + +type AddValuer interface { + addValue(string) } type Request struct { - header ippRequestHeader operationAttributes map[string]Attribute jobAttributes map[string]Attribute printerAttributes map[string]Attribute + header ippRequestHeader } func NewRequest() *Request { @@ -52,21 +55,29 @@ func (r Request) String() string { 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.marshal(body) - log.Infof("Header %v", r.header) + log.Debugf("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) + log.Debugf("got tag - %v", tag) if tag == operationAttributes { - var lastKeyword *keyWord + var lastAddValuer AddValuer nextoperationattr: for tag != endOfAttributes { @@ -74,7 +85,7 @@ func (r *Request) UnMarshal(body io.Reader) { if err != nil { log.Error(err.Error()) } - log.Infof("Value tag - %v", tag) + log.Debugf("Value tag - %v", tag) switch tag { case endOfAttributes: err = binary.Read(body, binary.BigEndian, &tag) @@ -85,40 +96,61 @@ func (r *Request) UnMarshal(body io.Reader) { if err != nil { log.Error(err.Error()) } - log.Infof("got tag - %v", tag) + log.Debugf("got tag - %v", tag) err = binary.Read(body, binary.BigEndian, &tag) if err != nil { log.Error(err.Error()) } - log.Infof("got tag - %v", tag) + log.Debugf("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) + log.Debugf("%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) + log.Debugf("%v %v", u.name, u.value) case naturalLanguageValueTag: n := NewNaturalLanguage("", "") n.unmarshal(body) r.operationAttributes[n.name] = n - log.Infof("%v %v", n.name, n.value) + log.Debugf("%v %v", n.name, n.value) case keyWordValueTag: name, value := unmarshalSingleValue(body) if name == "" { - lastKeyword.AddValue(value) + lastAddValuer.addValue(value) } else { - k := NewKeyWord() - k.name = name - k.AddValue(value) - r.operationAttributes[k.name] = k - lastKeyword = k + k := NewKeyWord(name, value) + r.operationAttributes[name] = k + lastAddValuer = k } + log.Debugf("%v : %v", name, value) + case nameWithoutLanguageValueTag: + n := NewNameWithoutLanguage("", "") + n.unmarshal(body) + r.operationAttributes[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) + r.operationAttributes[name] = m + lastAddValuer = m + } + case jobAttributes: + log.Debug("Start job attributes") + case resolutionValueTag: + res := NewResolution() + res.unmarshal(body) + r.operationAttributes["resolution"] = res + log.Debugf("Resolution %v", res) + default: log.Errorf("Unsupported tag %v", tag) diff --git a/packages/ipp/request_test.go b/packages/ipp/request_test.go index 7dfafdc..3e522de 100644 --- a/packages/ipp/request_test.go +++ b/packages/ipp/request_test.go @@ -67,7 +67,7 @@ func TestUnmarshalRequestPrinterAttributes(T *testing.T) { 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).values + 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") diff --git a/packages/ipp/resolution.go b/packages/ipp/resolution.go new file mode 100644 index 0000000..4bc2fa4 --- /dev/null +++ b/packages/ipp/resolution.go @@ -0,0 +1,48 @@ +package ipp + +import ( + "encoding/binary" + "fmt" + "io" +) + +type resolution struct { + name string + crossFeedResolution int32 + feedResolution int32 + units int8 +} + +func NewResolution() *resolution { + r := new(resolution) + 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", r.crossFeedResolution, r.feedResolution, r.units) +} + +func (r *resolution) marshal() []byte { + return []byte{} +} + +func (r *resolution) valueTag() tag { + return resolutionValueTag +} diff --git a/packages/ipp/response.go b/packages/ipp/response.go index 2a86b39..39c3b6c 100644 --- a/packages/ipp/response.go +++ b/packages/ipp/response.go @@ -1,6 +1,9 @@ package ipp -import "encoding/binary" +import ( + "encoding/binary" + "fmt" +) type ippResponseHeader struct { versionNumber versionNumber @@ -8,6 +11,10 @@ type ippResponseHeader struct { 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 { a := make([]byte, 8, 8) binary.BigEndian.PutUint16(a[0:2], uint16(h.versionNumber)) @@ -53,7 +60,20 @@ func NewResponse(code statusCode, requestId uint32) *Response { // } func (r Response) String() string { - return "" + + 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 { diff --git a/packages/ipp/setofstrings.go b/packages/ipp/setofstrings.go new file mode 100644 index 0000000..485da7a --- /dev/null +++ b/packages/ipp/setofstrings.go @@ -0,0 +1,88 @@ +package ipp + +import "encoding/binary" + +type setOfStrings struct { + vTag tag + name string + values []string +} + +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 +} diff --git a/packages/mdnsserver/server.go b/packages/mdnsserver/server.go index 92f6ae4..92e1179 100644 --- a/packages/mdnsserver/server.go +++ b/packages/mdnsserver/server.go @@ -24,7 +24,7 @@ func Run(ctx context.Context) { log.Fatalf("EntryGroupNew() failed: %v", err) } - hostname, err := a.GetHostName() + //hostname, err := a.GetHostName() if err != nil { log.Fatalf("GetHostName() failed: %v", err) } @@ -40,7 +40,7 @@ func Run(ctx context.Context) { txt = append(txt, []byte("Color=T")) txt = append(txt, []byte("rp=ipp/print")) txt = append(txt, []byte("ty=ChroBro 001")) - err = eg.AddService(avahi.InterfaceUnspec, avahi.ProtoUnspec, 0, hostname, "_ipp._tcp", "local", fqdn, 1234, txt) + err = eg.AddService(avahi.InterfaceUnspec, avahi.ProtoUnspec, 0, "ChroBro 1000", "_ipp._tcp", "local", fqdn, 1234, txt) if err != nil { log.Fatalf("AddService() failed: %v", err) } -- 2.34.1 From 77ef627fe284da56002fb534bad86dd8253ea243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20S=C3=B6lver?= Date: Sat, 26 Dec 2020 20:58:38 +0100 Subject: [PATCH 6/6] More development. More types. Fixed attribute groups in requests. Started on client. Saving data to file. --- main.go | 77 ---------- packages/ipp/enum.go | 5 +- packages/ipp/messages.go | 44 +++--- packages/ipp/namewithoutlanguage.go | 20 ++- packages/ipp/operationid_string.go | 2 +- packages/ipp/printerstate_string.go | 2 +- packages/ipp/request.go | 210 ++++++++++++++++----------- packages/ipp/request_test.go | 2 +- packages/ipp/resolution.go | 21 ++- packages/ipp/response.go | 2 +- packages/ipp/setofstrings.go | 2 +- server/handlegetjobs.go | 8 + server/handlegetprinterattributes.go | 28 ++++ server/handleprintjob.go | 22 +++ server/main.go | 69 +++++++++ 15 files changed, 307 insertions(+), 207 deletions(-) delete mode 100644 main.go create mode 100644 server/handlegetjobs.go create mode 100644 server/handlegetprinterattributes.go create mode 100644 server/handleprintjob.go create mode 100644 server/main.go diff --git a/main.go b/main.go deleted file mode 100644 index 4010e48..0000000 --- a/main.go +++ /dev/null @@ -1,77 +0,0 @@ -package main - -import ( - "context" - "fmt" - "ippserver/packages/ipp" - "ippserver/packages/mdnsserver" - "net/http" - - log "github.com/sirupsen/logrus" -) - -func main() { - customFormatter := new(log.TextFormatter) - customFormatter.TimestampFormat = "2006-01-02 15:04:05" - log.SetFormatter(customFormatter) - customFormatter.FullTimestamp = true - log.SetLevel(log.DebugLevel) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - go mdnsserver.Run(ctx) - - http.HandleFunc("/ipp/print", handle) - - log.Info("http server started on :1234") - err := http.ListenAndServe(":1234", nil) - if err != nil { - log.Fatal("ListenAndServe: " + err.Error()) - } -} - -func handle(w http.ResponseWriter, r *http.Request) { - log.Infoln("handle") - if r.Method != http.MethodPost { - http.Error(w, "Unsupported method", http.StatusMethodNotAllowed) - - } - //body := make([]byte, r.ContentLength) - //io.ReadFull(r.Body, body) - //log.Infof("Body %x", body) - - request := ipp.NewRequest() - //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) - fmt.Printf("Request: \n%v\n", request) - response := ipp.NewResponse(ipp.SuccessfulOk, request.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")) - - fmt.Printf("Response:\n%v\n", response) - data := response.Marshal() - - log.Debugf("% x", data) - w.Write(data) - -} diff --git a/packages/ipp/enum.go b/packages/ipp/enum.go index 89b5309..4123774 100644 --- a/packages/ipp/enum.go +++ b/packages/ipp/enum.go @@ -5,6 +5,8 @@ import ( "encoding/binary" "fmt" "io" + + log "github.com/sirupsen/logrus" ) type enum struct { @@ -27,8 +29,7 @@ func (e *enum) valueTag() tag { } func (e *enum) unmarshal(byteStream io.Reader) { - //e.name, e.value = unmarshalSingleValue(byteStream) - + log.Warn("Unmarshal of enum is not implemented yet") } func (e *enum) marshal() []byte { diff --git a/packages/ipp/messages.go b/packages/ipp/messages.go index 1aa6a7c..39985f6 100644 --- a/packages/ipp/messages.go +++ b/packages/ipp/messages.go @@ -55,33 +55,33 @@ const ( ) // Operation-id, defined in rfc8011 -type operationId uint16 +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 + 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 const ( - Idle printerState = 3 - Preocessing printerState = 4 - Stopped printerState = 5 + Idle printerState = 3 + Processing printerState = 4 + Stopped printerState = 5 ) type statusCode uint16 @@ -101,17 +101,13 @@ func (v versionNumber) String() string { 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) } diff --git a/packages/ipp/namewithoutlanguage.go b/packages/ipp/namewithoutlanguage.go index 6888a5f..fdaac16 100644 --- a/packages/ipp/namewithoutlanguage.go +++ b/packages/ipp/namewithoutlanguage.go @@ -2,30 +2,30 @@ package ipp import "io" -type nameWithoutLanguage struct { +type NameWithoutLanguage struct { name string value string } -func NewNameWithoutLanguage(name, value string) *nameWithoutLanguage { - c := new(nameWithoutLanguage) +func NewNameWithoutLanguage(name, value string) *NameWithoutLanguage { + c := new(NameWithoutLanguage) c.name = name c.value = value return c } -func (c nameWithoutLanguage) String() string { +func (c NameWithoutLanguage) String() string { return c.name + ":" + c.value } -func (c *nameWithoutLanguage) valueTag() tag { +func (c *NameWithoutLanguage) valueTag() tag { return nameWithoutLanguageValueTag } -func (c *nameWithoutLanguage) unmarshal(byteStream io.Reader) { +func (c *NameWithoutLanguage) unmarshal(byteStream io.Reader) { c.name, c.value = unmarshalSingleValue(byteStream) } -func (c *nameWithoutLanguage) marshal() []byte { +func (c *NameWithoutLanguage) marshal() []byte { l := 5 + len(c.name) + len(c.value) b := make([]byte, l, l) b[0] = byte(nameWithoutLanguageValueTag) @@ -33,9 +33,13 @@ func (c *nameWithoutLanguage) marshal() []byte { return b } -func (c *nameWithoutLanguage) size() int { +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 +} diff --git a/packages/ipp/operationid_string.go b/packages/ipp/operationid_string.go index efb1b35..2d90b82 100644 --- a/packages/ipp/operationid_string.go +++ b/packages/ipp/operationid_string.go @@ -36,7 +36,7 @@ var ( _operationId_index_1 = [...]uint8{0, 12, 25, 34} ) -func (i operationId) String() string { +func (i OperationId) String() string { switch { case 2 <= i && i <= 14: i -= 2 diff --git a/packages/ipp/printerstate_string.go b/packages/ipp/printerstate_string.go index 4f13146..fb82ddd 100644 --- a/packages/ipp/printerstate_string.go +++ b/packages/ipp/printerstate_string.go @@ -9,7 +9,7 @@ func _() { // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[Idle-3] - _ = x[Preocessing-4] + _ = x[Processing-4] _ = x[Stopped-5] } diff --git a/packages/ipp/request.go b/packages/ipp/request.go index 42d5a60..c9ad66a 100644 --- a/packages/ipp/request.go +++ b/packages/ipp/request.go @@ -1,6 +1,7 @@ package ipp import ( + "bytes" "encoding/binary" "fmt" "io" @@ -10,16 +11,25 @@ import ( type ippRequestHeader struct { versionNumber versionNumber - operationId operationId + operationId OperationId requestId uint32 } -func (h *ippRequestHeader) marshal(byteStream io.Reader) { +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) } @@ -41,8 +51,11 @@ type Request struct { header ippRequestHeader } -func NewRequest() *Request { +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) @@ -67,7 +80,7 @@ func (r Request) String() string { } func (r *Request) UnMarshal(body io.Reader) { - r.header.marshal(body) + r.header.unmarshal(body) log.Debugf("Header %v", r.header) var tag tag err := binary.Read(body, binary.BigEndian, &tag) @@ -75,95 +88,116 @@ func (r *Request) UnMarshal(body io.Reader) { 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) + } - if tag == operationAttributes { - var lastAddValuer AddValuer - nextoperationattr: - for tag != endOfAttributes { - - err = binary.Read(body, binary.BigEndian, &tag) - if err != nil { - log.Error(err.Error()) - } - log.Debugf("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.Debugf("got tag - %v", tag) - err = binary.Read(body, binary.BigEndian, &tag) - if err != nil { - log.Error(err.Error()) - } - log.Debugf("got tag - %v", tag) - - break nextoperationattr - case charsetValueTag: - c := NewCharSetValue("", "") - c.unmarshal(body) - r.operationAttributes[c.name] = c - log.Debugf("%v %v", c.name, c.value) - case uriValueTag: - u := NewUriValue("", "") - u.unmarshal(body) - r.operationAttributes[u.name] = u - log.Debugf("%v %v", u.name, u.value) - case naturalLanguageValueTag: - n := NewNaturalLanguage("", "") - n.unmarshal(body) - r.operationAttributes[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) - r.operationAttributes[name] = k - lastAddValuer = k - } - log.Debugf("%v : %v", name, value) - case nameWithoutLanguageValueTag: - n := NewNameWithoutLanguage("", "") - n.unmarshal(body) - r.operationAttributes[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) - r.operationAttributes[name] = m - lastAddValuer = m - } - case jobAttributes: - log.Debug("Start job attributes") - case resolutionValueTag: - res := NewResolution() - res.unmarshal(body) - r.operationAttributes["resolution"] = res - log.Debugf("Resolution %v", res) - - default: - log.Errorf("Unsupported 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) } - 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 (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() +} diff --git a/packages/ipp/request_test.go b/packages/ipp/request_test.go index 3e522de..296f7af 100644 --- a/packages/ipp/request_test.go +++ b/packages/ipp/request_test.go @@ -59,7 +59,7 @@ var testRequest = []byte{0x01, 0x01, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x11, func TestUnmarshalRequestPrinterAttributes(T *testing.T) { buf := bytes.NewBuffer(testRequest) - req := NewRequest() + req := NewRequest(GetPrinterAttributes, 17) req.UnMarshal(buf) fmt.Print(req) diff --git a/packages/ipp/resolution.go b/packages/ipp/resolution.go index 4bc2fa4..9ba8fe9 100644 --- a/packages/ipp/resolution.go +++ b/packages/ipp/resolution.go @@ -1,6 +1,7 @@ package ipp import ( + "bytes" "encoding/binary" "fmt" "io" @@ -13,8 +14,12 @@ type resolution struct { units int8 } -func NewResolution() *resolution { +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 } @@ -36,11 +41,21 @@ func (r *resolution) unmarshal(byteStream io.Reader) { } func (r resolution) String() string { - return fmt.Sprintf("%v:%v:%v", r.crossFeedResolution, r.feedResolution, r.units) + return fmt.Sprintf("%v:%v,%v,%v", r.name, r.crossFeedResolution, r.feedResolution, r.units) } func (r *resolution) marshal() []byte { - return []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 { diff --git a/packages/ipp/response.go b/packages/ipp/response.go index 39c3b6c..fca8e1c 100644 --- a/packages/ipp/response.go +++ b/packages/ipp/response.go @@ -25,10 +25,10 @@ func (h *ippResponseHeader) marshal() []byte { } type Response struct { - header ippResponseHeader operationAttributes []Attribute jobAttributes []Attribute printerAttributes []Attribute + header ippResponseHeader } func NewResponse(code statusCode, requestId uint32) *Response { diff --git a/packages/ipp/setofstrings.go b/packages/ipp/setofstrings.go index 485da7a..241ff24 100644 --- a/packages/ipp/setofstrings.go +++ b/packages/ipp/setofstrings.go @@ -3,9 +3,9 @@ package ipp import "encoding/binary" type setOfStrings struct { - vTag tag name string values []string + vTag tag } func NewSetOfStrings(name string, t tag, values []string) *setOfStrings { diff --git a/server/handlegetjobs.go b/server/handlegetjobs.go new file mode 100644 index 0000000..5ba3aed --- /dev/null +++ b/server/handlegetjobs.go @@ -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 +} diff --git a/server/handlegetprinterattributes.go b/server/handlegetprinterattributes.go new file mode 100644 index 0000000..1b0c6dd --- /dev/null +++ b/server/handlegetprinterattributes.go @@ -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 +} diff --git a/server/handleprintjob.go b/server/handleprintjob.go new file mode 100644 index 0000000..b44f03d --- /dev/null +++ b/server/handleprintjob.go @@ -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 +} diff --git a/server/main.go b/server/main.go new file mode 100644 index 0000000..6e2e98f --- /dev/null +++ b/server/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "context" + "ippserver/packages/ipp" + "ippserver/packages/mdnsserver" + "net/http" + + log "github.com/sirupsen/logrus" +) + +func main() { + customFormatter := new(log.TextFormatter) + customFormatter.TimestampFormat = "2006-01-02 15:04:05" + log.SetFormatter(customFormatter) + customFormatter.FullTimestamp = true + log.SetLevel(log.InfoLevel) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go mdnsserver.Run(ctx) + + http.HandleFunc("/ipp/print", handle) + + log.Info("http server started on :1234") + err := http.ListenAndServe(":1234", nil) + if err != nil { + log.Fatal("ListenAndServe: " + err.Error()) + } +} + +func handle(w http.ResponseWriter, r *http.Request) { + log.Infoln("handle") + if r.Method != http.MethodPost { + http.Error(w, "Unsupported method", http.StatusMethodNotAllowed) + + } + log.Info(r.Header) + //body := make([]byte, r.ContentLength) + //io.ReadFull(r.Body, body) + //log.Infof("Body %x", body) + + 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) + log.Infof("Request: \n%v\n", request) + var response *ipp.Response + switch request.Operation() { + 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() + + //log.Debugf("% x", data) + w.Write(data) + +} -- 2.34.1