diff --git a/packages/ipp/enum.go b/packages/ipp/enum.go new file mode 100644 index 0000000..4123774 --- /dev/null +++ b/packages/ipp/enum.go @@ -0,0 +1,52 @@ +package ipp + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + + log "github.com/sirupsen/logrus" +) + +type enum struct { + name string + value int32 +} + +func NewEnum(name string, value int32) *enum { + e := new(enum) + e.name = name + e.value = value + return e +} + +func (e enum) String() string { + return e.name + ":" + fmt.Sprint(e.value) +} +func (e *enum) valueTag() tag { + return enumValueTag +} + +func (e *enum) unmarshal(byteStream io.Reader) { + log.Warn("Unmarshal of enum is not implemented yet") +} + +func (e *enum) marshal() []byte { + l := 3 + len(e.name) + 6 + b := make([]byte, 0, l) + buf := bytes.NewBuffer(b) + buf.WriteByte(byte(enumValueTag)) + binary.Write(buf, binary.BigEndian, uint16(len(e.name))) + buf.WriteString(e.name) + binary.Write(buf, binary.BigEndian, uint16(4)) + binary.Write(buf, binary.BigEndian, e.value) + return buf.Bytes() +} + +func (e *enum) size() int { + l := 1 + 4 // The attribute tag + 2 lengths + l += len(e.name) + l += 4 + return l +} diff --git a/packages/ipp/keyword.go b/packages/ipp/keyword.go index 655479a..f702cdc 100644 --- a/packages/ipp/keyword.go +++ b/packages/ipp/keyword.go @@ -1,45 +1,33 @@ package ipp -import "io" - type keyWord struct { - name string - values []string + sos *setOfStrings } -func newKeyWord() *keyWord { +func NewKeyWord(name string, values ...string) *keyWord { + k := new(keyWord) + k.sos = NewSetOfStrings(name, keyWordValueTag, values) return k } -func (k *keyWord) string() string { - return "a uriValue" -} -func (k *keyWord) valueTag() tag { - return keyWordValueTag -} - -func (k *keyWord) unmarshal(byteStream io.Reader) { - -} - -func (k *keyWord) marshal() []byte { - return []byte{} -} - -func (k *keyWord) addValue(v string) { - k.values = append(k.values, v) +func (k keyWord) String() string { + return k.sos.String() } func (k *keyWord) size() int { - l := 1 + 2 // The value tag (0x44) + name-length field (2 bytes) - l += len(k.name) - l += 2 // value-length field (2 bytes) - l += len(k.values[0]) - // Add all additional values - for _, v := range k.values[1:] { - l += 1 + 4 // The value tag (0x44) + 2 length fields (2 bytes) - l += len(v) - } - return l + return k.sos.size() } + +func (k *keyWord) valueTag() tag { + return k.sos.valueTag() +} + +func (k *keyWord) marshal() []byte { + return k.sos.marshal() +} + +func (k *keyWord) addValue(v string) { + k.sos.AddValue(v) +} + diff --git a/packages/ipp/keyword_test.go b/packages/ipp/keyword_test.go new file mode 100644 index 0000000..fb2fbc7 --- /dev/null +++ b/packages/ipp/keyword_test.go @@ -0,0 +1,36 @@ +package ipp + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// func TestUnmarshalSimpleKeyword(T *testing.T) { +// testdata := []byte{ +// 0x00, 0x14, +// 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x2d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, +// 0x00, 0x16, +// 0x70, 0x72, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x2d, 0x6d, 0x61, 0x6b, 0x65, 0x2d, 0x61, 0x6e, 0x64, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x6c, +// } +// buf := bytes.NewBuffer(testdata) +// var k keyWord +// k.unmarshal(buf) +// assert.Equal(T, k.name, "requested-attributes") +// assert.Equal(T, k.values[0], "printer-make-and-model") +// } + +func TestMarshalSimpleKeyword(T *testing.T) { + testdata := []byte{ + 0x44, 0x00, 0x14, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x2d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, + 0x00, 0x16, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x2d, 0x6d, 0x61, 0x6b, 0x65, 0x2d, 0x61, 0x6e, 0x64, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x6c, + } + + k := NewKeyWord("requested-attributes") + k.addValue("printer-make-and-model") + m := k.marshal() + assert.Equal(T, testdata, m, "Should be equal") + +} diff --git a/packages/ipp/messages.go b/packages/ipp/messages.go index da6c279..39985f6 100644 --- a/packages/ipp/messages.go +++ b/packages/ipp/messages.go @@ -4,8 +4,6 @@ import ( "encoding/binary" "fmt" "io" - - log "github.com/sirupsen/logrus" ) // References @@ -45,37 +43,45 @@ const ( endCollectionValueTag tag = 0x37 // Character string values - textWithoutLanguagageValueTag tag = 0x41 - nameWithoutLanguagageValueTag tag = 0x42 - keyWordValueTag tag = 0x44 - uriValueTag tag = 0x45 - uriSchemeValueTag tag = 0x46 - charsetValueTag tag = 0x47 - naturalLanguagageValueTag tag = 0x48 - mimeMediaTypeValueTag tag = 0x49 - memberAttrNameValueTag tag = 0x4a + textWithoutLanguageValueTag tag = 0x41 + nameWithoutLanguageValueTag tag = 0x42 + keyWordValueTag tag = 0x44 + uriValueTag tag = 0x45 + uriSchemeValueTag tag = 0x46 + charsetValueTag tag = 0x47 + naturalLanguageValueTag tag = 0x48 + mimeMediaTypeValueTag tag = 0x49 + memberAttrNameValueTag tag = 0x4a ) // Operation-id, defined in rfc8011 -type operationId uint16 +type OperationId uint16 const ( - PrintJob operationId = 0x0002 - PrintURI operationId = 0x0003 - ValidateJob operationId = 0x0004 - CreateJob operationId = 0x0005 - SendDocument operationId = 0x0006 - SendURI operationId = 0x0007 - CancelJob operationId = 0x0008 - GetJobAttributes operationId = 0x0009 - GetJobs operationId = 0x000a - GetPrinterAttributes operationId = 0x000b - HoldJob operationId = 0x000c - ReleaseJob operationId = 0x000d - RestartJob operationId = 0x000e - PausePrinter operationId = 0x0010 - ResumePrinter operationId = 0x0011 - PurgeJobs operationId = 0x0012 + PrintJob OperationId = 0x0002 + PrintURI OperationId = 0x0003 + ValidateJob OperationId = 0x0004 + CreateJob OperationId = 0x0005 + SendDocument OperationId = 0x0006 + SendURI OperationId = 0x0007 + CancelJob OperationId = 0x0008 + GetJobAttributes OperationId = 0x0009 + GetJobs OperationId = 0x000a + GetPrinterAttributes OperationId = 0x000b + HoldJob OperationId = 0x000c + ReleaseJob OperationId = 0x000d + RestartJob OperationId = 0x000e + PausePrinter OperationId = 0x0010 + ResumePrinter OperationId = 0x0011 + PurgeJobs OperationId = 0x0012 +) + +type printerState int32 + +const ( + Idle printerState = 3 + Processing printerState = 4 + Stopped printerState = 5 ) type statusCode uint16 @@ -92,151 +98,16 @@ func (v versionNumber) String() string { return fmt.Sprintf("%x.%x", vn&0xff00>>8, vn&0x00ff) } -type ippRequestHeader struct { - versionNumber versionNumber - operationId operationId - requestId uint32 -} - -func (h *ippRequestHeader) marshal(byteStream io.Reader) { - binary.Read(byteStream, binary.BigEndian, &h.versionNumber) - binary.Read(byteStream, binary.BigEndian, &h.operationId) - binary.Read(byteStream, binary.BigEndian, &h.requestId) -} - -func (h ippRequestHeader) String() string { - return fmt.Sprintf("Version number: %v Operation Id: %v Request Id: %v", h.versionNumber, h.operationId, h.requestId) -} - -type Attribute interface { - valueTag() tag - unmarshal(io.Reader) - marshal() []byte - size() int -} - -type Request struct { - header ippRequestHeader - operationAttributes map[string]Attribute - jobAttributes map[string]Attribute - printerAttributes map[string]Attribute -} - -func NewRequest() *Request { - r := new(Request) - r.operationAttributes = make(map[string]Attribute) - r.jobAttributes = make(map[string]Attribute) - r.printerAttributes = make(map[string]Attribute) - - return r -} - -func (r Request) String() string { - s := r.header.String() + "\n" + " OperationAttributes" + "\n" - for _, a := range r.operationAttributes { - s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag()) - } - return s -} - -// log.Info(r.Header) -// log.Info(r.ContentLength) - -//body := make([]byte, r.ContentLength) -//body.Read(body) - -func (r *Request) UnMarshal(body io.Reader) { - r.header.marshal(body) - log.Infof("Header %v", r.header) - var tag tag - err := binary.Read(body, binary.BigEndian, &tag) - if err != nil { - log.Error(err.Error()) - } - log.Infof("got tag - %v", tag) - - if tag == operationAttributes { - nextoperationattr: - for tag != endOfAttributes { - var lastKeyword *keyWord - err = binary.Read(body, binary.BigEndian, &tag) - if err != nil { - log.Error(err.Error()) - } - log.Infof("Value tag - %v", tag) - switch tag { - case endOfAttributes: - err = binary.Read(body, binary.BigEndian, &tag) - if err == io.EOF { - // No more data - return - } - if err != nil { - log.Error(err.Error()) - } - log.Infof("got tag - %v", tag) - err = binary.Read(body, binary.BigEndian, &tag) - if err != nil { - log.Error(err.Error()) - } - log.Infof("got tag - %v", tag) - - break nextoperationattr - case charsetValueTag: - c := NewCharSetValue("", "") - c.unmarshal(body) - r.operationAttributes[c.name] = c - log.Infof("%v %v", c.name, c.value) - case uriValueTag: - u := newUriValue("", "") - u.unmarshal(body) - r.operationAttributes[u.name] = u - log.Infof("%v %v", u.name, u.value) - case naturalLanguagageValueTag: - n := newNaturalLanguagage("", "") - n.unmarshal(body) - r.operationAttributes[n.name] = n - log.Infof("%v %v", n.name, n.value) - case keyWordValueTag: - k := newKeyWord() - k.unmarshal(body) - r.operationAttributes[k.name] = k - if k.name == "" { - lastKeyword.addValue(k.values[0]) - } else { - lastKeyword = k - } - default: - log.Errorf("Unsupported tag %v", tag) - - } - } - log.Infof("Value tag %v", tag) - } else { - log.Error("unexpected tag") - // TODO Return something sensible here - - } -} - -func (r *Request) RequestId() uint32 { - return r.header.requestId -} - func unmarshalSingleValue(byteStream io.Reader) (string, string) { var length uint16 binary.Read(byteStream, binary.BigEndian, &length) - //log.Infof("Length %v", length) attributeName := make([]byte, length) if length > 0 { binary.Read(byteStream, binary.BigEndian, attributeName) - //log.Infof("Valuename %v", string(attributeName)) } binary.Read(byteStream, binary.BigEndian, &length) - //log.Infof("Length %v", length) attributeValue := make([]byte, length) binary.Read(byteStream, binary.BigEndian, attributeValue) - //log.Infof("Value %v", string(attributeValue)) return string(attributeName), string(attributeValue) } diff --git a/packages/ipp/mimemediatype.go b/packages/ipp/mimemediatype.go new file mode 100644 index 0000000..1e5c4b6 --- /dev/null +++ b/packages/ipp/mimemediatype.go @@ -0,0 +1,32 @@ +package ipp + +type mimeMediaType struct { + sos *setOfStrings +} + +func NewMimeMediaType(name string, values ...string) *mimeMediaType { + + m := new(mimeMediaType) + m.sos = NewSetOfStrings(name, mimeMediaTypeValueTag, values) + return m +} + +func (m mimeMediaType) String() string { + return m.sos.String() +} + +func (m *mimeMediaType) size() int { + return m.sos.size() +} + +func (m *mimeMediaType) valueTag() tag { + return m.sos.valueTag() +} + +func (m *mimeMediaType) marshal() []byte { + return m.sos.marshal() +} + +func (m *mimeMediaType) addValue(v string) { + m.sos.AddValue(v) +} diff --git a/packages/ipp/namewithoutlanguage.go b/packages/ipp/namewithoutlanguage.go new file mode 100644 index 0000000..fdaac16 --- /dev/null +++ b/packages/ipp/namewithoutlanguage.go @@ -0,0 +1,45 @@ +package ipp + +import "io" + +type NameWithoutLanguage struct { + name string + value string +} + +func NewNameWithoutLanguage(name, value string) *NameWithoutLanguage { + c := new(NameWithoutLanguage) + c.name = name + c.value = value + return c +} + +func (c NameWithoutLanguage) String() string { + return c.name + ":" + c.value +} +func (c *NameWithoutLanguage) valueTag() tag { + return nameWithoutLanguageValueTag +} + +func (c *NameWithoutLanguage) unmarshal(byteStream io.Reader) { + c.name, c.value = unmarshalSingleValue(byteStream) +} + +func (c *NameWithoutLanguage) marshal() []byte { + l := 5 + len(c.name) + len(c.value) + b := make([]byte, l, l) + b[0] = byte(nameWithoutLanguageValueTag) + marshalNameValue(c.name, c.value, b[1:]) + return b +} + +func (c *NameWithoutLanguage) size() int { + l := 1 + 4 // The attribute tag + 2 lengths + l += len(c.name) + l += len(c.value) + return l +} + +func (n NameWithoutLanguage) Value() string { + return n.value +} diff --git a/packages/ipp/naturallanguagage.go b/packages/ipp/naturallanguagage.go deleted file mode 100644 index 1f27a55..0000000 --- a/packages/ipp/naturallanguagage.go +++ /dev/null @@ -1,43 +0,0 @@ -package ipp - -import ( - "io" -) - -type naturalLanguagage struct { - name string - value string -} - -func newNaturalLanguagage(name, value string) *naturalLanguagage { - c := new(naturalLanguagage) - c.name = name - c.value = value - return c -} - -func (c naturalLanguagage) String() string { - return c.name + ":" + c.value -} -func (c *naturalLanguagage) valueTag() tag { - return naturalLanguagageValueTag -} - -func (c *naturalLanguagage) unmarshal(byteStream io.Reader) { - c.name, c.value = unmarshalSingleValue(byteStream) -} - -func (c *naturalLanguagage) marshal() []byte { - l := 5 + len(c.name) + len(c.value) - b := make([]byte, l, l) - b[0] = byte(naturalLanguagageValueTag) - marshalNameValue(c.name, c.value, b[1:]) - return b -} - -func (c *naturalLanguagage) size() int { - l := 1 + 4 // The attribute tag + 2 lengths - l += len(c.name) - l += len(c.value) - return l -} diff --git a/packages/ipp/naturallanguage.go b/packages/ipp/naturallanguage.go new file mode 100644 index 0000000..43da042 --- /dev/null +++ b/packages/ipp/naturallanguage.go @@ -0,0 +1,43 @@ +package ipp + +import ( + "io" +) + +type naturalLanguage struct { + name string + value string +} + +func NewNaturalLanguage(name, value string) *naturalLanguage { + c := new(naturalLanguage) + c.name = name + c.value = value + return c +} + +func (c naturalLanguage) String() string { + return c.name + ":" + c.value +} +func (c *naturalLanguage) valueTag() tag { + return naturalLanguageValueTag +} + +func (c *naturalLanguage) unmarshal(byteStream io.Reader) { + c.name, c.value = unmarshalSingleValue(byteStream) +} + +func (c *naturalLanguage) marshal() []byte { + l := 5 + len(c.name) + len(c.value) + b := make([]byte, l, l) + b[0] = byte(naturalLanguageValueTag) + marshalNameValue(c.name, c.value, b[1:]) + return b +} + +func (c *naturalLanguage) size() int { + l := 1 + 4 // The attribute tag + 2 lengths + l += len(c.name) + l += len(c.value) + return l +} diff --git a/packages/ipp/operationid_string.go b/packages/ipp/operationid_string.go index efb1b35..2d90b82 100644 --- a/packages/ipp/operationid_string.go +++ b/packages/ipp/operationid_string.go @@ -36,7 +36,7 @@ var ( _operationId_index_1 = [...]uint8{0, 12, 25, 34} ) -func (i operationId) String() string { +func (i OperationId) String() string { switch { case 2 <= i && i <= 14: i -= 2 diff --git a/packages/ipp/printerstate_string.go b/packages/ipp/printerstate_string.go new file mode 100644 index 0000000..fb82ddd --- /dev/null +++ b/packages/ipp/printerstate_string.go @@ -0,0 +1,26 @@ +// Code generated by "stringer -type printerState ."; DO NOT EDIT. + +package ipp + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Idle-3] + _ = x[Processing-4] + _ = x[Stopped-5] +} + +const _printerState_name = "IdlePreocessingStopped" + +var _printerState_index = [...]uint8{0, 4, 15, 22} + +func (i printerState) String() string { + i -= 3 + if i < 0 || i >= printerState(len(_printerState_index)-1) { + return "printerState(" + strconv.FormatInt(int64(i+3), 10) + ")" + } + return _printerState_name[_printerState_index[i]:_printerState_index[i+1]] +} diff --git a/packages/ipp/request.go b/packages/ipp/request.go new file mode 100644 index 0000000..c9ad66a --- /dev/null +++ b/packages/ipp/request.go @@ -0,0 +1,203 @@ +package ipp + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + + log "github.com/sirupsen/logrus" +) + +type ippRequestHeader struct { + versionNumber versionNumber + operationId OperationId + requestId uint32 +} + +func (h *ippRequestHeader) unmarshal(byteStream io.Reader) { + binary.Read(byteStream, binary.BigEndian, &h.versionNumber) + binary.Read(byteStream, binary.BigEndian, &h.operationId) + binary.Read(byteStream, binary.BigEndian, &h.requestId) +} + +func (h *ippRequestHeader) marshal() []byte { + b := make([]byte, 0, 8) + buf := bytes.NewBuffer(b) + binary.Write(buf, binary.BigEndian, h.versionNumber) + binary.Write(buf, binary.BigEndian, h.operationId) + binary.Write(buf, binary.BigEndian, h.requestId) + return buf.Bytes() +} + +func (h ippRequestHeader) String() string { + return fmt.Sprintf("Version number: %v Operation Id: %v Request Id: %v", h.versionNumber, h.operationId, h.requestId) +} + +type Attribute interface { + valueTag() tag + marshal() []byte + //size() int +} + +type AddValuer interface { + addValue(string) +} + +type Request struct { + operationAttributes map[string]Attribute + jobAttributes map[string]Attribute + printerAttributes map[string]Attribute + header ippRequestHeader +} + +func NewRequest(op OperationId, requestId uint32) *Request { + r := new(Request) + r.header.operationId = op + r.header.requestId = requestId + r.header.versionNumber = 0x0200 + r.operationAttributes = make(map[string]Attribute) + r.jobAttributes = make(map[string]Attribute) + r.printerAttributes = make(map[string]Attribute) + + return r +} + +func (r Request) String() string { + s := r.header.String() + "\n" + " OperationAttributes" + "\n" + for _, a := range r.operationAttributes { + s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag()) + } + s = s + " PrinterAttributes" + "\n" + for _, a := range r.printerAttributes { + s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag()) + } + s = s + " JobAttributes" + "\n" + for _, a := range r.jobAttributes { + s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag()) + } + return s +} + +func (r *Request) UnMarshal(body io.Reader) { + r.header.unmarshal(body) + log.Debugf("Header %v", r.header) + var tag tag + err := binary.Read(body, binary.BigEndian, &tag) + if err != nil { + log.Error(err.Error()) + } + log.Debugf("got tag - %v", tag) + var currentAttributeGroup map[string]Attribute + switch tag { + case operationAttributes: + currentAttributeGroup = r.operationAttributes + case jobAttributes: + currentAttributeGroup = r.jobAttributes + case printerAttributes: + currentAttributeGroup = r.printerAttributes + default: + log.Errorf("Unknown tag %v", tag) + } + + var lastAddValuer AddValuer + for { + err = binary.Read(body, binary.BigEndian, &tag) + if err != nil { + log.Errorf("End of input before end of attributes tag (%v)", err.Error()) + } + log.Debugf("Value tag - %v", tag) + switch tag { + case endOfAttributes: + return + case charsetValueTag: + c := NewCharSetValue("", "") + c.unmarshal(body) + currentAttributeGroup[c.name] = c + log.Debugf("%v %v", c.name, c.value) + case uriValueTag: + u := NewUriValue("", "") + u.unmarshal(body) + currentAttributeGroup[u.name] = u + log.Debugf("%v %v", u.name, u.value) + case naturalLanguageValueTag: + n := NewNaturalLanguage("", "") + n.unmarshal(body) + currentAttributeGroup[n.name] = n + log.Debugf("%v %v", n.name, n.value) + case keyWordValueTag: + name, value := unmarshalSingleValue(body) + if name == "" { + lastAddValuer.addValue(value) + } else { + k := NewKeyWord(name, value) + currentAttributeGroup[name] = k + lastAddValuer = k + } + log.Debugf("%v : %v", name, value) + case nameWithoutLanguageValueTag: + n := NewNameWithoutLanguage("", "") + n.unmarshal(body) + currentAttributeGroup[n.name] = n + log.Debugf("%v %v", n.name, n.value) + case mimeMediaTypeValueTag: + name, value := unmarshalSingleValue(body) + if name == "" { + lastAddValuer.addValue(value) + } else { + m := NewMimeMediaType(name, value) + currentAttributeGroup[name] = m + lastAddValuer = m + } + log.Debugf("%v : %v", name, value) + case jobAttributes: + log.Debug("Start job attributes") + currentAttributeGroup = r.jobAttributes + case resolutionValueTag: + res := NewResolution("", 0, 0) + res.unmarshal(body) + currentAttributeGroup[res.name] = res + log.Debugf("Resolution %v", res) + default: + log.Errorf("Unsupported tag %v", tag) + } + } +} + +func (r *Request) RequestId() uint32 { + return r.header.requestId +} + +func (r *Request) Operation() OperationId { + return r.header.operationId +} + +func (r *Request) GetAttribute(name string) Attribute { + return r.operationAttributes[name] +} + +func (r *Request) Marshal() []byte { + // //s = r.size + var buf bytes.Buffer + buf.Write(r.header.marshal()) + if len(r.operationAttributes) > 0 { + buf.WriteByte(byte(operationAttributes)) + for _, e := range r.operationAttributes { + buf.Write(e.marshal()) + } + } + if len(r.jobAttributes) > 0 { + buf.WriteByte(byte(jobAttributes)) + for _, e := range r.jobAttributes { + buf.Write(e.marshal()) + } + } + if len(r.printerAttributes) > 0 { + buf.WriteByte(byte(printerAttributes)) + for _, e := range r.printerAttributes { + buf.Write(e.marshal()) + } + } + buf.WriteByte(byte(endOfAttributes)) + return buf.Bytes() +} diff --git a/packages/ipp/request_test.go b/packages/ipp/request_test.go new file mode 100644 index 0000000..296f7af --- /dev/null +++ b/packages/ipp/request_test.go @@ -0,0 +1,79 @@ +package ipp + +import ( + "bytes" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +var testRequest = []byte{0x01, 0x01, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x11, + 0x01, + 0x47, + 0x00, 0x12, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, + 0x00, 0x05, + 0x75, 0x74, 0x66, 0x2d, 0x38, + 0x48, + 0x00, 0x1b, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2d, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x61, 0x6c, 0x2d, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, + 0x00, 0x05, + 0x65, 0x6e, 0x2d, 0x75, 0x73, + 0x45, // uriValueTag + 0x00, 0x0b, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x2d, 0x75, 0x72, 0x69, + 0x00, 0x20, + 0x69, 0x70, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x30, 0x2e, 0x32, 0x3a, 0x31, 0x32, 0x33, 0x34, 0x2f, 0x69, 0x70, 0x70, 0x2f, 0x70, 0x72, 0x69, 0x6e, 0x74, + 0x44, // keywordValueTag + 0x00, 0x14, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x2d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, + 0x00, 0x16, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x2d, 0x6d, 0x61, 0x6b, 0x65, 0x2d, 0x61, 0x6e, 0x64, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x6c, + 0x44, // keywordValueTag + 0x00, 0x00, + 0x00, 0x16, + 0x69, 0x70, 0x70, 0x2d, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2d, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, + 0x44, // keywordValueTag + 0x00, 0x00, + 0x00, 0x16, + 0x69, 0x70, 0x70, 0x2d, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2d, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, + 0x44, // keywordValueTag + 0x00, 0x00, + 0x00, 0x19, + 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2d, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2d, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, + 0x44, + 0x00, 0x00, + 0x00, 0x0d, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x44, + 0x00, 0x00, + 0x00, 0x15, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2d, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x73, + 0x44, + 0x00, 0x00, + 0x00, 0x15, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x03} + +func TestUnmarshalRequestPrinterAttributes(T *testing.T) { + buf := bytes.NewBuffer(testRequest) + + req := NewRequest(GetPrinterAttributes, 17) + + req.UnMarshal(buf) + fmt.Print(req) + assert.Equal(T, versionNumber(0x0101), req.header.versionNumber, "Wrong version number") + assert.Equal(T, GetPrinterAttributes, req.header.operationId, "Wrong Operation") + assert.Equal(T, uint32(17), req.header.requestId, "Wrong request id") + assert.Len(T, req.operationAttributes, 4) + v := req.operationAttributes["requested-attributes"].(*keyWord).sos.values + assert.Len(T, v, 7) + assert.Contains(T, v, "printer-make-and-model") + assert.Contains(T, v, "ipp-versions-supported") + assert.Contains(T, v, "ipp-features-supported") + assert.Contains(T, v, "document-format-supported") + assert.Contains(T, v, "printer-state-reasons") + assert.Contains(T, v, "printer-state-message") + assert.Contains(T, v, "printer-state") +} diff --git a/packages/ipp/resolution.go b/packages/ipp/resolution.go new file mode 100644 index 0000000..9ba8fe9 --- /dev/null +++ b/packages/ipp/resolution.go @@ -0,0 +1,63 @@ +package ipp + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" +) + +type resolution struct { + name string + crossFeedResolution int32 + feedResolution int32 + units int8 +} + +func NewResolution(name string, xfres int32, fres int32) *resolution { + r := new(resolution) + r.name = name + r.crossFeedResolution = xfres + r.feedResolution = fres + r.units = 3 // 3 seems to mean dpi (rfc3805) + return r +} + +func (r *resolution) unmarshal(byteStream io.Reader) { + var length uint16 + binary.Read(byteStream, binary.BigEndian, &length) + attributeName := make([]byte, length) + if length > 0 { + binary.Read(byteStream, binary.BigEndian, attributeName) + } + r.name = string(attributeName) + binary.Read(byteStream, binary.BigEndian, &length) + if length != 9 { + panic("Wrong length in resolution") + } + binary.Read(byteStream, binary.BigEndian, &r.crossFeedResolution) + binary.Read(byteStream, binary.BigEndian, &r.feedResolution) + binary.Read(byteStream, binary.BigEndian, &r.units) +} + +func (r resolution) String() string { + return fmt.Sprintf("%v:%v,%v,%v", r.name, r.crossFeedResolution, r.feedResolution, r.units) +} + +func (r *resolution) marshal() []byte { + + b := make([]byte, 0, 14+len(r.name)) + buf := bytes.NewBuffer(b) + buf.WriteByte(byte(resolutionValueTag)) + binary.Write(buf, binary.BigEndian, uint16(len(r.name))) + buf.WriteString(r.name) + binary.Write(buf, binary.BigEndian, uint16(9)) + binary.Write(buf, binary.BigEndian, int32(r.crossFeedResolution)) + binary.Write(buf, binary.BigEndian, int32(r.feedResolution)) + buf.WriteByte(byte(r.units)) + return buf.Bytes() +} + +func (r *resolution) valueTag() tag { + return resolutionValueTag +} diff --git a/packages/ipp/response.go b/packages/ipp/response.go index e1f36e0..fca8e1c 100644 --- a/packages/ipp/response.go +++ b/packages/ipp/response.go @@ -1,6 +1,9 @@ package ipp -import "encoding/binary" +import ( + "encoding/binary" + "fmt" +) type ippResponseHeader struct { versionNumber versionNumber @@ -8,6 +11,10 @@ type ippResponseHeader struct { requestId uint32 } +func (h ippResponseHeader) String() string { + return fmt.Sprintf("Version number: %v Status code: %v Request Id: %v", h.versionNumber, h.statusCode, h.requestId) +} + func (h *ippResponseHeader) marshal() []byte { a := make([]byte, 8, 8) binary.BigEndian.PutUint16(a[0:2], uint16(h.versionNumber)) @@ -18,10 +25,10 @@ func (h *ippResponseHeader) marshal() []byte { } type Response struct { - header ippResponseHeader operationAttributes []Attribute jobAttributes []Attribute printerAttributes []Attribute + header ippResponseHeader } func NewResponse(code statusCode, requestId uint32) *Response { @@ -52,6 +59,23 @@ func NewResponse(code statusCode, requestId uint32) *Response { // } +func (r Response) String() string { + + s := r.header.String() + "\n" + " OperationAttributes" + "\n" + for _, a := range r.operationAttributes { + s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag()) + } + s = s + " PrinterAttributes" + "\n" + for _, a := range r.printerAttributes { + s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag()) + } + s = s + " JobAttributes" + "\n" + for _, a := range r.jobAttributes { + s = s + fmt.Sprintf(" %v (%v)\n", a, a.valueTag()) + } + return s +} + func (r *Response) Marshal() []byte { a := make([]byte, 0, 20) a = append(a, r.header.marshal()...) diff --git a/packages/ipp/setofstrings.go b/packages/ipp/setofstrings.go new file mode 100644 index 0000000..241ff24 --- /dev/null +++ b/packages/ipp/setofstrings.go @@ -0,0 +1,88 @@ +package ipp + +import "encoding/binary" + +type setOfStrings struct { + name string + values []string + vTag tag +} + +func NewSetOfStrings(name string, t tag, values []string) *setOfStrings { + s := new(setOfStrings) + s.name = name + s.vTag = t + s.values = values //make([]string, 0) + return s +} + +func (s setOfStrings) String() string { + r := s.name + " :" + for _, v := range s.values { + r = r + " " + v + } + return r +} +func (s *setOfStrings) valueTag() tag { + return s.vTag +} + +// func (k *keyWord) unmarshal(byteStream io.Reader) { +// if len(k.values) == 0 { +// var v string +// k.name, v = unmarshalSingleValue(byteStream) +// k.values = append(k.values, v) +// } else { +// var v string +// _, v = unmarshalSingleValue(byteStream) +// k.values = append(k.values, v) +// } +// } + +func (s *setOfStrings) marshal() []byte { + l := 5 + len(s.name) + len(s.values[0]) + for i := range s.values[1:] { + l += 5 + len(s.values[i+1]) + } + res := make([]byte, l, l) + p := 0 + res[p] = byte(s.vTag) + p += 1 + binary.BigEndian.PutUint16(res[p:p+2], uint16(len(s.name))) + p += 2 + copy(res[p:], []byte(s.name)) + p += len(s.name) + binary.BigEndian.PutUint16(res[p:p+2], uint16(len(s.values[0]))) + p += 2 + copy(res[p:], []byte(s.values[0])) + p += len(s.values[0]) + for i := range s.values[1:] { + res[p] = byte(s.vTag) + p += 1 + binary.BigEndian.PutUint16(res[p:p+2], uint16(0)) + p = p + 2 + binary.BigEndian.PutUint16(res[p:p+2], uint16(len(s.values[i+1]))) + p = p + 2 + copy(res[p:], []byte(s.values[i+1])) + p += len(s.values[i+1]) + } + + return res +} + +func (s *setOfStrings) AddValue(v string) { + s.values = append(s.values, v) +} + +func (s *setOfStrings) size() int { + l := 1 + 2 // The value tag (0x44) + name-length field (2 bytes) + l += len(s.name) + l += 2 // value-length field (2 bytes) + l += len(s.values[0]) + // Add all additional values + for _, v := range s.values[1:] { + l += 1 + 4 // The value tag (0x44) + 2 length fields (2 bytes) + l += len(v) + } + return l +} diff --git a/packages/ipp/tag_string.go b/packages/ipp/tag_string.go index 8101cc3..c7c4eb5 100644 --- a/packages/ipp/tag_string.go +++ b/packages/ipp/tag_string.go @@ -28,13 +28,13 @@ func _() { _ = x[textWithLanguageValueTag-53] _ = x[nameWithLanguageValueTag-54] _ = x[endCollectionValueTag-55] - _ = x[textWithoutLanguagageValueTag-65] - _ = x[nameWithoutLanguagageValueTag-66] + _ = x[textWithoutLanguageValueTag-65] + _ = x[nameWithoutLanguageValueTag-66] _ = x[keyWordValueTag-68] _ = x[uriValueTag-69] _ = x[uriSchemeValueTag-70] _ = x[charsetValueTag-71] - _ = x[naturalLanguagageValueTag-72] + _ = x[naturalLanguageValueTag-72] _ = x[mimeMediaTypeValueTag-73] _ = x[memberAttrNameValueTag-74] } diff --git a/packages/ipp/textWithoutLanguage.go b/packages/ipp/textWithoutLanguage.go new file mode 100644 index 0000000..1c53a13 --- /dev/null +++ b/packages/ipp/textWithoutLanguage.go @@ -0,0 +1,41 @@ +package ipp + +import "io" + +type textWithoutLanguage struct { + name string + value string +} + +func NewtextWithoutLanguage(name, value string) *textWithoutLanguage { + c := new(textWithoutLanguage) + c.name = name + c.value = value + return c +} + +func (c textWithoutLanguage) String() string { + return c.name + ":" + c.value +} +func (c *textWithoutLanguage) valueTag() tag { + return textWithoutLanguageValueTag +} + +func (c *textWithoutLanguage) unmarshal(byteStream io.Reader) { + c.name, c.value = unmarshalSingleValue(byteStream) +} + +func (c *textWithoutLanguage) marshal() []byte { + l := 5 + len(c.name) + len(c.value) + b := make([]byte, l, l) + b[0] = byte(textWithoutLanguageValueTag) + marshalNameValue(c.name, c.value, b[1:]) + return b +} + +func (c *textWithoutLanguage) size() int { + l := 1 + 4 // The attribute tag + 2 lengths + l += len(c.name) + l += len(c.value) + return l +} diff --git a/packages/ipp/urivalue.go b/packages/ipp/urivalue.go index b570c56..79c2b63 100644 --- a/packages/ipp/urivalue.go +++ b/packages/ipp/urivalue.go @@ -9,7 +9,7 @@ type uriValue struct { value string } -func newUriValue(name, value string) *uriValue { +func NewUriValue(name, value string) *uriValue { u := new(uriValue) u.name = name u.value = value diff --git a/packages/mdnsserver/server.go b/packages/mdnsserver/server.go index db33db6..92e1179 100644 --- a/packages/mdnsserver/server.go +++ b/packages/mdnsserver/server.go @@ -24,7 +24,7 @@ func Run(ctx context.Context) { log.Fatalf("EntryGroupNew() failed: %v", err) } - hostname, err := a.GetHostName() + //hostname, err := a.GetHostName() if err != nil { log.Fatalf("GetHostName() failed: %v", err) } @@ -39,7 +39,8 @@ func Run(ctx context.Context) { txt = append(txt, []byte("product=coola-skrivaren")) txt = append(txt, []byte("Color=T")) txt = append(txt, []byte("rp=ipp/print")) - err = eg.AddService(avahi.InterfaceUnspec, avahi.ProtoUnspec, 0, hostname, "_ipp._tcp", "local", fqdn, 1234, txt) + txt = append(txt, []byte("ty=ChroBro 001")) + err = eg.AddService(avahi.InterfaceUnspec, avahi.ProtoUnspec, 0, "ChroBro 1000", "_ipp._tcp", "local", fqdn, 1234, txt) if err != nil { log.Fatalf("AddService() failed: %v", err) } diff --git a/server/handlegetjobs.go b/server/handlegetjobs.go new file mode 100644 index 0000000..5ba3aed --- /dev/null +++ b/server/handlegetjobs.go @@ -0,0 +1,8 @@ +package main + +import "ippserver/packages/ipp" + +func handleGetJobs(r *ipp.Request) *ipp.Response { + response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestId()) + return response +} diff --git a/server/handlegetprinterattributes.go b/server/handlegetprinterattributes.go new file mode 100644 index 0000000..1b0c6dd --- /dev/null +++ b/server/handlegetprinterattributes.go @@ -0,0 +1,28 @@ +package main + +import "ippserver/packages/ipp" + +func handleGetPrinterAttributes(r *ipp.Request) *ipp.Response { + response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestId()) + var a ipp.Attribute + a = ipp.NewCharSetValue("attributes-charset", "utf-8") + response.AddOperatonAttribute(a) + a = ipp.NewNaturalLanguage("attributes-natural-language", "en") + response.AddOperatonAttribute(a) + a = ipp.NewUriValue("printer-uri", "ipp://drpork:1234/ipp/print") + response.AddOperatonAttribute(a) + a = ipp.NewtextWithoutLanguage("printer-make-and-model", "ChroBro 001") + response.AddOperatonAttribute(a) + a = ipp.NewEnum("printer-state", int32(ipp.Idle)) + response.AddOperatonAttribute(a) + a = ipp.NewKeyWord("ipp-versions-supported", "1.0", "1.1", "2.0") + response.AddOperatonAttribute(a) + response.AddOperatonAttribute(ipp.NewKeyWord("ipp-features-supported", "wfds-print-1.0")) + response.AddOperatonAttribute(ipp.NewMimeMediaType("document-format-supported", "image/pwg-raster")) + response.AddOperatonAttribute(ipp.NewKeyWord("media-supported", "iso_a4_210x297mm")) + response.AddOperatonAttribute(ipp.NewKeyWord("sides-supported", "one-sided", "two-sided-long-edge", "two-sided-short-edge")) + response.AddOperatonAttribute(ipp.NewKeyWord("print-color-mode-supported", "auto", "color", "monochrome")) + response.AddOperatonAttribute(ipp.NewResolution("printer-resolution-default", 600, 600)) + response.AddOperatonAttribute(ipp.NewBoolean("printer-is-accepting-jobs", true)) + return response +} diff --git a/server/handleprintjob.go b/server/handleprintjob.go new file mode 100644 index 0000000..b44f03d --- /dev/null +++ b/server/handleprintjob.go @@ -0,0 +1,22 @@ +package main + +import ( + "io" + "ippserver/packages/ipp" + "os" +) + +func handlePrintJob(r *ipp.Request, byteStream io.Reader) *ipp.Response { + + a := r.GetAttribute("job-name") + //a.(nameWithoutLanguage).Value + f, err := os.Create(a.(*ipp.NameWithoutLanguage).Value()) + if err != nil { + panic("fail") + } + defer f.Close() + io.Copy(f, byteStream) + response := ipp.NewResponse(ipp.SuccessfulOk, r.RequestId()) + + return response +} diff --git a/main.go b/server/main.go similarity index 51% rename from main.go rename to server/main.go index eadf1cc..6e2e98f 100644 --- a/main.go +++ b/server/main.go @@ -2,7 +2,6 @@ package main import ( "context" - "fmt" "ippserver/packages/ipp" "ippserver/packages/mdnsserver" "net/http" @@ -15,6 +14,7 @@ func main() { 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() @@ -35,18 +35,35 @@ func handle(w http.ResponseWriter, r *http.Request) { http.Error(w, "Unsupported method", http.StatusMethodNotAllowed) } - // body := make([]byte, r.ContentLength) - // io.ReadFull(r.Body, body) - // log.Infof("Body %x", body) + log.Info(r.Header) + //body := make([]byte, r.ContentLength) + //io.ReadFull(r.Body, body) + //log.Infof("Body %x", body) - request := ipp.NewRequest() + request := ipp.NewRequest(0, 0) + //rdata := make([]byte, r.ContentLength) + //io.ReadFull(r.Body, rdata) + //log.Printf("Data %x", rdata) + //fmt.Printf("data % #0x", rdata) + //buf := bytes.NewBuffer(rdata) request.UnMarshal(r.Body) - fmt.Printf("%v", request) - response := ipp.NewResponse(ipp.SuccessfulOk, request.RequestId()) - a := ipp.NewCharSetValue("attributes-charset", "utf-8") - response.AddOperatonAttribute(a) + log.Infof("Request: \n%v\n", request) + var response *ipp.Response + switch request.Operation() { + case ipp.GetPrinterAttributes: + response = handleGetPrinterAttributes(request) + case ipp.PrintJob: + response = handlePrintJob(request, r.Body) + case ipp.GetJobs: + response = handleGetJobs(request) + default: + response = ipp.NewResponse(ipp.ClientErrorBadRequest, request.RequestId()) + } + + log.Infof("Response:\n%v\n", response) data := response.Marshal() - log.Infof("% x", data) + + //log.Debugf("% x", data) w.Write(data) }