16 Commits

Author SHA1 Message Date
61abe8dbd4 Merge pull request 'Add flags to proxy and add license headers.' (#19) from add-flags-and-license-headers into master
Reviewed-on: #19
2021-06-08 20:06:55 +00:00
2d74da1c91 Add flags to proxy and add license headers. 2021-06-08 19:21:22 +00:00
21cf29f651 Avoid panic if printer is off or unreachable for some reason.
Closes #16
Closes #17
Closes #15
2021-05-23 16:08:48 +00:00
72ee8c7d1b Merge pull request 'add-proxy' (#14) from add-proxy into master
Reviewed-on: #14
2021-03-17 18:51:43 +01:00
henrik
27dec52bf7 CLeanups 2021-03-17 16:01:19 +01:00
617d7834cc A working proxy exists. 2021-02-28 10:29:54 +01:00
ce94bf0d89 Merge pull request 'Add unmarshalling to the boolean attribute.' (#13) from boolean-unmarshal into master
Reviewed-on: #13
2021-02-09 21:38:11 +01:00
b1fafb43b2 Add unmarshalling to the boolean attribute. 2021-02-09 21:34:58 +01:00
5c11761c48 Merge pull request 'Add collection type, use buffered bytestream.' (#12) from add-collection-type into master
Reviewed-on: #12
2021-02-09 21:34:12 +01:00
48aa81238d Add collection type, use buffered bytestream.
Fixed some more attribute types.
2021-02-09 21:31:27 +01:00
0805cec129 Fix lint warnings. 2021-01-09 12:10:43 +01:00
f449b535db Add rangeOfInteger attribute. 2020-12-29 21:45:00 +01:00
1eff8711d3 Add integer attribute type.
Merges some common code between integer and enum.
Enum can now bw a set.
Add valuer must take a interface{} in addValue since it can be different types.
2020-12-29 20:53:01 +01:00
e0a0c88d95 Start client implementation. 2020-12-28 20:16:59 +01:00
53aaaf5521 Merge attribute handling from request and response to commen code. 2020-12-28 20:05:01 +01:00
04a4b4157f 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>
2020-12-27 09:16:32 +01:00
43 changed files with 2143 additions and 366 deletions

125
client/main.go Normal file
View File

@@ -0,0 +1,125 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package main
import (
"bytes"
"fmt"
"io"
"ippserver/packages/ipp"
"net/http"
"os"
log "github.com/sirupsen/logrus"
)
const printerURI = "brn30055cb5e3ae.local:631/ipp/print"
//application/ipp
// brn30055cb5e3ae.local:631/ipp/print
func main() {
customFormatter := new(log.TextFormatter)
customFormatter.TimestampFormat = "2006-01-02 15:04:05"
log.SetFormatter(customFormatter)
customFormatter.FullTimestamp = true
log.SetLevel(log.InfoLevel)
request := ipp.NewRequest(ipp.GetPrinterAttributes, 10)
request.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8"))
request.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en"))
request.AddOperatonAttribute(ipp.NewURIValue("printer-uri", "ipp://"+printerURI))
r := request.Marshal()
b := bytes.NewBuffer(r)
httpResponse, err := http.Post("http://"+"brn30055cb5e3ae.local:631/ipp/print", "application/ipp", b)
if err != nil {
fmt.Print(err)
return
}
rb := ipp.NewResponse(0, 0)
rb.UnMarshal(httpResponse.Body)
fmt.Print(rb)
// fmt.Print("\n-------------\n")
// printFile("4-untitled")
}
func printFile(fname string) {
f, err := os.Open(fname)
if err != nil {
log.Errorf("Failed to open file %v", fname)
return
}
// fileContents, err := ioutil.ReadAll(f)
// if err != nil {
// log.Errorf("Failed to read file %v", fname)
// return
// }
request := ipp.NewRequest(ipp.PrintJob, 10)
request.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8"))
request.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en"))
request.AddOperatonAttribute(ipp.NewURIValue("printer-uri", "ipp://"+printerURI))
request.AddOperatonAttribute(ipp.NewNameWithoutLanguage("requesting-user-name", "chronos"))
request.AddOperatonAttribute(ipp.NewNameWithoutLanguage("job-name", "foobar"))
request.AddOperatonAttribute(ipp.NewMimeMediaType("document-format", "image/pwg-raster"))
//request.AddJobAttribute(ipp.NewNameWithoutLanguage("ColorModel", "Gray"))
//request.AddJobAttribute(ipp.NewNameWithoutLanguage("cupsPrintQuality", "Normal"))
//request.AddJobAttribute(ipp.NewNameWithoutLanguage("Duplex", "DuplexNoTumble"))
//request.AddJobAttribute(ipp.NewNameWithoutLanguage("job-originating-host-name", "localhost"))
//request.AddJobAttribute(ipp.NewURIValue("name string", value string)
request.AddJobAttribute(ipp.NewKeyWord("media", "iso_a4_210x297mm"))
//request.AddJobAttribute(ipp.NewKeyWord("multiple-document-handling", "separate-documents-collated-copies"))
request.AddJobAttribute(ipp.NewKeyWord("output-bin", "face-down"))
//request.AddJobAttribute(ipp.NewNameWithoutLanguage("PageSize", "A4"))
request.AddJobAttribute(ipp.NewKeyWord("print-color-mode", "monochrome"))
request.AddJobAttribute(ipp.NewSetOfResolution("printer-resolution", ipp.Resolution{CrossFeedResolution: 600, FeedResolution: 600, Unit: 3}))
request.AddJobAttribute(ipp.NewKeyWord("sides", "two-sided-long-edge"))
fmt.Print(request)
r := request.Marshal()
b := bytes.NewBuffer(r)
mr := io.MultiReader(b, f)
httpResponse, err := http.Post("http://"+"brn30055cb5e3ae.local:631/ipp/print", "application/ipp", mr)
if err != nil {
fmt.Print(err)
return
}
fmt.Print("\n---response------\n")
log.Printf("HTTP status: %v %v", httpResponse.StatusCode, httpResponse.Status)
// resp, err := io.ReadAll(httpResponse.Body)
// if err != nil {
// log.Error("could not read response")
// return
// }
// fmt.Printf("% x", resp)
rb := ipp.NewResponse(0, 0)
rb.UnMarshal(httpResponse.Body)
fmt.Print(rb)
}
/*
requesting-user-name:chronos (nameWithoutLanguagageValueTag)
job-name:2 - Untitled (nameWithoutLanguagageValueTag)
document-format : image/pwg-raster (mimeMediaTypeValueTag)
document-format : application/octet-stream (mimeMediaTypeValueTag)
PrinterAttributes
JobAttributes
ColorModel:Gray (nameWithoutLanguagageValueTag)
cupsPrintQuality:Normal (nameWithoutLanguagageValueTag)
Duplex:DuplexNoTumble (nameWithoutLanguagageValueTag)
job-originating-host-name:localhost (nameWithoutLanguagageValueTag)
job-uuid:urn:uuid:67a09d25-5df8-3026-6e9c-b55f7afee4ca (uriValueTag)
media : iso_a4_210x297mm (keywordValueTag)
multiple-document-handling : separate-documents-collated-copies (keywordValueTag)
output-bin : face-down (keywordValueTag)
PageSize:A4 (nameWithoutLanguagageValueTag)
print-color-mode : monochrome (keywordValueTag)
printer-resolution:[{600 600 3}] (resolutionValueTag)
sides : two-sided-long-edge (keywordValueTag)
*/

52
main.go
View File

@@ -1,52 +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
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()
request.UnMarshal(r.Body)
fmt.Printf("%v", request)
response := ipp.NewResponse(ipp.SuccessfulOk, request.RequestId())
a := ipp.NewCharSetValue("attributes-charset", "utf-8")
response.AddOperatonAttribute(a)
data := response.Marshal()
log.Infof("% x", data)
w.Write(data)
}

68
packages/ipp/boolean.go Normal file
View File

@@ -0,0 +1,68 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
import (
"bytes"
"encoding/binary"
"fmt"
"io"
)
// Boolean is the ipp attribute boolean
type Boolean struct {
name string
value bool
}
// NewBoolean creates a nre boolean attribute
func NewBoolean(name string, value bool) *Boolean {
b := new(Boolean)
b.name = name
b.value = value
return b
}
// Name gets tha name of the boolean attribute
func (b Boolean) Name() string {
return b.name
}
func (b Boolean) String() string {
return b.name + ":" + fmt.Sprint(b.value)
}
func (b *Boolean) valueTag() tag {
return booleanValueTag
}
func (b *Boolean) unmarshal(byteStream io.Reader) {
name, data := unmarshalSingleAttribute(byteStream)
b.name = name
if data[0] == 0 {
b.value = false
return
}
b.value = true
}
func (b *Boolean) marshal() []byte {
l := 5 + len(b.name)
ba := make([]byte, 0, l)
buf := bytes.NewBuffer(ba)
buf.WriteByte(byte(booleanValueTag))
binary.Write(buf, binary.BigEndian, uint16(len(b.name)))
buf.WriteString(b.name)
binary.Write(buf, binary.BigEndian, uint16(1))
if b.value {
buf.WriteByte(byte(1))
} else {
buf.WriteByte(byte(0))
}
return buf.Bytes()
}
func (b *Boolean) size() int {
l := 5 + len(b.name)
return l
}

View File

@@ -1,3 +1,5 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
type charsetAttribute struct {

View File

@@ -1,45 +1,52 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
import (
"io"
)
type charSetValue struct {
type CharSetValue struct {
name string
value string
}
func NewCharSetValue(name string, value string) *charSetValue {
c := new(charSetValue)
func NewCharSetValue(name string, value string) *CharSetValue {
c := new(CharSetValue)
c.name = name
c.value = value
return c
}
func (c charSetValue) String() string {
func (c CharSetValue) Name() string {
return c.name
}
func (c CharSetValue) String() string {
return c.name + ":" + c.value
}
func (c *charSetValue) valueTag() tag {
func (c *CharSetValue) valueTag() tag {
return charsetValueTag
}
func (c *charSetValue) unmarshal(byteStream io.Reader) {
func (c *CharSetValue) unmarshal(byteStream io.Reader) {
c.name, c.value = unmarshalSingleValue(byteStream)
}
func (c *charSetValue) marshal() []byte {
func (c *CharSetValue) marshal() []byte {
l := 5 + len(c.name) + len(c.value)
b := make([]byte, l, l)
b := make([]byte, l)
b[0] = byte(charsetValueTag)
marshalNameValue(c.name, c.value, b[1:])
return b
}
func (c *charSetValue) marshalInto([]byte) int {
func (c *CharSetValue) marshalInto([]byte) int {
return 0
}
func (c *charSetValue) size() int {
func (c *CharSetValue) size() int {
l := 1 + 4 // The attribute tag + 2 lengths
l += len(c.name)
l += len(c.value)

View File

@@ -0,0 +1,76 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
import (
"bufio"
"encoding/binary"
log "github.com/sirupsen/logrus"
)
// Currently the collection data is just consumed and dropped from the bytestream
func consumeCollection(byteStream *bufio.Reader) {
// RFC8010 Section 3.1.6
var length uint16
binary.Read(byteStream, binary.BigEndian, &length)
collectionName := make([]byte, length)
if length > 0 {
binary.Read(byteStream, binary.BigEndian, collectionName)
}
log.Info("collection name " + string(collectionName))
err := binary.Read(byteStream, binary.BigEndian, &length) //Always zero ??
if err != nil {
log.Fatal("error ", err.Error())
}
if length != 0 {
log.Fatal("Should be zero")
}
// Member attributes
done:
for {
var t tag
binary.Read(byteStream, binary.BigEndian, &t)
log.Debug("Collection tag ", t)
switch t {
case endCollectionValueTag:
binary.Read(byteStream, binary.BigEndian, &length)
if length != 0 {
log.Fatal("Should be zero")
}
binary.Read(byteStream, binary.BigEndian, &length)
if length != 0 {
log.Fatal("Should be zero")
}
case memberAttrNameValueTag:
// RFC8010 Section 3.7.1
binary.Read(byteStream, binary.BigEndian, &length) //Always zero ??
if length != 0 {
log.Fatal("Should be zero")
}
binary.Read(byteStream, binary.BigEndian, &length) // Value length
memberName := make([]byte, length)
binary.Read(byteStream, binary.BigEndian, memberName)
log.Debugf("Member name: %v", string(memberName))
var memberValueTag tag
binary.Read(byteStream, binary.BigEndian, &memberValueTag)
log.Debug("Member value tag: ", memberValueTag)
binary.Read(byteStream, binary.BigEndian, &length) //Always zero ??
if length != 0 {
log.Fatal("Should be zero")
}
var memberValueLength uint16
binary.Read(byteStream, binary.BigEndian, &memberValueLength)
memberValue := make([]byte, memberValueLength)
binary.Read(byteStream, binary.BigEndian, memberValue)
log.Debugf("Member Value: % x", memberValue)
default:
// Next tag is one that can not be handled in the collection
// Put it back in the byte stream and return to main loop
byteStream.UnreadByte()
break done
}
}
log.Debug("Collection done")
}

43
packages/ipp/enum.go Normal file
View File

@@ -0,0 +1,43 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
import (
"fmt"
)
type Enum struct {
name string
values []int32
}
func NewEnum(name string, values ...int32) *Enum {
e := new(Enum)
e.name = name
e.values = values
return e
}
func (e Enum) Name() string {
return e.name
}
func (e Enum) String() string {
return e.name + ":" + fmt.Sprint(e.values)
}
func (e *Enum) valueTag() tag {
return enumValueTag
}
func (e *Enum) size() int {
return 9 + len(e.name)
}
func (e *Enum) addValue(v interface{}) {
e.values = append(e.values, v.(int32))
}
func (e *Enum) marshal() []byte {
return marshalInteger(enumValueTag, e.name, e.values)
}

69
packages/ipp/integer.go Normal file
View File

@@ -0,0 +1,69 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
import (
"bytes"
"encoding/binary"
"fmt"
"io"
)
type Integer struct {
name string
values []int32
}
func NewInteger(name string, values ...int32) *Integer {
e := new(Integer)
e.name = name
e.values = values
return e
}
func (i Integer) Name() string {
return i.name
}
func (i Integer) String() string {
return i.name + ":" + fmt.Sprint(i.values)
}
func (i *Integer) valueTag() tag {
return integerValueTag
}
func (i *Integer) size() int {
return 9 + len(i.name) // The attribute tag + 2 lengths
}
func (i *Integer) addValue(v interface{}) {
i.values = append(i.values, v.(int32))
}
func (i *Integer) marshal() []byte {
return marshalInteger(integerValueTag, i.name, i.values)
}
func marshalInteger(t tag, name string, values []int32) []byte {
l := 9 + len(name) + 9*(len(values)-1)
b := make([]byte, 0, l)
buf := bytes.NewBuffer(b)
buf.WriteByte(byte(t))
binary.Write(buf, binary.BigEndian, uint16(len(name)))
buf.WriteString(name)
binary.Write(buf, binary.BigEndian, uint16(4))
binary.Write(buf, binary.BigEndian, values[0])
for _, v := range values[1:] {
buf.WriteByte(byte(t))
binary.Write(buf, binary.BigEndian, uint16(0))
binary.Write(buf, binary.BigEndian, uint16(4))
binary.Write(buf, binary.BigEndian, v)
}
return buf.Bytes()
}
func unmarshalSingleInteger(byteStream io.Reader) (string, int32) {
name, data := unmarshalSingleAttribute(byteStream)
return name, int32(binary.BigEndian.Uint32(data))
}

View File

@@ -0,0 +1,40 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
)
func TestUnMarshalSinglePositiveInteger(T *testing.T) {
testdata := []byte{
0x00, 0x04,
0x66, 0x6c, 0x6f, 0x70, //flop
0x00, 0x04,
0x00, 0x0, 0x0, 0x4,
}
buf := bytes.NewBuffer(testdata)
n, v := unmarshalSingleInteger(buf)
assert.Equal(T, "flop", n, "Should be equal")
assert.Equal(T, int32(4), v, "Should be equal")
}
func TestUnMarshalSingleNegativeInteger(T *testing.T) {
testdata := []byte{
0x00, 0x04,
0x66, 0x6c, 0x6f, 0x70, //flop
0x00, 0x04,
0xff, 0xff, 0xff, 0xfc,
}
buf := bytes.NewBuffer(testdata)
n, v := unmarshalSingleInteger(buf)
assert.Equal(T, "flop", n, "Should be equal")
assert.Equal(T, int32(-4), v, "Should be equal")
}

View File

@@ -1,45 +1,39 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
import "io"
type keyWord struct {
name string
values []string
type KeyWord struct {
sos *SetOfStrings
}
func newKeyWord() *keyWord {
k := new(keyWord)
func NewKeyWord(name string, values ...string) *KeyWord {
k := new(KeyWord)
k.sos = NewSetOfStrings(name, keyWordValueTag, values)
return k
}
func (k *keyWord) string() string {
return "a uriValue"
}
func (k *keyWord) valueTag() tag {
return keyWordValueTag
func (k KeyWord) Name() string {
return k.sos.name
}
func (k *keyWord) unmarshal(byteStream io.Reader) {
func (k KeyWord) String() string {
return k.sos.String()
}
func (k *keyWord) marshal() []byte {
return []byte{}
func (k *KeyWord) size() int {
return k.sos.size()
}
func (k *keyWord) addValue(v string) {
k.values = append(k.values, v)
func (k *KeyWord) valueTag() tag {
return k.sos.valueTag()
}
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
func (k *KeyWord) marshal() []byte {
return k.sos.marshal()
}
func (k *KeyWord) addValue(v interface{}) {
k.sos.AddValue(v.(string))
}

View File

@@ -0,0 +1,38 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
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")
}

View File

@@ -1,7 +1,14 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
//Package ipp provides functonality to handle ipp messages
//go:generate stringer -type jobState -type printerState
package ipp
import (
"bufio"
"encoding/binary"
"errors"
"fmt"
"io"
@@ -12,6 +19,9 @@ import (
// https://tools.ietf.org/html/rfc8010
// https://tools.ietf.org/html/rfc8011
// ErrNilAttribute is returned when an attempt to use nil as a attribute
var ErrNilAttribute = errors.New("can not add use nil as attribute")
// Defined value tags
// from rfc8010
type tag uint8
@@ -45,44 +55,70 @@ 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
type operationId uint16
// OperationID is defined in rfc8011
type OperationID uint16
const (
PrintJob operationId = 0x0002
PrintURI operationId = 0x0003
ValidateJob operationId = 0x0004
CreateJob operationId = 0x0005
SendDocument operationId = 0x0006
SendURI operationId = 0x0007
CancelJob operationId = 0x0008
GetJobAttributes operationId = 0x0009
GetJobs operationId = 0x000a
GetPrinterAttributes operationId = 0x000b
HoldJob operationId = 0x000c
ReleaseJob operationId = 0x000d
RestartJob operationId = 0x000e
PausePrinter operationId = 0x0010
ResumePrinter operationId = 0x0011
PurgeJobs operationId = 0x0012
PrintJob OperationID = 0x0002
PrintURI OperationID = 0x0003
ValidateJob OperationID = 0x0004
CreateJob OperationID = 0x0005
SendDocument OperationID = 0x0006
SendURI OperationID = 0x0007
CancelJob OperationID = 0x0008
GetJobAttributes OperationID = 0x0009
GetJobs OperationID = 0x000a
GetPrinterAttributes OperationID = 0x000b
HoldJob OperationID = 0x000c
ReleaseJob OperationID = 0x000d
RestartJob OperationID = 0x000e
PausePrinter OperationID = 0x0010
ResumePrinter OperationID = 0x0011
PurgeJobs OperationID = 0x0012
)
type printerState int32
// printerstate defenitions
const (
Idle printerState = 3
Processing printerState = 4
Stopped printerState = 5
)
// jobstate defined in rfc8011 ch 5.3.7
type jobState uint16
const (
pending jobState = 3
pendingHeld jobState = 4
processing jobState = 5
processingStoppped jobState = 6
cancelled jobState = 7
aborted jobState = 8
completed jobState = 9
)
type statusCode uint16
// status code defenitions
const (
SuccessfulOk statusCode = 0x0000
ClientErrorBadRequest statusCode = 0x0400
SuccessfulOk statusCode = 0x0000
SuccessfulOkIgnoredOrSubstitutedAttributes statusCode = 0x0001
SuccessfulOkConflictingAttributes statusCode = 0x0002
ClientErrorBadRequest statusCode = 0x0400
ServerErrorServiceUnavailable statusCode = 0x0502
)
type versionNumber uint16
@@ -92,151 +128,29 @@ 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())
func unmarshalSingleAttribute(byteStream io.Reader) (string, []byte) {
var length uint16
binary.Read(byteStream, binary.BigEndian, &length)
attributeName := make([]byte, length)
if length > 0 {
binary.Read(byteStream, binary.BigEndian, attributeName)
}
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
binary.Read(byteStream, binary.BigEndian, &length)
attributeValue := make([]byte, length)
binary.Read(byteStream, binary.BigEndian, attributeValue)
return string(attributeName), attributeValue
}
func unmarshalSingleValue(byteStream io.Reader) (string, string) {
var length uint16
binary.Read(byteStream, binary.BigEndian, &length)
//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)
}
@@ -250,3 +164,188 @@ func marshalNameValue(name, value string, b []byte) {
p += 2
copy(b[p:], []byte(value))
}
type Attribute interface {
Name() string
valueTag() tag
marshal() []byte
//size() int
}
type Attributes struct {
operation []Attribute
printer []Attribute
job []Attribute
unsupported []Attribute
}
func (a *Attributes) String() string {
s := " OperationAttributes" + "\n"
for _, a := range a.operation {
s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag())
}
s = s + " PrinterAttributes" + "\n"
for _, a := range a.printer {
s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag())
}
s = s + " JobAttributes" + "\n"
for _, a := range a.job {
s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag())
}
s = s + " Unsupported" + "\n"
for _, a := range a.unsupported {
s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag())
}
return s
}
func (a *Attributes) addAttribute(group tag, attr Attribute) {
switch group {
case operationAttributes:
a.operation = append(a.operation, attr)
case jobAttributes:
a.job = append(a.job, attr)
case printerAttributes:
a.printer = append(a.printer, attr)
case unsupportedAttributes:
a.unsupported = append(a.unsupported, attr)
default:
log.Errorf("Unknown attribute group %v", group)
}
}
func UnMarshalAttributes(bytestream *bufio.Reader) *Attributes {
a := new(Attributes)
var t tag
err := binary.Read(bytestream, binary.BigEndian, &t)
if err != nil {
log.Error(err.Error())
}
log.Debugf("got tag - %v", t)
if t != operationAttributes && t != jobAttributes && t != printerAttributes {
log.Errorf("Unknown attribute group tag %v", t)
return nil
}
currentAttributeGroup := t
var lastAddValuer AddValuer
for {
err = binary.Read(bytestream, binary.BigEndian, &t)
if err != nil {
log.Fatal("End of input before end of attributes tag (%v)", err.Error())
}
log.Debugf("Value tag - %v", t)
switch t {
case endOfAttributes:
return a
case charsetValueTag:
c := NewCharSetValue("", "")
c.unmarshal(bytestream)
a.addAttribute(currentAttributeGroup, c)
log.Debugf("%v %v", c.name, c.value)
case booleanValueTag:
na := NewBoolean("", false)
na.unmarshal(bytestream)
a.addAttribute(currentAttributeGroup, na)
case uriValueTag:
u := NewURIValue("", "")
u.unmarshal(bytestream)
a.addAttribute(currentAttributeGroup, u)
log.Debugf("%v %v", u.name, u.value)
case naturalLanguageValueTag:
n := NewNaturalLanguage("", "")
n.unmarshal(bytestream)
a.addAttribute(currentAttributeGroup, n)
log.Debugf("%v %v", n.name, n.value)
case keyWordValueTag:
name, value := unmarshalSingleValue(bytestream)
if name == "" {
lastAddValuer.addValue(value)
} else {
k := NewKeyWord(name, value)
a.addAttribute(currentAttributeGroup, k)
lastAddValuer = k
}
log.Debugf("%v : %v", name, value)
case nameWithoutLanguageValueTag:
n := NewNameWithoutLanguage("", "")
n.unmarshal(bytestream)
a.addAttribute(currentAttributeGroup, n)
log.Debugf("%v %v", n.name, n.value)
case textWithoutLanguageValueTag:
attr := NewtextWithoutLanguage("", "")
attr.unmarshal(bytestream)
a.addAttribute(currentAttributeGroup, attr)
log.Debugf("%v %v", attr.name, attr.value)
case mimeMediaTypeValueTag:
name, value := unmarshalSingleValue(bytestream)
if name == "" {
lastAddValuer.addValue(value)
} else {
m := NewMimeMediaType(name, value)
a.addAttribute(currentAttributeGroup, m)
lastAddValuer = m
}
log.Debugf("%v : %v", name, value)
case integerValueTag:
name, value := unmarshalSingleInteger(bytestream)
if name == "" {
lastAddValuer.addValue(value)
} else {
i := NewInteger(name, value)
a.addAttribute(currentAttributeGroup, i)
lastAddValuer = i
}
log.Debugf("%v : %v", name, value)
case rangeOfIntegerValueTag:
name, value := unmarshalSingleRangeOfInteger(bytestream)
if name == "" {
lastAddValuer.addValue(value)
} else {
r := NewRangeOfInteger(name, value)
a.addAttribute(currentAttributeGroup, r)
lastAddValuer = r
}
log.Debugf("%v : %v", name, value)
case enumValueTag:
name, value := unmarshalSingleInteger(bytestream)
if name == "" {
lastAddValuer.addValue(value)
} else {
e := NewEnum(name, value)
a.addAttribute(currentAttributeGroup, e)
lastAddValuer = e
}
log.Debugf("%v : %v", name, value)
case begCollectionValueTag:
// For now just consume the collection
consumeCollection(bytestream)
case unsupportedValueTag:
attr := NewUnsupportedValue()
attr.unmarshal(bytestream)
a.addAttribute(currentAttributeGroup, attr)
case jobAttributes:
log.Debug("Start job attributes")
currentAttributeGroup = jobAttributes
case printerAttributes:
log.Debug("Start printer attributes")
currentAttributeGroup = printerAttributes
case operationAttributes:
log.Debug("Start operation attributes")
currentAttributeGroup = operationAttributes
case unsupportedAttributes:
log.Debug("Start unsupported attributes")
currentAttributeGroup = unsupportedAttributes
case resolutionValueTag:
res := NewSetOfResolution("")
res.unmarshal(bytestream)
a.addAttribute(currentAttributeGroup, res)
log.Debugf("Resolution %v", res)
default:
log.Errorf("Unsupported tag %v (%x)", t, uint8(t))
}
}
}

View File

@@ -0,0 +1,38 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
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) Name() string {
return m.sos.name
}
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 interface{}) {
m.sos.AddValue(v.(string))
}

View File

@@ -0,0 +1,51 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
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 (n NameWithoutLanguage) Name() string {
return n.name
}
func (n NameWithoutLanguage) String() string {
return n.name + ":" + n.value
}
func (n *NameWithoutLanguage) valueTag() tag {
return nameWithoutLanguageValueTag
}
func (n *NameWithoutLanguage) unmarshal(byteStream io.Reader) {
n.name, n.value = unmarshalSingleValue(byteStream)
}
func (n *NameWithoutLanguage) marshal() []byte {
l := 5 + len(n.name) + len(n.value)
b := make([]byte, l)
b[0] = byte(nameWithoutLanguageValueTag)
marshalNameValue(n.name, n.value, b[1:])
return b
}
func (n *NameWithoutLanguage) size() int {
l := 1 + 4 // The attribute tag + 2 lengths
l += len(n.name)
l += len(n.value)
return l
}
func (n NameWithoutLanguage) Value() string {
return n.value
}

View File

@@ -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
}

View File

@@ -0,0 +1,50 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
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) Name() string {
return c.name
}
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)
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
}

View File

@@ -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

View File

@@ -0,0 +1,26 @@
// Code generated by "stringer -type jobState -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 = "IdleProcessingStopped"
var _printerState_index = [...]uint8{0, 4, 14, 21}
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]]
}

View File

@@ -0,0 +1,57 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
import (
"encoding/binary"
"fmt"
"io"
log "github.com/sirupsen/logrus"
)
type IRange struct {
lower int32
upper int32
}
type RangeOfInteger struct {
name string
values []IRange
}
func NewRangeOfInteger(name string, values ...IRange) *RangeOfInteger {
r := new(RangeOfInteger)
r.name = name
r.values = values
return r
}
func (r *RangeOfInteger) Name() string {
return r.name
}
func (r RangeOfInteger) String() string {
return r.name + ":" + fmt.Sprint(r.values)
}
func (r *RangeOfInteger) valueTag() tag {
return rangeOfIntegerValueTag
}
func (r *RangeOfInteger) marshal() []byte {
log.Error("marshal rangeOfInteger is not implemented yet")
return []byte{}
}
func (r *RangeOfInteger) addValue(v interface{}) {
r.values = append(r.values, v.(IRange))
}
func unmarshalSingleRangeOfInteger(byteStream io.Reader) (string, IRange) {
name, data := unmarshalSingleAttribute(byteStream)
var r IRange
r.lower = int32(binary.BigEndian.Uint32(data[0:4]))
r.upper = int32(binary.BigEndian.Uint32(data[4:8]))
return name, r
}

View File

@@ -0,0 +1,26 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
)
func TestUnMarshalSingleRange(T *testing.T) {
testdata := []byte{
0x00, 0x04,
0x66, 0x6c, 0x6f, 0x70, //flop
0x00, 0x08,
0x00, 0x0, 0x0, 0x4, 0x00, 0x0, 0x0, 0x5,
}
buf := bytes.NewBuffer(testdata)
n, v := unmarshalSingleRangeOfInteger(buf)
assert.Equal(T, "flop", n, "Should be equal")
assert.Equal(T, int32(4), v.lower, "Should be equal")
assert.Equal(T, int32(5), v.upper, "Should be equal")
}

151
packages/ipp/request.go Normal file
View File

@@ -0,0 +1,151 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
import (
"bufio"
"bytes"
"encoding/binary"
"fmt"
"io"
)
type ippMessageHeader struct {
versionNumber versionNumber
operationID OperationID
requestID uint32
}
func (h *ippMessageHeader) 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 *ippMessageHeader) 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 ippMessageHeader) String() string {
return fmt.Sprintf("Version number: %v Operation Id: %v Request Id: %v", h.versionNumber, h.operationID, h.requestID)
}
type AddValuer interface {
addValue(interface{})
}
type Request struct {
a *Attributes
header ippMessageHeader
}
func NewRequest(op OperationID, requestID uint32) *Request {
r := new(Request)
r.header.operationID = op
r.header.requestID = requestID
r.header.versionNumber = 0x0200
r.a = new(Attributes)
return r
}
func (r Request) String() string {
return r.header.String() + "\n" + r.a.String()
}
func (r *Request) UnMarshal(body io.Reader) {
buffbody := bufio.NewReader(body)
r.header.unmarshal(buffbody)
r.a = UnMarshalAttributes(buffbody)
}
func (r *Request) RequestID() uint32 {
return r.header.requestID
}
// Operation returns the operation is of the request.
func (r *Request) Operation() OperationID {
return r.header.operationID
}
func (r *Request) GetAttribute(name string) Attribute {
for _, a := range r.a.operation {
if a.Name() == name {
return a
}
}
for _, a := range r.a.job {
if a.Name() == name {
return a
}
}
for _, a := range r.a.printer {
if a.Name() == name {
return a
}
}
for _, a := range r.a.unsupported {
if a.Name() == name {
return a
}
}
return nil
}
// Marshal converts the request object to a ipp request
func (r *Request) Marshal() []byte {
var buf bytes.Buffer
buf.Write(r.header.marshal())
if len(r.a.operation) > 0 {
buf.WriteByte(byte(operationAttributes))
for _, e := range r.a.operation {
buf.Write(e.marshal())
}
}
if len(r.a.job) > 0 {
buf.WriteByte(byte(jobAttributes))
for _, e := range r.a.job {
buf.Write(e.marshal())
}
}
if len(r.a.printer) > 0 {
buf.WriteByte(byte(printerAttributes))
for _, e := range r.a.printer {
buf.Write(e.marshal())
}
}
buf.WriteByte(byte(endOfAttributes))
return buf.Bytes()
}
// AddPrinterAttribute adds a printer attribute to the request object
func (r *Request) AddPrinterAttribute(a Attribute) error {
if a != nil {
r.a.addAttribute(printerAttributes, a)
return nil
}
return ErrNilAttribute
}
// AddOperatonAttribute adds a operation attribute to the request object
func (r *Request) AddOperatonAttribute(a Attribute) error {
if a != nil {
r.a.addAttribute(operationAttributes, a)
return nil
}
return ErrNilAttribute
}
// AddJobAttribute adds a job attribute to the request object
func (r *Request) AddJobAttribute(a Attribute) error {
if a != nil {
r.a.addAttribute(jobAttributes, a)
return nil
}
return ErrNilAttribute
}

View File

@@ -0,0 +1,81 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
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.a.operation, 4)
v := req.GetAttribute("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")
}

