11 Commits

42 changed files with 1477 additions and 421 deletions

View File

@@ -1,14 +1,20 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package main package main
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"ippserver/packages/ipp" "ippserver/packages/ipp"
"net/http" "net/http"
"os"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
const printerURI = "brn30055cb5e3ae.local:631/ipp/print"
//application/ipp //application/ipp
// brn30055cb5e3ae.local:631/ipp/print // brn30055cb5e3ae.local:631/ipp/print
func main() { func main() {
@@ -17,14 +23,12 @@ func main() {
customFormatter.TimestampFormat = "2006-01-02 15:04:05" customFormatter.TimestampFormat = "2006-01-02 15:04:05"
log.SetFormatter(customFormatter) log.SetFormatter(customFormatter)
customFormatter.FullTimestamp = true customFormatter.FullTimestamp = true
log.SetLevel(log.DebugLevel) log.SetLevel(log.InfoLevel)
const printerUri = "brn30055cb5e3ae.local:631/ipp/print"
request := ipp.NewRequest(ipp.GetPrinterAttributes, 10) request := ipp.NewRequest(ipp.GetPrinterAttributes, 10)
request.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8")) request.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8"))
request.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en")) request.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en"))
request.AddOperatonAttribute(ipp.NewURIValue("printer-uri", "ipp://"+printerUri)) request.AddOperatonAttribute(ipp.NewURIValue("printer-uri", "ipp://"+printerURI))
r := request.Marshal() r := request.Marshal()
b := bytes.NewBuffer(r) b := bytes.NewBuffer(r)
httpResponse, err := http.Post("http://"+"brn30055cb5e3ae.local:631/ipp/print", "application/ipp", b) httpResponse, err := http.Post("http://"+"brn30055cb5e3ae.local:631/ipp/print", "application/ipp", b)
@@ -34,6 +38,88 @@ func main() {
} }
rb := ipp.NewResponse(0, 0) rb := ipp.NewResponse(0, 0)
rb.UnMarshal(httpResponse.Body) rb.UnMarshal(httpResponse.Body)
fmt.Print(r) 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)
*/

View File

@@ -1,3 +1,5 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp package ipp
import ( import (
@@ -5,39 +7,69 @@ import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io" "io"
log "github.com/sirupsen/logrus"
) )
type boolean struct { // Boolean is the ipp attribute boolean
type Boolean struct {
name string name string
value bool value bool
} }
func NewBoolean(name string, value bool) *boolean { // NewBoolean creates a nre boolean attribute
b := new(boolean) func NewBoolean(name string, value bool) *Boolean {
b := new(Boolean)
b.name = name b.name = name
b.value = value b.value = value
return b return b
} }
func (b boolean) Name() string { // Name gets tha name of the boolean attribute
func (b Boolean) Name() string {
return b.name return b.name
} }
func (b boolean) String() string { func (b Boolean) String() string {
return b.name + ":" + fmt.Sprint(b.value) return b.name + ":" + fmt.Sprint(b.value)
} }
func (b *boolean) valueTag() tag { func (b *Boolean) valueTag() tag {
return booleanValueTag return booleanValueTag
} }
func (b *boolean) unmarshal(byteStream io.Reader) { func unmarshalSingleAttribute(byteStream io.Reader) (string, []byte) {
log.Warn("Unmarshal of boolean is not implemented yet") var length uint16
binary.Read(byteStream, binary.BigEndian, &length)
attributeName := make([]byte, length)
if length > 0 {
binary.Read(byteStream, binary.BigEndian, attributeName)
}
binary.Read(byteStream, binary.BigEndian, &length)
attributeValue := make([]byte, length)
binary.Read(byteStream, binary.BigEndian, attributeValue)
return string(attributeName), attributeValue
} }
func (b *boolean) marshal() []byte { func (b *Boolean) 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)
}
b.name = string(attributeName)
binary.Read(byteStream, binary.BigEndian, &length)
data := make([]byte, length)
binary.Read(byteStream, binary.BigEndian, data)
//name, data := unmarshalSingleAttribute(byteStream)
if data[0] == 0 {
b.value = false
return
}
b.value = true
}
func (b *Boolean) marshal() []byte {
l := 5 + len(b.name) l := 5 + len(b.name)
ba := make([]byte, 0, l) ba := make([]byte, 0, l)
buf := bytes.NewBuffer(ba) buf := bytes.NewBuffer(ba)
@@ -51,9 +83,4 @@ func (b *boolean) marshal() []byte {
buf.WriteByte(byte(0)) buf.WriteByte(byte(0))
} }
return buf.Bytes() 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 package ipp
type charsetAttribute struct { type charsetAttribute struct {

View File

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

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")
}

View File

@@ -0,0 +1,94 @@
34
00 00
00 11 // Name length
6d 65 64 69 61 2d 63 6f 6c 2d 64 65 66 61 75 6c 74 // "media-col-default"
00 00 // Always zero
4a // Collection member
00 00 // Always zero
00 0a // Value length
6d 65 64 69 61 2d 74 79 70 65 // "media-type"
44 // Keyword
00 00 00 0a
73 74 61 74 69 6f 6e 65 72 79 // "stationery"
4a // Collection member
00 00 // Always zero
00 0a // Value length
6d 65 64 69 61 2d 73 69 7a 65 // Member name "media-size"
34 // Begin Collection
00 00 // Name Length (In this case there is no name)
00 00 // Always zero
4a
00 00
00 0b
78 2d 64 69 6d 65 6e 73 69 6f 6e // "x-dimension"
21 // Integer value
00 00
00 04
00 00 52 08
4a
00 00
00 0b
79 2d 64 69 6d 65 6e 73 69 6f 6e // "y-dimension"
21 // Integer value
00 00
00 04
00 00 74 04
37 00 00 00 00 // End
4a
00 00
00 13
6d 65 64 69 61 2d 62 6f 74 74 6f 6d 2d 6d 61 72 67 69 6e // "media-bottom-margin"
21
00 00
00 04
00 00 01 b0
4a
00 00
00 11
6d 65 64 69 61 2d 6c 65 66 74 2d 6d 61 72 67 69 6e
21
00 00
00 04
00 00 01 b0
4a
00 00
00 12
6d 65 64 69 61 2d 72 69 67 68 74 2d 6d 61 72 67 69 6e // "media-right-margin"
21 00 00 00 04 00 00 01 b0
4a
00 00
00 10
6d 65 64 69 61 2d 74 6f 70 2d 6d 61 72 67 69 6e
21 00 00 00 04 00 00 01 b0
4a
00 00
00 0c
6d 65 64 69 61 2d 73 6f 75 72 63 65 // "media-source"
44 // Keyword
00 00
00 04
61 75 74 6f // "auto"
37 00 00 00 00 // End
23 00 1d 6f 72 69 65 6e 74 61 74 69 6f 6e 2d 72 65
From ipptool
media-col-default (collection) =
{media-type=stationery media-size={x-dimension=21000 y-dimension=29700}
media-bottom-margin=432
media-left-margin=432
media-right-margin=432
media-top-margin=432 media-source=auto}

View File

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

View File

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

View File

@@ -1,6 +1,9 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp package ipp
import ( import (
"bufio"
"bytes" "bytes"
"testing" "testing"
@@ -16,10 +19,12 @@ func TestUnMarshalSinglePositiveInteger(T *testing.T) {
} }
buf := bytes.NewBuffer(testdata) buf := bytes.NewBuffer(testdata)
bufbuf := bufio.NewReader(buf)
n, v := unmarshalSingleInteger(buf) var i Integer
assert.Equal(T, "flop", n, "Should be equal") i.unmarshal(bufbuf)
assert.Equal(T, int32(4), v, "Should be equal") assert.Equal(T, "flop", i.name, "Should be equal")
assert.Equal(T, int32(4), i.values[0], "Should be equal")
} }
func TestUnMarshalSingleNegativeInteger(T *testing.T) { func TestUnMarshalSingleNegativeInteger(T *testing.T) {
@@ -29,10 +34,11 @@ func TestUnMarshalSingleNegativeInteger(T *testing.T) {
0x00, 0x04, 0x00, 0x04,
0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xfc,
} }
buf := bytes.NewBuffer(testdata) buf := bytes.NewBuffer(testdata)
bufbuf := bufio.NewReader(buf)
n, v := unmarshalSingleInteger(buf) var i Integer
assert.Equal(T, "flop", n, "Should be equal") i.unmarshal(bufbuf)
assert.Equal(T, int32(-4), v, "Should be equal") assert.Equal(T, "flop", i.name, "Should be equal")
assert.Equal(T, int32(-4), i.values[0], "Should be equal")
} }

