├── 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 | ![State change](diagram/flow.svg) 6 | 7 | 8 | Decoding the data stream transitions state from Starting to Ready: 9 | 10 | ![State change](diagram/state.svg) 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 |
Starting
Starting
Compatible
Compatible
Ready
Ready
State change
State change
End
End
Start
Start
Recovery
Recovery
Magic & Version?
Magic & Version?
N
N
Y
Y
Header?
Header?
Y
Y
N
N
-------------------------------------------------------------------------------- /proto/diagram/flow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
start
start
end
end
Debit/Credit?
Debit/Credit?
Decode Header
Decode Header
Calc Balance
Calc Balance
Start/End Autopay?
Start/End Autopay?
Calc Aggregate
Calc Aggregate
Summary
Summary
Next?
Next?
N
N
Decode Record
Decode Record
Y
Y
Y
Y
Y
Y
N
N
N
N
MPS7 Version Supported?
MPS7 Version Supported?
Recovery
Recovery
Y
Y
N
N
--------------------------------------------------------------------------------