View File

@@ -0,0 +1,97 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
import (
"bufio"
"bytes"
"encoding/binary"
"fmt"
log "github.com/sirupsen/logrus"
)
// Resolution represents a ipp resolution attribute
type Resolution struct {
CrossFeedResolution int32
FeedResolution int32
Unit int8 // 3 seems to mean dpi (rfc3805)
}
// SetOfResolutions represents a set ipp resolution attributes
type SetOfResolutions struct {
name string
sor []Resolution
}
// NewSetOfResolution creats a new set of ipp resolution attributes
func NewSetOfResolution(name string, resSet ...Resolution) *SetOfResolutions {
r := new(SetOfResolutions)
r.name = name
r.sor = resSet
return r
}
func (r *SetOfResolutions) unmarshal(byteStream *bufio.Reader) {
var length uint16
var res Resolution
for {
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, &res.CrossFeedResolution)
binary.Read(byteStream, binary.BigEndian, &res.FeedResolution)
binary.Read(byteStream, binary.BigEndian, &res.Unit)
r.sor = append(r.sor, res)
var t tag
err := binary.Read(byteStream, binary.BigEndian, &t)
if err != nil {
log.Error(err.Error())
}
if t != resolutionValueTag {
byteStream.UnreadByte()
return
}
}
}
// Name returns the name of the attribute
func (r SetOfResolutions) Name() string {
return r.name
}
func (r SetOfResolutions) String() string {
return fmt.Sprintf("%v:%v", r.name, r.sor)
}
func (r *SetOfResolutions) marshal() []byte {
b := make([]byte, 0, 14+len(r.name)+14*(len(r.sor)-1))
buf := bytes.NewBuffer(b)
for i, res := range r.sor {
buf.WriteByte(byte(resolutionValueTag))
if i == 0 {
binary.Write(buf, binary.BigEndian, uint16(len(r.name)))
buf.WriteString(r.name)
} else {
binary.Write(buf, binary.BigEndian, uint16(0))
}
binary.Write(buf, binary.BigEndian, uint16(9))
binary.Write(buf, binary.BigEndian, int32(res.CrossFeedResolution))
binary.Write(buf, binary.BigEndian, int32(res.FeedResolution))
buf.WriteByte(byte(res.Unit))
}
return buf.Bytes()
}
func (r *SetOfResolutions) valueTag() tag {
return resolutionValueTag
}

