13 Commits

Author SHA1 Message Date
5864035064 Avoid panic if printer is off or unreachable for some reason.
Closes #16
Closes #17
Closes #15
2021-05-23 15:21:31 +00: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
39 changed files with 1492 additions and 438 deletions

123
client/main.go Normal file
View File

@@ -0,0 +1,123 @@
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)
*/

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

@@ -0,0 +1,66 @@
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

@@ -4,42 +4,47 @@ 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) String() string { func (c CharSetValue) Name() string {
return c.name
}
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, l) b := make([]byte, l)
b[0] = byte(charsetValueTag) b[0] = byte(charsetValueTag)
marshalNameValue(c.name, c.value, b[1:]) marshalNameValue(c.name, c.value, b[1:])
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 { func (c *CharSetValue) size() int {
l := 1 + 4 // The attribute tag + 2 lengths l := 1 + 4 // The attribute tag + 2 lengths
l += len(c.name) l += len(c.name)
l += len(c.value) l += len(c.value)

View File

@@ -0,0 +1,74 @@
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

@@ -1,52 +1,41 @@
package ipp package ipp
import ( import (
"bytes"
"encoding/binary"
"fmt" "fmt"
"io"
log "github.com/sirupsen/logrus"
) )
type enum struct { type Enum struct {
name string name string
value int32 values []int32
} }
func NewEnum(name string, value int32) *enum { func NewEnum(name string, values ...int32) *Enum {
e := new(enum) e := new(Enum)
e.name = name e.name = name
e.value = value e.values = values
return e return e
} }
func (e enum) String() string { func (e Enum) Name() string {
return e.name + ":" + fmt.Sprint(e.value) return e.name
} }
func (e *enum) valueTag() tag {
func (e Enum) String() string {
return e.name + ":" + fmt.Sprint(e.values)
}
func (e *Enum) valueTag() tag {
return enumValueTag return enumValueTag
} }
func (e *enum) unmarshal(byteStream io.Reader) { func (e *Enum) size() int {
log.Warn("Unmarshal of enum is not implemented yet") return 9 + len(e.name)
} }
func (e *enum) marshal() []byte { func (e *Enum) addValue(v interface{}) {
l := 3 + len(e.name) + 6 e.values = append(e.values, v.(int32))
b := make([]byte, 0, l)
buf := bytes.NewBuffer(b)
buf.WriteByte(byte(enumValueTag))
binary.Write(buf, binary.BigEndian, uint16(len(e.name)))
buf.WriteString(e.name)
binary.Write(buf, binary.BigEndian, uint16(4))
binary.Write(buf, binary.BigEndian, e.value)
return buf.Bytes()
} }
func (e *enum) size() int { func (e *Enum) marshal() []byte {
l := 1 + 4 // The attribute tag + 2 lengths return marshalInteger(enumValueTag, e.name, e.values)
l += len(e.name)
l += 4
return l
} }

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

@@ -0,0 +1,67 @@
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,38 @@
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,33 +1,37 @@
package ipp package ipp
type keyWord struct { type KeyWord struct {
sos *setOfStrings 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) String() string { func (k KeyWord) Name() string {
return k.sos.name
}
func (k KeyWord) String() string {
return k.sos.String() return k.sos.String()
} }
func (k *keyWord) size() int { func (k *KeyWord) size() int {
return k.sos.size() return k.sos.size()
} }
func (k *keyWord) valueTag() tag { 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 string) { func (k *KeyWord) addValue(v interface{}) {
k.sos.AddValue(v) k.sos.AddValue(v.(string))
} }

View File

@@ -1,15 +1,24 @@
//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"
log "github.com/sirupsen/logrus"
) )
// References // References
// 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
@@ -54,41 +63,59 @@ const (
memberAttrNameValueTag tag = 0x4a memberAttrNameValueTag tag = 0x4a
) )
// Operation-id, defined in rfc8011 // OperationID is defined in rfc8011
type OperationId uint16 type OperationID uint16
const ( const (
PrintJob OperationId = 0x0002 PrintJob OperationID = 0x0002
PrintURI OperationId = 0x0003 PrintURI OperationID = 0x0003
ValidateJob OperationId = 0x0004 ValidateJob OperationID = 0x0004
CreateJob OperationId = 0x0005 CreateJob OperationID = 0x0005
SendDocument OperationId = 0x0006 SendDocument OperationID = 0x0006
SendURI OperationId = 0x0007 SendURI OperationID = 0x0007
CancelJob OperationId = 0x0008 CancelJob OperationID = 0x0008
GetJobAttributes OperationId = 0x0009 GetJobAttributes OperationID = 0x0009
GetJobs OperationId = 0x000a GetJobs OperationID = 0x000a
GetPrinterAttributes OperationId = 0x000b GetPrinterAttributes OperationID = 0x000b
HoldJob OperationId = 0x000c HoldJob OperationID = 0x000c
ReleaseJob OperationId = 0x000d ReleaseJob OperationID = 0x000d
RestartJob OperationId = 0x000e RestartJob OperationID = 0x000e
PausePrinter OperationId = 0x0010 PausePrinter OperationID = 0x0010
ResumePrinter OperationId = 0x0011 ResumePrinter OperationID = 0x0011
PurgeJobs OperationId = 0x0012 PurgeJobs OperationID = 0x0012
) )
type printerState int32 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
) )
type statusCode uint16 // jobstate defined in rfc8011 ch 5.3.7
type jobState uint16
const ( const (
SuccessfulOk statusCode = 0x0000 pending jobState = 3
ClientErrorBadRequest statusCode = 0x0400 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
SuccessfulOkIgnoredOrSubstitutedAttributes statusCode = 0x0001
SuccessfulOkConflictingAttributes statusCode = 0x0002
ClientErrorBadRequest statusCode = 0x0400
ServerErrorServiceUnavailable statusCode = 0x0502
) )
type versionNumber uint16 type versionNumber uint16
@@ -98,6 +125,19 @@ 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) {
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 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)
@@ -121,3 +161,188 @@ func marshalNameValue(name, value string, b []byte) {
p += 2 p += 2
copy(b[p:], []byte(value)) 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

@@ -1,32 +1,36 @@
package ipp package ipp
type mimeMediaType struct { type MimeMediaType struct {
sos *setOfStrings 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) String() string { func (m MimeMediaType) Name() string {
return m.sos.name
}
func (m MimeMediaType) String() string {
return m.sos.String() return m.sos.String()
} }
func (m *mimeMediaType) size() int { func (m *MimeMediaType) size() int {
return m.sos.size() return m.sos.size()
} }
func (m *mimeMediaType) valueTag() tag { 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 string) { func (m *MimeMediaType) addValue(v interface{}) {
m.sos.AddValue(v) m.sos.AddValue(v.(string))
} }

View File

@@ -14,29 +14,33 @@ func NewNameWithoutLanguage(name, value string) *NameWithoutLanguage {
return c return c
} }
func (c NameWithoutLanguage) String() string { func (n NameWithoutLanguage) Name() string {
return c.name + ":" + c.value return n.name
} }
func (c *NameWithoutLanguage) valueTag() tag {
func (n NameWithoutLanguage) String() string {
return n.name + ":" + n.value
}
func (n *NameWithoutLanguage) valueTag() tag {
return nameWithoutLanguageValueTag return nameWithoutLanguageValueTag
} }
func (c *NameWithoutLanguage) unmarshal(byteStream io.Reader) { func (n *NameWithoutLanguage) unmarshal(byteStream io.Reader) {
c.name, c.value = unmarshalSingleValue(byteStream) n.name, n.value = unmarshalSingleValue(byteStream)
} }
func (c *NameWithoutLanguage) marshal() []byte { func (n *NameWithoutLanguage) marshal() []byte {
l := 5 + len(c.name) + len(c.value) l := 5 + len(n.name) + len(n.value)
b := make([]byte, l, l) b := make([]byte, l)
b[0] = byte(nameWithoutLanguageValueTag) b[0] = byte(nameWithoutLanguageValueTag)
marshalNameValue(c.name, c.value, b[1:]) marshalNameValue(n.name, n.value, b[1:])
return b return b
} }
func (c *NameWithoutLanguage) size() int { func (n *NameWithoutLanguage) size() int {
l := 1 + 4 // The attribute tag + 2 lengths l := 1 + 4 // The attribute tag + 2 lengths
l += len(c.name) l += len(n.name)
l += len(c.value) l += len(n.value)
return l return l
} }

View File

@@ -4,38 +4,43 @@ 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) String() string { func (c NaturalLanguage) Name() string {
return c.name
}
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, 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 { func (c *NaturalLanguage) size() int {
l := 1 + 4 // The attribute tag + 2 lengths l := 1 + 4 // The attribute tag + 2 lengths
l += len(c.name) l += len(c.name)
l += len(c.value) l += len(c.value)

View File

@@ -36,7 +36,7 @@ var (
_operationId_index_1 = [...]uint8{0, 12, 25, 34} _operationId_index_1 = [...]uint8{0, 12, 25, 34}
) )
func (i OperationId) String() string { func (i OperationID) String() string {
switch { switch {
case 2 <= i && i <= 14: case 2 <= i && i <= 14:
i -= 2 i -= 2

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

@@ -0,0 +1,55 @@
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,24 @@
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")
}

View File

@@ -1,203 +1,149 @@
package ipp package ipp
import ( import (
"bufio"
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io" "io"
log "github.com/sirupsen/logrus"
) )
type ippRequestHeader struct { type ippMessageHeader struct {
versionNumber versionNumber versionNumber versionNumber
operationId OperationId operationID OperationID
requestId uint32 requestID uint32
} }
func (h *ippRequestHeader) unmarshal(byteStream io.Reader) { func (h *ippMessageHeader) unmarshal(byteStream io.Reader) {
binary.Read(byteStream, binary.BigEndian, &h.versionNumber) binary.Read(byteStream, binary.BigEndian, &h.versionNumber)
binary.Read(byteStream, binary.BigEndian, &h.operationId) binary.Read(byteStream, binary.BigEndian, &h.operationID)
binary.Read(byteStream, binary.BigEndian, &h.requestId) binary.Read(byteStream, binary.BigEndian, &h.requestID)
} }
func (h *ippRequestHeader) marshal() []byte { func (h *ippMessageHeader) marshal() []byte {
b := make([]byte, 0, 8) b := make([]byte, 0, 8)
buf := bytes.NewBuffer(b) buf := bytes.NewBuffer(b)
binary.Write(buf, binary.BigEndian, h.versionNumber) binary.Write(buf, binary.BigEndian, h.versionNumber)
binary.Write(buf, binary.BigEndian, h.operationId) binary.Write(buf, binary.BigEndian, h.operationID)
binary.Write(buf, binary.BigEndian, h.requestId) binary.Write(buf, binary.BigEndian, h.requestID)
return buf.Bytes() return buf.Bytes()
} }
func (h ippRequestHeader) String() string { func (h ippMessageHeader) String() string {
return fmt.Sprintf("Version number: %v Operation Id: %v Request Id: %v", h.versionNumber, h.operationId, h.requestId) return fmt.Sprintf("Version number: %v Operation Id: %v Request Id: %v", h.versionNumber, h.operationID, h.requestID)
}
type Attribute interface {
valueTag() tag
marshal() []byte
//size() int
} }
type AddValuer interface { type AddValuer interface {
addValue(string) addValue(interface{})
} }
type Request struct { type Request struct {
operationAttributes map[string]Attribute a *Attributes
jobAttributes map[string]Attribute header ippMessageHeader
printerAttributes map[string]Attribute
header ippRequestHeader
} }
func NewRequest(op OperationId, requestId uint32) *Request { func NewRequest(op OperationID, requestID uint32) *Request {
r := new(Request) r := new(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.operationAttributes = make(map[string]Attribute) r.a = new(Attributes)
r.jobAttributes = make(map[string]Attribute)
r.printerAttributes = make(map[string]Attribute)
return r return r
} }
func (r Request) String() string { func (r Request) String() string {
s := r.header.String() + "\n" + " OperationAttributes" + "\n" return r.header.String() + "\n" + r.a.String()
for _, a := range r.operationAttributes {
s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag())
}
s = s + " PrinterAttributes" + "\n"
for _, a := range r.printerAttributes {
s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag())
}
s = s + " JobAttributes" + "\n"
for _, a := range r.jobAttributes {
s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag())
}
return s
} }
func (r *Request) UnMarshal(body io.Reader) { func (r *Request) UnMarshal(body io.Reader) {
r.header.unmarshal(body) buffbody := bufio.NewReader(body)
log.Debugf("Header %v", r.header) r.header.unmarshal(buffbody)
var tag tag r.a = UnMarshalAttributes(buffbody)
err := binary.Read(body, binary.BigEndian, &tag)
if err != nil {
log.Error(err.Error())
}
log.Debugf("got tag - %v", tag)
var currentAttributeGroup map[string]Attribute
switch tag {
case operationAttributes:
currentAttributeGroup = r.operationAttributes
case jobAttributes:
currentAttributeGroup = r.jobAttributes
case printerAttributes:
currentAttributeGroup = r.printerAttributes
default:
log.Errorf("Unknown tag %v", tag)
}
var lastAddValuer AddValuer
for {
err = binary.Read(body, binary.BigEndian, &tag)
if err != nil {
log.Errorf("End of input before end of attributes tag (%v)", err.Error())
}
log.Debugf("Value tag - %v", tag)
switch tag {
case endOfAttributes:
return
case charsetValueTag:
c := NewCharSetValue("", "")
c.unmarshal(body)
currentAttributeGroup[c.name] = c
log.Debugf("%v %v", c.name, c.value)
case uriValueTag:
u := NewUriValue("", "")
u.unmarshal(body)
currentAttributeGroup[u.name] = u
log.Debugf("%v %v", u.name, u.value)
case naturalLanguageValueTag:
n := NewNaturalLanguage("", "")
n.unmarshal(body)
currentAttributeGroup[n.name] = n
log.Debugf("%v %v", n.name, n.value)
case keyWordValueTag:
name, value := unmarshalSingleValue(body)
if name == "" {
lastAddValuer.addValue(value)
} else {
k := NewKeyWord(name, value)
currentAttributeGroup[name] = k
lastAddValuer = k
}
log.Debugf("%v : %v", name, value)
case nameWithoutLanguageValueTag:
n := NewNameWithoutLanguage("", "")
n.unmarshal(body)
currentAttributeGroup[n.name] = n
log.Debugf("%v %v", n.name, n.value)
case mimeMediaTypeValueTag:
name, value := unmarshalSingleValue(body)
if name == "" {
lastAddValuer.addValue(value)
} else {
m := NewMimeMediaType(name, value)
currentAttributeGroup[name] = m
lastAddValuer = m
}
log.Debugf("%v : %v", name, value)
case jobAttributes:
log.Debug("Start job attributes")
currentAttributeGroup = r.jobAttributes
case resolutionValueTag:
res := NewResolution("", 0, 0)
res.unmarshal(body)
currentAttributeGroup[res.name] = res
log.Debugf("Resolution %v", res)
default:
log.Errorf("Unsupported tag %v", tag)
}
}
} }
func (r *Request) RequestId() uint32 { func (r *Request) RequestID() uint32 {
return r.header.requestId return r.header.requestID
} }
func (r *Request) Operation() OperationId { // Operation returns the operation is of the request.
return r.header.operationId func (r *Request) Operation() OperationID {
return r.header.operationID
} }
func (r *Request) GetAttribute(name string) Attribute { func (r *Request) GetAttribute(name string) Attribute {
return r.operationAttributes[name] 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 { func (r *Request) Marshal() []byte {
// //s = r.size
var buf bytes.Buffer var buf bytes.Buffer
buf.Write(r.header.marshal()) buf.Write(r.header.marshal())
if len(r.operationAttributes) > 0 { if len(r.a.operation) > 0 {
buf.WriteByte(byte(operationAttributes)) buf.WriteByte(byte(operationAttributes))
for _, e := range r.operationAttributes { for _, e := range r.a.operation {
buf.Write(e.marshal()) buf.Write(e.marshal())
} }
} }
if len(r.jobAttributes) > 0 { if len(r.a.job) > 0 {
buf.WriteByte(byte(jobAttributes)) buf.WriteByte(byte(jobAttributes))
for _, e := range r.jobAttributes { for _, e := range r.a.job {
buf.Write(e.marshal()) buf.Write(e.marshal())
} }
} }
if len(r.printerAttributes) > 0 { if len(r.a.printer) > 0 {
buf.WriteByte(byte(printerAttributes)) buf.WriteByte(byte(printerAttributes))
for _, e := range r.printerAttributes { for _, e := range r.a.printer {
buf.Write(e.marshal()) buf.Write(e.marshal())
} }
} }
buf.WriteByte(byte(endOfAttributes)) buf.WriteByte(byte(endOfAttributes))
return buf.Bytes() 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

@@ -64,10 +64,10 @@ func TestUnmarshalRequestPrinterAttributes(T *testing.T) {
req.UnMarshal(buf) req.UnMarshal(buf)
fmt.Print(req) fmt.Print(req)
assert.Equal(T, versionNumber(0x0101), req.header.versionNumber, "Wrong version number") assert.Equal(T, versionNumber(0x0101), req.header.versionNumber, "Wrong version number")
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.operationAttributes, 4) assert.Len(T, req.a.operation, 4)
v := req.operationAttributes["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,63 +1,95 @@
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) String() string { // Name returns the name of the attribute
return fmt.Sprintf("%v:%v,%v,%v", r.name, r.crossFeedResolution, r.feedResolution, r.units) func (r SetOfResolutions) Name() string {
return r.name
} }
func (r *resolution) marshal() []byte { func (r SetOfResolutions) String() string {
return fmt.Sprintf("%v:%v", r.name, r.sor)
}
b := make([]byte, 0, 14+len(r.name)) func (r *SetOfResolutions) marshal() []byte {
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,115 +1,144 @@
package ipp package ipp
import ( import (
"bufio"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io"
) )
type ippResponseHeader struct { type ippResponseHeader struct {
versionNumber versionNumber versionNumber versionNumber
statusCode statusCode statusCode statusCode
requestId uint32 requestID uint32
} }
func (h ippResponseHeader) String() string { func (h ippResponseHeader) String() string {
return fmt.Sprintf("Version number: %v Status code: %v Request Id: %v", h.versionNumber, h.statusCode, h.requestId) return fmt.Sprintf("Version number: %v Status code: %v Request Id: %v", h.versionNumber, h.statusCode, h.requestID)
} }
func (h *ippResponseHeader) marshal() []byte { func (h *ippResponseHeader) marshal() []byte {
a := make([]byte, 8, 8) a := make([]byte, 8)
binary.BigEndian.PutUint16(a[0:2], uint16(h.versionNumber)) binary.BigEndian.PutUint16(a[0:2], uint16(h.versionNumber))
binary.BigEndian.PutUint16(a[2:4], uint16(h.statusCode)) 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 return a
} }
type Response struct { func (h *ippResponseHeader) unmarshal(byteStream io.Reader) {
operationAttributes []Attribute binary.Read(byteStream, binary.BigEndian, &h.versionNumber)
jobAttributes []Attribute binary.Read(byteStream, binary.BigEndian, &h.statusCode)
printerAttributes []Attribute binary.Read(byteStream, binary.BigEndian, &h.requestID)
header ippResponseHeader
} }
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 := new(Response)
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
r.operationAttributes = make([]Attribute, 0)
r.printerAttributes = make([]Attribute, 0)
r.jobAttributes = make([]Attribute, 0)
return r 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 { func (r Response) String() string {
return r.header.String() + "\n" + r.a.String()
s := r.header.String() + "\n" + " OperationAttributes" + "\n"
for _, a := range r.operationAttributes {
s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag())
}
s = s + " PrinterAttributes" + "\n"
for _, a := range r.printerAttributes {
s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag())
}
s = s + " JobAttributes" + "\n"
for _, a := range r.jobAttributes {
s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag())
}
return s
} }
func (r Response) 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()...)
if len(r.operationAttributes) > 0 { if len(r.a.operation) > 0 {
a = append(a, byte(operationAttributes)) a = append(a, byte(operationAttributes))
for _, e := range r.operationAttributes { for _, e := range r.a.operation {
a = append(a, e.marshal()...) a = append(a, e.marshal()...)
} }
} }
if len(r.jobAttributes) > 0 { if len(r.a.job) > 0 {
a = append(a, byte(jobAttributes)) a = append(a, byte(jobAttributes))
for _, e := range r.jobAttributes { for _, e := range r.a.job {
a = append(a, e.marshal()...) a = append(a, e.marshal()...)
} }
} }
if len(r.printerAttributes) > 0 { if len(r.a.printer) > 0 {
a = append(a, byte(printerAttributes)) a = append(a, byte(printerAttributes))
for _, e := range r.printerAttributes { for _, e := range r.a.printer {
a = append(a, e.marshal()...) a = append(a, e.marshal()...)
} }
} }
a = append(a, byte(endOfAttributes)) a = append(a, byte(endOfAttributes))
return a return a
} }
func (r *Response) AddPrinterAttribute(a Attribute) { // UnMarshal unmarshals a ipp response into a response object
r.printerAttributes = append(r.printerAttributes, a) 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) { // AddPrinterAttribute adds a printer attribute to the response object
r.operationAttributes = append(r.operationAttributes, a) 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) { // AddOperatonAttribute adds a printer attribute to the response object
r.jobAttributes = append(r.jobAttributes, a) 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

@@ -10,7 +10,7 @@ func TestMarshalResponseHeader(T *testing.T) {
h.versionNumber = 0x0101 h.versionNumber = 0x0101
h.statusCode = SuccessfulOk h.statusCode = SuccessfulOk
h.requestId = 0xdeadbeef h.requestID = 0xdeadbeef
b := h.marshal() b := h.marshal()
fmt.Printf("% x\n", b) fmt.Printf("% x\n", b)

View File

@@ -2,28 +2,31 @@ package ipp
import "encoding/binary" import "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
} }
@@ -39,15 +42,15 @@ func (s *setOfStrings) valueTag() tag {
// } // }
// } // }
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])
} }
res := make([]byte, l, 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 +61,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,11 +73,12 @@ 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 { func (s *SetOfStrings) size() int {
l := 1 + 2 // The value tag (0x44) + name-length field (2 bytes) l := 1 + 2 // The value tag (0x44) + name-length field (2 bytes)
l += len(s.name) l += len(s.name)
l += 2 // value-length field (2 bytes) l += 2 // value-length field (2 bytes)

View File

@@ -1,41 +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) String() string {
return c.name + ":" + c.value
}
func (c *textWithoutLanguage) valueTag() tag {
return textWithoutLanguageValueTag
}
func (c *textWithoutLanguage) unmarshal(byteStream io.Reader) {
c.name, c.value = unmarshalSingleValue(byteStream)
}
func (c *textWithoutLanguage) marshal() []byte {
l := 5 + len(c.name) + len(c.value)
b := make([]byte, l, l)
b[0] = byte(textWithoutLanguageValueTag)
marshalNameValue(c.name, c.value, b[1:])
return b
}
func (c *textWithoutLanguage) size() int {
l := 1 + 4 // The attribute tag + 2 lengths
l += len(c.name)
l += len(c.value)
return l
}

View File

@@ -0,0 +1,49 @@
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,40 @@
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

@@ -4,37 +4,49 @@ 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) 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 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

@@ -8,7 +8,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

@@ -24,11 +24,6 @@ func Run(ctx context.Context) {
log.Fatalf("EntryGroupNew() failed: %v", err) log.Fatalf("EntryGroupNew() failed: %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("GetHostNameFqdn() failed: %v", err)
@@ -36,11 +31,11 @@ func Run(ctx context.Context) {
var txt [][]byte var txt [][]byte
txt = append(txt, []byte("note=burken")) txt = append(txt, []byte("note=burken"))
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, "ChroBroPrint", "_ipp._tcp", "local", fqdn, 1234, txt)
if err != nil { if err != nil {
log.Fatalf("AddService() failed: %v", err) log.Fatalf("AddService() failed: %v", err)
} }

View File

@@ -0,0 +1,10 @@
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,45 @@
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", "ipp://"+printerURI))
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("http://"+"brn30055cb5e3ae.local:631/ipp/print", "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
}

46
proxy/handlegetjobs.go Normal file
View File

@@ -0,0 +1,46 @@
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", "ipp://"+printerURI))
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("http://"+"brn30055cb5e3ae.local:631/ipp/print", "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,36 @@
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
}

67
proxy/handleprintjob.go Normal file
View File

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

80
proxy/main.go Normal file
View File

@@ -0,0 +1,80 @@
package main
import (
"context"
"flag"
"ippserver/packages/ipp"
"ippserver/packages/mdnsserver"
"net/http"
"sync"
log "github.com/sirupsen/logrus"
)
var (
loglevel string
location string
)
func init() {
flag.StringVar(&loglevel, "loglevel", "info", "The wanted loglevel error/info/debug")
}
func main() {
customFormatter := new(log.TextFormatter)
customFormatter.TimestampFormat = "2006-01-02 15:04:05"
log.SetFormatter(customFormatter)
customFormatter.FullTimestamp = true
log.SetLevel(log.InfoLevel)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go mdnsserver.Run(ctx)
http.HandleFunc("/ipp/print", handle)
log.Info("http server started on :1234")
err := http.ListenAndServe(":1234", nil)
if err != nil {
log.Fatal("ListenAndServe: " + err.Error())
}
}
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,10 @@
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

@@ -3,6 +3,6 @@ package main
import "ippserver/packages/ipp" import "ippserver/packages/ipp"
func handleGetJobs(r *ipp.Request) *ipp.Response { func handleGetJobs(r *ipp.Request) *ipp.Response {
response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestId()) response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestID())
return response return response
} }

View File

@@ -3,26 +3,34 @@ 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

@@ -16,7 +16,9 @@ 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

@@ -30,7 +30,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,8 +55,10 @@ 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())
} }
log.Infof("Response:\n%v\n", response) log.Infof("Response:\n%v\n", response)