// TODO: fields of notice (last notice, serial, failure notice,..)
// TODO: limits and cert type specific input validation
// TODO: fix Cert mess
+// TODO: nonce is id, id is even number of hex digits (require only drawer.nonce to be uniq)
+// TODO: denom, issuer from key (key representation: armor?)
import (
"bytes"
"Last-Credit-Serial": "int",
"Last-Debit-Serial": "int",
"Maturity-Date": "date",
- "Nonce": "text",
+ "Nonce": "id",
"Notes": "text",
"References": "ids",
"Serial": "int",
Denomination string
Issuer string
AuthorizedBy string
- MaturityDate *int64 // optional
- ExpiryDate *int64 // optional
- Nonce *string // optional
+ MaturityDate *int64 // optional
+ ExpiryDate *int64 // optional
+ Nonce string
Notes *string // optional
}
}
type Cert struct {
- IsDebit bool
Holder string
Serial int64
Balance int64
Denomination string
Issuer string
Date int64
- Difference int64
- Draft string
- Beneficiary *string // only in debit cert
- Drawer *string // only in credit cert
- DebitCert *string // only in credit cert
AuthorizedBy string
Notes *string // optional
LastDebitSerial int64 // 0 if none
LastCreditSerial int64 // 0 if none
LastCert *string // nil if serial == 1
References []string
+ Difference int64
+ Draft string
}
type DebitCert struct {
- Holder string
- Serial int64
- Balance int64
- Denomination string
- Issuer string
- Date int64
- Difference int64
- Draft string
+ Cert
Beneficiary string
- AuthorizedBy string
- Notes *string // optional
- LastDebitSerial int64 // 0 if none
- LastCreditSerial int64 // 0 if none
- LastCert *string // nil if serial == 1
- References []string
}
type CreditCert struct {
- Holder string
- Serial int64
- Balance int64
- Denomination string
- Issuer string
- Date int64
- Difference int64
- Draft string
+ Cert
Drawer string
DebitCert string
- AuthorizedBy string
- Notes *string // optional
- LastDebitSerial int64 // 0 if none
- LastCreditSerial int64 // 0 if none
- LastCert *string // ? if serial == 1
- References []string
}
type BounceCert struct {
cert = new(Cert)
switch x := v.(type) {
case *DebitCert:
- cert.IsDebit = true
- cert.Beneficiary = &x.Beneficiary
-
- cert.Holder = x.Holder
- cert.Serial = x.Serial
- cert.Balance = x.Balance
- cert.Denomination = x.Denomination
- cert.Issuer = x.Issuer
- cert.Date = x.Date
- cert.Difference = x.Difference
- cert.Draft = x.Draft
- cert.AuthorizedBy = x.AuthorizedBy
- cert.Notes = x.Notes
- cert.LastDebitSerial = x.LastDebitSerial
- cert.LastCreditSerial = x.LastCreditSerial
- cert.LastCert = x.LastCert
- cert.References = x.References
-
+ cert = &x.Cert
case *CreditCert:
- cert.IsDebit = false
- cert.Drawer = &x.Drawer
- cert.DebitCert = &x.DebitCert
-
- cert.Holder = x.Holder
- cert.Serial = x.Serial
- cert.Balance = x.Balance
- cert.Denomination = x.Denomination
- cert.Issuer = x.Issuer
- cert.Date = x.Date
- cert.Difference = x.Difference
- cert.Draft = x.Draft
- cert.AuthorizedBy = x.AuthorizedBy
- cert.Notes = x.Notes
- cert.LastDebitSerial = x.LastDebitSerial
- cert.LastCreditSerial = x.LastCreditSerial
- cert.LastCert = x.LastCert
- cert.References = x.References
+ cert = &x.Cert
default:
err = fmt.Errorf("ToCert: only debit or credit document can be converted to cert")
}
return
}
+func cleanBody(s []byte) []byte {
+ nl := []byte{'\n'}
+ a := bytes.Split(s, nl)
+ for i := range a {
+ a[i] = bytes.TrimRight(a[i], " \t")
+ }
+ return bytes.Join(a, nl)
+}
+
// sha1 sum of the (cleaned) document body as uppercase hex string
func Id(c *Signed) string {
h := sha1.New()
func Verify(c *Signed, key openpgp.KeyRing) (err error) {
msg := bytes.NewBuffer(c.Body)
sig := bytes.NewBuffer(c.Signature)
-// TODO: verify signature
- _,_ = msg,sig
-// _, err = openpgp.CheckArmoredDetachedSignature(key, msg, sig)
+ // TODO: verify signature
+ _, _ = msg, sig
+ // _, err = openpgp.CheckArmoredDetachedSignature(key, msg, sig)
return
}
func Sign(body []byte, key *openpgp.Entity) (c *Signed, err error) {
c = new(Signed)
c.Hash = "SHA256"
- c.Body = body
+ c.Body = cleanBody(body)
w := new(bytes.Buffer)
- w.Write([]byte("\n-----BEGIN PGP SIGNATURE-----\n\nTODO: signature\n"))
-// err = openpgp.ArmoredDetachSignText(w, key, bytes.NewBuffer(c.Body))
+ err = openpgp.ArmoredDetachSignText(w, key, bytes.NewBuffer(c.Body))
+ if err != nil {
+ return
+ }
+ // close armored document with a \n
+ _, _ = w.Write([]byte{'\n'})
c.Signature = w.Bytes()
return
}
}
// parse doc fields into a struct according to the document type
-func ParseStruct(doc *Document) (iv interface{}, err error) {
- switch doc.Type {
- case "Draft":
- iv = new(Draft)
- case "Notice":
- iv = new(Notice)
- case "DebitCert":
- iv = new(DebitCert)
- case "CreditCert":
- iv = new(CreditCert)
- case "BounceCert":
- iv = new(BounceCert)
- default:
- err = fmt.Errorf("ParseStruct: unkown doc type: %s", doc.Type)
- return
- }
- seen := make(map[string]bool)
- v := reflect.ValueOf(iv).Elem()
+func parseStruct(v reflect.Value, fields map[string]string, seen map[string]bool) (err error) {
t := v.Type()
n := v.NumField()
- nokey := 0
- for i := 0; i < n; i++ {
+ for i := 0; i < n && err == nil; i++ {
ft := t.Field(i)
fv := v.Field(i)
+ if ft.Anonymous && fv.Kind() == reflect.Struct {
+ err = parseStruct(fv, fields, seen)
+ continue
+ }
key := fieldname[ft.Name]
if key == "" {
key = ft.Name
}
- seen[key] = true
- s, ok := doc.Fields[key]
+ s, ok := fields[key]
if !ok {
if fv.Kind() == reflect.Ptr {
// missing optional key: leave the pointer as nil
- nokey++
continue
}
- return nil, fmt.Errorf("ParseStruct: field %s of %s is missing\n", key, t.Name())
+ return fmt.Errorf("ParseStruct: field %s of %s is missing\n", key, t.Name())
}
+ seen[key] = true
if fv.Kind() == reflect.Ptr {
if s == "" || s == "-" {
// TODO
default:
panic("bad field type " + key + " " + fieldtype[key])
}
- if err != nil {
- return
- }
}
- if len(doc.Fields)+nokey != n {
- for k := range doc.Fields {
- if !seen[k] {
- err = fmt.Errorf("ParseStruct: unknown field %s in %s", k, t.Name())
+ return
+}
+
+func ParseStruct(doc *Document) (iv interface{}, err error) {
+ switch doc.Type {
+ case "Draft":
+ iv = new(Draft)
+ case "Notice":
+ iv = new(Notice)
+ case "DebitCert":
+ iv = new(DebitCert)
+ case "CreditCert":
+ iv = new(CreditCert)
+ case "BounceCert":
+ iv = new(BounceCert)
+ default:
+ err = fmt.Errorf("ParseStruct: unkown doc type: %s", doc.Type)
+ return
+ }
+ seen := make(map[string]bool)
+ err = parseStruct(reflect.ValueOf(iv).Elem(), doc.Fields, seen)
+ if err != nil {
+ return
+ }
+ if len(doc.Fields) != len(seen) {
+ for f := range doc.Fields {
+ if !seen[f] {
+ err = fmt.Errorf("ParseStruct: unknown field %s in %s", f, doc.Type)
return
}
}
}
// turn a struct into a document
-func FormatStruct(iv interface{}) (doc *Document, err error) {
- v := reflect.ValueOf(iv)
- if v.Kind() != reflect.Ptr || v.IsNil() || v.Elem().Kind() != reflect.Struct {
- panic("input is not a pointer to struct")
- }
- v = v.Elem()
+func formatStruct(v reflect.Value, doc *Document) (err error) {
t := v.Type()
n := v.NumField()
- doc = new(Document)
- doc.Type = t.Name()
- doc.Fields = make(map[string]string)
for i := 0; i < n; i++ {
ft := t.Field(i)
fv := v.Field(i)
+ if ft.Anonymous && fv.Kind() == reflect.Struct {
+ err = formatStruct(fv, doc)
+ if err != nil {
+ return
+ }
+ continue
+ }
key := fieldname[ft.Name]
if key == "" {
key = ft.Name
return
}
+// turn a struct into a document
+func FormatStruct(iv interface{}) (doc *Document, err error) {
+ v := reflect.ValueOf(iv)
+ if v.Kind() != reflect.Ptr || v.IsNil() || v.Elem().Kind() != reflect.Struct {
+ panic("input is not a pointer to struct")
+ }
+ doc = new(Document)
+ doc.Type = v.Elem().Type().Name()
+ doc.Fields = make(map[string]string)
+ err = formatStruct(v.Elem(), doc)
+ return
+}
+
func ParseFields(s []byte) (fields map[string]string, rest []byte, err error) {
rest = s
fields = make(map[string]string)