View File

@@ -1,91 +1,146 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
import "encoding/binary"
import (
"bufio"
"encoding/binary"
"fmt"
"io"
)
type ippResponseHeader struct {
versionNumber versionNumber
statusCode statusCode
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 {
a := make([]byte, 8, 8)
a := make([]byte, 8)
binary.BigEndian.PutUint16(a[0:2], uint16(h.versionNumber))
binary.BigEndian.PutUint16(a[2:4], uint16(h.statusCode))
binary.BigEndian.PutUint32(a[4:8], h.requestId)
binary.BigEndian.PutUint32(a[4:8], h.requestID)
return a
}
type Response struct {
header ippResponseHeader
operationAttributes []Attribute
jobAttributes []Attribute
printerAttributes []Attribute
func (h *ippResponseHeader) unmarshal(byteStream io.Reader) {
binary.Read(byteStream, binary.BigEndian, &h.versionNumber)
binary.Read(byteStream, binary.BigEndian, &h.statusCode)
binary.Read(byteStream, binary.BigEndian, &h.requestID)
}
func NewResponse(code statusCode, requestId uint32) *Response {
// Response represens a ipp response object
type Response struct {
a *Attributes
header ippResponseHeader
}
// NewResponse creates a new ipp response object
func NewResponse(code statusCode, requestID uint32) *Response {
r := new(Response)
r.a = new(Attributes)
r.header.versionNumber = 0x0101
r.header.requestId = requestId
r.header.requestID = requestID
r.header.statusCode = code
r.operationAttributes = make([]Attribute, 0)
r.printerAttributes = make([]Attribute, 0)
r.jobAttributes = make([]Attribute, 0)
return r
}
// func (r *Response) length() int {
// l := 8 + 1
// if len(r.jobAttributes) > 0 {
// l += 1 //
// for _, e := range r.jobAttributes {
// l += e.length()
// }
// }
// for _, e := range r.operationAttributes {
// l += e.length()
// }
// for _, e := range r.printerAttributes {
// l += e.length()
// }
func (r Response) String() string {
return r.header.String() + "\n" + r.a.String()
}
// }
func (r Response) Header() ippResponseHeader {
return r.header
}
// Marshal converts the response object to a wire formatted byte stream.
func (r *Response) Marshal() []byte {
a := make([]byte, 0, 20)
a = append(a, r.header.marshal()...)
if len(r.operationAttributes) > 0 {
if len(r.a.operation) > 0 {
a = append(a, byte(operationAttributes))
for _, e := range r.operationAttributes {
for _, e := range r.a.operation {
a = append(a, e.marshal()...)
}
}
if len(r.jobAttributes) > 0 {
if len(r.a.job) > 0 {
a = append(a, byte(jobAttributes))
for _, e := range r.jobAttributes {
for _, e := range r.a.job {
a = append(a, e.marshal()...)
}
}
if len(r.printerAttributes) > 0 {
if len(r.a.printer) > 0 {
a = append(a, byte(printerAttributes))
for _, e := range r.printerAttributes {
for _, e := range r.a.printer {
a = append(a, e.marshal()...)
}
}
a = append(a, byte(endOfAttributes))
return a
}
func (r *Response) AddPrinterAttribute(a Attribute) {
r.printerAttributes = append(r.printerAttributes, a)
// UnMarshal unmarshals a ipp response into a response object
func (r *Response) UnMarshal(body io.Reader) {
buffbody := bufio.NewReader(body)
r.header.unmarshal(buffbody)
r.a = UnMarshalAttributes(buffbody)
}
func (r *Response) AddOperatonAttribute(a Attribute) {
r.operationAttributes = append(r.operationAttributes, a)
// AddPrinterAttribute adds a printer attribute to the response object
func (r *Response) AddPrinterAttribute(a Attribute) error {
if a != nil {
r.a.addAttribute(printerAttributes, a)
return nil
}
return ErrNilAttribute
}
func (r *Response) AddJobAttribute(a Attribute) {
r.jobAttributes = append(r.jobAttributes, a)
// AddOperatonAttribute adds a printer attribute to the response object
func (r *Response) AddOperatonAttribute(a Attribute) error {
if a != nil {
r.a.addAttribute(operationAttributes, a)
return nil
}
return ErrNilAttribute
}
// AddJobAttribute adds a printer attribute to the response object
func (r *Response) AddJobAttribute(a Attribute) error {
if a != nil {
r.a.addAttribute(jobAttributes, a)
return nil
}
return ErrNilAttribute
}
// GetAttribute retreives a attribute by name from the response object
// returns nil a atribute with the provided name can be found.
func (r *Response) GetAttribute(name string) Attribute {
for _, a := range r.a.operation {
if a.Name() == name {
return a
}
}
for _, a := range r.a.job {
if a.Name() == name {
return a
}
}
for _, a := range r.a.printer {
if a.Name() == name {
return a
}
}
for _, a := range r.a.unsupported {
if a.Name() == name {
return a
}
}
return nil
}

View File

@@ -1,3 +1,5 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
import (
@@ -10,7 +12,7 @@ func TestMarshalResponseHeader(T *testing.T) {
h.versionNumber = 0x0101
h.statusCode = SuccessfulOk
h.requestId = 0xdeadbeef
h.requestID = 0xdeadbeef
b := h.marshal()
fmt.Printf("% x\n", b)

View File

@@ -0,0 +1,94 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
import "encoding/binary"
// SetOfStrings is the strings attribute
type SetOfStrings struct {
name string
values []string
vTag tag
}
// NewSetOfStrings creates a new strings attribute
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)
p := 0
res[p] = byte(s.vTag)
p++
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++
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
}
// AddValue adds a new sring to the set
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
}

View File

@@ -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]
}

View File

@@ -0,0 +1,51 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
import "io"
// TextWithoutLanguage is the TextWithoutLanguage attribute
type TextWithoutLanguage struct {
name string
value string
}
// NewtextWithoutLanguage creates a new textWithoutLanguage attribute
func NewtextWithoutLanguage(name, value string) *TextWithoutLanguage {
c := new(TextWithoutLanguage)
c.name = name
c.value = value
return c
}
// Name returns the name of the attribute
func (c TextWithoutLanguage) Name() string {
return c.name
}
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)
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
}

View File

@@ -0,0 +1,42 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
import (
"io"
log "github.com/sirupsen/logrus"
)
// UnSupportedValue represents a ipp unsupported attributes attribute
type UnSupportedValue struct {
name string
}
// NewUnsupportedValue creates a new unsupported attributes attribute
func NewUnsupportedValue() *UnSupportedValue {
c := new(UnSupportedValue)
return c
}
func (c *UnSupportedValue) unmarshal(byteStream io.Reader) {
c.name, _ = unmarshalSingleValue(byteStream)
}
// Name returns the name of the attribute
func (c *UnSupportedValue) Name() string {
return c.name
}
func (c *UnSupportedValue) marshal() (data []byte) {
log.Error("Unmarshal unsupported value is not implemented yet")
return
}
func (c *UnSupportedValue) valueTag() tag {
return unsupportedValueTag
}
func (c UnSupportedValue) String() string {
return c.name
}

View File

@@ -1,40 +1,54 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
import (
"io"
)
type uriValue struct {
// URIValue represents a ipp URI value attribute
type URIValue struct {
name string
value string
}
func newUriValue(name, value string) *uriValue {
u := new(uriValue)
// NewURIValue creates a new URIValue attribute
func NewURIValue(name, value string) *URIValue {
u := new(URIValue)
u.name = name
u.value = value
return u
}
func (u uriValue) String() string {
// Name returns the name of the attribute
func (u URIValue) Name() string {
return u.name
}
// Value returns the value of the attribute
func (u URIValue) Value() string {
return u.value
}
func (u URIValue) String() string {
return u.name + ":" + u.value
}
func (u *uriValue) valueTag() tag {
func (u *URIValue) valueTag() tag {
return uriValueTag
}
func (u *uriValue) unmarshal(byteStream io.Reader) {
func (u *URIValue) unmarshal(byteStream io.Reader) {
u.name, u.value = unmarshalSingleValue(byteStream)
}
func (u *uriValue) marshal() []byte {
func (u *URIValue) marshal() []byte {
res := make([]byte, u.size())
res[0] = byte(uriValueTag)
marshalNameValue(u.name, u.value, res[1:])
return res
}
func (u *uriValue) size() int {
func (u *URIValue) size() int {
l := 1 + 4 // The attribute tag + 2 lengths
l += len(u.name)
l += len(u.value)

View File

@@ -1,3 +1,5 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
import (
@@ -8,7 +10,7 @@ import (
)
func TestMarshalUriValue(T *testing.T) {
var u uriValue
var u URIValue
u.name = "foo"
u.value = "bar"
b := u.marshal()

View File

@@ -1,7 +1,10 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package mdnsserver
import (
"context"
"fmt"
log "github.com/sirupsen/logrus"
@@ -9,44 +12,40 @@ import (
"github.com/holoplot/go-avahi"
)
func Run(ctx context.Context) {
func Run(ctx context.Context, location string, port uint16, name string) {
conn, err := dbus.SystemBus()
if err != nil {
return
}
a, err := avahi.ServerNew(conn)
if err != nil {
log.Fatalf("Avahi new failed: %v", err)
log.Fatalf("Failed to connect to avahi: %v", err)
}
eg, err := a.EntryGroupNew()
if err != nil {
log.Fatalf("EntryGroupNew() failed: %v", err)
}
hostname, err := a.GetHostName()
if err != nil {
log.Fatalf("GetHostName() failed: %v", err)
log.Fatalf("Failed to create entry group: %v", err)
}
fqdn, err := a.GetHostNameFqdn()
if err != nil {
log.Fatalf("GetHostNameFqdn() failed: %v", err)
log.Fatalf("failed to get hostname: %v", err)
}
var txt [][]byte
txt = append(txt, []byte("note=burken"))
txt = append(txt, []byte("product=coola-skrivaren"))
notestring := fmt.Sprintf("note=%v", location)
txt = append(txt, []byte(notestring))
txt = append(txt, []byte("product=ChroBroPrint V1"))
txt = append(txt, []byte("Color=T"))
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=ChroBroPrint"))
err = eg.AddService(avahi.InterfaceUnspec, avahi.ProtoUnspec, 0, name, "_ipp._tcp", "local", fqdn, port, txt)
if err != nil {
log.Fatalf("AddService() failed: %v", err)
log.Fatalf("Failed to add service to avahi: %v", err)
}
err = eg.Commit()
if err != nil {
log.Fatalf("Commit() failed: %v", err)
log.Fatalf("Failed to commit avahi changes: %v", err)
}
<-ctx.Done()

View File

@@ -0,0 +1,12 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package main
import "ippserver/packages/ipp"
func handleValidateJob(r *ipp.Request) *ipp.Response {
response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID())
response.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8"))
response.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en"))
return response
}

View File

@@ -0,0 +1,47 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package main
import (
"bytes"
"ippserver/packages/ipp"
"net/http"
log "github.com/sirupsen/logrus"
)
func handleGetJobAttributes(r *ipp.Request, requestID uint32) (*ipp.Response, error) {
request := ipp.NewRequest(ipp.GetJobAttributes, requestID)
request.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8"))
request.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en"))
request.AddOperatonAttribute(ipp.NewURIValue("printer-uri", ippurl.String()))
request.AddOperatonAttribute(r.GetAttribute("job-id"))
request.AddOperatonAttribute(r.GetAttribute("requesting-user-name"))
request.AddOperatonAttribute(r.GetAttribute("requested-attributes"))
log.Infof("Downstream request\n%v\n", request)
downStreamRequest := request.Marshal()
b := bytes.NewBuffer(downStreamRequest)
downStreamResponse, err := http.Post(httpurl.String(), "application/ipp", b)
if err != nil {
return nil, err
}
rb := ipp.NewResponse(0, 0)
rb.UnMarshal(downStreamResponse.Body)
log.Infof("Downstream response\n%v\n", rb)
response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID())
response.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8"))
response.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en"))
response.AddJobAttribute(rb.GetAttribute("job-id"))
response.AddJobAttribute(rb.GetAttribute("job-name"))
response.AddJobAttribute(rb.GetAttribute("job-originating-user-name"))
response.AddJobAttribute(rb.GetAttribute("job-state"))
response.AddJobAttribute(rb.GetAttribute("job-state-reasons"))
return response, nil
}

48
proxy/handlegetjobs.go Normal file
View File

@@ -0,0 +1,48 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package main
import (
"bytes"
"ippserver/packages/ipp"
"net/http"
log "github.com/sirupsen/logrus"
)
func handleGetJobs(r *ipp.Request, requestID uint32) (*ipp.Response, error) {
request := ipp.NewRequest(ipp.GetJobs, requestID)
request.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8"))
request.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en"))
request.AddOperatonAttribute(ipp.NewURIValue("printer-uri", ippurl.String()))
request.AddOperatonAttribute(r.GetAttribute("requesting-user-name"))
request.AddOperatonAttribute(r.GetAttribute("requested-attributes"))
log.Infof("Downstream request\n%v\n", request)
downStreamRequest := request.Marshal()
b := bytes.NewBuffer(downStreamRequest)
downStreamResponse, err := http.Post(httpurl.String(), "application/ipp", b)
if err != nil {
return nil, err
}
//log.Printf("response HTTP status: %v %v", downStreamResponse.StatusCode, downStreamResponse.Status)
rb := ipp.NewResponse(0, 0)
rb.UnMarshal(downStreamResponse.Body)
log.Infof("Downstream response\n%v\n", rb)
response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID())
response.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8"))
response.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en"))
a := rb.GetAttribute("job-id")
response.AddJobAttribute(a)
response.AddJobAttribute(rb.GetAttribute("job-name"))
response.AddJobAttribute(rb.GetAttribute("job-originating-user-name"))
response.AddJobAttribute(rb.GetAttribute("job-state"))
response.AddJobAttribute(rb.GetAttribute("job-state-reasons"))
return response, nil
}

View File

@@ -0,0 +1,38 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package main
import "ippserver/packages/ipp"
func handleGetPrinterAttributes(r *ipp.Request) *ipp.Response {
response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID())
response.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8"))
response.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en"))
response.AddPrinterAttribute(ipp.NewURIValue("printer-uri", "ipp://drpork:1234/ipp/print"))
response.AddPrinterAttribute(ipp.NewtextWithoutLanguage("printer-make-and-model", "ChroBroPrint"))
response.AddPrinterAttribute(ipp.NewEnum("printer-state", int32(ipp.Idle)))
response.AddPrinterAttribute(ipp.NewEnum("operations-supported", int32(ipp.PrintJob), int32(ipp.ValidateJob), int32(ipp.CancelJob), int32(ipp.GetJobAttributes), int32(ipp.GetJobs), int32(ipp.GetPrinterAttributes)))
response.AddPrinterAttribute(ipp.NewKeyWord("printer-state-reasons", "none"))
response.AddPrinterAttribute(ipp.NewKeyWord("ipp-versions-supported", "1.0", "1.1", "2.0"))
response.AddPrinterAttribute(ipp.NewKeyWord("ipp-features-supported", "wfds-print-1.0"))
response.AddPrinterAttribute(ipp.NewMimeMediaType("document-format-supported", "image/pwg-raster"))
response.AddPrinterAttribute(ipp.NewKeyWord("media-supported", "iso_a4_210x297mm"))
response.AddPrinterAttribute(ipp.NewKeyWord("media-default", "iso_a4_210x297mm"))
response.AddPrinterAttribute(ipp.NewKeyWord("output-mode-supported", "color", "auto", "monochrome"))
response.AddPrinterAttribute(ipp.NewKeyWord("output-mode-default", "color"))
response.AddPrinterAttribute(ipp.NewBoolean("color-supported", true))
response.AddPrinterAttribute(ipp.NewKeyWord("sides-supported", "one-sided", "two-sided-long-edge", "two-sided-short-edge"))
response.AddPrinterAttribute(ipp.NewKeyWord("print-color-mode-supported", "auto", "color", "monochrome"))
response.AddPrinterAttribute(ipp.NewKeyWord("job-creation-attributes-supported", "copies", "finishings", "ipp-attribute-fidelity",
"job-name", "media", "media-col", "orientation-requested", "output-bin", "output-mode", "print-quality", "printer-resolution",
"requesting-user-name", "sides", "print-color-mode"))
response.AddPrinterAttribute(ipp.NewEnum("print-quality-supported", int32(4), int32(5))) //normal,high
res1 := ipp.Resolution{CrossFeedResolution: 600, FeedResolution: 600, Unit: 3}
res2 := ipp.Resolution{CrossFeedResolution: 2400, FeedResolution: 600, Unit: 3}
response.AddPrinterAttribute(ipp.NewSetOfResolution("printer-resolution-default", res1))
response.AddPrinterAttribute(ipp.NewSetOfResolution("printer-resolution-supported", res1, res2))
response.AddPrinterAttribute(ipp.NewBoolean("printer-is-accepting-jobs", true))
return response
}

70
proxy/handleprintjob.go Normal file
View File

@@ -0,0 +1,70 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package main
import (
"bytes"
"io"
"ippserver/packages/ipp"
"net/http"
"net/url"
"strings"
log "github.com/sirupsen/logrus"
)
//const printerURI = "brn30055cb5e3ae.local:631/ipp/print"
func handlePrintJob(r *ipp.Request, byteStream io.Reader, requestID uint32) (*ipp.Response, error) {
// This request is what will be sent to the real printer
request := ipp.NewRequest(ipp.PrintJob, requestID)
request.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8"))
request.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en"))
//request.AddOperatonAttribute(ipp.NewURIValue("printer-uri", "ipp://"+printerURI))
request.AddOperatonAttribute(ipp.NewURIValue("printer-uri", ippurl.String()))
request.AddOperatonAttribute(r.GetAttribute("requesting-user-name"))
request.AddOperatonAttribute(r.GetAttribute("job-name"))
request.AddOperatonAttribute(ipp.NewMimeMediaType("document-format", "image/pwg-raster"))
request.AddJobAttribute(r.GetAttribute("sides"))
request.AddJobAttribute(r.GetAttribute("media"))
request.AddJobAttribute(r.GetAttribute("print-color-mode"))
request.AddJobAttribute(r.GetAttribute("printer-resolution"))
request.AddJobAttribute(r.GetAttribute("output-bin"))
log.Infof("Downstream request\n%v\n", request)
downStreamRequest := request.Marshal()
b := bytes.NewBuffer(downStreamRequest)
mr := io.MultiReader(b, byteStream)
//downStreamResponse, err := http.Post("http://"+"brn30055cb5e3ae.local:631/ipp/print", "application/ipp", mr)
downStreamResponse, err := http.Post(httpurl.String(), "application/ipp", mr)
if err != nil {
return nil, err
}
rb := ipp.NewResponse(0, 0)
rb.UnMarshal(downStreamResponse.Body)
log.Infof("Downstream response\n%v\n", rb)
response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID())
response.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8"))
response.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en"))
jua := rb.GetAttribute("job-uri").(*ipp.URIValue)
if jua != nil {
u, err := url.Parse(jua.Value())
if err != nil {
log.Error("could not parse job-uri")
}
l := strings.Split(u.Path, "/")
j := l[len(l)-1]
joburi := r.GetAttribute("printer-uri").(*ipp.URIValue).Value() + "/" + j
response.AddJobAttribute(ipp.NewURIValue("job-uri", joburi))
}
response.AddJobAttribute(rb.GetAttribute("job-id"))
response.AddJobAttribute(rb.GetAttribute("job-state"))
response.AddJobAttribute(rb.GetAttribute("job-state-reasons"))
return response, nil
}

102
proxy/main.go Normal file
View File

@@ -0,0 +1,102 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package main
import (
"context"
"flag"
"fmt"
"ippserver/packages/ipp"
"ippserver/packages/mdnsserver"
"net/http"
"net/url"
"sync"
log "github.com/sirupsen/logrus"
)
var (
loglevel string
location string
port uint
printerURI string
printerName string
ippurl, httpurl *url.URL
)
func init() {
flag.StringVar(&loglevel, "loglevel", "info", "The wanted loglevel error/info/debug")
flag.StringVar(&location, "location", "somewhere", "locaton of the printer as shown in mDNS")
flag.UintVar(&port, "port", 1234, "tcp port")
flag.StringVar(&printerURI, "printer", "", "URL to the real printer, typical ipp://printername.local:631/ipp/print")
flag.StringVar(&printerName, "name", "ChroBroPrint", "Name of the printer advertised with mDNS")
}
func main() {
var err error
flag.Parse()
customFormatter := new(log.TextFormatter)
customFormatter.TimestampFormat = "2006-01-02 15:04:05"
log.SetFormatter(customFormatter)
customFormatter.FullTimestamp = true
log.SetLevel(log.InfoLevel)
ippurl, err = url.Parse(printerURI)
if err != nil {
fmt.Printf("Failed to parse printer URL %v", err.Error())
return
}
httpurl, _ = url.Parse(printerURI)
httpurl.Scheme = "http"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go mdnsserver.Run(ctx, location, uint16(port), printerName)
http.HandleFunc("/ipp/print", handle)
log.Infof("http server starting on :%v", port)
err = http.ListenAndServe(fmt.Sprintf(":%v", port), nil)
if err != nil {
log.Fatal("ListenAndServe: " + err.Error())
}
}
var mut sync.Mutex
var requestID uint32
func handle(w http.ResponseWriter, r *http.Request) {
mut.Lock()
requestID++
mut.Unlock()
if r.Method != http.MethodPost {
http.Error(w, "Unsupported method", http.StatusMethodNotAllowed)
}
request := ipp.NewRequest(0, 0)
request.UnMarshal(r.Body)
log.Infof("Upstream Request: id: %v op: %v", request.RequestID(), request.Operation())
var response *ipp.Response
var err error
switch request.Operation() {
case ipp.GetPrinterAttributes:
response = handleGetPrinterAttributes(request)
case ipp.PrintJob:
response, err = handlePrintJob(request, r.Body, requestID)
case ipp.GetJobs:
response, err = handleGetJobs(request, requestID)
case ipp.ValidateJob:
response = handleValidateJob(request)
case ipp.GetJobAttributes:
response, err = handleGetJobAttributes(request, requestID)
default:
response = ipp.NewResponse(ipp.ClientErrorBadRequest, request.RequestID())
}
if err != nil {
log.Errorf("Failed to handle request: %v", err.Error())
response = ipp.NewResponse(ipp.ServerErrorServiceUnavailable, request.RequestID())
}
log.Infof("Upstream Response: %v", response.Header())
data := response.Marshal()
w.Write(data)
}

View File

@@ -0,0 +1,12 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package main
import "ippserver/packages/ipp"
func handleValidateJob(r *ipp.Request) *ipp.Response {
response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID())
response.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8"))
response.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en"))
return response
}

10
server/handlegetjobs.go Normal file
View File

@@ -0,0 +1,10 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package main
import "ippserver/packages/ipp"
func handleGetJobs(r *ipp.Request) *ipp.Response {
response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID())
return response
}

View File

@@ -0,0 +1,38 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package main
import "ippserver/packages/ipp"
func handleGetPrinterAttributes(r *ipp.Request) *ipp.Response {
response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID())
response.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8"))
response.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en"))
response.AddPrinterAttribute(ipp.NewURIValue("printer-uri", "ipp://drpork:1234/ipp/print"))
response.AddPrinterAttribute(ipp.NewtextWithoutLanguage("printer-make-and-model", "ChroBroPrint"))
response.AddPrinterAttribute(ipp.NewEnum("printer-state", int32(ipp.Idle)))
response.AddPrinterAttribute(ipp.NewEnum("operations-supported", int32(ipp.PrintJob), int32(ipp.ValidateJob), int32(ipp.CancelJob), int32(ipp.GetJobAttributes), int32(ipp.GetJobs), int32(ipp.GetPrinterAttributes)))
response.AddPrinterAttribute(ipp.NewKeyWord("printer-state-reasons", "none"))
response.AddPrinterAttribute(ipp.NewKeyWord("ipp-versions-supported", "1.0", "1.1", "2.0"))
response.AddPrinterAttribute(ipp.NewKeyWord("ipp-features-supported", "wfds-print-1.0"))
response.AddPrinterAttribute(ipp.NewMimeMediaType("document-format-supported", "image/pwg-raster"))
response.AddPrinterAttribute(ipp.NewKeyWord("media-supported", "iso_a4_210x297mm"))
response.AddPrinterAttribute(ipp.NewKeyWord("media-default", "iso_a4_210x297mm"))
response.AddPrinterAttribute(ipp.NewKeyWord("output-mode-supported", "color", "auto", "monochrome"))
response.AddPrinterAttribute(ipp.NewKeyWord("output-mode-default", "color"))
response.AddPrinterAttribute(ipp.NewBoolean("color-supported", true))
response.AddPrinterAttribute(ipp.NewKeyWord("sides-supported", "one-sided", "two-sided-long-edge", "two-sided-short-edge"))
response.AddPrinterAttribute(ipp.NewKeyWord("print-color-mode-supported", "auto", "color", "monochrome"))
response.AddPrinterAttribute(ipp.NewKeyWord("job-creation-attributes-supported", "copies", "finishings", "ipp-attribute-fidelity",
"job-name", "media", "media-col", "orientation-requested", "output-bin", "output-mode", "print-quality", "printer-resolution",
"requesting-user-name", "sides", "print-color-mode"))
response.AddPrinterAttribute(ipp.NewEnum("print-quality-supported", int32(4), int32(5))) //normal,high
res1 := ipp.Resolution{CrossFeedResolution: 600, FeedResolution: 600, Unit: 3}
res2 := ipp.Resolution{CrossFeedResolution: 2400, FeedResolution: 600, Unit: 3}
response.AddPrinterAttribute(ipp.NewSetOfResolution("printer-resolution-default", res1))
response.AddPrinterAttribute(ipp.NewSetOfResolution("printer-resolution-supported", res1, res2))
response.AddPrinterAttribute(ipp.NewBoolean("printer-is-accepting-jobs", true))
return response
}

26
server/handleprintjob.go Normal file
View File

@@ -0,0 +1,26 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
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())
response.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8"))
response.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en"))
return response
}

72
server/main.go Normal file
View File

@@ -0,0 +1,72 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
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, "some location", 1234, "ChroBroPrint")
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) {
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)
case ipp.ValidateJob:
response = handleValidateJob(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)
}