const ClearSignedHeader = "-----BEGIN PGP SIGNED MESSAGE-----\n"
-// (non-standard) MIME subtype for epoint documents, see RFC 2045 and RFC 2046
+// (non-standard) MIME type for epoint documents, see RFC 2045 and RFC 2046
var ContentType = map[string]string{
"cert": "text/plain.epoint.cert; charset=utf-8",
"draft": "text/plain.epoint.draft; charset=utf-8",
// Draft document represents an obligation transfer order
type Draft struct {
- Drawer string `marshal:"id"` // ID of the payer (signer of the document)
- Beneficiary string `marshal:"id"` // ID of the payee
- Amount int64 // amount transfered
+ Drawer string `marshal:"id"`
+ Beneficiary string `marshal:"id"`
+ Amount int64
Denomination string
- IssueDate int64 `marshal:"date" key:"Issue-Date"`
- // Draft is bounced before this date
+ Issuer string `marshal:"id"`
+ AuthorizedBy string `marshal:"id" key:"Authorized-By"`
+ Date int64 `marshal:"date"`
MaturityDate int64 `marshal:"date" key:"Maturity-Date"`
- Notes string // Arbitrary text notes of the drawer
- Nonce string // unique number
- Server string `marshal:"id"` // ID of the server
- Drawee string `marshal:"id"` // ID of the obligation issuer
+ ExpiryDate int64 `marshal:"date" key:"Expiry-Date"`
+ Notes string
+ Nonce string
// useful if more strict date of issue information is needed
//References []string
}
// References previous certificate (if any)
// and the transfer related other documents
type Cert struct {
- Holder string `marshal:"id"` // ID of the creditor
- Serial uint32 // serial number, number of certs of the holder
- Date int64 `marshal:"date"` // date of issue
- Balance int64 // current obligation value
+ Holder string `marshal:"id"`
+ Serial int64
+ Date int64 `marshal:"date"`
+ Balance int64
Denomination string
- Server string `marshal:"id"` // ID of the server
- Issuer string `marshal:"id"` // ID of the obligation issuer (drawee?)
- LastDebitSerial uint32 `key:"Last-Debit-Serial"` // serial of the last draft cert or 0
- LastCreditSerial uint32 `key:"Last-Credit-Serial"` // serial of the last credit cert or 0
- LastCert string `marshal:"id" key:"Last-Cert"` // ID of the previous cert if any
- Difference int64 // difference from previous balance
- Draft string `marshal:"id"` // draft ID related to the transfer
- Drawer string `marshal:"id"` // ID of the drawer in the transaction
- DrawerSerial uint32 `key:"Drawer-Serial"` // serial of the drawer's related debit cert
- DrawerCert string `marshal:"id" key:"Drawer-Cert"` // ID of the drawer's related debit cert
- Notes string // Arbitrary text notes of the server (signer)
- References []string `marshal:"idlist"` // cert IDs for timestamping the system
+ Issuer string `marshal:"id"`
+ AuthorizedBy string `marshal:"id" key:"Authorized-By"`
+ LastDebitSerial int64 `key:"Last-Debit-Serial"`
+ LastCreditSerial int64 `key:"Last-Credit-Serial"`
+ LastCert string `marshal:"id" key:"Last-Cert"`
+ Difference int64
+ Draft string `marshal:"id"`
+ Drawer string `marshal:"id"`
+ DrawerCert string `marshal:"id" key:"Drawer-Cert"`
+ Notes string // TODO: server or drawer?
+ References []string `marshal:"idlist"`
}
-func DecodeClearSigned(s []byte) (c *ClearSigned, err error) {
+func ParseClearSigned(s []byte) (c *ClearSigned, err error) {
hash, body, sig := split(s)
if len(sig) == 0 {
- err = fmt.Errorf("DecodeClearSigned could parse the signed document")
+ err = fmt.Errorf("ParseClearSigned could parse the signed document")
return
}
c = &ClearSigned{string(hash), trimspace(dashunesc(body)), sig}
return
}
-func EncodeClearSigned(c *ClearSigned) (data []byte, err error) {
+func FormatClearSigned(c *ClearSigned) (data []byte, err error) {
s := ClearSignedHeader
if c.Hash != "" {
s += "Hash: " + c.Hash + "\n"
}
// TODO: limit errors
-func render(v interface{}) ([]byte, error) {
+func format(v interface{}) ([]byte, error) {
doctype := ""
switch v.(type) {
case *Draft:
return
}
-func RenderDraft(draft *Draft) ([]byte, error) {
- return render(draft)
+func FormatDraft(draft *Draft) ([]byte, error) {
+ return format(draft)
}
func ParseCert(s []byte) (cert *Cert, err error) {
return
}
-func RenderCert(cert *Cert) ([]byte, error) {
- return render(cert)
-}
-
-func formatId(s string) string {
- return fmt.Sprintf("%040X", s)
+func FormatCert(cert *Cert) ([]byte, error) {
+ return format(cert)
}
func parseId(s string) (string, error) {
- dst := make([]byte, 20)
if len(s) != 40 {
return "", fmt.Errorf("parseId: expected 40 characters; got %d", len(s))
}
+ dst := make([]byte, len(s)/2)
_, err := hex.Decode(dst, []byte(s))
- return string(dst), err
+ return s, err
}
-func quoteValue(s string) string {
+func formatId(s string) string {
return s
}
-func unquoteValue(s string) (string, error) {
+func parseString(s string) (string, error) {
+ if len(s) > 140 {
+ return "", fmt.Errorf("parseString: 140 chars limit is exceeded")
+ }
return s, nil
}
-func formatDate(i int64) string {
- return time.SecondsToUTC(i).Format(time.RFC3339)
+func formatString(s string) string {
+ return s
}
func parseDate(s string) (int64, error) {
return t.Seconds(), nil
}
-func marshal(iv interface{}) []Field {
+func formatDate(i int64) string {
+ return time.SecondsToUTC(i).Format(time.RFC3339)
+}
+
+func unmarshal(fields []Field, iv interface{}) error {
v := reflect.ValueOf(iv)
if v.Kind() != reflect.Ptr || v.IsNil() || v.Elem().Kind() != reflect.Struct {
panic("unmarshal: input is not a pointer to struct")
v = v.Elem()
t := v.Type()
n := v.NumField()
- fields := []Field{}
+ if len(fields) != n {
+ return fmt.Errorf("unmarshal: %s has %d fields, got %d\n", t.Name(), n, len(fields))
+ }
for i := 0; i < n; i++ {
ft := t.Field(i)
fv := v.Field(i)
if k == "" {
k = ft.Name
}
- val := ""
+ if fields[i].Key != k {
+ return fmt.Errorf("unmarshal: field %d of %s (%s) is missing\n", i+1, t.Name(), k)
+ }
+ s := fields[i].Value
+ var err error
switch fv.Kind() {
case reflect.String:
+ var val string
switch m {
case "id":
- val = formatId(fv.String())
+ val, err = parseId(s)
case "":
- val = quoteValue(fv.String())
+ val, err = parseString(s)
default:
panic("bad string field tag")
}
+ fv.SetString(val)
case reflect.Int, reflect.Int32, reflect.Int64:
+ var val int64
switch m {
case "date":
- val = formatDate(fv.Int())
+ val, err = parseDate(s)
case "":
- val = strconv.Itoa64(fv.Int())
+ val, err = strconv.Atoi64(s)
default:
panic("bad int field tag")
}
+ fv.SetInt(val)
case reflect.Uint, reflect.Uint32, reflect.Uint64:
+ var val uint64
switch m {
case "":
- val = strconv.Uitoa64(fv.Uint())
+ val, err = strconv.Atoui64(s)
default:
panic("bad uint field tag")
}
+ fv.SetUint(val)
case reflect.Slice:
+ var val []string
switch m {
case "idlist":
if fv.Type().Elem().Kind() != reflect.String {
panic("only string slice is supported")
}
- k := fv.Len()
- for j := 0; j < k; j++ {
- if j > 0 {
- val += " "
+ ids := strings.Split(s, " ")
+ val = make([]string, len(ids))
+ for j := range val {
+ val[j], err = parseId(ids[j])
+ if err != nil {
+ return err
}
- val += formatId(fv.Index(j).String())
}
default:
panic("bad slice field tag")
}
+ fv.Set(reflect.ValueOf(val))
default:
panic("bad field type")
}
- fields = append(fields, Field{k, val})
+ if err != nil {
+ return err
+ }
}
- return fields
+ return nil
}
-func unmarshal(fields []Field, iv interface{}) error {
+func marshal(iv interface{}) []Field {
v := reflect.ValueOf(iv)
if v.Kind() != reflect.Ptr || v.IsNil() || v.Elem().Kind() != reflect.Struct {
panic("unmarshal: input is not a pointer to struct")
v = v.Elem()
t := v.Type()
n := v.NumField()
- if len(fields) != n {
- return fmt.Errorf("unmarshal: %s has %d fields, got %d\n", t.Name(), n, len(fields))
- }
+ fields := []Field{}
for i := 0; i < n; i++ {
ft := t.Field(i)
fv := v.Field(i)
if k == "" {
k = ft.Name
}
- if fields[i].Key != k {
- return fmt.Errorf("unmarshal: field %d of %s (%s) is missing\n", i, t.Name(), k)
- }
- s := fields[i].Value
- var err error
+ val := ""
switch fv.Kind() {
case reflect.String:
- var val string
switch m {
case "id":
- val, err = parseId(s)
+ val = formatId(fv.String())
case "":
- val, err = unquoteValue(s)
+ val = formatString(fv.String())
default:
panic("bad string field tag")
}
- fv.SetString(val)
case reflect.Int, reflect.Int32, reflect.Int64:
- var val int64
switch m {
case "date":
- val, err = parseDate(s)
+ val = formatDate(fv.Int())
case "":
- val, err = strconv.Atoi64(s)
+ val = strconv.Itoa64(fv.Int())
default:
panic("bad int field tag")
}
- fv.SetInt(val)
case reflect.Uint, reflect.Uint32, reflect.Uint64:
- var val uint64
switch m {
case "":
- val, err = strconv.Atoui64(s)
+ val = strconv.Uitoa64(fv.Uint())
default:
panic("bad uint field tag")
}
- fv.SetUint(val)
case reflect.Slice:
- var val []string
switch m {
case "idlist":
if fv.Type().Elem().Kind() != reflect.String {
panic("only string slice is supported")
}
- ids := strings.Split(s, " ")
- val = make([]string, len(ids))
- for j := range val {
- val[j], err = parseId(ids[j])
- if err != nil {
- return err
+ k := fv.Len()
+ for j := 0; j < k; j++ {
+ if j > 0 {
+ val += " "
}
+ val += formatId(fv.Index(j).String())
}
default:
panic("bad slice field tag")
}
- fv.Set(reflect.ValueOf(val))
default:
panic("bad field type")
}
- if err != nil {
- return err
- }
+ fields = append(fields, Field{k, val})
}
- return nil
+ return fields
}
func getLine(data []byte) (line, rest []byte) {