View File

@@ -1,37 +1,42 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp package ipp
type keyWord struct { import (
sos *setOfStrings "bufio"
)
type KeyWord struct {
sos *SetOfStrings
} }
func NewKeyWord(name string, values ...string) *keyWord { func NewKeyWord(name string, values ...string) *KeyWord {
k := new(keyWord) k := new(KeyWord)
k.sos = NewSetOfStrings(name, keyWordValueTag, values) k.sos = NewSetOfStrings(name, keyWordValueTag, values)
return k return k
} }
func (k keyWord) Name() string { func (k KeyWord) Name() string {
return k.sos.name return k.sos.name
} }
func (k keyWord) String() string { func (k KeyWord) String() string {
return k.sos.String() return k.sos.String()
} }
func (k *keyWord) size() int { func (k *KeyWord) valueTag() tag {
return k.sos.size()
}
func (k *keyWord) valueTag() tag {
return k.sos.valueTag() return k.sos.valueTag()
} }
func (k *keyWord) marshal() []byte { func (k *KeyWord) marshal() []byte {
return k.sos.marshal() return k.sos.marshal()
} }
func (k *keyWord) addValue(v interface{}) { func (k *KeyWord) unmarshal(byteStream *bufio.Reader) {
k.sos.AddValue(v.(string)) k.sos.unmarshal(byteStream, keyWordValueTag)
} }
func (k *KeyWord) addValue(v interface{}) {
k.sos.AddValue(v.(string))
}

View File

