copy the other repo and make packages install.
This commit is contained in:
193
timestamp.go
Normal file
193
timestamp.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package opentimestamps
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type dumpConfig struct {
|
||||
showMessage bool
|
||||
showFlat bool
|
||||
}
|
||||
|
||||
var defaultDumpConfig dumpConfig = dumpConfig{
|
||||
showMessage: true,
|
||||
showFlat: false,
|
||||
}
|
||||
|
||||
// A timestampLink with the opCode being the link edge. The reference
|
||||
// implementation uses a map, but the implementation is a bit complex. A list
|
||||
// should work as well.
|
||||
type tsLink struct {
|
||||
opCode opCode
|
||||
timestamp *Timestamp
|
||||
}
|
||||
|
||||
// A Timestamp can contain many attestations and operations.
|
||||
type Timestamp struct {
|
||||
Message []byte
|
||||
Attestations []Attestation
|
||||
ops []tsLink
|
||||
}
|
||||
|
||||
// Walk calls the passed function f for this timestamp and all
|
||||
// downstream timestamps that are chained via operations.
|
||||
func (t *Timestamp) Walk(f func(t *Timestamp)) {
|
||||
f(t)
|
||||
for _, l := range t.ops {
|
||||
l.timestamp.Walk(f)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Timestamp) encode(ctx *serializationContext) error {
|
||||
n := len(t.Attestations) + len(t.ops)
|
||||
if n == 0 {
|
||||
return fmt.Errorf("cannot encode empty timestamp")
|
||||
}
|
||||
prefixAtt := []byte{0x00}
|
||||
prefixOp := []byte{}
|
||||
nextNode := func(prefix []byte) error {
|
||||
n -= 1
|
||||
if n > 0 {
|
||||
return ctx.writeByte(0xff)
|
||||
}
|
||||
if len(prefix) > 0 {
|
||||
return ctx.writeBytes(prefix)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// FIXME attestations should be sorted
|
||||
for _, att := range t.Attestations {
|
||||
if err := nextNode(prefixAtt); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := encodeAttestation(ctx, att); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// FIXME ops should be sorted
|
||||
for _, op := range t.ops {
|
||||
if err := nextNode(prefixOp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := op.opCode.encode(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := op.timestamp.encode(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Timestamp) DumpIndent(w io.Writer, indent int, cfg dumpConfig) {
|
||||
if cfg.showMessage {
|
||||
fmt.Fprintf(w, strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(w, "message %x\n", t.Message)
|
||||
}
|
||||
for _, att := range t.Attestations {
|
||||
fmt.Fprint(w, strings.Repeat(" ", indent))
|
||||
fmt.Fprintln(w, att)
|
||||
}
|
||||
|
||||
for _, tsLink := range t.ops {
|
||||
fmt.Fprint(w, strings.Repeat(" ", indent))
|
||||
fmt.Fprintln(w, tsLink.opCode)
|
||||
// fmt.Fprint(w, strings.Repeat(" ", indent))
|
||||
// if the timestamp is indeed tree-shaped, show it like that
|
||||
if !cfg.showFlat || len(t.ops) > 1 {
|
||||
indent += 1
|
||||
}
|
||||
tsLink.timestamp.DumpIndent(w, indent, cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Timestamp) DumpWithConfig(cfg dumpConfig) string {
|
||||
b := &bytes.Buffer{}
|
||||
t.DumpIndent(b, 0, cfg)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (t *Timestamp) Dump() string {
|
||||
return t.DumpWithConfig(defaultDumpConfig)
|
||||
}
|
||||
|
||||
func parseTagOrAttestation(
|
||||
ts *Timestamp,
|
||||
ctx *deserializationContext,
|
||||
tag byte,
|
||||
message []byte,
|
||||
limit int,
|
||||
) error {
|
||||
if tag == 0x00 {
|
||||
a, err := ParseAttestation(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ts.Attestations = append(ts.Attestations, a)
|
||||
} else {
|
||||
op, err := parseOp(ctx, tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newMessage, err := op.apply(message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nextTs := &Timestamp{Message: newMessage}
|
||||
err = parse(nextTs, ctx, newMessage, limit-1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ts.ops = append(ts.ops, tsLink{op, nextTs})
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parse(
|
||||
ts *Timestamp, ctx *deserializationContext, message []byte, limit int,
|
||||
) error {
|
||||
if limit == 0 {
|
||||
return fmt.Errorf("recursion limit")
|
||||
}
|
||||
var tag byte
|
||||
var err error
|
||||
for {
|
||||
tag, err = ctx.readByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag == 0xff {
|
||||
tag, err = ctx.readByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err := parseTagOrAttestation(ts, ctx, tag, message, limit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return parseTagOrAttestation(ts, ctx, tag, message, limit)
|
||||
}
|
||||
|
||||
func newTimestampFromContext(
|
||||
ctx *deserializationContext, message []byte,
|
||||
) (*Timestamp, error) {
|
||||
recursionLimit := 1000
|
||||
ts := &Timestamp{Message: message}
|
||||
err := parse(ts, ctx, message, recursionLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ts, nil
|
||||
}
|
||||
|
||||
func NewTimestampFromReader(r io.Reader, message []byte) (*Timestamp, error) {
|
||||
return newTimestampFromContext(newDeserializationContext(r), message)
|
||||
}
|
||||
Reference in New Issue
Block a user