├── go.sum
├── go.mod
└── proto
├── README.md
├── header.go
├── record.go
├── COMMENTS
├── plog.go
├── proto.go
├── mps7.go
├── cmd
└── main.go
├── proto_test.go
└── diagram
├── state.svg
└── flow.svg
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
2 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/patterns/adhoc
2 |
3 | go 1.13
4 |
5 | require github.com/pkg/errors v0.8.1
6 |
--------------------------------------------------------------------------------
/proto/README.md:
--------------------------------------------------------------------------------
1 | # proto "MPS7"
2 |
3 | The high level program flow:
4 |
5 | 
6 |
7 |
8 | Decoding the data stream transitions state from Starting to Ready:
9 |
10 | 
11 |
12 | ## Quickstart
13 | 1. git clone the repo
14 | 2. build the executable
15 | 3. set the path to the input file
16 |
17 | ```
18 | git clone https://github.com/patterns/adhoc
19 | cd adhoc && go build -o proto proto/cmd/*.go
20 | ./proto -infile=path/to/txnlog.dat
21 | ```
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/proto/header.go:
--------------------------------------------------------------------------------
1 | package proto
2 |
3 | // Header is the protocol header data
4 | type Header struct {
5 | prefix string
6 | version byte
7 | length uint32
8 | }
9 |
10 | // Prefix is the magic field from the header data
11 | func (h Header) Prefix() string {
12 | return h.prefix
13 | }
14 |
15 | // Version is the version field from the header data
16 | func (h Header) Version() byte {
17 | return h.version
18 | }
19 |
20 | // Len is the record count from the header data
21 | func (h Header) Len() uint32 {
22 | return h.length
23 | }
24 |
25 | // Protocol is required for the ProtData interface
26 | func (h Header) Protocol() {
27 | //todo indicate MPS7 support
28 | }
29 |
--------------------------------------------------------------------------------
/proto/record.go:
--------------------------------------------------------------------------------
1 | package proto
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | type Record struct {
8 | Rectype Rtype
9 | stamp uint32
10 | User uint64
11 |
12 | // todo ?eliminate because we embed the common fields
13 | Dollars float64
14 | }
15 |
16 | type Rtype byte
17 |
18 | const (
19 | Debit Rtype = iota
20 | Credit
21 | StartAutopay
22 | EndAutopay
23 | )
24 |
25 | // Protocol is required for ProtData interface
26 | func (r Record) Protocol() {
27 | //todo indicate MPS7 support
28 | }
29 |
30 | func (r Record) String() string {
31 | return fmt.Sprintf("%s, %d, %d, %f",
32 | r.Rectype, r.stamp, r.User, r.Dollars)
33 | }
34 |
35 | func (r Rtype) String() string {
36 | switch r {
37 | case Debit:
38 | return "Debit"
39 | case Credit:
40 | return "Credit"
41 | case StartAutopay:
42 | return "StartAutopay"
43 | case EndAutopay:
44 | return "EndAutopay"
45 | default:
46 | return "None"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/proto/COMMENTS:
--------------------------------------------------------------------------------
1 | Compiling the Proto homework
2 |
3 | Golang is required to compile the program. If your machine does not have the Golang toolchain, please visit https://golang.org/doc/install and follow the installation steps.
4 |
5 | Uncompress the source files:
6 |
7 | tar -xzf homework.tgz
8 |
9 | This should make a copy of the file set and save them into the local directory "adhoc". Next, change into "adhoc" to make it the working directory and compile:
10 |
11 | cd adhoc && go build -o homework ./proto/cmd
12 |
13 | This will create and name the program as "homework". To have the program process the "txnlog.dat" file, there are two ways. If you have permission to add files to the directory where "txnlog.dat" is located, copying the "homework" program there should work because the program defaults to looking for "txnlog.dat" in the same directory.
14 |
15 | The other way is better if the "txnlog.dat" can have different names (e.g., txnlog10.dat). The "homework" program will take a command line argument (-infile) to specify the path of the .dat file:
16 |
17 | ./homework -infile=path/to/dat/file
18 |
19 |
20 | If everything is okay, the summary should print to screen, similar to:
21 |
22 | total credit amount=9366.02
23 | total debit amount=18203.70
24 | autopays started=10
25 | autopays ended=8
26 | balance for user 2456938384156277127=0.00
27 |
--------------------------------------------------------------------------------
/proto/plog.go:
--------------------------------------------------------------------------------
1 | package proto
2 |
3 | import (
4 | "fmt"
5 | "github.com/pkg/errors"
6 | "log"
7 | "os"
8 | "path/filepath"
9 | )
10 |
11 | type Plog struct {
12 | // Embed the native logger
13 | log.Logger
14 |
15 | verbose bool
16 | writer *os.File
17 | prefix string
18 | }
19 |
20 | func NewLog(verbose bool) (pl *Plog, err error) {
21 | pl = &Plog{verbose: verbose}
22 | err = nil
23 |
24 | // For verbose output, also write to file
25 | if verbose {
26 | // Re-purpose magic tag as prefix (may want as parameter)
27 | pl.prefix = prefixMPS7
28 | var wd string
29 | wd, err = os.Getwd()
30 | if err != nil {
31 | err = errors.Wrap(err, "Log work dir failed")
32 | return
33 | }
34 | pl.writer, err = os.Create(
35 | filepath.Join(wd, fmt.Sprintf("%s.log", pl.prefix)))
36 | if err != nil {
37 | err = errors.Wrap(err, "Log file create failed")
38 | return
39 | }
40 |
41 | pl.SetFlags(log.LstdFlags)
42 | pl.SetPrefix(fmt.Sprintf("%s:", pl.prefix))
43 |
44 | // Direct log output to the file
45 | pl.SetOutput(pl.writer)
46 | }
47 |
48 | return
49 | }
50 |
51 | // Info writes message to stdout and log
52 | func (p *Plog) Info(msg string) {
53 | fmt.Println(msg)
54 |
55 | if p.verbose {
56 | p.Print(msg)
57 | }
58 | }
59 |
60 | // Verbose writes message to log
61 | func (p *Plog) Verbose(msg string) {
62 | // Verbose lines are skipped when
63 | // verbose flag is not enabled
64 | if p.verbose {
65 | p.Print(msg)
66 | }
67 | }
68 |
69 | func (p *Plog) Close() {
70 | if p.verbose {
71 | p.writer.Sync()
72 | p.writer.Close()
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/proto/proto.go:
--------------------------------------------------------------------------------
1 | package proto
2 |
3 | import (
4 | "github.com/pkg/errors"
5 | "io"
6 | )
7 |
8 | // Worker is the file worker
9 | type Worker struct {
10 | hdr Header
11 | state pstate
12 | dec *Decoder
13 | }
14 |
15 | // pstate are the different worker stages
16 | type pstate uint32
17 |
18 | const (
19 | None pstate = 1 << iota
20 | Starting
21 | Compatible
22 | Ready
23 | Recovery
24 | )
25 |
26 | // ProtData is the data type supported in Decode
27 | type ProtData interface {
28 | Protocol()
29 | }
30 |
31 | // NewWorker makes a new worker given the input stream
32 | func NewWorker(r io.Reader) Worker {
33 | return Worker{
34 | state: Starting,
35 | dec: NewDecoder(r),
36 | }
37 | }
38 |
39 | // Next extracts another record
40 | func (p Worker) Next() (Record, error) {
41 | if p.state != Ready {
42 | err := errors.New("Not Ready - compatible version required")
43 | return Record{}, err
44 | }
45 |
46 | var rec Record
47 | err := p.dec.Decode(&rec)
48 | if err != nil {
49 | err = errors.Wrap(err, "Next failed - decode error")
50 | return Record{}, err
51 | }
52 |
53 | return rec, nil
54 | }
55 |
56 | // header extracts header fields
57 | func (p Worker) header() (Header, error) {
58 | if p.state != Starting {
59 | err := errors.New("Wrong state - header already consumed")
60 | return Header{}, err
61 | }
62 |
63 | var hdr Header
64 | err := p.dec.Decode(&hdr)
65 | if err != nil {
66 | err = errors.Wrap(err, "header failed - Decode error")
67 | return Header{}, err
68 | }
69 |
70 | return hdr, nil
71 | }
72 |
73 | // Compatible checks version is supported
74 | func (p *Worker) Compatible(ver byte) bool {
75 | // todo ?Do we accept future/greater version values
76 |
77 | // Re-entrant, only process header fields once
78 | if p.state == Starting {
79 | var err error
80 | p.hdr, err = p.header()
81 | if err != nil {
82 | // Mark worker state as in recovery,
83 | // to do self-repair
84 | p.state = Recovery
85 | return false
86 | }
87 | p.state = Compatible
88 | }
89 |
90 | if p.hdr.version == ver && p.hdr.prefix == prefixMPS7 {
91 | p.state = Ready
92 | return true
93 | }
94 | return false
95 | }
96 |
97 | // Len is the record count
98 | func (p Worker) Len() uint32 {
99 | if p.state&(Ready|Compatible) != 0 {
100 | // Ready or Compatible state are when
101 | // the header fields are okay
102 | return p.hdr.Len()
103 | }
104 |
105 | // todo ?Is this an (fatal) error or
106 | return 0
107 | }
108 |
--------------------------------------------------------------------------------
/proto/mps7.go:
--------------------------------------------------------------------------------
1 | package proto
2 |
3 | import (
4 | "encoding/binary"
5 | "github.com/pkg/errors"
6 | "io"
7 | "math"
8 | )
9 |
10 | // prefixMPS7 is the required header prefix for protocol
11 | const prefixMPS7 = "MPS7"
12 |
13 | // Decoder decodes records from a MPS7 stream
14 | type Decoder struct {
15 | r io.Reader
16 | }
17 |
18 | // NewDecoder reads from the MPS7 stream
19 | func NewDecoder(r io.Reader) *Decoder {
20 | return &Decoder{r: r}
21 | }
22 |
23 | func (dec *Decoder) Decode(v ProtData) error {
24 |
25 | switch test := v.(type) {
26 | case *Record:
27 | return dec.decodeRec(test)
28 |
29 | case *Header:
30 | return dec.decodeHdr(test)
31 |
32 | default:
33 | return errors.New("Decode failed - unknown type")
34 | }
35 |
36 | return nil
37 | }
38 |
39 | // Decode reads the next MPS7 encoded record and stores it in rec
40 | func (dec *Decoder) decodeRec(rec *Record) error {
41 |
42 | // MPS7 fixed-size spec
43 | var data struct {
44 | Type byte
45 | Stamp [4]byte
46 | User [8]byte
47 | }
48 |
49 | // Read stream in network byte order
50 | err := binary.Read(dec.r, binary.BigEndian, &data)
51 | if err != nil {
52 | return errors.Wrap(err, "Decode failed - data does not match 1|4|8 bytes")
53 | }
54 |
55 | rtype := Rtype(data.Type)
56 | stamp := binary.BigEndian.Uint32(data.Stamp[:])
57 | user := binary.BigEndian.Uint64(data.User[:])
58 |
59 | *rec = Record{
60 | Rectype: rtype,
61 | stamp: stamp,
62 | User: user,
63 | }
64 |
65 | // Debit/credit means there is a 8 byte field for dollars
66 | if rtype == Debit || rtype == Credit {
67 | buf := make([]byte, 8)
68 | err = binary.Read(dec.r, binary.BigEndian, &buf)
69 | if err != nil {
70 | return errors.Wrap(err, "Decode failed - dollars not read")
71 | }
72 |
73 | bits := binary.BigEndian.Uint64(buf[:])
74 | rec.Dollars = math.Float64frombits(bits)
75 | }
76 |
77 | return nil
78 | }
79 |
80 | // Decode reads the MPS7 encoded header and stores it in hdr
81 | func (dec *Decoder) decodeHdr(hdr *Header) error {
82 | // MPS7 fixed-size spec
83 | var data struct {
84 | Magic [4]byte
85 | Version byte
86 | Length [4]byte
87 | }
88 |
89 | // Read in network byte order
90 | err := binary.Read(dec.r, binary.BigEndian, &data)
91 | if err != nil {
92 | return errors.Wrap(err, "Decode failed - data does not match 4|1|4 bytes")
93 | }
94 |
95 | pre := string(data.Magic[:])
96 | ver := data.Version
97 | len := binary.BigEndian.Uint32(data.Length[:])
98 |
99 | *hdr = Header{prefix: pre, version: ver, length: len}
100 | return nil
101 | }
102 |
--------------------------------------------------------------------------------
/proto/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "io"
7 | "os"
8 |
9 | adhoc "github.com/patterns/adhoc/proto"
10 | )
11 |
12 | // protomgr holds the cmd results
13 | type protomgr struct {
14 | l *adhoc.Plog
15 | rdr io.ReadCloser
16 | infile *string
17 | verbose *bool
18 | version byte
19 | qry *uint64
20 | agg map[adhoc.Rtype]float64
21 | bal map[uint64]float64
22 | }
23 |
24 | func main() {
25 | mgr := newMgr()
26 | defer mgr.Close()
27 |
28 | worker := adhoc.NewWorker(mgr.rdr)
29 |
30 | // Check file version is compatible (with '1')
31 | vok := worker.Compatible(mgr.version)
32 | if !vok {
33 | mgr.l.Verbose(fmt.Sprintf("MPS7 version - %x unsupported", mgr.version))
34 | return
35 | }
36 |
37 | dispatch(worker, &mgr)
38 | summary(mgr)
39 | }
40 |
41 | func newMgr() protomgr {
42 | var err error
43 |
44 | // Initialize params
45 | mgr := protomgr{version: byte(1)}
46 | mgr.infile = flag.String("infile", "txnlog.dat", "Path to binary (log) file")
47 | mgr.verbose = flag.Bool("verbose", false, "Enable extra messages")
48 | mgr.qry = flag.Uint64("qry", 2456938384156277127, "User ID to look-up")
49 | flag.Parse()
50 |
51 | // Enable logging
52 | mgr.l, err = adhoc.NewLog(*mgr.verbose)
53 | if err != nil {
54 | fmt.Println("Debug logger failed")
55 | panic(err)
56 | }
57 |
58 | mgr.rdr, err = os.Open(*mgr.infile)
59 | if err != nil {
60 | mgr.l.Verbose(fmt.Sprintf("Error file open - %s", *mgr.infile))
61 | panic(err)
62 | }
63 | return mgr
64 | }
65 |
66 | func (m protomgr) Close() {
67 | m.l.Close()
68 | m.rdr.Close()
69 | }
70 |
71 | // dispatch loops through records and runs associated actions
72 | func dispatch(worker adhoc.Worker, mgr *protomgr) {
73 | var rec adhoc.Record
74 | var err error
75 |
76 | // initialize the results maps
77 | mgr.bal = map[uint64]float64{}
78 | mgr.agg = map[adhoc.Rtype]float64{}
79 | mgr.agg[adhoc.Credit] = 0
80 | mgr.agg[adhoc.Debit] = 0
81 |
82 | for i := uint32(0); i < worker.Len(); i++ {
83 | rec, err = worker.Next()
84 | if err != nil {
85 | panic(err)
86 | }
87 |
88 | mgr.l.Verbose(rec.String())
89 |
90 | switch rec.Rectype {
91 | case adhoc.StartAutopay, adhoc.EndAutopay:
92 | actionAggregate(rec, mgr)
93 |
94 | case adhoc.Debit, adhoc.Credit:
95 | actionBalance(rec, mgr)
96 | }
97 | }
98 | }
99 |
100 | // actionAggregate are steps for Start/End autopay transactions
101 | func actionAggregate(rec adhoc.Record, mgr *protomgr) {
102 | if count, ok := mgr.agg[rec.Rectype]; ok {
103 | mgr.agg[rec.Rectype] = count + 1
104 | } else {
105 | mgr.agg[rec.Rectype] = 1
106 | }
107 | }
108 |
109 | // actionBalance are steps for Debit/Credit transactions
110 | func actionBalance(rec adhoc.Record, mgr *protomgr) {
111 | // Accumulate money of user
112 | sum := rec.Dollars
113 | if rec.Rectype == adhoc.Debit {
114 | // debit subtracts from balance
115 | sum = (-1) * rec.Dollars
116 | }
117 | if dol, ok := mgr.bal[rec.User]; ok {
118 | sum = sum + dol
119 | }
120 | mgr.bal[rec.User] = sum
121 |
122 | // Also total the money as aggregate
123 | mgr.agg[rec.Rectype] = mgr.agg[rec.Rectype] + rec.Dollars
124 | }
125 |
126 | // summary displays results
127 | func summary(mgr protomgr) {
128 | mgr.l.Info(fmt.Sprintf("total credit amount=%.2f", mgr.agg[adhoc.Credit]))
129 | mgr.l.Info(fmt.Sprintf("total debit amount=%.2f", mgr.agg[adhoc.Debit]))
130 | mgr.l.Info(fmt.Sprintf("autopays started=%d", int(mgr.agg[adhoc.StartAutopay])))
131 | mgr.l.Info(fmt.Sprintf("autopays ended=%d", int(mgr.agg[adhoc.EndAutopay])))
132 | mgr.l.Info(fmt.Sprintf("balance for user %d=%.2f", *mgr.qry, mgr.bal[*mgr.qry]))
133 | }
134 |
--------------------------------------------------------------------------------
/proto/proto_test.go:
--------------------------------------------------------------------------------
1 | package proto
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 |
7 | "math"
8 | "testing"
9 | "time"
10 | )
11 |
12 | func TestHeader(t *testing.T) {
13 | const (
14 | expmagic = "MPS7"
15 | expversion = 5
16 | explength = 123
17 | )
18 |
19 | // Simulate the Header spec which is 4|1|4 bytes
20 | buf := []byte{'M', 'P', 'S', '7', expversion, 0, 0, 0, explength}
21 |
22 | // Make io.Reader for the buffer
23 | nb := bytes.NewBuffer(buf)
24 |
25 | // Start the worker with the input stream
26 | hlp := NewWorker(nb)
27 |
28 | // Direct call to the Header matching
29 | hd, err := hlp.header()
30 | if err != nil {
31 | t.Error("Failed header extraction")
32 | }
33 |
34 | if hd.Len() != explength {
35 | t.Errorf("Expected Header length %d, got %d", explength, hd.Len())
36 | }
37 | if hd.Version() != expversion {
38 | t.Errorf("Expected Header version %v, got %d", expversion, hd.Version())
39 | }
40 | if hd.Prefix() != expmagic {
41 | t.Errorf("Expected Header prefix %s, got %s", expmagic, hd.Prefix())
42 | }
43 | }
44 |
45 | func TestHeaderCompatible(t *testing.T) {
46 | // Expected field values for the test
47 | const (
48 | expmagic = "MPS7"
49 | expversion = 5
50 | explength = 123
51 | )
52 |
53 | // Simulate the Header spec which is 4|1|4 bytes
54 | buf := []byte{'M', 'P', 'S', '7', expversion, 0, 0, 0, explength}
55 |
56 | // Make io.Reader for the buffer
57 | nb := bytes.NewBuffer(buf)
58 |
59 | // Start the worker with the input stream
60 | hlp := NewWorker(nb)
61 |
62 | // Run the Header format matching
63 | // and check the spec version
64 | vok := hlp.Compatible(byte(expversion))
65 |
66 | if !vok {
67 | t.Errorf("Expected Compatible version %d", expversion)
68 | }
69 |
70 | if hlp.Compatible(byte(0)) {
71 | t.Error("Expected Compatible version mismatch")
72 | }
73 |
74 | if hlp.state != Ready {
75 | t.Error("Expected Worker state to have advanced to Ready")
76 | }
77 | }
78 |
79 | func TestHeaderRecordLen(t *testing.T) {
80 | // Expected field values for the test
81 | const (
82 | expmagic = "MPS7"
83 | expversion = 5
84 | explength = 123
85 | )
86 |
87 | // Simulate the Header spec which is 4|1|4 bytes
88 | buf := []byte{'M', 'P', 'S', '7', expversion, 0, 0, 0, explength}
89 |
90 | // Make io.Reader for the buffer
91 | nb := bytes.NewBuffer(buf)
92 |
93 | // Start the Worker with the input stream
94 | hlp := NewWorker(nb)
95 |
96 | // Run the Header format matching
97 | // and check the spec version
98 | hlp.Compatible(byte(expversion))
99 |
100 | if hlp.Len() != explength {
101 | t.Errorf("Expected Header length %d, got %d", explength, hlp.Len())
102 | }
103 |
104 | if hlp.Len() == 0 {
105 | t.Error("Expected Header length to be non-zero")
106 | }
107 |
108 | if hlp.state != Ready {
109 | t.Error("Expected Worker state to have advanced to Ready")
110 | }
111 | }
112 |
113 | func TestRecordFields(t *testing.T) {
114 | exptype := EndAutopay
115 | hlp, expuser, expstamp := newRecordTestData(exptype)
116 |
117 | // Run the Record format matching
118 | rec, err := hlp.Next()
119 | if err != nil {
120 | t.Error("Worker Next call failed on error")
121 | }
122 |
123 | // Verify stamp field
124 | stamp := uint32(expstamp.Unix())
125 | if stamp != rec.stamp {
126 | t.Errorf("Expected Record stamp %x, got %x\n", stamp, rec.stamp)
127 | }
128 |
129 | // Verify type field
130 | if exptype != rec.Rectype {
131 | t.Errorf("Expected Record type %x, got %x\n", exptype, rec.Rectype)
132 | }
133 |
134 | // Verify user field
135 | if expuser != rec.User {
136 | t.Errorf("Expected Record user %d, got %d\n", expuser, rec.User)
137 | }
138 | }
139 |
140 | func TestRecordDebit(t *testing.T) {
141 | exptype := Debit
142 | expdollars := 4.99
143 | hlp, expuser, expstamp := newRecordTestData(exptype, expdollars)
144 |
145 | // Run the Record format matching
146 | rec, err := hlp.Next()
147 | if err != nil {
148 | t.Error("Worker Next call failed on error")
149 | }
150 |
151 | // Verify stamp field
152 | stamp := uint32(expstamp.Unix())
153 | if stamp != rec.stamp {
154 | t.Errorf("Expected Record stamp %x, got %x\n", stamp, rec.stamp)
155 | }
156 |
157 | // Verify type field
158 | if exptype != rec.Rectype {
159 | t.Errorf("Expected Record type %x, got %x\n", exptype, rec.Rectype)
160 | }
161 |
162 | // Verify user field
163 | if expuser != rec.User {
164 | t.Errorf("Expected Record user %d, got %d\n", expuser, rec.User)
165 | }
166 |
167 | // Verify dollars field
168 | if expdollars != rec.Dollars {
169 | t.Errorf("Expected Record dollars %f, got %f\n", expdollars, rec.Dollars)
170 | }
171 | }
172 |
173 | func TestRecordCredit(t *testing.T) {
174 | exptype := Credit
175 | expdollars := 8.99
176 | hlp, expuser, expstamp := newRecordTestData(exptype, expdollars)
177 |
178 | // Run the Record format matching
179 | rec, err := hlp.Next()
180 | if err != nil {
181 | t.Error("Worker Next call failed on error")
182 | }
183 |
184 | // Verify stamp field
185 | stamp := uint32(expstamp.Unix())
186 | if stamp != rec.stamp {
187 | t.Errorf("Expected Record stamp %x, got %x\n", stamp, rec.stamp)
188 | }
189 |
190 | // Verify type field
191 | if exptype != rec.Rectype {
192 | t.Errorf("Expected Record type %x, got %x\n", exptype, rec.Rectype)
193 | }
194 |
195 | // Verify user field
196 | if expuser != rec.User {
197 | t.Errorf("Expected Record user %d, got %d\n", expuser, rec.User)
198 | }
199 |
200 | // Verify dollars field
201 | if expdollars != rec.Dollars {
202 | t.Errorf("Expected Record dollars %f, got %f\n", expdollars, rec.Dollars)
203 | }
204 | }
205 |
206 | ////////
207 | // test worker routines
208 | ////////
209 |
210 | func newRecordTestData(rt Rtype, dollars ...float64) (h Worker, u uint64, s time.Time) {
211 | // Expected field values for the test
212 | exptype := rt
213 | expuser := uint64(12)
214 | expstamp := time.Date(2001, time.September, 9, 1, 46, 40, 0, time.UTC)
215 |
216 | // Simulate the Record spec which is 1|4|8 bytes
217 | // 1|4|8|8 bytes when type is Debit/Credit
218 | buf := []byte{
219 | byte(exptype),
220 | 0x3b, 0x9a, 0xca, 0x00,
221 | 0, 0, 0, 0, 0, 0, 0, 12,
222 | }
223 |
224 | // For Debit or Credit records, allow dollars field
225 | if rt == Debit || rt == Credit {
226 | dol := make([]byte, 8)
227 | binary.BigEndian.PutUint64(dol, math.Float64bits(dollars[0]))
228 | buf = append(buf, dol...)
229 | }
230 |
231 | // Make a io.Reader for buffer
232 | nb := bytes.NewBuffer(buf)
233 |
234 | // Start the worker with the input stream
235 | h = NewWorker(nb)
236 | u = expuser
237 | s = expstamp
238 |
239 | // Skip Header hlpsing and jump to
240 | // matching Records
241 | h.state = Ready
242 |
243 | return
244 | }
245 |
--------------------------------------------------------------------------------
/proto/diagram/state.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/proto/diagram/flow.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------