@@ -1,6 +1,10 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp package ipp
import ( import (
"bufio"
"bytes"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -20,6 +24,28 @@ import (
// assert.Equal(T, k.values[0], "printer-make-and-model") // assert.Equal(T, k.values[0], "printer-make-and-model")
// } // }
func TestUnmarshalKeywordWithMultipleStrings(t *testing.T) {
// test data from rfc8010 example A8 Get-Jobs request
testdata := []byte{0x00, 0x14,
'r', 'e', 'q', 'u', 'e', 's', 't', 'e', 'd', '-', 'a', 't', 't', 'r', 'i', 'b', 'u', 't', 'e', 's',
0x00, 0x06,
'j', 'o', 'b', '-', 'i', 'd',
0x44, 0x00, 0x00, 0x00, 0x08,
'j', 'o', 'b', '-', 'n', 'a', 'm', 'e',
0x44, 0x00, 0x00, 0x00, 0x0f,
'd', 'o', 'c', 'u', 'm', 'e', 'n', 't', '-', 'f', 'o', 'r', 'm', 'a', 't',
}
buf := bytes.NewBuffer(testdata)
r := bufio.NewReader(buf)
k := NewKeyWord("")
k.unmarshal(r)
assert.Equal(t, k.Name(), "requested-attributes")
assert.Len(t, k.sos.values, 3)
assert.Equal(t, k.sos.values[0], "job-id")
assert.Equal(t, k.sos.values[1], "job-name")
assert.Equal(t, k.sos.values[2], "document-format")
}
func TestMarshalSimpleKeyword(T *testing.T) { func TestMarshalSimpleKeyword(T *testing.T) {
testdata := []byte{ testdata := []byte{
0x44, 0x00, 0x14, 0x44, 0x00, 0x14,
@@ -34,3 +60,28 @@ func TestMarshalSimpleKeyword(T *testing.T) {
assert.Equal(T, testdata, m, "Should be equal") assert.Equal(T, testdata, m, "Should be equal")
} }
func TestUnMarshalKeywordWithAdditionalValues(T *testing.T) {
testdata := []byte{
0x00, 0x04,
0x72, 0x65, 0x71, 0x75,
0x00, 0x06,
0x70, 0x72, 0x69, 0x6e, 0x74, 0x65,
0x44, 0x00, 0x00,
0x00, 0x04,
0x6e, 0x69, 0x72, 0x70,
0x44, 0x00, 0x00,
0x00, 0x05,
0x70, 0x72, 0x69, 0x6e, 0x74, 0x00,
}
b := bytes.NewBuffer(testdata)
buf := bufio.NewReader(b)
k := NewKeyWord("")
k.unmarshal(buf)
assert.Equal(T, "requ", k.sos.name, "Wrong name")
assert.Equal(T, "printe", k.sos.values[0], "Wrong value 0")
assert.Equal(T, "nirp", k.sos.values[1], "Wrong value 1")
assert.Equal(T, "print", k.sos.values[2], "Wrong value 2")
}

View File

@@ -1,8 +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 //Package ipp provides functonality to handle ipp messages
//go:generate stringer -type jobState -type printerState
package ipp package ipp
import ( import (
"bufio"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"io" "io"
@@ -13,6 +19,9 @@ import (
// https://tools.ietf.org/html/rfc8010 // https://tools.ietf.org/html/rfc8010
// https://tools.ietf.org/html/rfc8011 // 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 // Defined value tags
// from rfc8010 // from rfc8010
type tag uint8 type tag uint8
@@ -81,17 +90,35 @@ const (
type printerState int32 type printerState int32
// printerstate defenitions
const ( const (
Idle printerState = 3 Idle printerState = 3
Processing printerState = 4 Processing printerState = 4
Stopped printerState = 5 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 type statusCode uint16
// status code defenitions
const ( const (
SuccessfulOk statusCode = 0x0000 SuccessfulOk statusCode = 0x0000
ClientErrorBadRequest statusCode = 0x0400 SuccessfulOkIgnoredOrSubstitutedAttributes statusCode = 0x0001
SuccessfulOkConflictingAttributes statusCode = 0x0002
ClientErrorBadRequest statusCode = 0x0400
ServerErrorServiceUnavailable statusCode = 0x0502
) )
type versionNumber uint16 type versionNumber uint16
@@ -101,19 +128,8 @@ func (v versionNumber) String() string {
return fmt.Sprintf("%x.%x", vn&0xff00>>8, vn&0x00ff) return fmt.Sprintf("%x.%x", vn&0xff00>>8, vn&0x00ff)
} }
func unmarshalSingleAttribute(byteStream io.Reader) (string, []byte) { // unmarshalSingleValue reads a single key value pair from the byte stream
var length uint16 // RFC 8010: Attribute-with-one-value
binary.Read(byteStream, binary.BigEndian, &length)
attributeName := make([]byte, length)
if length > 0 {
binary.Read(byteStream, binary.BigEndian, attributeName)
}
binary.Read(byteStream, binary.BigEndian, &length)
attributeValue := make([]byte, length)
binary.Read(byteStream, binary.BigEndian, attributeValue)
return string(attributeName), attributeValue
}
func unmarshalSingleValue(byteStream io.Reader) (string, string) { func unmarshalSingleValue(byteStream io.Reader) (string, string) {
var length uint16 var length uint16
binary.Read(byteStream, binary.BigEndian, &length) binary.Read(byteStream, binary.BigEndian, &length)
@@ -138,20 +154,62 @@ func marshalNameValue(name, value string, b []byte) {
copy(b[p:], []byte(value)) copy(b[p:], []byte(value))
} }
// func readAttribute(byteStream *bufio.Reader) (string, [][]byte) {
// var length uint16
// values := make([][]byte, 0, 1)
// binary.Read(byteStream, binary.BigEndian, &length)
// attributeName := make([]byte, length)
// if length > 0 {
// binary.Read(byteStream, binary.BigEndian, attributeName)
// }
// binary.Read(byteStream, binary.BigEndian, attributeValue)
// values = append(values, string(attributeValue))
// next, err := byteStream.Peek(1)
// if err != nil {
// panic("Failed to peek")
// }
// if next[0] != byte(keyWordValueTag) {
// // No additional values
// return string(attributeName), values
// }
// binary.Read(byteStream, binary.BigEndian, &length)
// for length > 0 {
// attributeValue := make([]byte, length)
// binary.Read(byteStream, binary.BigEndian, &length)
// attributeValue := make([]byte, length)
// if length > 0 {
// binary.Read(byteStream, binary.BigEndian, attributeValue)
// }
// values = append(values, string(attributeValue))
// next, err := byteStream.Peek(1)
// if err != nil {
// panic("Failed to peek")
// }
// if next[0] != byte(keyWordValueTag) {
// // No additional values
// return string(attributeName), values
// }
// }
// }
type Attribute interface { type Attribute interface {
Name() string Name() string
valueTag() tag valueTag() tag
marshal() []byte marshal() []byte
//size() int
} }
type attributes struct { type Attributes struct {
operation []Attribute operation []Attribute
printer []Attribute printer []Attribute
job []Attribute job []Attribute
unsupported []Attribute
} }
func (a *attributes) String() string { func (a *Attributes) String() string {
s := " OperationAttributes" + "\n" s := " OperationAttributes" + "\n"
for _, a := range a.operation { for _, a := range a.operation {
s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag()) s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag())
@@ -164,10 +222,15 @@ func (a *attributes) String() string {
for _, a := range a.job { for _, a := range a.job {
s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag()) 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 return s
} }
func (a *attributes) addAttribute(group tag, attr Attribute) { func (a *Attributes) addAttribute(group tag, attr Attribute) {
switch group { switch group {
case operationAttributes: case operationAttributes:
a.operation = append(a.operation, attr) a.operation = append(a.operation, attr)
@@ -175,16 +238,18 @@ func (a *attributes) addAttribute(group tag, attr Attribute) {
a.job = append(a.job, attr) a.job = append(a.job, attr)
case printerAttributes: case printerAttributes:
a.printer = append(a.printer, attr) a.printer = append(a.printer, attr)
case unsupportedAttributes:
a.unsupported = append(a.unsupported, attr)
default: default:
log.Error("Unknown attribute group") log.Errorf("Unknown attribute group %v", group)
} }
} }
func UnMarshalAttributues(body io.Reader) *attributes { func UnMarshalAttributes(bytestream *bufio.Reader) *Attributes {
a := new(attributes) a := new(Attributes)
var t tag var t tag
err := binary.Read(body, binary.BigEndian, &t) err := binary.Read(bytestream, binary.BigEndian, &t)
if err != nil { if err != nil {
log.Error(err.Error()) log.Error(err.Error())
} }
@@ -195,11 +260,10 @@ func UnMarshalAttributues(body io.Reader) *attributes {
} }
currentAttributeGroup := t currentAttributeGroup := t
var lastAddValuer AddValuer
for { for {
err = binary.Read(body, binary.BigEndian, &t) err = binary.Read(bytestream, binary.BigEndian, &t)
if err != nil { if err != nil {
log.Errorf("End of input before end of attributes tag (%v)", err.Error()) log.Fatalf("End of input before end of attributes tag (%v)", err.Error())
} }
log.Debugf("Value tag - %v", t) log.Debugf("Value tag - %v", t)
switch t { switch t {
@@ -207,74 +271,65 @@ func UnMarshalAttributues(body io.Reader) *attributes {
return a return a
case charsetValueTag: case charsetValueTag:
c := NewCharSetValue("", "") c := NewCharSetValue("", "")
c.unmarshal(body) c.unmarshal(bytestream)
a.addAttribute(currentAttributeGroup, c) a.addAttribute(currentAttributeGroup, c)
log.Debugf("%v %v", c.name, c.value) log.Debugf("%v %v", c.name, c.value)
case booleanValueTag:
na := NewBoolean("", false)
na.unmarshal(bytestream)
a.addAttribute(currentAttributeGroup, na)
case uriValueTag: case uriValueTag:
u := NewURIValue("", "") u := NewURIValue("", "")
u.unmarshal(body) u.unmarshal(bytestream)
a.addAttribute(currentAttributeGroup, u) a.addAttribute(currentAttributeGroup, u)
log.Debugf("%v %v", u.name, u.value) log.Debugf("%v %v", u.name, u.value)
case naturalLanguageValueTag: case naturalLanguageValueTag:
n := NewNaturalLanguage("", "") n := NewNaturalLanguage("", "")
n.unmarshal(body) n.unmarshal(bytestream)
a.addAttribute(currentAttributeGroup, n) a.addAttribute(currentAttributeGroup, n)
log.Debugf("%v %v", n.name, n.value) log.Debugf("%v %v", n.name, n.value)
case keyWordValueTag: case keyWordValueTag:
name, value := unmarshalSingleValue(body) k := NewKeyWord("", "")
if name == "" { k.unmarshal(bytestream)
lastAddValuer.addValue(value) a.addAttribute(currentAttributeGroup, k)
} else { log.Debugf("%v : %v", k.Name(), k.String())
k := NewKeyWord(name, value)
a.addAttribute(currentAttributeGroup, k)
lastAddValuer = k
}
log.Debugf("%v : %v", name, value)
case nameWithoutLanguageValueTag: case nameWithoutLanguageValueTag:
n := NewNameWithoutLanguage("", "") n := NewNameWithoutLanguage("", "")
n.unmarshal(body) n.unmarshal(bytestream)
a.addAttribute(currentAttributeGroup, n) a.addAttribute(currentAttributeGroup, n)
log.Debugf("%v %v", n.name, n.value) 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: case mimeMediaTypeValueTag:
name, value := unmarshalSingleValue(body) m := NewMimeMediaType("")
if name == "" { m.unmarshal(bytestream)
lastAddValuer.addValue(value) a.addAttribute(currentAttributeGroup, m)
} else { log.Debugf("%v : %v", m.Name(), m.String())
m := NewMimeMediaType(name, value)
a.addAttribute(currentAttributeGroup, m)
lastAddValuer = m
}
log.Debugf("%v : %v", name, value)
case integerValueTag: case integerValueTag:
name, value := unmarshalSingleInteger(body) i := NewInteger("")
if name == "" { i.unmarshal(bytestream)
lastAddValuer.addValue(value) a.addAttribute(currentAttributeGroup, i)
} else { log.Debugf("%v : %v", i.name, i.values)
i := NewInteger(name, value)
a.addAttribute(currentAttributeGroup, i)
lastAddValuer = i
}
log.Debugf("%v : %v", name, value)
case rangeOfIntegerValueTag: case rangeOfIntegerValueTag:
name, value := unmarshalSingleRangeOfInteger(body) r := NewRangeOfInteger("")
if name == "" { r.unmarshal(bytestream)
lastAddValuer.addValue(value) a.addAttribute(currentAttributeGroup, r)
} else { log.Debugf("%v : %v", r.name, r.values)
r := NewRangeOfInteger(name, value)
a.addAttribute(currentAttributeGroup, r)
lastAddValuer = r
}
log.Debugf("%v : %v", name, value)
case enumValueTag: case enumValueTag:
name, value := unmarshalSingleInteger(body) e := NewEnum("")
if name == "" { e.unmarshal(bytestream)
lastAddValuer.addValue(value) a.addAttribute(currentAttributeGroup, e)
} else { log.Debugf("%v : %v", e.name, e.values)
e := NewEnum(name, value) case begCollectionValueTag:
a.addAttribute(currentAttributeGroup, e) // For now just consume the collection
lastAddValuer = e consumeCollection(bytestream)
} case unsupportedValueTag:
log.Debugf("%v : %v", name, value) attr := NewUnsupportedValue()
attr.unmarshal(bytestream)
a.addAttribute(currentAttributeGroup, attr)
case jobAttributes: case jobAttributes:
log.Debug("Start job attributes") log.Debug("Start job attributes")
currentAttributeGroup = jobAttributes currentAttributeGroup = jobAttributes
@@ -284,13 +339,16 @@ func UnMarshalAttributues(body io.Reader) *attributes {
case operationAttributes: case operationAttributes:
log.Debug("Start operation attributes") log.Debug("Start operation attributes")
currentAttributeGroup = operationAttributes currentAttributeGroup = operationAttributes
case unsupportedAttributes:
log.Debug("Start unsupported attributes")
currentAttributeGroup = unsupportedAttributes
case resolutionValueTag: case resolutionValueTag:
res := NewResolution("", 0, 0) res := NewSetOfResolution("")
res.unmarshal(body) res.unmarshal(bytestream)
a.addAttribute(currentAttributeGroup, res) a.addAttribute(currentAttributeGroup, res)
log.Debugf("Resolution %v", res) log.Debugf("Resolution %v", res)
default: default:
log.Errorf("Unsupported tag %v", t) log.Errorf("Unsupported tag %v (%x)", t, uint8(t))
} }
} }
} }

View File

@@ -1,36 +1,40 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp package ipp
type mimeMediaType struct { import "bufio"
sos *setOfStrings
type MimeMediaType struct {
sos *SetOfStrings
} }
func NewMimeMediaType(name string, values ...string) *mimeMediaType { func NewMimeMediaType(name string, values ...string) *MimeMediaType {
m := new(mimeMediaType) m := new(MimeMediaType)
m.sos = NewSetOfStrings(name, mimeMediaTypeValueTag, values) m.sos = NewSetOfStrings(name, mimeMediaTypeValueTag, values)
return m return m
} }
func (m mimeMediaType) Name() string { func (m MimeMediaType) Name() string {
return m.sos.name return m.sos.name
} }
func (m mimeMediaType) String() string { func (m MimeMediaType) String() string {
return m.sos.String() return m.sos.String()
} }
func (m *mimeMediaType) size() int { func (m *MimeMediaType) valueTag() tag {
return m.sos.size()
}
func (m *mimeMediaType) valueTag() tag {
return m.sos.valueTag() return m.sos.valueTag()
} }
func (m *mimeMediaType) marshal() []byte { func (m *MimeMediaType) marshal() []byte {
return m.sos.marshal() return m.sos.marshal()
} }
func (m *mimeMediaType) addValue(v interface{}) { func (m *MimeMediaType) unmarshal(byteStream *bufio.Reader) {
m.sos.unmarshal(byteStream, mimeMediaTypeValueTag)
}
func (m *MimeMediaType) addValue(v interface{}) {
m.sos.AddValue(v.(string)) m.sos.AddValue(v.(string))
} }

View File

@@ -1,3 +1,5 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp package ipp
import "io" import "io"
@@ -37,13 +39,6 @@ func (n *NameWithoutLanguage) marshal() []byte {
return b 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 { func (n NameWithoutLanguage) Value() string {
return n.value return n.value
} }

View File

@@ -1,48 +1,43 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp package ipp
import ( import (
"io" "io"
) )
type naturalLanguage struct { type NaturalLanguage struct {
name string name string
value string value string
} }
func NewNaturalLanguage(name, value string) *naturalLanguage { func NewNaturalLanguage(name, value string) *NaturalLanguage {
c := new(naturalLanguage) c := new(NaturalLanguage)
c.name = name c.name = name
c.value = value c.value = value
return c return c
} }
func (c naturalLanguage) Name() string { func (c NaturalLanguage) Name() string {
return c.name return c.name
} }
func (c naturalLanguage) String() string { func (c NaturalLanguage) String() string {
return c.name + ":" + c.value return c.name + ":" + c.value
} }
func (c *naturalLanguage) valueTag() tag { func (c *NaturalLanguage) valueTag() tag {
return naturalLanguageValueTag return naturalLanguageValueTag
} }
func (c *naturalLanguage) unmarshal(byteStream io.Reader) { func (c *NaturalLanguage) unmarshal(byteStream io.Reader) {
c.name, c.value = unmarshalSingleValue(byteStream) c.name, c.value = unmarshalSingleValue(byteStream)
} }
func (c *naturalLanguage) marshal() []byte { func (c *NaturalLanguage) marshal() []byte {
l := 5 + len(c.name) + len(c.value) l := 5 + len(c.name) + len(c.value)
b := make([]byte, l) b := make([]byte, l)
b[0] = byte(naturalLanguageValueTag) b[0] = byte(naturalLanguageValueTag)
marshalNameValue(c.name, c.value, b[1:]) marshalNameValue(c.name, c.value, b[1:])
return b 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

@@ -1,4 +1,4 @@
// Code generated by "stringer -type printerState ."; DO NOT EDIT. // Code generated by "stringer -type jobState -type printerState"; DO NOT EDIT.
package ipp package ipp
@@ -13,9 +13,9 @@ func _() {
_ = x[Stopped-5] _ = x[Stopped-5]
} }
const _printerState_name = "IdlePreocessingStopped" const _printerState_name = "IdleProcessingStopped"
var _printerState_index = [...]uint8{0, 4, 15, 22} var _printerState_index = [...]uint8{0, 4, 14, 21}
func (i printerState) String() string { func (i printerState) String() string {
i -= 3 i -= 3

View File

@@ -1,9 +1,11 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp package ipp
import ( import (
"bufio"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@@ -13,43 +15,73 @@ type IRange struct {
upper int32 upper int32
} }
type rangeOfInteger struct { type RangeOfInteger struct {
name string name string
values []IRange values []IRange
} }
func NewRangeOfInteger(name string, values ...IRange) *rangeOfInteger { func NewRangeOfInteger(name string, values ...IRange) *RangeOfInteger {
r := new(rangeOfInteger) r := new(RangeOfInteger)
r.name = name r.name = name
r.values = values r.values = values
return r return r
} }
func (r *rangeOfInteger) Name() string { func (r *RangeOfInteger) Name() string {
return r.name return r.name
} }
func (r rangeOfInteger) String() string { func (r RangeOfInteger) String() string {
return r.name + ":" + fmt.Sprint(r.values) return r.name + ":" + fmt.Sprint(r.values)
} }
func (r *rangeOfInteger) valueTag() tag { func (r *RangeOfInteger) valueTag() tag {
return rangeOfIntegerValueTag return rangeOfIntegerValueTag
} }
func (r *rangeOfInteger) marshal() []byte { func (r *RangeOfInteger) marshal() []byte {
log.Error("marshal rangeOfInteger is not implemented yet") log.Error("marshal rangeOfInteger is not implemented yet")
return []byte{} return []byte{}
} }
func (r *rangeOfInteger) addValue(v interface{}) { func (r *RangeOfInteger) unmarshal(byteStream *bufio.Reader) error {
r.values = append(r.values, v.(IRange)) var length uint16
r.values = make([]IRange, 0, 1)
binary.Read(byteStream, binary.BigEndian, &length)
name := make([]byte, length)
if length > 0 {
binary.Read(byteStream, binary.BigEndian, name)
}
r.name = string(name)
binary.Read(byteStream, binary.BigEndian, &length)
if length != 8 {
return fmt.Errorf("wrong value-length of range-of-integer attribute %v", length)
}
for length > 0 {
var i IRange
err := binary.Read(byteStream, binary.BigEndian, &i.lower)
if err != nil {
return err
}
err = binary.Read(byteStream, binary.BigEndian, &i.upper)
if err != nil {
return err
}
r.values = append(r.values, i)
next, err := byteStream.Peek(3)
if err != nil {
break
}
if next[0] != byte(rangeOfIntegerValueTag) || next[1] != 0x00 || next[2] != 0x00 {
break
}
// Remove the value tag with the zero length from the stream
byteStream.Discard(3)
binary.Read(byteStream, binary.BigEndian, &length)
}
return nil
} }
func unmarshalSingleRangeOfInteger(byteStream io.Reader) (string, IRange) { func (r *RangeOfInteger) addValue(v interface{}) {
name, data := unmarshalSingleAttribute(byteStream) r.values = append(r.values, v.(IRange))
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

@@ -1,24 +1,53 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp package ipp
import ( import (
"bufio"
"bytes" "bytes"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestUnMarshalSingleRange(T *testing.T) { func TestUnmarshalSingleRange(T *testing.T) {
testdata := []byte{ testdata := []byte{
0x00, 0x04, 0x00, 0x04,
0x66, 0x6c, 0x6f, 0x70, //flop 0x66, 0x6c, 0x6f, 0x70, //flop
0x00, 0x08, 0x00, 0x08,
0x00, 0x0, 0x0, 0x4, 0x00, 0x0, 0x0, 0x5, 0x00, 0x0, 0x0, 0x4, 0x00, 0x0, 0x0, 0x5,
} }
b := bytes.NewBuffer(testdata)
buf := bufio.NewReader(b)
buf := bytes.NewBuffer(testdata) var r RangeOfInteger
r.unmarshal(buf)
assert.Equal(T, "flop", r.name, "Should be equal")
assert.Equal(T, int32(4), r.values[0].lower, "Should be equal")
assert.Equal(T, int32(5), r.values[0].upper, "Should be equal")
}
func TestUnmarshalDualRanges(T *testing.T) {
testdata := []byte{
0x00, 0x04,
0x66, 0x6c, 0x6f, 0x70, //flop
0x00, 0x08,
0x00, 0x0, 0x0, 0x4, 0x00, 0x0, 0x0, 0x5,
0x33, 0x00, 0x00,
0x00, 0x08,
0x00, 0x0, 0x0, 0x6, 0x00, 0x0, 0x0, 0x9,
}
b := bytes.NewBuffer(testdata)
buf := bufio.NewReader(b)
var r RangeOfInteger
r.unmarshal(buf)
assert.Equal(T, "flop", r.name, "Should be equal")
assert.Equal(T, int32(4), r.values[0].lower, "Should be equal")
assert.Equal(T, int32(5), r.values[0].upper, "Should be equal")
assert.Equal(T, int32(6), r.values[1].lower, "Should be equal")
assert.Equal(T, int32(9), r.values[1].upper, "Should be equal")
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")
} }

View File

@@ -1,6 +1,9 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp package ipp
import ( import (
"bufio"
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
@@ -37,7 +40,7 @@ type AddValuer interface {
} }
type Request struct { type Request struct {
a *attributes a *Attributes
header ippMessageHeader header ippMessageHeader
} }
@@ -46,7 +49,7 @@ func NewRequest(op OperationID, requestID uint32) *Request {
r.header.operationID = op r.header.operationID = op
r.header.requestID = requestID r.header.requestID = requestID
r.header.versionNumber = 0x0200 r.header.versionNumber = 0x0200
r.a = new(attributes) r.a = new(Attributes)
return r return r
} }
@@ -55,15 +58,16 @@ func (r Request) String() string {
} }
func (r *Request) UnMarshal(body io.Reader) { func (r *Request) UnMarshal(body io.Reader) {
r.header.unmarshal(body) buffbody := bufio.NewReader(body)
//log.Printf("Header %v", r.header) r.header.unmarshal(buffbody)
r.a = UnMarshalAttributues(body) r.a = UnMarshalAttributes(buffbody)
} }
func (r *Request) RequestID() uint32 { func (r *Request) RequestID() uint32 {
return r.header.requestID return r.header.requestID
} }
// Operation returns the operation is of the request.
func (r *Request) Operation() OperationID { func (r *Request) Operation() OperationID {
return r.header.operationID return r.header.operationID
} }
@@ -74,9 +78,25 @@ func (r *Request) GetAttribute(name string) Attribute {
return a 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 return nil
} }
// Marshal converts the request object to a ipp request
func (r *Request) Marshal() []byte { func (r *Request) Marshal() []byte {
var buf bytes.Buffer var buf bytes.Buffer
@@ -103,14 +123,29 @@ func (r *Request) Marshal() []byte {
return buf.Bytes() return buf.Bytes()
} }
func (r *Request) AddPrinterAttribute(a Attribute) { // AddPrinterAttribute adds a printer attribute to the request object
r.a.addAttribute(printerAttributes, a) func (r *Request) AddPrinterAttribute(a Attribute) error {
if a != nil {
r.a.addAttribute(printerAttributes, a)
return nil
}
return ErrNilAttribute
} }
func (r *Request) AddOperatonAttribute(a Attribute) { // AddOperatonAttribute adds a operation attribute to the request object
r.a.addAttribute(operationAttributes, a) func (r *Request) AddOperatonAttribute(a Attribute) error {
if a != nil {
r.a.addAttribute(operationAttributes, a)
return nil
}
return ErrNilAttribute
} }
func (r *Request) AddJobAttribute(a Attribute) { // AddJobAttribute adds a job attribute to the request object
r.a.addAttribute(jobAttributes, a) func (r *Request) AddJobAttribute(a Attribute) error {
if a != nil {
r.a.addAttribute(jobAttributes, a)
return nil
}
return ErrNilAttribute
} }

View File

@@ -1,3 +1,5 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp package ipp
import ( import (
@@ -67,7 +69,7 @@ func TestUnmarshalRequestPrinterAttributes(T *testing.T) {
assert.Equal(T, GetPrinterAttributes, req.header.operationID, "Wrong Operation") assert.Equal(T, GetPrinterAttributes, req.header.operationID, "Wrong Operation")
assert.Equal(T, uint32(17), req.header.requestID, "Wrong request id") assert.Equal(T, uint32(17), req.header.requestID, "Wrong request id")
assert.Len(T, req.a.operation, 4) assert.Len(T, req.a.operation, 4)
v := req.GetAttribute("requested-attributes").(*keyWord).sos.values v := req.GetAttribute("requested-attributes").(*KeyWord).sos.values
assert.Len(T, v, 7) assert.Len(T, v, 7)
assert.Contains(T, v, "printer-make-and-model") assert.Contains(T, v, "printer-make-and-model")
assert.Contains(T, v, "ipp-versions-supported") assert.Contains(T, v, "ipp-versions-supported")

View File

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

View File

@@ -1,6 +1,9 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp package ipp
import ( import (
"bufio"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io" "io"
@@ -31,14 +34,16 @@ func (h *ippResponseHeader) unmarshal(byteStream io.Reader) {
binary.Read(byteStream, binary.BigEndian, &h.requestID) binary.Read(byteStream, binary.BigEndian, &h.requestID)
} }
// Response represens a ipp response object
type Response struct { type Response struct {
a *attributes a *Attributes
header ippResponseHeader header ippResponseHeader
} }
// NewResponse creates a new ipp response object
func NewResponse(code statusCode, requestID uint32) *Response { func NewResponse(code statusCode, requestID uint32) *Response {
r := new(Response) r := new(Response)
r.a = new(attributes) r.a = new(Attributes)
r.header.versionNumber = 0x0101 r.header.versionNumber = 0x0101
r.header.requestID = requestID r.header.requestID = requestID
r.header.statusCode = code r.header.statusCode = code
@@ -49,6 +54,11 @@ func (r Response) String() string {
return r.header.String() + "\n" + r.a.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 { func (r *Response) Marshal() []byte {
a := make([]byte, 0, 20) a := make([]byte, 0, 20)
a = append(a, r.header.marshal()...) a = append(a, r.header.marshal()...)
@@ -74,20 +84,63 @@ func (r *Response) Marshal() []byte {
return a return a
} }
// UnMarshal unmarshals a ipp response into a response object
func (r *Response) UnMarshal(body io.Reader) { func (r *Response) UnMarshal(body io.Reader) {
r.header.unmarshal(body) buffbody := bufio.NewReader(body)
//log.Printf("Header %v", r.header) r.header.unmarshal(buffbody)
r.a = UnMarshalAttributues(body) r.a = UnMarshalAttributes(buffbody)
} }
func (r *Response) AddPrinterAttribute(a Attribute) { // AddPrinterAttribute adds a printer attribute to the response object
r.a.addAttribute(printerAttributes, a) func (r *Response) AddPrinterAttribute(a Attribute) error {
if a != nil {
r.a.addAttribute(printerAttributes, a)
return nil
}
return ErrNilAttribute
} }
func (r *Response) AddOperatonAttribute(a Attribute) { // AddOperatonAttribute adds a printer attribute to the response object
r.a.addAttribute(operationAttributes, a) func (r *Response) AddOperatonAttribute(a Attribute) error {
if a != nil {
r.a.addAttribute(operationAttributes, a)
return nil
}
return ErrNilAttribute
} }
func (r *Response) AddJobAttribute(a Attribute) { // AddJobAttribute adds a printer attribute to the response object
r.a.addAttribute(jobAttributes, a) 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 package ipp
import ( import (

View File

@@ -0,0 +1,56 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp
import (
"bufio"
"encoding/binary"
"fmt"
)
// setOfIntegers is a helper type that is used to handle integer types
// which only differs in tag value.
type setOfIntegers struct {
name string
values []int32
}
func unmarshalIntegers(byteStream *bufio.Reader, valueTag tag) (*setOfIntegers, error) {
s := new(setOfIntegers)
s.values = make([]int32, 0, 1)
var length uint16
binary.Read(byteStream, binary.BigEndian, &length)
name := make([]byte, length)
if length > 0 {
binary.Read(byteStream, binary.BigEndian, name)
}
s.name = string(name)
binary.Read(byteStream, binary.BigEndian, &length)
if length != 4 {
return nil, fmt.Errorf("wrong value-length of integer attribute %v", length)
}
var value int32 //valueBytes := make([]byte, length)
for length > 0 {
err := binary.Read(byteStream, binary.BigEndian, &value)
if err != nil {
return nil, err
}
s.values = append(s.values, value)
next, err := byteStream.Peek(3)
if err != nil {
// end of byte stream
break
}
if next[0] != byte(valueTag) || next[1] != 0x00 || next[2] != 0x00 {
// End of attribute
break
}
// Remove the value tag with the zero length from the stream
byteStream.Discard(3)
binary.Read(byteStream, binary.BigEndian, &length)
}
return s, nil
}

View File

@@ -1,45 +1,73 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp package ipp
import "encoding/binary" import (
"bufio"
"encoding/binary"
)
type setOfStrings struct { // SetOfStrings is the strings attribute
type SetOfStrings struct {
name string name string
values []string values []string
vTag tag vTag tag
} }
func NewSetOfStrings(name string, t tag, values []string) *setOfStrings { // NewSetOfStrings creates a new strings attribute
s := new(setOfStrings) func NewSetOfStrings(name string, t tag, values []string) *SetOfStrings {
s := new(SetOfStrings)
s.name = name s.name = name
s.vTag = t s.vTag = t
s.values = values //make([]string, 0) s.values = values //make([]string, 0)
return s return s
} }
func (s setOfStrings) String() string { func (s SetOfStrings) String() string {
r := s.name + " :" r := s.name + " :"
for _, v := range s.values { for _, v := range s.values {
r = r + " " + v r = r + " " + v
} }
return r return r
} }
func (s *setOfStrings) valueTag() tag {
func (s *SetOfStrings) valueTag() tag {
return s.vTag return s.vTag
} }
// func (k *keyWord) unmarshal(byteStream io.Reader) { func (s *SetOfStrings) unmarshal(byteStream *bufio.Reader, valueTag tag) error {
// if len(k.values) == 0 { var length uint16
// var v string s.vTag = valueTag
// k.name, v = unmarshalSingleValue(byteStream) s.values = make([]string, 0, 1)
// k.values = append(k.values, v) binary.Read(byteStream, binary.BigEndian, &length)
// } else { name := make([]byte, length)
// var v string if length > 0 {
// _, v = unmarshalSingleValue(byteStream) binary.Read(byteStream, binary.BigEndian, name)
// k.values = append(k.values, v) }
// } s.name = string(name)
// } binary.Read(byteStream, binary.BigEndian, &length)
for length > 0 {
valueBytes := make([]byte, length)
err := binary.Read(byteStream, binary.BigEndian, valueBytes)
if err != nil {
return err
}
s.values = append(s.values, string(valueBytes))
next, err := byteStream.Peek(3)
if err != nil {
break
}
if next[0] != byte(valueTag) || next[1] != 0x00 || next[2] != 0x00 {
break
}
// Remove the value tag with the zero length from the stream
byteStream.Discard(3)
binary.Read(byteStream, binary.BigEndian, &length)
}
return nil
}
func (s *setOfStrings) marshal() []byte { func (s *SetOfStrings) marshal() []byte {
l := 5 + len(s.name) + len(s.values[0]) l := 5 + len(s.name) + len(s.values[0])
for i := range s.values[1:] { for i := range s.values[1:] {
l += 5 + len(s.values[i+1]) l += 5 + len(s.values[i+1])
@@ -47,7 +75,7 @@ func (s *setOfStrings) marshal() []byte {
res := make([]byte, l) res := make([]byte, l)
p := 0 p := 0
res[p] = byte(s.vTag) res[p] = byte(s.vTag)
p += 1 p++
binary.BigEndian.PutUint16(res[p:p+2], uint16(len(s.name))) binary.BigEndian.PutUint16(res[p:p+2], uint16(len(s.name)))
p += 2 p += 2
copy(res[p:], []byte(s.name)) copy(res[p:], []byte(s.name))
@@ -58,7 +86,7 @@ func (s *setOfStrings) marshal() []byte {
p += len(s.values[0]) p += len(s.values[0])
for i := range s.values[1:] { for i := range s.values[1:] {
res[p] = byte(s.vTag) res[p] = byte(s.vTag)
p += 1 p++
binary.BigEndian.PutUint16(res[p:p+2], uint16(0)) binary.BigEndian.PutUint16(res[p:p+2], uint16(0))
p = p + 2 p = p + 2
binary.BigEndian.PutUint16(res[p:p+2], uint16(len(s.values[i+1]))) binary.BigEndian.PutUint16(res[p:p+2], uint16(len(s.values[i+1])))
@@ -70,19 +98,7 @@ func (s *setOfStrings) marshal() []byte {
return res return res
} }
func (s *setOfStrings) AddValue(v string) { // AddValue adds a new sring to the set
func (s *SetOfStrings) AddValue(v string) {
s.values = append(s.values, v) 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

@@ -1,46 +0,0 @@
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) 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,44 @@
// 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
}

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,44 +1,54 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package ipp package ipp
import ( import (
"io" "io"
) )
type uriValue struct { // URIValue represents a ipp URI value attribute
type URIValue struct {
name string name string
value string value string
} }
func NewURIValue(name, value string) *uriValue { // NewURIValue creates a new URIValue attribute
u := new(uriValue) func NewURIValue(name, value string) *URIValue {
u := new(URIValue)
u.name = name u.name = name
u.value = value u.value = value
return u return u
} }
func (u uriValue) Name() string { // Name returns the name of the attribute
func (u URIValue) Name() string {
return u.name return u.name
} }
func (u uriValue) String() string { // 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 return u.name + ":" + u.value
} }
func (u *uriValue) valueTag() tag { func (u *URIValue) valueTag() tag {
return uriValueTag return uriValueTag
} }
func (u *uriValue) unmarshal(byteStream io.Reader) { func (u *URIValue) unmarshal(byteStream io.Reader) {
u.name, u.value = unmarshalSingleValue(byteStream) u.name, u.value = unmarshalSingleValue(byteStream)
} }
func (u *uriValue) marshal() []byte { func (u *URIValue) marshal() []byte {
res := make([]byte, u.size()) res := make([]byte, u.size())
res[0] = byte(uriValueTag) res[0] = byte(uriValueTag)
marshalNameValue(u.name, u.value, res[1:]) marshalNameValue(u.name, u.value, res[1:])
return res return res
} }
func (u *uriValue) size() int { func (u *URIValue) size() int {
l := 1 + 4 // The attribute tag + 2 lengths l := 1 + 4 // The attribute tag + 2 lengths
l += len(u.name) l += len(u.name)
l += len(u.value) 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 package ipp
import ( import (
@@ -8,7 +10,7 @@ import (
) )
func TestMarshalUriValue(T *testing.T) { func TestMarshalUriValue(T *testing.T) {
var u uriValue var u URIValue
u.name = "foo" u.name = "foo"
u.value = "bar" u.value = "bar"
b := u.marshal() 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 package mdnsserver
import ( import (
"context" "context"
"fmt"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@@ -9,45 +12,40 @@ import (
"github.com/holoplot/go-avahi" "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() conn, err := dbus.SystemBus()
if err != nil { if err != nil {
return return
} }
a, err := avahi.ServerNew(conn) a, err := avahi.ServerNew(conn)
if err != nil { if err != nil {
log.Fatalf("Avahi new failed: %v", err) log.Fatalf("Failed to connect to avahi: %v", err)
} }
eg, err := a.EntryGroupNew() eg, err := a.EntryGroupNew()
if err != nil { if err != nil {
log.Fatalf("EntryGroupNew() failed: %v", err) log.Fatalf("Failed to create entry group: %v", err)
}
//hostname, err := a.GetHostName()
if err != nil {
log.Fatalf("GetHostName() failed: %v", err)
} }
fqdn, err := a.GetHostNameFqdn() fqdn, err := a.GetHostNameFqdn()
if err != nil { if err != nil {
log.Fatalf("GetHostNameFqdn() failed: %v", err) log.Fatalf("failed to get hostname: %v", err)
} }
var txt [][]byte var txt [][]byte
notestring := fmt.Sprintf("note=%v", location)
txt = append(txt, []byte("note=burken")) txt = append(txt, []byte(notestring))
txt = append(txt, []byte("product=coola-skrivaren")) txt = append(txt, []byte("product=ChroBroPrint V1"))
txt = append(txt, []byte("Color=T")) txt = append(txt, []byte("Color=T"))
txt = append(txt, []byte("rp=ipp/print")) txt = append(txt, []byte("rp=ipp/print"))
txt = append(txt, []byte("ty=ChroBro 001")) txt = append(txt, []byte("ty=ChroBroPrint"))
err = eg.AddService(avahi.InterfaceUnspec, avahi.ProtoUnspec, 0, "ChroBro 1000", "_ipp._tcp", "local", fqdn, 1234, txt) err = eg.AddService(avahi.InterfaceUnspec, avahi.ProtoUnspec, 0, name, "_ipp._tcp", "local", fqdn, port, txt)
if err != nil { if err != nil {
log.Fatalf("AddService() failed: %v", err) log.Fatalf("Failed to add service to avahi: %v", err)
} }
err = eg.Commit() err = eg.Commit()
if err != nil { if err != nil {
log.Fatalf("Commit() failed: %v", err) log.Fatalf("Failed to commit avahi changes: %v", err)
} }
<-ctx.Done() <-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
}

View File

@@ -1,3 +1,5 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package main package main
import "ippserver/packages/ipp" import "ippserver/packages/ipp"

View File

@@ -1,28 +1,38 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package main package main
import "ippserver/packages/ipp" import "ippserver/packages/ipp"
func handleGetPrinterAttributes(r *ipp.Request) *ipp.Response { func handleGetPrinterAttributes(r *ipp.Request) *ipp.Response {
response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID()) response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID())
var a ipp.Attribute
a = ipp.NewCharSetValue("attributes-charset", "utf-8") response.AddOperatonAttribute(ipp.NewCharSetValue("attributes-charset", "utf-8"))
response.AddOperatonAttribute(a) response.AddOperatonAttribute(ipp.NewNaturalLanguage("attributes-natural-language", "en"))
a = ipp.NewNaturalLanguage("attributes-natural-language", "en")
response.AddOperatonAttribute(a) response.AddPrinterAttribute(ipp.NewURIValue("printer-uri", "ipp://drpork:1234/ipp/print"))
a = ipp.NewURIValue("printer-uri", "ipp://drpork:1234/ipp/print") response.AddPrinterAttribute(ipp.NewtextWithoutLanguage("printer-make-and-model", "ChroBroPrint"))
response.AddOperatonAttribute(a) response.AddPrinterAttribute(ipp.NewEnum("printer-state", int32(ipp.Idle)))
a = ipp.NewtextWithoutLanguage("printer-make-and-model", "ChroBro 001") 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.AddOperatonAttribute(a) response.AddPrinterAttribute(ipp.NewKeyWord("printer-state-reasons", "none"))
a = ipp.NewEnum("printer-state", int32(ipp.Idle)) response.AddPrinterAttribute(ipp.NewKeyWord("ipp-versions-supported", "1.0", "1.1", "2.0"))
response.AddOperatonAttribute(a) response.AddPrinterAttribute(ipp.NewKeyWord("ipp-features-supported", "wfds-print-1.0"))
a = ipp.NewKeyWord("ipp-versions-supported", "1.0", "1.1", "2.0") response.AddPrinterAttribute(ipp.NewMimeMediaType("document-format-supported", "image/pwg-raster"))
response.AddOperatonAttribute(a) response.AddPrinterAttribute(ipp.NewKeyWord("media-supported", "iso_a4_210x297mm"))
response.AddOperatonAttribute(ipp.NewKeyWord("ipp-features-supported", "wfds-print-1.0")) response.AddPrinterAttribute(ipp.NewKeyWord("media-default", "iso_a4_210x297mm"))
response.AddOperatonAttribute(ipp.NewMimeMediaType("document-format-supported", "image/pwg-raster")) response.AddPrinterAttribute(ipp.NewKeyWord("output-mode-supported", "color", "auto", "monochrome"))
response.AddOperatonAttribute(ipp.NewKeyWord("media-supported", "iso_a4_210x297mm")) response.AddPrinterAttribute(ipp.NewKeyWord("output-mode-default", "color"))
response.AddOperatonAttribute(ipp.NewKeyWord("sides-supported", "one-sided", "two-sided-long-edge", "two-sided-short-edge")) response.AddPrinterAttribute(ipp.NewBoolean("color-supported", true))
response.AddOperatonAttribute(ipp.NewKeyWord("print-color-mode-supported", "auto", "color", "monochrome")) response.AddPrinterAttribute(ipp.NewKeyWord("sides-supported", "one-sided", "two-sided-long-edge", "two-sided-short-edge"))
response.AddOperatonAttribute(ipp.NewResolution("printer-resolution-default", 600, 600)) response.AddPrinterAttribute(ipp.NewKeyWord("print-color-mode-supported", "auto", "color", "monochrome"))
response.AddOperatonAttribute(ipp.NewBoolean("printer-is-accepting-jobs", true)) 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 return response
} }

View File

@@ -1,3 +1,5 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package main package main
import ( import (
@@ -17,6 +19,8 @@ func handlePrintJob(r *ipp.Request, byteStream io.Reader) *ipp.Response {
defer f.Close() defer f.Close()
io.Copy(f, byteStream) io.Copy(f, byteStream)
response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID()) 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 return response
} }

View File

@@ -1,3 +1,5 @@
// Copyright 2021, Henrik Sölver henrik.solver@gmail.com
// SPDX-License-Identifier: BSD-3-Clause
package main package main
import ( import (
@@ -18,7 +20,7 @@ func main() {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
go mdnsserver.Run(ctx) go mdnsserver.Run(ctx, "some location", 1234, "ChroBroPrint")
http.HandleFunc("/ipp/print", handle) http.HandleFunc("/ipp/print", handle)
@@ -30,7 +32,6 @@ func main() {
} }
func handle(w http.ResponseWriter, r *http.Request) { func handle(w http.ResponseWriter, r *http.Request) {
log.Infoln("handle")
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
http.Error(w, "Unsupported method", http.StatusMethodNotAllowed) http.Error(w, "Unsupported method", http.StatusMethodNotAllowed)
@@ -56,6 +57,8 @@ func handle(w http.ResponseWriter, r *http.Request) {
response = handlePrintJob(request, r.Body) response = handlePrintJob(request, r.Body)
case ipp.GetJobs: case ipp.GetJobs:
response = handleGetJobs(request) response = handleGetJobs(request)
case ipp.ValidateJob:
response = handleValidateJob(request)
default: default:
response = ipp.NewResponse(ipp.ClientErrorBadRequest, request.RequestID()) response = ipp.NewResponse(ipp.ClientErrorBadRequest, request.RequestID())
} }