diff --git a/cmd/fitactivity/README.md b/cmd/fitactivity/README.md index 53e4bd7e..4bc8508e 100644 --- a/cmd/fitactivity/README.md +++ b/cmd/fitactivity/README.md @@ -167,7 +167,7 @@ Output: ```sh About: - fitactivity is a program to handle FIT files based on provided command. + fitactivity is a program to manage FIT files based on provided command. Usage: fitactivity [command] @@ -228,6 +228,7 @@ Subcommand Flags (only if subcommand is provided): --rdp float64 reduce method: RDP [Ramer-Douglas-Peucker] based on GPS points, epsilon > 0 --distance float64 reduce method: distance interval in meters --time uint32 reduce method: time interval in seconds + remove: (select at least one) --unknown bool remove unknown messages --nums string remove message numbers (value separated by comma) diff --git a/cmd/fitactivity/main.go b/cmd/fitactivity/main.go index b3004c70..b4e5cdf8 100644 --- a/cmd/fitactivity/main.go +++ b/cmd/fitactivity/main.go @@ -344,7 +344,7 @@ loop: return err } - fit.FileHeader.ProtocolVersion = byte(latestProtocolVersion(fits)) + fit.FileHeader.ProtocolVersion = latestProtocolVersion(fits) fit.FileHeader.ProfileVersion = latestProfileVersion(fits) for _, subcommand := range subcommands { @@ -1050,14 +1050,14 @@ func formatThousand(v int) string { return result.String() } -func latestProtocolVersion(fits []*proto.FIT) byte { - var version = byte(proto.V1) +func latestProtocolVersion(fits []*proto.FIT) proto.Version { + var version = proto.V1 for i := range fits { if fits[i].FileHeader.ProtocolVersion > version { version = fits[i].FileHeader.ProtocolVersion } } - return byte(proto.Version(version)) + return version } func latestProfileVersion(fits []*proto.FIT) uint16 { diff --git a/cmd/fitprint/printer/printer.go b/cmd/fitprint/printer/printer.go index cbfe53c8..16848134 100644 --- a/cmd/fitprint/printer/printer.go +++ b/cmd/fitprint/printer/printer.go @@ -74,6 +74,7 @@ func Print(path string) error { defer p.Close() dec := decoder.New(f, + decoder.WithMesgDefListener(p), decoder.WithMesgListener(p), decoder.WithBroadcastOnly(), decoder.WithIgnoreChecksum(), @@ -127,16 +128,23 @@ File Header: const channelBuffer = 1000 type printer struct { - w io.Writer - poolc chan proto.Message - mesgc chan proto.Message - done chan struct{} - active bool - count int + w io.Writer + localMessageDefinitions [proto.LocalMesgNumMask + 1]proto.MessageDefinition + poolc chan proto.Message + messagec chan message + done chan struct{} + active bool + count int fieldDescriptions []*mesgdef.FieldDescription } +type message struct { + proto.Message + Reserved byte + Architecture byte +} + func New(w io.Writer) *printer { p := &printer{w: w} p.reset() @@ -151,20 +159,20 @@ func New(w io.Writer) *printer { } func (p *printer) loop() { - for mesg := range p.mesgc { - switch mesg.Num { + for m := range p.messagec { + switch m.Num { case mesgnum.FieldDescription: - p.fieldDescriptions = append(p.fieldDescriptions, mesgdef.NewFieldDescription(&mesg)) + p.fieldDescriptions = append(p.fieldDescriptions, mesgdef.NewFieldDescription(&m.Message)) } - p.print(mesg) - p.poolc <- mesg + p.print(m) + p.poolc <- m.Message p.count++ } close(p.done) } func (p *printer) reset() { - p.mesgc = make(chan proto.Message, channelBuffer) + p.messagec = make(chan message, channelBuffer) p.done = make(chan struct{}) p.count = 0 p.active = true @@ -174,7 +182,7 @@ func (p *printer) Wait() { if !p.active { return } - close(p.mesgc) + close(p.messagec) <-p.done p.active = false } @@ -184,6 +192,12 @@ func (p *printer) Close() { close(p.poolc) } +var _ decoder.MesgDefListener = (*printer)(nil) + +func (p *printer) OnMesgDef(mesgDef proto.MessageDefinition) { + p.localMessageDefinitions[proto.LocalMesgNum(mesgDef.Header)] = mesgDef +} + var _ decoder.MesgListener = (*printer)(nil) func (p *printer) OnMesg(mesg proto.Message) { @@ -192,10 +206,10 @@ func (p *printer) OnMesg(mesg proto.Message) { go p.loop() p.active = true } - p.mesgc <- p.prep(mesg) + p.messagec <- p.prep(mesg) } -func (p *printer) prep(mesg proto.Message) proto.Message { +func (p *printer) prep(mesg proto.Message) message { m := <-p.poolc if cap(m.Fields) < len(mesg.Fields) { @@ -212,20 +226,22 @@ func (p *printer) prep(mesg proto.Message) proto.Message { copy(m.DeveloperFields, mesg.DeveloperFields) mesg.DeveloperFields = m.DeveloperFields - return mesg + mesgDef := p.localMessageDefinitions[proto.LocalMesgNum(mesg.Header)] + + return message{Message: mesg, Reserved: mesgDef.Reserved, Architecture: mesgDef.Architecture} } -func (p *printer) print(mesg proto.Message) { - numstr := mesg.Num.String() +func (p *printer) print(m message) { + numstr := m.Num.String() if strings.HasPrefix(numstr, "MesgNumInvalid") { numstr = factory.NameUnknown } fmt.Fprintf(p.w, "%s (num: %d, arch: %d, fields[-]: %d, developerFields[+]: %d) [%d]:\n", - numstr, mesg.Num, mesg.Architecture, len(mesg.Fields), len(mesg.DeveloperFields), p.count) + numstr, m.Num, m.Architecture, len(m.Fields), len(m.DeveloperFields), p.count) - for j := range mesg.Fields { - field := &mesg.Fields[j] + for j := range m.Fields { + field := &m.Fields[j] var ( isDynamicField = false @@ -237,7 +253,7 @@ func (p *printer) print(mesg proto.Message) { units = field.Units ) - if subField := field.SubFieldSubtitution(&mesg); subField != nil { + if subField := field.SubFieldSubtitution(&m.Message); subField != nil { isDynamicField = true name = subField.Name baseType = subField.Type.BaseType() @@ -291,8 +307,8 @@ func (p *printer) print(mesg proto.Message) { ) } - for i := range mesg.DeveloperFields { - devField := &mesg.DeveloperFields[i] + for i := range m.DeveloperFields { + devField := &m.DeveloperFields[i] fieldDesc := p.getFieldDescription(devField.DeveloperDataIndex, devField.Num) if fieldDesc == nil { continue diff --git a/decoder/accumulator.go b/decoder/accumulator.go index 24468f93..5b741b41 100644 --- a/decoder/accumulator.go +++ b/decoder/accumulator.go @@ -8,53 +8,60 @@ import ( "github.com/muktihari/fit/profile/typedef" ) +// Accumulator is value accumulator. type Accumulator struct { - AccumulatedValues []AccumulatedValue // use slice over map since len(values) is relatively small + values []value // use slice over map since len(values) is relatively small } +// NewAccumulator creates new accumulator. func NewAccumulator() *Accumulator { - return &Accumulator{} // No need to make AccumulatedValues as it will be created on append anyway. + return &Accumulator{} } -func (a *Accumulator) Collect(mesgNum typedef.MesgNum, destFieldNum byte, value uint32) { - for i := range a.AccumulatedValues { - field := &a.AccumulatedValues[i] - if field.MesgNum == mesgNum && field.DestFieldNum == destFieldNum { - field.Value = value - field.Last = value +// Collect collects value, it will either append the value when not exist or replace existing one. +func (a *Accumulator) Collect(mesgNum typedef.MesgNum, destFieldNum byte, val uint32) { + for i := range a.values { + av := &a.values[i] + if av.mesgNum == mesgNum && av.fieldNum == destFieldNum { + av.value = val + av.last = val return } } - a.AccumulatedValues = append(a.AccumulatedValues, AccumulatedValue{ - MesgNum: mesgNum, - DestFieldNum: destFieldNum, - Value: value, - Last: value, + a.values = append(a.values, value{ + mesgNum: mesgNum, + fieldNum: destFieldNum, + value: val, + last: val, }) } -func (a *Accumulator) Accumulate(mesgNum typedef.MesgNum, destFieldNum byte, value uint32, bits byte) uint32 { - for i := range a.AccumulatedValues { - av := &a.AccumulatedValues[i] - if av.MesgNum == mesgNum && av.DestFieldNum == destFieldNum { - return av.Accumulate(value, bits) +// Accumulate calculates the accumulated value and update accordingly. It returns the original value +// when the corresponding value does not exist. +func (a *Accumulator) Accumulate(mesgNum typedef.MesgNum, destFieldNum byte, val uint32, bits byte) uint32 { + for i := range a.values { + av := &a.values[i] + if av.mesgNum == mesgNum && av.fieldNum == destFieldNum { + return av.accumulate(val, bits) } } - return value + return val } -func (a *Accumulator) Reset() { a.AccumulatedValues = a.AccumulatedValues[:0] } +// Reset resets the accumulator. Tt retains the underlying storage for use by +// future use to reduce memory allocs. +func (a *Accumulator) Reset() { a.values = a.values[:0] } -type AccumulatedValue struct { - MesgNum typedef.MesgNum - DestFieldNum byte - Last uint32 - Value uint32 +type value struct { + mesgNum typedef.MesgNum + fieldNum byte + last uint32 + value uint32 } -func (a *AccumulatedValue) Accumulate(value uint32, bits byte) uint32 { +func (a *value) accumulate(val uint32, bits byte) uint32 { var mask uint32 = (1 << bits) - 1 - a.Value += (value - a.Last) & mask - a.Last = value - return a.Value + a.value += (val - a.last) & mask + a.last = val + return a.value } diff --git a/decoder/accumulator_test.go b/decoder/accumulator_test.go index 85d73992..a9db1bbe 100644 --- a/decoder/accumulator_test.go +++ b/decoder/accumulator_test.go @@ -90,13 +90,13 @@ func TestAccumulatorReset(t *testing.T) { accumu := NewAccumulator() accumu.Collect(mesgnum.Record, fieldnum.RecordSpeed, 1000) - if len(accumu.AccumulatedValues) != 1 { - t.Fatalf("expected AccumulatedValues is 1, got: %d", len(accumu.AccumulatedValues)) + if len(accumu.values) != 1 { + t.Fatalf("expected AccumulatedValues is 1, got: %d", len(accumu.values)) } accumu.Reset() - if len(accumu.AccumulatedValues) != 0 { - t.Fatalf("expected AccumulatedValues is 0 after reset, got: %d", len(accumu.AccumulatedValues)) + if len(accumu.values) != 0 { + t.Fatalf("expected AccumulatedValues is 0 after reset, got: %d", len(accumu.values)) } } diff --git a/decoder/decoder.go b/decoder/decoder.go index b983fdbb..e6aa1a21 100644 --- a/decoder/decoder.go +++ b/decoder/decoder.go @@ -30,7 +30,7 @@ func (e errorString) Error() string { return string(e) } const ( // Integrity errors - ErrNotAFitFile = errorString("not a FIT file") + ErrNotFITFile = errorString("not a FIT file") ErrDataSizeZero = errorString("data size zero") ErrCRCChecksumMismatch = errorString("crc checksum mismatch") @@ -40,8 +40,6 @@ const ( ErrInvalidBaseType = errorString("invalid basetype") ) -const littleEndian = 0 - // Decoder is FIT file decoder. See New() for details. type Decoder struct { readBuffer *readBuffer // read from io.Reader with buffer without extra copying. @@ -81,7 +79,8 @@ type Decoder struct { // Factory defines a contract that any Factory containing these method can be used by the Decoder. type Factory interface { - // CreateField create new field based on defined messages in the factory. If not found, it returns new field with "unknown" name. + // CreateField creates new field based on defined messages in the factory. + // If not found, it returns new field with "unknown" name. CreateField(mesgNum typedef.MesgNum, num byte) proto.Field } @@ -183,7 +182,8 @@ func WithReadBufferSize(size int) Option { // The FIT protocol allows for multiple FIT files to be chained together in a single FIT file. // Each FIT file in the chain must be a properly formatted FIT file (header, data records, CRC). // -// To decode chained FIT files, use Next() to check if r hasn't reach EOF and next bytes are still a valid FIT sequences. +// To decode a chained FIT file containing multiple FIT data, invoke Decode() or DecodeWithContext() +// method multiple times. For convenience, we can wrap it with the Next() method as follows (optional): // // for dec.Next() { // fit, err := dec.Decode() @@ -327,8 +327,7 @@ func (d *Decoder) discardMessages() (err error) { // PeekFileHeader decodes only up to FileHeader (first 12-14 bytes) without decoding the whole reader. // -// After this method is invoked, Decode picks up where this left then continue decoding next messages instead of starting from zero. -// This method is idempotent and can be invoked even after Decode has been invoked. +// If we choose to continue, Decode picks up where this left then continue decoding next messages instead of starting from zero. func (d *Decoder) PeekFileHeader() (*proto.FileHeader, error) { if d.err != nil { return nil, d.err @@ -342,8 +341,7 @@ func (d *Decoder) PeekFileHeader() (*proto.FileHeader, error) { // PeekFileId decodes only up to FileId message without decoding the whole reader. // FileId message should be the first message of any FIT file, otherwise return an error. // -// After this method is invoked, Decode picks up where this left then continue decoding next messages instead of starting from zero. -// This method is idempotent and can be invoked even after Decode has been invoked. +// If we choose to continue, Decode picks up where this left then continue decoding next messages instead of starting from zero. func (d *Decoder) PeekFileId() (*mesgdef.FileId, error) { if d.err != nil { return nil, d.err @@ -367,14 +365,14 @@ func (d *Decoder) Next() bool { if !d.sequenceCompleted { return true } - d.reset() // reset values for the next chained FIT file + d.sequenceCompleted = false // err is saved in the func, any exported will call this func anyway. return d.decodeFileHeaderOnce() == nil } // Decode method decodes `r` into FIT data. One invocation will produce one valid FIT data or // an error if it occurs. To decode a chained FIT file containing multiple FIT data, invoke this -// method multiple times, however, the invocation must be wrapped with Next() method as follows: +// method multiple times. For convenience, we can wrap it with the Next() method as follows (optional): // // for dec.Next() { // fit, err := dec.Decode() @@ -396,12 +394,14 @@ func (d *Decoder) Decode() (*proto.FIT, error) { if d.err = d.decodeCRC(); d.err != nil { return nil, d.err } - d.sequenceCompleted = true - return &proto.FIT{ + fit := &proto.FIT{ FileHeader: d.fileHeader, Messages: d.messages, CRC: d.crc, - }, nil + } + d.reset() + d.sequenceCompleted = true + return fit, nil } // Discard discards a single FIT file sequence and returns any error encountered. This method directs the Decoder to @@ -443,6 +443,7 @@ func (d *Decoder) Discard() error { if _, d.err = d.readN(2); d.err != nil { // Discard File CRC return d.err } + d.reset() d.sequenceCompleted = true return d.err } @@ -463,7 +464,7 @@ func (d *Decoder) decodeFileHeader() error { size := b[0] if size != 12 && size != 14 { // current spec is either 12 or 14 - return fmt.Errorf("file header size [%d] is invalid: %w", size, ErrNotAFitFile) + return fmt.Errorf("file header size [%d] is invalid: %w", size, ErrNotFITFile) } _, _ = d.crc16.Write(b) @@ -476,12 +477,12 @@ func (d *Decoder) decodeFileHeader() error { // PERF: Neither string(b[7:11]) nor assigning proto.DataTypeFIT constant to a variable escape to the heap. if string(b[7:11]) != proto.DataTypeFIT { - return ErrNotAFitFile + return ErrNotFITFile } d.fileHeader = proto.FileHeader{ Size: size, - ProtocolVersion: b[0], + ProtocolVersion: proto.Version(b[0]), ProfileVersion: binary.LittleEndian.Uint16(b[1:3]), DataSize: binary.LittleEndian.Uint32(b[3:7]), DataType: proto.DataTypeFIT, @@ -556,7 +557,7 @@ func (d *Decoder) decodeMessageDefinition(header byte) error { mesgDef.Header = header mesgDef.Reserved = b[0] mesgDef.Architecture = b[1] - if mesgDef.Architecture == littleEndian { + if mesgDef.Architecture == proto.LittleEndian { mesgDef.MesgNum = typedef.MesgNum(binary.LittleEndian.Uint16(b[2:4])) } else { mesgDef.MesgNum = typedef.MesgNum(binary.BigEndian.Uint16(b[2:4])) @@ -610,7 +611,10 @@ func (d *Decoder) decodeMessageDefinition(header byte) error { d.localMessageDefinitions[localMesgNum] = mesgDef if len(d.options.mesgDefListeners) > 0 { - mesgDef := mesgDef.Clone() // Clone since we don't have control of the object lifecycle outside Decoder. + // Clone since we don't have control of the object lifecycle outside Decoder. + mesgDef := *mesgDef + mesgDef.FieldDefinitions = append(mesgDef.FieldDefinitions[:0:0], mesgDef.FieldDefinitions...) + mesgDef.DeveloperFieldDefinitions = append(mesgDef.DeveloperFieldDefinitions[:0:0], mesgDef.DeveloperFieldDefinitions...) for i := range d.options.mesgDefListeners { d.options.mesgDefListeners[i].OnMesgDef(mesgDef) // blocking or non-blocking depends on listeners' implementation. } @@ -631,8 +635,6 @@ func (d *Decoder) decodeMessageData(header byte) (err error) { mesg := proto.Message{Num: mesgDef.MesgNum} mesg.Header = header - mesg.Reserved = mesgDef.Reserved - mesg.Architecture = mesgDef.Architecture mesg.Fields = d.fieldsArray[:0] if (header & proto.MesgCompressedHeaderMask) == proto.MesgCompressedHeaderMask { // Compressed Timestamp Message Data @@ -1011,12 +1013,14 @@ func (d *Decoder) DecodeWithContext(ctx context.Context) (*proto.FIT, error) { if d.err = d.decodeCRC(); d.err != nil { return nil, d.err } - d.sequenceCompleted = true - return &proto.FIT{ + fit := &proto.FIT{ FileHeader: d.fileHeader, Messages: d.messages, CRC: d.crc, - }, nil + } + d.reset() + d.sequenceCompleted = true + return fit, nil } func checkContext(ctx context.Context) error { diff --git a/decoder/decoder_test.go b/decoder/decoder_test.go index 12ff6b8f..590eabc5 100644 --- a/decoder/decoder_test.go +++ b/decoder/decoder_test.go @@ -25,7 +25,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/muktihari/fit/factory" - "github.com/muktihari/fit/internal/kit" "github.com/muktihari/fit/kit/datetime" "github.com/muktihari/fit/kit/hash" "github.com/muktihari/fit/kit/hash/crc16" @@ -484,12 +483,12 @@ func TestCheckIntegrity(t *testing.T) { r: func() io.Reader { h := proto.FileHeader{ Size: 14, - ProtocolVersion: byte(proto.V2), + ProtocolVersion: proto.V2, ProfileVersion: profile.Version, DataSize: 0, DataType: proto.DataTypeFIT, } - b, _ := h.MarshalBinary() + b, _ := h.MarshalAppend(nil) crc := crc16.New(nil) crc.Write(b[:12]) binary.LittleEndian.PutUint16(b[12:14], crc.Sum16()) @@ -568,13 +567,13 @@ func TestCheckIntegrity(t *testing.T) { // Chained FIT File but with next sequence header is b := append(b[:0:0], b...) h := headerForTest() - nextb, _ := h.MarshalBinary() + nextb, _ := h.MarshalAppend(nil) nextb[0] = 100 // alter FileHeader's Size b = append(b, nextb...) return bytes.NewReader(b) }(), n: 1, - err: ErrNotAFitFile, + err: ErrNotFITFile, }, } @@ -610,62 +609,60 @@ func createFitForTest() (proto.FIT, []byte) { fit := proto.FIT{ FileHeader: headerForTest(), Messages: []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(uint8(typedef.FileActivity)), - ), - factory.CreateMesgOnly(mesgnum.DeveloperDataId).WithFields( + }}, + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdApplicationId).WithValue([]byte{0, 1, 2, 3}), - ), - factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue([]string{"Heart Rate"}), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordHeartRate)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint8)), - ), - factory.CreateMesgOnly(mesgnum.Record).WithFields( + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(time.Now())), - ), - factory.CreateMesgOnly(mesgnum.Record). - WithFields( - factory.CreateField(mesgnum.Record, fieldnum.RecordDistance).WithValue(uint32(0)), - factory.CreateField(mesgnum.Record, fieldnum.RecordCadence).WithValue(uint8(77)), - ). - WithDeveloperFields( - proto.DeveloperField{ - DeveloperDataIndex: 0, - Num: 0, - Value: proto.Uint8(100), - }, - ), + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordDistance).WithValue(uint32(0)), + factory.CreateField(mesgnum.Record, fieldnum.RecordCadence).WithValue(uint8(77)), + }, DeveloperFields: []proto.DeveloperField{ + { + DeveloperDataIndex: 0, + Num: 0, + Value: proto.Uint8(100), + }, + }}, }, } for i := 0; i < 100; i++ { fit.Messages = append(fit.Messages, - factory.CreateMesgOnly(mesgnum.Record).WithFields( - factory.CreateField(mesgnum.Record, fieldnum.RecordDistance).WithValue(uint32((i+1)*1000)), + proto.Message{Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordDistance).WithValue(uint32((i + 1) * 1000)), factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(time.Now())), - ), + }}, ) } bytesbuffer := new(bytes.Buffer) - b, _ := fit.FileHeader.MarshalBinary() + b, _ := fit.FileHeader.MarshalAppend(nil) bytesbuffer.Write(b) // Marshal and calculate data size and crc checksum crc16checker := crc16.New(nil) for i := range fit.Messages { mesg := fit.Messages[i] - mesgDef := proto.CreateMessageDefinition(&mesg) - b, _ := mesgDef.MarshalBinary() + mesgDef, _ := proto.NewMessageDefinition(&mesg) + b, _ := mesgDef.MarshalAppend(nil) bytesbuffer.Write(b) crc16checker.Write(b) - b, err := mesg.MarshalBinary() + b, err := mesg.MarshalAppend(nil, proto.LittleEndian) if err != nil { panic(err) } @@ -851,7 +848,7 @@ func TestNext(t *testing.T) { // New header of the next chained FIT sequences. header := headerForTest() - b, _ := header.MarshalBinary() + b, _ := header.MarshalAppend(nil) buf = append(buf, b...) r := func() io.Reader { @@ -890,8 +887,8 @@ func TestNext(t *testing.T) { t.Fatalf("should have next, return false") } - if len(dec.accumulator.AccumulatedValues) != 0 { - t.Fatalf("expected accumulator's AccumulatedValues is 0, got: %d", len(dec.accumulator.AccumulatedValues)) + if len(dec.accumulator.values) != 0 { + t.Fatalf("expected accumulator's AccumulatedValues is 0, got: %d", len(dec.accumulator.values)) } if dec.crc16.Sum16() != 0 { // not necessary since reset every decode header anyway, but let's just add it @@ -984,7 +981,7 @@ func makeDecodeTableTest() []decodeTestCase { return }) }(), - err: ErrNotAFitFile, + err: ErrNotFITFile, }, { name: "decode messages return error", @@ -1127,7 +1124,7 @@ func TestDecodeFileHeader(t *testing.T) { return }) }(), - err: ErrNotAFitFile, + err: ErrNotFITFile, }, { name: "decode header invalid size", @@ -1187,7 +1184,7 @@ func TestDecodeFileHeader(t *testing.T) { return }) }(), - err: ErrNotAFitFile, + err: ErrNotFITFile, }, { name: "decode crc == 0x000", @@ -1274,7 +1271,7 @@ func TestDecodeMessageDefinition(t *testing.T) { r io.Reader opts []Option header byte - mesgDef proto.MessageDefinition + mesgDef *proto.MessageDefinition err error }{ { @@ -1297,8 +1294,11 @@ func TestDecodeMessageDefinition(t *testing.T) { opts: []Option{ WithMesgDefListener(fnMesgDefListener(func(mesgDef proto.MessageDefinition) {})), }, - header: proto.MesgDefinitionMask, - mesgDef: proto.CreateMessageDefinition(&fit.Messages[0]), // file_id + header: proto.MesgDefinitionMask, + mesgDef: func() *proto.MessageDefinition { + mesgDef, _ := proto.NewMessageDefinition(&fit.Messages[0]) // file_i, proto.LittleEndiand + return mesgDef + }(), }, { name: "decode read return io.EOF when retrieving init data", @@ -1406,7 +1406,7 @@ func TestDecodeMessageDefinition(t *testing.T) { if err != nil { return } - mesgDef := *dec.localMessageDefinitions[proto.MesgDefinitionMask&proto.LocalMesgNumMask] + mesgDef := dec.localMessageDefinitions[proto.MesgDefinitionMask&proto.LocalMesgNumMask] if len(mesgDef.DeveloperFieldDefinitions) == 0 { mesgDef.DeveloperFieldDefinitions = nil } @@ -1709,7 +1709,7 @@ func TestDecodeFields(t *testing.T) { }, }, } - mesgb, _ := mesg.MarshalBinary() + mesgb, _ := mesg.MarshalAppend(nil, proto.LittleEndian) mesgb = mesgb[1:] // splice mesg header cur := 0 return fnReader(func(b []byte) (n int, err error) { @@ -1752,7 +1752,7 @@ func TestDecodeFields(t *testing.T) { }, }, } - mesgb, _ := mesg.MarshalBinary() + mesgb, _ := mesg.MarshalAppend(nil, proto.LittleEndian) mesgb = mesgb[1:] // splice mesg header cur := 0 return fnReader(func(b []byte) (n int, err error) { @@ -1817,18 +1817,18 @@ func TestExpandComponents(t *testing.T) { }{ { name: "expand components single happy flow", - mesg: factory.CreateMesgOnly(mesgnum.Record).WithFields( + mesg: proto.Message{Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed).WithValue(uint16(1000)), - ), + }}, containingField: factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed).WithValue(uint16(1000)), components: factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed).Components, nFieldAfterExpansion: 2, // 1 for speed, +1 expand field enhanced_speed }, { name: "expand components multiple happy flow", - mesg: factory.CreateMesgOnly(mesgnum.Event).WithFields( + mesg: proto.Message{Num: mesgnum.Event, Fields: []proto.Field{ factory.CreateField(mesgnum.Event, fieldnum.EventEvent).WithValue(uint8(typedef.EventFrontGearChange)), - ), + }}, containingField: factory.CreateField(mesgnum.Event, fieldnum.EventData).WithValue(uint32(0x27010E08)), components: func() []proto.Component { subfields := factory.CreateField(mesgnum.Event, fieldnum.EventData).SubFields @@ -1843,9 +1843,9 @@ func TestExpandComponents(t *testing.T) { }, { name: "expand components run out bits for the last component", - mesg: factory.CreateMesgOnly(mesgnum.Event).WithFields( + mesg: proto.Message{Num: mesgnum.Event, Fields: []proto.Field{ factory.CreateField(mesgnum.Event, fieldnum.EventEvent).WithValue(uint8(typedef.EventFrontGearChange)), - ), + }}, containingField: factory.CreateField(mesgnum.Event, fieldnum.EventData).WithValue(uint32(0x00010E08)), components: func() []proto.Component { subfields := factory.CreateField(mesgnum.Event, fieldnum.EventData).SubFields @@ -1860,27 +1860,27 @@ func TestExpandComponents(t *testing.T) { }, { name: "expand components containing field value mismatch", - mesg: factory.CreateMesgOnly(mesgnum.Record).WithFields( + mesg: proto.Message{Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed).WithValue("invalid value"), - ), + }}, containingField: factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed).WithValue("invalid value"), components: factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed).Components, nFieldAfterExpansion: 1, }, { name: "expand components accumulate", - mesg: factory.CreateMesgOnly(mesgnum.Hr).WithFields( + mesg: proto.Message{Num: mesgnum.Hr, Fields: []proto.Field{ factory.CreateField(mesgnum.Hr, fieldnum.HrEventTimestamp).WithValue(uint8(10)), - ), + }}, containingField: factory.CreateField(mesgnum.Hr, fieldnum.HrEventTimestamp12).WithValue(uint8(10)), components: factory.CreateField(mesgnum.Hr, fieldnum.HrEventTimestamp12).Components, nFieldAfterExpansion: 2, }, { name: "expand components do not expand when containing field's value is invalid", - mesg: factory.CreateMesgOnly(mesgnum.Session).WithFields( + mesg: proto.Message{Num: mesgnum.Session, Fields: []proto.Field{ factory.CreateField(mesgnum.Session, fieldnum.SessionAvgSpeed).WithValue(uint16(basetype.Uint16Invalid)), - ), + }}, containingField: factory.CreateField(mesgnum.Session, fieldnum.SessionAvgSpeed).WithValue(uint16(basetype.Uint16Invalid)), components: factory.CreateField(mesgnum.Session, fieldnum.SessionAvgSpeed).Components, nFieldAfterExpansion: 1, @@ -1903,7 +1903,7 @@ func TestExpandMutipleComponents(t *testing.T) { compressedSepeedDistanceField := factory.CreateField(mesgnum.Record, fieldnum.RecordCompressedSpeedDistance). WithValue([]byte{0, 4, 1}) - mesg := factory.CreateMesgOnly(mesgnum.Record).WithFields(compressedSepeedDistanceField) + mesg := proto.Message{Num: mesgnum.Record, Fields: []proto.Field{compressedSepeedDistanceField}} dec := New(nil) dec.expandComponents(&mesg, &compressedSepeedDistanceField, compressedSepeedDistanceField.Components) @@ -2004,10 +2004,10 @@ func TestExpandMutipleComponentsDynamicField(t *testing.T) { }, ) - mesg := fac.CreateMesgOnly(customMesgNum).WithFields( + mesg := proto.Message{Num: customMesgNum, Fields: []proto.Field{ fac.CreateField(customMesgNum, 0).WithValue(uint8(10)), // event fac.CreateField(customMesgNum, 2).WithValue(uint32(10)), // compressed_data - ) + }} dec := New(nil, WithFactory(fac)) fieldToExpand := mesg.FieldByNum(2) @@ -2044,14 +2044,14 @@ func TestDecodeDeveloperFields(t *testing.T) { 0, }, fieldDescription: mesgdef.NewFieldDescription( - kit.Ptr(factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + &proto.Message{Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("Heart Rate"), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordHeartRate)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint8)), - )), + }}, ), mesgDef: &proto.MessageDefinition{ Header: proto.MesgDefinitionMask, @@ -2070,14 +2070,14 @@ func TestDecodeDeveloperFields(t *testing.T) { name: "decode developer fields missing fieldDescription with developer data index 1", r: fnReaderOK, fieldDescription: mesgdef.NewFieldDescription( - kit.Ptr(factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + &proto.Message{Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("Heart Rate"), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordHeartRate)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint8)), - )), + }}, ), mesgDef: &proto.MessageDefinition{ Header: proto.MesgDefinitionMask, @@ -2096,14 +2096,14 @@ func TestDecodeDeveloperFields(t *testing.T) { name: "decode developer fields missing field description number", r: fnReaderOK, fieldDescription: mesgdef.NewFieldDescription( - kit.Ptr(factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + &proto.Message{Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("Heart Rate"), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordHeartRate)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint8)), - )), + }}, ), mesgDef: &proto.MessageDefinition{ Header: proto.MesgDefinitionMask, @@ -2122,14 +2122,14 @@ func TestDecodeDeveloperFields(t *testing.T) { name: "decode developer fields missing field description number but unable to read acquired bytes", r: fnReaderErr, fieldDescription: mesgdef.NewFieldDescription( - kit.Ptr(factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + &proto.Message{Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("Heart Rate"), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordHeartRate)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint8)), - )), + }}, ), mesgDef: &proto.MessageDefinition{ Header: proto.MesgDefinitionMask, @@ -2149,14 +2149,14 @@ func TestDecodeDeveloperFields(t *testing.T) { name: "decode developer fields got io.EOF", r: fnReaderErr, fieldDescription: mesgdef.NewFieldDescription( - kit.Ptr(factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + &proto.Message{Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("Heart Rate"), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordHeartRate)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint8)), - )), + }}, ), mesgDef: &proto.MessageDefinition{ Header: proto.MesgDefinitionMask, @@ -2176,14 +2176,14 @@ func TestDecodeDeveloperFields(t *testing.T) { name: "decode developer field, devField def's size is zero, skip", r: fnReaderOK, fieldDescription: mesgdef.NewFieldDescription( - kit.Ptr(factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + &proto.Message{Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("Heart Rate"), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordHeartRate)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint8)), - )), + }}, ), mesgDef: &proto.MessageDefinition{ Header: proto.MesgDefinitionMask, @@ -2208,14 +2208,14 @@ func TestDecodeDeveloperFields(t *testing.T) { name: "decode developer field, devField def's size 1 < 4 size of uint32 ", r: fnReaderOK, fieldDescription: mesgdef.NewFieldDescription( - kit.Ptr(factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + &proto.Message{Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("Heart Rate"), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordHeartRate)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint32)), - )), + }}, ), mesgDef: &proto.MessageDefinition{ Header: proto.MesgDefinitionMask, @@ -2245,14 +2245,14 @@ func TestDecodeDeveloperFields(t *testing.T) { name: "decode developer fields field description has invalid basetype", r: fnReaderOK, fieldDescription: mesgdef.NewFieldDescription( - kit.Ptr(factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + &proto.Message{Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("Heart Rate"), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordHeartRate)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(255)), - )), + }}, ), mesgDef: &proto.MessageDefinition{ Header: proto.MesgDefinitionMask, @@ -2584,6 +2584,7 @@ func TestReset(t *testing.T) { dec.Reset(buf, tc.opts...) if diff := cmp.Diff(dec, tc.dec, + cmp.AllowUnexported(Accumulator{}), cmp.AllowUnexported(options{}), cmp.AllowUnexported(Decoder{}), cmp.AllowUnexported(readBuffer{}), @@ -2645,8 +2646,11 @@ func BenchmarkDecodeMessageData(b *testing.B) { factory.CreateField(mesgnum.Record, fieldnum.RecordTemperature).WithValue(int8(32)), }, } - mesgDef := proto.CreateMessageDefinition(&mesg) - mesgb, err := mesg.MarshalBinary() + mesgDef, err := proto.NewMessageDefinition(&mesg) + if err != nil { + b.Fatal(err) + } + mesgb, err := mesg.MarshalAppend(nil, proto.LittleEndian) if err != nil { b.Fatalf("marshal binary: %v", err) } @@ -2662,7 +2666,7 @@ func BenchmarkDecodeMessageData(b *testing.B) { }) dec := New(r, WithIgnoreChecksum(), WithNoComponentExpansion(), WithBroadcastOnly()) - dec.localMessageDefinitions[0] = &mesgDef + dec.localMessageDefinitions[0] = mesgDef b.StartTimer() for i := 0; i < b.N; i++ { diff --git a/decoder/raw.go b/decoder/raw.go index 511c9174..421c1a8b 100644 --- a/decoder/raw.go +++ b/decoder/raw.go @@ -107,7 +107,7 @@ func (d *RawDecoder) Decode(r io.Reader, fn func(flag RawFlag, b []byte) error) fileHeaderSize := d.BytesArray[0] if fileHeaderSize != 12 && fileHeaderSize != 14 { - return n, fmt.Errorf("file header's size [%d]: %w", fileHeaderSize, ErrNotAFitFile) + return n, fmt.Errorf("file header's size [%d]: %w", fileHeaderSize, ErrNotFITFile) } nr, err = io.ReadFull(r, d.BytesArray[1:fileHeaderSize]) @@ -117,7 +117,7 @@ func (d *RawDecoder) Decode(r io.Reader, fn func(flag RawFlag, b []byte) error) } if string(d.BytesArray[8:12]) != proto.DataTypeFIT { - return n, ErrNotAFitFile + return n, ErrNotFITFile } fileHeaderDataSize := binary.LittleEndian.Uint32(d.BytesArray[4:8]) diff --git a/decoder/raw_test.go b/decoder/raw_test.go index 744c2613..ebd7ba03 100644 --- a/decoder/raw_test.go +++ b/decoder/raw_test.go @@ -114,7 +114,7 @@ func TestRawDecoderDecode(t *testing.T) { }) }(), fn: fnDecodeRawOK, - err: ErrNotAFitFile, + err: ErrNotFITFile, }, { name: "unexpected EOF when decode header", @@ -146,7 +146,7 @@ func TestRawDecoderDecode(t *testing.T) { }) }(), fn: fnDecodeRawOK, - err: ErrNotAFitFile, + err: ErrNotFITFile, }, { name: "fn FileHeader returns io.EOF", @@ -197,13 +197,13 @@ func TestRawDecoderDecode(t *testing.T) { { name: "decode mesgDef fields return io.EOF", r: func() io.Reader { - mesg := factory.CreateMesg(mesgnum.Record).WithFields( + mesg := proto.Message{Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(time.Now())), - ) + }} h := headerForTest() - buf, _ := h.MarshalBinary() - mesgDef := proto.CreateMessageDefinition(&mesg) - mesgDefb, _ := mesgDef.MarshalBinary() + buf, _ := h.MarshalAppend(nil) + mesgDef, _ := proto.NewMessageDefinition(&mesg) + mesgDefb, _ := mesgDef.MarshalAppend(nil) buf = append(buf, mesgDefb...) cur := 0 @@ -221,19 +221,20 @@ func TestRawDecoderDecode(t *testing.T) { { name: "decode mesgDef n developer fields return io.EOF", r: func() io.Reader { - mesg := factory.CreateMesg(mesgnum.Record).WithFields( - factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(time.Now())), - ).WithDeveloperFields( - proto.DeveloperField{ - DeveloperDataIndex: 0, - Num: 0, - Value: proto.Uint8(100), - }, - ) + mesg := proto.Message{Num: mesgnum.Record, + Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(time.Now())), + }, DeveloperFields: []proto.DeveloperField{ + { + DeveloperDataIndex: 0, + Num: 0, + Value: proto.Uint8(100), + }, + }} h := headerForTest() - buf, _ := h.MarshalBinary() - mesgDef := proto.CreateMessageDefinition(&mesg) - mesgDefb, _ := mesgDef.MarshalBinary() + buf, _ := h.MarshalAppend(nil) + mesgDef, _ := proto.NewMessageDefinition(&mesg) + mesgDefb, _ := mesgDef.MarshalAppend(nil) buf = append(buf, mesgDefb...) cur := 0 @@ -251,19 +252,19 @@ func TestRawDecoderDecode(t *testing.T) { { name: "decode mesgDef developer fields return io.EOF", r: func() io.Reader { - mesg := factory.CreateMesg(mesgnum.Record).WithFields( + mesg := proto.Message{Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(time.Now())), - ).WithDeveloperFields( - proto.DeveloperField{ + }, DeveloperFields: []proto.DeveloperField{ + { DeveloperDataIndex: 0, Num: 0, Value: proto.Uint8(100), }, - ) + }} h := headerForTest() - buf, _ := h.MarshalBinary() - mesgDef := proto.CreateMessageDefinition(&mesg) - mesgDefb, _ := mesgDef.MarshalBinary() + buf, _ := h.MarshalAppend(nil) + mesgDef, _ := proto.NewMessageDefinition(&mesg) + mesgDefb, _ := mesgDef.MarshalAppend(nil) buf = append(buf, mesgDefb...) cur := 0 @@ -281,19 +282,19 @@ func TestRawDecoderDecode(t *testing.T) { { name: "decode mesgDef fn return io.EOF", r: func() io.Reader { - mesg := factory.CreateMesg(mesgnum.Record).WithFields( + mesg := proto.Message{Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(time.Now())), - ).WithDeveloperFields( - proto.DeveloperField{ + }, DeveloperFields: []proto.DeveloperField{ + { DeveloperDataIndex: 0, Num: 0, Value: proto.Uint8(100), }, - ) + }} h := headerForTest() - buf, _ := h.MarshalBinary() - mesgDef := proto.CreateMessageDefinition(&mesg) - mesgDefb, _ := mesgDef.MarshalBinary() + buf, _ := h.MarshalAppend(nil) + mesgDef, _ := proto.NewMessageDefinition(&mesg) + mesgDefb, _ := mesgDef.MarshalAppend(nil) buf = append(buf, mesgDefb...) cur := 0 @@ -316,12 +317,12 @@ func TestRawDecoderDecode(t *testing.T) { { name: "decode mesg, mesgDef not found", r: func() io.Reader { - mesg := factory.CreateMesg(mesgnum.Record).WithFields( + mesg := proto.Message{Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(time.Now())), - ) + }} h := headerForTest() - buf, _ := h.MarshalBinary() - mesgb, _ := mesg.MarshalBinary() + buf, _ := h.MarshalAppend(nil) + mesgb, _ := mesg.MarshalAppend(nil, proto.LittleEndian) buf = append(buf, mesgb...) cur := 0 @@ -339,13 +340,13 @@ func TestRawDecoderDecode(t *testing.T) { { name: "decode mesg, read return io.EOF", r: func() io.Reader { - mesg := factory.CreateMesg(mesgnum.Record).WithFields( + mesg := proto.Message{Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(time.Now())), - ) + }} h := headerForTest() - buf, _ := h.MarshalBinary() - mesgDef := proto.CreateMessageDefinition(&mesg) - mesgDefb, _ := mesgDef.MarshalBinary() + buf, _ := h.MarshalAppend(nil) + mesgDef, _ := proto.NewMessageDefinition(&mesg) + mesgDefb, _ := mesgDef.MarshalAppend(nil) buf = append(buf, mesgDefb...) buf = append(buf, mesgDefb[0]&proto.LocalMesgNumMask) @@ -384,15 +385,15 @@ func TestRawDecoderDecode(t *testing.T) { { name: "decode crc return io.EOF", r: func() io.Reader { - mesg := factory.CreateMesg(mesgnum.Record).WithFields( + mesg := proto.Message{Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(time.Now())), - ) + }} h := headerForTest() - buf, _ := h.MarshalBinary() - mesgDef := proto.CreateMessageDefinition(&mesg) - mesgDefb, _ := mesgDef.MarshalBinary() + buf, _ := h.MarshalAppend(nil) + mesgDef, _ := proto.NewMessageDefinition(&mesg) + mesgDefb, _ := mesgDef.MarshalAppend(nil) buf = append(buf, mesgDefb...) - mesgb, _ := mesg.MarshalBinary() + mesgb, _ := mesg.MarshalAppend(nil, proto.LittleEndian) buf = append(buf, mesgb...) binary.LittleEndian.PutUint32(buf[4:8], uint32(len(buf)-14)) @@ -469,13 +470,13 @@ func BenchmarkRawDecoderDecode(b *testing.B) { // But since it's big, it's should be good to benchmark. f, err := os.Open("../testdata/big_activity.fit") if err != nil { - panic(err) + b.Fatal(err) } defer f.Close() all, err := io.ReadAll(f) if err != nil { - panic(err) + b.Fatal(err) } buf := bytes.NewBuffer(all) diff --git a/docs/usage.md b/docs/usage.md index a6cd28ce..4a919c22 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -300,7 +300,7 @@ func main() { ### Decode Chained FIT Files -A single invocation of `Decode()` will process exactly one FIT sequence. To decode chained FIT files, wrap the decode process with a loop and use `dec.Next()` to check whether next sequence of bytes are still a valid FIT sequence. +A single invocation of `Decode()` will process exactly one FIT sequence. To decode a chained FIT file containing multiple FIT data, invoke Decode() or DecodeWithContext() method multiple times. For convenience, we can wrap it with the Next() method as follows (optional): ```go ... @@ -344,7 +344,7 @@ You can also use [PeekFileHeader()](#-Peek-FileHeader), [PeekFileId()](#Peek-Fil ### Peek FileHeader -We can verify whether the given file is a FIT file by checking the File Header (first 12-14 bytes). PeekFileHeader decodes only up to FileHeader (first 12-14 bytes) without decoding the whole reader. After this method is invoked, Decode picks up where this left then continue decoding next messages instead of starting from zero. This method is idempotent and can be invoked even after Decode has been invoked. +We can verify whether the given file is a FIT file by checking the File Header (first 12-14 bytes). PeekFileHeader decodes only up to FileHeader (first 12-14 bytes) without decoding the whole reader. If we choose to continue, Decode picks up where this left then continue decoding next messages instead of starting from zero. ```go package main @@ -849,35 +849,32 @@ func main() { now := time.Now() fit := proto.FIT{ Messages: []proto.Message{ - // Use factory.CreateMesg if performance is not your main concern, - // it's slightly slower as it allocates all of the message's fields. - factory.CreateMesg(mesgnum.FileId).WithFieldValues(map[byte]any{ - fieldnum.FileIdType: typedef.FileActivity, - fieldnum.FileIdTimeCreated: datetime.ToUint32(now), - fieldnum.FileIdManufacturer: typedef.ManufacturerBryton, - fieldnum.FileIdProduct: uint16(1901), // Bryton Rider 420 - fieldnum.FileIdProductName: "Bryton Rider 420", - }), - // For better performance, consider using factory.CreateMesgOnly - // or other alternatives listed below, as these only allocate the - // specified fields. However, you can not use WithFieldValues method. - factory.CreateMesgOnly(mesgnum.Activity).WithFields( - factory.CreateField(mesgnum.Activity, fieldnum.ActivityType).WithValue(typedef.ActivityManual.Byte()), - factory.CreateField(mesgnum.Activity, fieldnum.ActivityTimestamp).WithValue(datetime.ToUint32(now)), - factory.CreateField(mesgnum.Activity, fieldnum.ActivityNumSessions).WithValue(uint16(1)), - ), - // Alternative #1: Directly compose like this, which is the same as above. - proto.Message{Num: mesgnum.Session}.WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ + factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(typedef.FileActivity.Byte()), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdManufacturer).WithValue(typedef.ManufacturerBryton.Uint16()), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdProduct).WithValue(uint16(1901)), // Bryton Rider 420 + factory.CreateField(mesgnum.FileId, fieldnum.FileIdProductName).WithValue("Bryton Rider 420"), + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now)), + factory.CreateField(mesgnum.Record, fieldnum.RecordDistance).WithValue(uint32(100)), + factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed).WithValue(uint16(1000)), + factory.CreateField(mesgnum.Record, fieldnum.RecordCadence).WithValue(uint8(78)), + factory.CreateField(mesgnum.Record, fieldnum.RecordHeartRate).WithValue(uint8(100)), + }}, + {Num: mesgnum.Session, Fields: []proto.Field{ + factory.CreateField(mesgnum.Session, fieldnum.SessionTimestamp).WithValue(datetime.ToUint32(now)), + factory.CreateField(mesgnum.Session, fieldnum.SessionTotalDistance).WithValue(uint32(100)), factory.CreateField(mesgnum.Session, fieldnum.SessionAvgSpeed).WithValue(uint16(1000)), factory.CreateField(mesgnum.Session, fieldnum.SessionAvgCadence).WithValue(uint8(78)), factory.CreateField(mesgnum.Session, fieldnum.SessionAvgHeartRate).WithValue(uint8(100)), - ), - // Alternative #2: Compose like this, which is as performant as - // the two examples above. - proto.Message{Num: mesgnum.Record, Fields: []proto.Field{ - {FieldBase: factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed).FieldBase, Value: proto.Uint16(1000)}, - {FieldBase: factory.CreateField(mesgnum.Record, fieldnum.RecordCadence).FieldBase, Value: proto.Uint8(78)}, - {FieldBase: factory.CreateField(mesgnum.Record, fieldnum.RecordHeartRate).FieldBase, Value: proto.Uint8(100)}, + }}, + {Num: mesgnum.Activity, Fields: []proto.Field{ + factory.CreateField(mesgnum.Activity, fieldnum.ActivityTimestamp).WithValue(datetime.ToUint32(now)), + factory.CreateField(mesgnum.Activity, fieldnum.ActivityType).WithValue(typedef.ActivityManual.Byte()), + factory.CreateField(mesgnum.Activity, fieldnum.ActivityLocalTimestamp).WithValue(datetime.ToUint32(now.Add(7 * time.Hour))), // GMT+7 + factory.CreateField(mesgnum.Activity, fieldnum.ActivityNumSessions).WithValue(uint16(1)), }}, }, } @@ -1147,8 +1144,10 @@ import ( "github.com/muktihari/fit/encoder" "github.com/muktihari/fit/factory" "github.com/muktihari/fit/kit/datetime" + "github.com/muktihari/fit/profile/typedef" "github.com/muktihari/fit/profile/untyped/fieldnum" "github.com/muktihari/fit/profile/untyped/mesgnum" + "github.com/muktihari/fit/proto" ) func main() { @@ -1164,12 +1163,12 @@ func main() { } // Simplified example, writing only this mesg. - mesg := factory.CreateMesgOnly(mesgnum.FileId).WithFields( + mesg := proto.Message{Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(typedef.FileActivity.Byte()), factory.CreateField(mesgnum.FileId, fieldnum.FileIdManufacturer).WithValue(typedef.ManufacturerDevelopment.Uint16()), factory.CreateField(mesgnum.FileId, fieldnum.FileIdProduct).WithValue(uint16(0)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(time.Now())), - ) + }} // Write per message, we can use this to write message as it arrives. // For example, message retrieved from decoder's Listener can be @@ -1180,8 +1179,7 @@ func main() { /* Write more messages */ - // This should be invoked for every sequence of FIT File - // (not every message) to finalize. + // After all messages have been written, invoke this to finalize. if err := streamEnc.SequenceCompleted(); err != nil { panic(err) } diff --git a/encoder/encoder.go b/encoder/encoder.go index 59acb0af..0d880563 100644 --- a/encoder/encoder.go +++ b/encoder/encoder.go @@ -34,9 +34,6 @@ const ( type headerOption byte const ( - littleEndian = 0 - bigEndian = 1 - // headerOptionNormal is the default header option. // This option has two sub-option to select from: // 1. LocalMessageTypeZero [Default] @@ -87,7 +84,7 @@ type options struct { func defaultOptions() options { return options{ - endianness: littleEndian, + endianness: proto.LittleEndian, headerOption: headerOptionNormal, writeBufferSize: defaultWriteBufferSize, } @@ -107,7 +104,7 @@ type Option func(o *options) // proto.V1, proto.V2, etc, which the validity is ensured. func WithProtocolVersion(protocolVersion proto.Version) Option { return func(o *options) { - if proto.Validate(byte(protocolVersion)) == nil { + if proto.Validate(protocolVersion) == nil { o.protocolVersion = protocolVersion } } @@ -124,7 +121,7 @@ func WithMessageValidator(validator MessageValidator) Option { // WithBigEndian directs the Encoder to encode values in Big-Endian bytes order (default: Little-Endian). func WithBigEndian() Option { - return func(o *options) { o.endianness = bigEndian } + return func(o *options) { o.endianness = proto.BigEndian } } // WithCompressedTimestampHeader directs the Encoder to compress timestamp in header to reduce file size. @@ -191,6 +188,10 @@ func New(w io.Writer, opts ...Option) *Encoder { protocolValidator: new(proto.Validator), localMesgNumLRU: new(lru), buf: make([]byte, 0, 1536), + mesgDef: proto.MessageDefinition{ + FieldDefinitions: make([]proto.FieldDefinition, 0, 255), + DeveloperFieldDefinitions: make([]proto.DeveloperFieldDefinition, 0, 255), + }, } e.Reset(w, opts...) return e @@ -314,11 +315,11 @@ func (e *Encoder) encodeFileHeader(header *proto.FileHeader) error { } if e.options.protocolVersion != 0 { // Override regardless the value in FileHeader. - header.ProtocolVersion = byte(e.options.protocolVersion) + header.ProtocolVersion = e.options.protocolVersion } else if header.ProtocolVersion == 0 { // Default when not specified in FileHeader. - header.ProtocolVersion = byte(proto.V1) + header.ProtocolVersion = proto.V1 } - e.protocolValidator.SetProtocolVersion(proto.Version(header.ProtocolVersion)) + e.protocolValidator.SetProtocolVersion(header.ProtocolVersion) header.DataType = proto.DataTypeFIT header.CRC = 0 // recalculated @@ -432,7 +433,6 @@ func (e *Encoder) encodeMessages(messages []proto.Message) error { // encodeMessage marshals and encodes message definition and its message into w. func (e *Encoder) encodeMessage(mesg *proto.Message) (err error) { mesg.Header = proto.MesgNormalHeaderMask - mesg.Architecture = e.options.endianness if err = e.options.messageValidator.Validate(mesg); err != nil { return fmt.Errorf("message validation failed: %w", err) @@ -463,12 +463,12 @@ func (e *Encoder) encodeMessage(mesg *proto.Message) (err error) { } } - proto.CreateMessageDefinitionTo(&e.mesgDef, mesg) - if err := e.protocolValidator.ValidateMessageDefinition(&e.mesgDef); err != nil { + mesgDef := e.newMessageDefinition(mesg) + if err := e.protocolValidator.ValidateMessageDefinition(mesgDef); err != nil { return err } - b, _ := e.mesgDef.MarshalAppend(e.buf[:0]) + b, _ := mesgDef.MarshalAppend(e.buf[:0]) localMesgNum, isNewMesgDef := e.localMesgNumLRU.Put(b) // This might alloc memory since we need to copy the item. if e.options.headerOption == headerOptionNormal { b[0] = (b[0] &^ proto.LocalMesgNumMask) | localMesgNum // Update the message definition header. @@ -486,7 +486,7 @@ func (e *Encoder) encodeMessage(mesg *proto.Message) (err error) { } // At this point, e.buf may grow. Re-assign e.buf in case slice has grown. - e.buf, err = mesg.MarshalAppend(e.buf[:0]) + e.buf, err = mesg.MarshalAppend(e.buf[:0], mesgDef.Architecture) if err != nil { return fmt.Errorf("marshal mesg failed: %w", err) } @@ -530,9 +530,40 @@ func (e *Encoder) compressTimestampIntoHeader(mesg *proto.Message) { mesg.RemoveFieldByNum(proto.FieldNumTimestamp) } +func (e *Encoder) newMessageDefinition(mesg *proto.Message) *proto.MessageDefinition { + e.mesgDef.Header = proto.MesgDefinitionMask + e.mesgDef.Reserved = 0 + e.mesgDef.Architecture = e.options.endianness + e.mesgDef.MesgNum = mesg.Num + e.mesgDef.FieldDefinitions = e.mesgDef.FieldDefinitions[:0] + e.mesgDef.DeveloperFieldDefinitions = e.mesgDef.DeveloperFieldDefinitions[:0] + + for i := range mesg.Fields { + e.mesgDef.FieldDefinitions = append(e.mesgDef.FieldDefinitions, proto.FieldDefinition{ + Num: mesg.Fields[i].Num, + Size: byte(proto.Sizeof(mesg.Fields[i].Value)), + BaseType: mesg.Fields[i].BaseType, + }) + } + + if len(mesg.DeveloperFields) == 0 { + return &e.mesgDef + } + + e.mesgDef.Header |= proto.DevDataMask + for i := range mesg.DeveloperFields { + e.mesgDef.DeveloperFieldDefinitions = append(e.mesgDef.DeveloperFieldDefinitions, proto.DeveloperFieldDefinition{ + Num: mesg.DeveloperFields[i].Num, + Size: byte(proto.Sizeof(mesg.DeveloperFields[i].Value)), + DeveloperDataIndex: mesg.DeveloperFields[i].DeveloperDataIndex, + }) + } + + return &e.mesgDef +} + func (e *Encoder) encodeCRC() error { - b := e.buf[:2] - binary.LittleEndian.PutUint16(b, e.crc16.Sum16()) + b := binary.LittleEndian.AppendUint16(e.buf[:0], e.crc16.Sum16()) n, err := e.w.Write(b) e.n += int64(n) diff --git a/encoder/encoder_bench_test.go b/encoder/encoder_bench_test.go index 7eec46d8..0b2212a5 100644 --- a/encoder/encoder_bench_test.go +++ b/encoder/encoder_bench_test.go @@ -35,77 +35,78 @@ var DiscardAt = discardAt{} func createFitForBenchmark(recodSize int) *proto.FIT { now := time.Now() - fit := new(proto.FIT) - fit.Messages = make([]proto.Message, 0, recodSize) - fit.Messages = append(fit.Messages, - factory.CreateMesg(mesgnum.FileId).WithFieldValues(map[byte]any{ - fieldnum.FileIdType: typedef.FileActivity, - fieldnum.FileIdManufacturer: typedef.ManufacturerBryton, - fieldnum.FileIdProductName: "1901", - fieldnum.FileIdNumber: uint16(0), - fieldnum.FileIdTimeCreated: datetime.ToUint32(now), - fieldnum.FileIdSerialNumber: uint32(5122), - }), - factory.CreateMesg(mesgnum.Sport).WithFieldValues(map[byte]any{ - fieldnum.SportSport: typedef.SportCycling, - fieldnum.SportSubSport: typedef.SubSportRoad, - }), - factory.CreateMesg(mesgnum.Activity).WithFieldValues(map[byte]any{ - fieldnum.ActivityTimestamp: datetime.ToUint32(now), - fieldnum.ActivityType: typedef.ActivityTypeCycling, - fieldnum.ActivityTotalTimerTime: uint32(30877.0 * 1000), - fieldnum.ActivityNumSessions: uint16(1), - fieldnum.ActivityEvent: typedef.EventActivity, - }), - factory.CreateMesg(mesgnum.Session).WithFieldValues(map[byte]any{ - fieldnum.SessionTimestamp: datetime.ToUint32(now), - fieldnum.SessionStartTime: datetime.ToUint32(now), - fieldnum.SessionTotalElapsedTime: uint32(30877.0 * 1000), - fieldnum.SessionTotalDistance: uint32(32172.05 * 100), - fieldnum.SessionSport: typedef.SportCycling, - fieldnum.SessionSubSport: typedef.SubSportRoad, - fieldnum.SessionTotalMovingTime: uint32(22079.0 * 1000), - fieldnum.SessionTotalCalories: uint16(12824), - fieldnum.SessionAvgSpeed: uint16(5.98 * 1000), - fieldnum.SessionMaxSpeed: uint16(13.05 * 1000), - fieldnum.SessionMaxAltitude: uint16((504.0 + 500) * 5), - fieldnum.SessionTotalAscent: uint16(909), - fieldnum.SessionTotalDescent: uint16(901), - fieldnum.SessionSwcLat: int32(0), - fieldnum.SessionSwcLong: int32(0), - fieldnum.SessionNecLat: int32(0), - fieldnum.SessionNecLong: int32(0), - }), - ) + fit := &proto.FIT{ + Messages: []proto.Message{ + {Num: mesgnum.FileId, Fields: []proto.Field{ + factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(typedef.FileActivity), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdManufacturer).WithValue(typedef.ManufacturerBryton), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdProduct).WithValue(uint16(1901)), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdProductName).WithValue("Rider 420"), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdNumber).WithValue(uint16(0)), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdSerialNumber).WithValue(uint32(5122)), + }}, + {Num: mesgnum.Sport, Fields: []proto.Field{ + factory.CreateField(mesgnum.Sport, fieldnum.SportSport).WithValue(typedef.SportCycling), + factory.CreateField(mesgnum.Sport, fieldnum.SportSubSport).WithValue(typedef.SubSportRoad), + }}, + {Num: mesgnum.Activity, Fields: []proto.Field{ + factory.CreateField(mesgnum.Activity, fieldnum.ActivityTimestamp).WithValue(datetime.ToUint32(now)), + factory.CreateField(mesgnum.Activity, fieldnum.ActivityType).WithValue(typedef.ActivityTypeCycling), + factory.CreateField(mesgnum.Activity, fieldnum.ActivityTotalTimerTime).WithValue(uint32(30877.0 * 1000)), + factory.CreateField(mesgnum.Activity, fieldnum.ActivityNumSessions).WithValue(uint16(1)), + factory.CreateField(mesgnum.Activity, fieldnum.ActivityEvent).WithValue(typedef.EventActivity), + }}, + {Num: mesgnum.Session, Fields: []proto.Field{ + factory.CreateField(mesgnum.Session, fieldnum.SessionTimestamp).WithValue(datetime.ToUint32(now)), + factory.CreateField(mesgnum.Session, fieldnum.SessionStartTime).WithValue(datetime.ToUint32(now)), + factory.CreateField(mesgnum.Session, fieldnum.SessionTotalElapsedTime).WithValue(uint32(30877.0 * 1000)), + factory.CreateField(mesgnum.Session, fieldnum.SessionTotalDistance).WithValue(uint32(32172.05 * 100)), + factory.CreateField(mesgnum.Session, fieldnum.SessionSport).WithValue(typedef.SportCycling), + factory.CreateField(mesgnum.Session, fieldnum.SessionSubSport).WithValue(typedef.SubSportRoad), + factory.CreateField(mesgnum.Session, fieldnum.SessionTotalMovingTime).WithValue(uint32(22079.0 * 1000)), + factory.CreateField(mesgnum.Session, fieldnum.SessionTotalCalories).WithValue(uint16(12824)), + factory.CreateField(mesgnum.Session, fieldnum.SessionAvgSpeed).WithValue(uint16(5.98 * 1000)), + factory.CreateField(mesgnum.Session, fieldnum.SessionMaxSpeed).WithValue(uint16(13.05 * 1000)), + factory.CreateField(mesgnum.Session, fieldnum.SessionMaxAltitude).WithValue(uint16((504.0 + 500) * 5)), + factory.CreateField(mesgnum.Session, fieldnum.SessionTotalAscent).WithValue(uint16(909)), + factory.CreateField(mesgnum.Session, fieldnum.SessionTotalDescent).WithValue(uint16(901)), + factory.CreateField(mesgnum.Session, fieldnum.SessionSwcLat).WithValue(int32(0)), + factory.CreateField(mesgnum.Session, fieldnum.SessionSwcLong).WithValue(int32(0)), + factory.CreateField(mesgnum.Session, fieldnum.SessionNecLat).WithValue(int32(0)), + factory.CreateField(mesgnum.Session, fieldnum.SessionNecLong).WithValue(int32(0)), + }}, + }, + } for i := 0; i < recodSize-len(fit.Messages); i++ { now = now.Add(time.Second) // only time is moving forward if i%100 == 0 { // add event every 100 message - fit.Messages = append(fit.Messages, factory.CreateMesgOnly(mesgnum.Event).WithFields( + fit.Messages = append(fit.Messages, proto.Message{Num: mesgnum.Event, Fields: []proto.Field{ factory.CreateField(mesgnum.Event, fieldnum.EventTimestamp).WithValue(datetime.ToUint32(now)), factory.CreateField(mesgnum.Event, fieldnum.EventEvent).WithValue(uint8(typedef.EventActivity)), factory.CreateField(mesgnum.Event, fieldnum.EventEventType).WithValue(uint8(typedef.EventTypeStop)), - )) + }}) now = now.Add(10 * time.Second) // gap - fit.Messages = append(fit.Messages, factory.CreateMesgOnly(mesgnum.Event).WithFields( + fit.Messages = append(fit.Messages, proto.Message{Num: mesgnum.Event, Fields: []proto.Field{ factory.CreateField(mesgnum.Event, fieldnum.EventTimestamp).WithValue(datetime.ToUint32(now)), factory.CreateField(mesgnum.Event, fieldnum.EventEvent).WithValue(uint8(typedef.EventActivity)), factory.CreateField(mesgnum.Event, fieldnum.EventEventType).WithValue(uint8(typedef.EventTypeStart)), - )) + }}) now = now.Add(time.Second) // gap } - record := factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.RecordTimestamp: datetime.ToUint32(now), - fieldnum.RecordPositionLat: int32(-90481372), - fieldnum.RecordPositionLong: int32(1323227263), - fieldnum.RecordSpeed: uint16(8.33 * 1000), - fieldnum.RecordDistance: uint32(405.81 * 100), - fieldnum.RecordHeartRate: uint8(110), - fieldnum.RecordCadence: uint8(85), - fieldnum.RecordAltitude: uint16((166.0 + 500.0) * 5.0), - fieldnum.RecordTemperature: int8(32), - }) + record := proto.Message{Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now)), + factory.CreateField(mesgnum.Record, fieldnum.RecordPositionLat).WithValue(int32(-90481372)), + factory.CreateField(mesgnum.Record, fieldnum.RecordPositionLong).WithValue(int32(1323227263)), + factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed).WithValue(uint16(8.33 * 1000)), + factory.CreateField(mesgnum.Record, fieldnum.RecordDistance).WithValue(uint32(405.81 * 100)), + factory.CreateField(mesgnum.Record, fieldnum.RecordHeartRate).WithValue(uint8(110)), + factory.CreateField(mesgnum.Record, fieldnum.RecordCadence).WithValue(uint8(85)), + factory.CreateField(mesgnum.Record, fieldnum.RecordAltitude).WithValue(uint16((166.0 + 500.0) * 5.0)), + factory.CreateField(mesgnum.Record, fieldnum.RecordTemperature).WithValue(int8(32)), + }} if i%200 == 0 { // assume every 200 record hr sensor is not sending any data record.RemoveFieldByNum(fieldnum.RecordHeartRate) diff --git a/encoder/encoder_test.go b/encoder/encoder_test.go index 08faeaf9..ab184acc 100644 --- a/encoder/encoder_test.go +++ b/encoder/encoder_test.go @@ -43,26 +43,26 @@ func TestEncodeRealFiles(t *testing.T) { now := time.Date(2023, 9, 15, 6, 0, 0, 0, time.UTC) fit := &proto.FIT{ Messages: []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdManufacturer).WithValue(typedef.ManufacturerBryton), factory.CreateField(mesgnum.FileId, fieldnum.FileIdProductName).WithValue("Bryton Active App"), - ), - factory.CreateMesgOnly(mesgnum.Activity).WithFields( + }}, + {Num: mesgnum.Activity, Fields: []proto.Field{ factory.CreateField(mesgnum.Activity, fieldnum.ActivityType).WithValue(typedef.ActivityTypeCycling), factory.CreateField(mesgnum.Activity, fieldnum.ActivityTimestamp).WithValue(datetime.ToUint32(now)), factory.CreateField(mesgnum.Activity, fieldnum.ActivityNumSessions).WithValue(uint16(1)), - ), - factory.CreateMesgOnly(mesgnum.Session).WithFields( + }}, + {Num: mesgnum.Session, Fields: []proto.Field{ factory.CreateField(mesgnum.Session, fieldnum.SessionAvgSpeed).WithValue(uint16(1000)), factory.CreateField(mesgnum.Session, fieldnum.SessionAvgCadence).WithValue(uint8(78)), factory.CreateField(mesgnum.Session, fieldnum.SessionAvgHeartRate).WithValue(uint8(100)), - ), - factory.CreateMesgOnly(mesgnum.Record).WithFields( + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed).WithValue(uint16(1000)), factory.CreateField(mesgnum.Record, fieldnum.RecordCadence).WithValue(uint8(78)), factory.CreateField(mesgnum.Record, fieldnum.RecordHeartRate).WithValue(uint8(100)), - ), + }}, }, } @@ -242,7 +242,7 @@ func TestOptions(t *testing.T) { opts: nil, expected: options{ multipleLocalMessageType: 0, - endianness: 0, + endianness: proto.LittleEndian, messageValidator: NewMessageValidator(), writeBufferSize: defaultWriteBufferSize, }, @@ -258,7 +258,7 @@ func TestOptions(t *testing.T) { }, expected: options{ multipleLocalMessageType: 15, - endianness: 1, + endianness: proto.BigEndian, protocolVersion: proto.V2, messageValidator: fnValidateOK, headerOption: headerOptionNormal, @@ -275,7 +275,7 @@ func TestOptions(t *testing.T) { }, expected: options{ multipleLocalMessageType: 0, - endianness: 1, + endianness: proto.BigEndian, protocolVersion: proto.V2, messageValidator: fnValidateOK, headerOption: headerOptionCompressedTimestamp, @@ -398,9 +398,9 @@ func makeEncodeWithDirectUpdateStrategyTableTest() []encodeWithDirectUpdateTestC { name: "happy flow coverage", fit: &proto.FIT{Messages: []proto.Message{ - factory.CreateMesg(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(typedef.FileActivity), - ), + }}, }}, w: mockWriterAt{fnWriteOK, fnWriteAtOK}, }, @@ -419,9 +419,9 @@ func makeEncodeWithDirectUpdateStrategyTableTest() []encodeWithDirectUpdateTestC { name: "encode crc error", fit: &proto.FIT{Messages: []proto.Message{ - factory.CreateMesg(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(typedef.FileActivity), - ), + }}, }}, w: func() io.Writer { fnWrites := []io.Writer{fnWriteOK, fnWriteOK, fnWriteOK, fnWriteErr} @@ -441,9 +441,9 @@ func makeEncodeWithDirectUpdateStrategyTableTest() []encodeWithDirectUpdateTestC { name: "update error", fit: &proto.FIT{FileHeader: proto.FileHeader{Size: 14, DataSize: 100, DataType: proto.DataTypeFIT}, Messages: []proto.Message{ - factory.CreateMesg(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(typedef.FileActivity), - ), + }}, }}, w: mockWriterAt{fnWriteOK, fnWriteAtErr}, err: io.EOF, @@ -510,9 +510,9 @@ func makeEncodeWithEarlyCheckStrategy() []encodeWithEarlyCheckStrategyTestCase { { name: "encode header error", fit: &proto.FIT{Messages: []proto.Message{ - factory.CreateMesg(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdManufacturer).WithValue(uint16(typedef.ManufacturerGarmin)), - ), + }}, }}, w: fnWriteErr, err: io.EOF, @@ -520,9 +520,9 @@ func makeEncodeWithEarlyCheckStrategy() []encodeWithEarlyCheckStrategyTestCase { { name: "encode messages error", fit: &proto.FIT{Messages: []proto.Message{ - factory.CreateMesg(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdManufacturer).WithValue(uint16(typedef.ManufacturerGarmin)), - ), + }}, }}, w: func() io.Writer { fnInstances := []io.Writer{fnWriteOK, fnWriteErr} @@ -614,7 +614,7 @@ func TestUpdateHeader(t *testing.T) { name: "writeSeeker using stub", header: proto.FileHeader{ Size: 12, - ProtocolVersion: byte(proto.V1), + ProtocolVersion: proto.V1, ProfileVersion: profile.Version, DataType: proto.DataTypeFIT, }, @@ -628,7 +628,7 @@ func TestUpdateHeader(t *testing.T) { expect: func() []byte { h := proto.FileHeader{ Size: 12, - ProtocolVersion: byte(proto.V1), + ProtocolVersion: proto.V1, ProfileVersion: profile.Version, DataType: proto.DataTypeFIT, DataSize: 2, // updated @@ -641,7 +641,7 @@ func TestUpdateHeader(t *testing.T) { name: "writerAt using stub", header: proto.FileHeader{ Size: 12, - ProtocolVersion: byte(proto.V1), + ProtocolVersion: proto.V1, ProfileVersion: profile.Version, DataType: proto.DataTypeFIT, }, @@ -651,7 +651,7 @@ func TestUpdateHeader(t *testing.T) { expect: func() []byte { h := proto.FileHeader{ Size: 12, - ProtocolVersion: byte(proto.V1), + ProtocolVersion: proto.V1, ProfileVersion: profile.Version, DataType: proto.DataTypeFIT, DataSize: 2, // updated @@ -830,7 +830,7 @@ func TestEncodeHeader(t *testing.T) { }, header: proto.FileHeader{ Size: 14, - ProtocolVersion: byte(proto.V1), + ProtocolVersion: proto.V1, ProfileVersion: 2135, DataSize: 136830, DataType: ".FIT", @@ -876,28 +876,28 @@ func TestEncodeMessage(t *testing.T) { }{ { name: "encode message with default header option happy flow", - mesg: factory.CreateMesg(mesgnum.FileId).WithFieldValues(map[byte]any{ - fieldnum.FileIdType: typedef.FileActivity, - }), + mesg: proto.Message{Num: mesgnum.FileId, Fields: []proto.Field{ + factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(typedef.FileActivity), + }}, w: fnWriteOK, }, { name: "encode message with big-endian", - mesg: factory.CreateMesg(mesgnum.FileId).WithFieldValues(map[byte]any{ - fieldnum.FileIdType: typedef.FileActivity, - }), + mesg: proto.Message{Num: mesgnum.FileId, Fields: []proto.Field{ + factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(typedef.FileActivity), + }}, w: fnWriteOK, opts: []Option{WithBigEndian()}, - endianness: bigEndian, + endianness: proto.BigEndian, }, { name: "encode message with header normal multiple local message type happy flow", opts: []Option{ WithNormalHeader(2), }, - mesg: factory.CreateMesg(mesgnum.FileId).WithFieldValues(map[byte]any{ - fieldnum.FileIdType: typedef.FileActivity, - }), + mesg: proto.Message{Num: mesgnum.FileId, Fields: []proto.Field{ + factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(typedef.FileActivity), + }}, w: fnWriteOK, }, { @@ -905,9 +905,9 @@ func TestEncodeMessage(t *testing.T) { opts: []Option{ WithCompressedTimestampHeader(), }, - mesg: factory.CreateMesg(mesgnum.FileId).WithFieldValues(map[byte]any{ - fieldnum.FileIdType: typedef.FileActivity, - }), + mesg: proto.Message{Num: mesgnum.FileId, Fields: []proto.Field{ + factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(typedef.FileActivity), + }}, w: fnWriteOK, }, { @@ -1008,8 +1008,8 @@ func TestEncodeMessage(t *testing.T) { t.Fatalf("message header should not contain Developer Data Flag") } - if tc.mesg.Architecture != tc.endianness { - t.Fatalf("expected endianness: %d, got: %d", tc.endianness, tc.mesg.Architecture) + if enc.mesgDef.Architecture != tc.endianness { + t.Fatalf("expected endianness: %d, got: %d", tc.endianness, enc.mesgDef.Architecture) } }) } @@ -1024,7 +1024,9 @@ func TestEncodeMessage(t *testing.T) { factory.CreateField(mesgnum.Record, fieldnum.RecordAltitude).WithValue(uint16((166.0 + 500.0) * 5.0)), }, } - expected := mesg.Clone() + expected := proto.Message{ + Fields: append(mesg.Fields[:0:0], mesg.Fields...), + } enc := New(io.Discard, WithCompressedTimestampHeader(), @@ -1053,17 +1055,17 @@ func TestEncodeMessage(t *testing.T) { func TestEncodeMessageWithMultipleLocalMessageType(t *testing.T) { now := time.Now() mesgs := []proto.Message{ - factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.RecordTimestamp: datetime.ToUint32(now), - }), - factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.RecordTimestamp: datetime.ToUint32(now.Add(time.Second)), - fieldnum.RecordHeartRate: uint8(70), - }), - factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.RecordTimestamp: datetime.ToUint32(now.Add(2 * time.Second)), - fieldnum.RecordSpeed: uint16(1000), - }), + {Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now)), + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now.Add(time.Second))), + factory.CreateField(mesgnum.Record, fieldnum.RecordHeartRate).WithValue(uint8(70)), + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now.Add(2 * time.Second))), + factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed).WithValue(uint16(1000)), + }}, } t.Run("multiple local mesg type", func(t *testing.T) { @@ -1072,7 +1074,7 @@ func TestEncodeMessageWithMultipleLocalMessageType(t *testing.T) { mesgs := append(mesgs[:0:0], mesgs...) for i := range mesgs { - mesgs[i] = mesgs[i].Clone() + mesgs[i].Fields = append(mesgs[i].Fields[:0:0], mesgs[i].Fields...) } buf := new(bytes.Buffer) @@ -1092,9 +1094,9 @@ func TestEncodeMessageWithMultipleLocalMessageType(t *testing.T) { } // add 4th mesg, header should be 0, reset. - mesg := factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.RecordTimestamp: datetime.ToUint32(now), - }) + mesg := proto.Message{Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now)), + }} buf.Reset() if err := enc.encodeMessage(&mesg); err != nil { t.Fatal(err) @@ -1120,10 +1122,10 @@ func makeEncodeMessagesTableTest() []encodeMessagesTestCase { name: "encode messages happy flow", mesgValidator: fnValidateOK, mesgs: []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdManufacturer).WithValue(uint16(typedef.ManufacturerGarmin)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdProduct).WithValue(uint16(typedef.GarminProductEdge1030)), - ), + }}, }, }, { @@ -1139,7 +1141,7 @@ func makeEncodeMessagesTableTest() []encodeMessagesTestCase { }, { name: "missing file_id mesg", - mesgs: []proto.Message{factory.CreateMesg(mesgnum.Record)}, + mesgs: []proto.Message{{Num: mesgnum.Record}}, err: ErrMissingFileId, }, } @@ -1183,22 +1185,22 @@ func TestCompressTimestampInHeader(t *testing.T) { { name: "compress timestamp in header happy flow", mesgs: []proto.Message{ - factory.CreateMesg(mesgnum.FileId).WithFieldValues(map[byte]any{ - fieldnum.FileIdManufacturer: typedef.ManufacturerGarmin, - fieldnum.FileIdTimeCreated: datetime.ToUint32(now), - }), - factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.RecordTimestamp: datetime.ToUint32(now), - }), - factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.RecordTimestamp: datetime.ToUint32(now.Add(time.Second)), // +1s - }), - factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.RecordTimestamp: datetime.ToUint32(now.Add(2 * time.Second)), // +2s - }), - factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.RecordTimestamp: datetime.ToUint32(now.Add(32 * time.Second)), // +32 rollover - }), + {Num: mesgnum.FileId, Fields: []proto.Field{ + factory.CreateField(mesgnum.FileId, fieldnum.FileIdManufacturer).WithValue(typedef.ManufacturerGarmin), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now)), + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now.Add(time.Second))), // +1), + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now.Add(2 * time.Second))), // +2), + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now.Add(32 * time.Second))), // +32 rollove), + }}, }, headers: []byte{ proto.MesgNormalHeaderMask, // file_id: has no timestamp @@ -1211,19 +1213,19 @@ func TestCompressTimestampInHeader(t *testing.T) { { name: "compress timestamp in header happy flow: roll over occurred exactly after 32 seconds", mesgs: []proto.Message{ - factory.CreateMesg(mesgnum.FileId).WithFieldValues(map[byte]any{ - fieldnum.FileIdManufacturer: typedef.ManufacturerGarmin, - fieldnum.FileIdTimeCreated: datetime.ToUint32(now), - }), - factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.RecordTimestamp: datetime.ToUint32(now), - }), - factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.RecordTimestamp: datetime.ToUint32(now.Add(32 * time.Second)), - }), - factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.RecordTimestamp: datetime.ToUint32(now.Add(33 * time.Second)), - }), + {Num: mesgnum.FileId, Fields: []proto.Field{ + factory.CreateField(mesgnum.FileId, fieldnum.FileIdManufacturer).WithValue(typedef.ManufacturerGarmin), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now)), + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now.Add(32 * time.Second))), + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now.Add(33 * time.Second))), + }}, }, headers: []byte{ proto.MesgNormalHeaderMask, // file_id: has no timestamp @@ -1235,13 +1237,13 @@ func TestCompressTimestampInHeader(t *testing.T) { { name: "timestamp less than DateTimeMin", mesgs: []proto.Message{ - factory.CreateMesg(mesgnum.FileId).WithFieldValues(map[byte]any{ - fieldnum.FileIdManufacturer: typedef.ManufacturerGarmin, - fieldnum.FileIdTimeCreated: datetime.ToUint32(now), - }), - factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.RecordTimestamp: uint32(1234), - }), + {Num: mesgnum.FileId, Fields: []proto.Field{ + factory.CreateField(mesgnum.FileId, fieldnum.FileIdManufacturer).WithValue(typedef.ManufacturerGarmin), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(uint32(1234)), + }}, }, headers: []byte{ proto.MesgNormalHeaderMask, @@ -1251,13 +1253,13 @@ func TestCompressTimestampInHeader(t *testing.T) { { name: "timestamp wrong type not uint32 or typedef.DateTime", mesgs: []proto.Message{ - factory.CreateMesg(mesgnum.FileId).WithFieldValues(map[byte]any{ - fieldnum.FileIdManufacturer: typedef.ManufacturerGarmin, - fieldnum.FileIdTimeCreated: datetime.ToUint32(now), - }), - factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.RecordTimestamp: typedef.DateTime(datetime.ToUint32(now)), - }), + {Num: mesgnum.FileId, Fields: []proto.Field{ + factory.CreateField(mesgnum.FileId, fieldnum.FileIdManufacturer).WithValue(typedef.ManufacturerGarmin), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(typedef.DateTime(datetime.ToUint32(now))), + }}, }, headers: []byte{ proto.MesgNormalHeaderMask, @@ -1267,13 +1269,13 @@ func TestCompressTimestampInHeader(t *testing.T) { { name: "timestamp wrong type not uint32 or typedef.DateTime", mesgs: []proto.Message{ - factory.CreateMesg(mesgnum.FileId).WithFieldValues(map[byte]any{ - fieldnum.FileIdManufacturer: typedef.ManufacturerGarmin, - fieldnum.FileIdTimeCreated: datetime.ToUint32(now), - }), - factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.RecordTimestamp: now, // time.Time{} - }), + {Num: mesgnum.FileId, Fields: []proto.Field{ + factory.CreateField(mesgnum.FileId, fieldnum.FileIdManufacturer).WithValue(typedef.ManufacturerGarmin), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(now), // time.Time{), + }}, }, headers: []byte{ proto.MesgNormalHeaderMask, @@ -1298,6 +1300,189 @@ func TestCompressTimestampInHeader(t *testing.T) { } } +func TestNewMessageDefinition(t *testing.T) { + tt := []struct { + name string + mesg *proto.Message + arch byte + mesgDef *proto.MessageDefinition + }{ + { + name: "fields only with non-array values", + mesg: &proto.Message{Num: mesgnum.FileId, Fields: []proto.Field{ + {FieldBase: &proto.FieldBase{Num: fieldnum.FileIdType, BaseType: basetype.Enum}, Value: proto.Uint8(typedef.FileActivity.Byte())}, + }}, + mesgDef: &proto.MessageDefinition{ + Header: proto.MesgDefinitionMask, + MesgNum: mesgnum.FileId, + FieldDefinitions: []proto.FieldDefinition{ + { + Num: fieldnum.FileIdType, + Size: 1, + BaseType: basetype.Enum, + }, + }, + }, + }, + { + name: "fields only with mesg architecture big-endian", + mesg: func() *proto.Message { + mesg := &proto.Message{Num: mesgnum.FileId, Fields: []proto.Field{ + {FieldBase: &proto.FieldBase{Num: fieldnum.FileIdType, BaseType: basetype.Enum}, Value: proto.Uint8(typedef.FileActivity.Byte())}, + }} + return mesg + }(), + arch: 1, + mesgDef: &proto.MessageDefinition{ + Header: proto.MesgDefinitionMask, + Architecture: proto.BigEndian, + MesgNum: mesgnum.FileId, + FieldDefinitions: []proto.FieldDefinition{ + { + Num: fieldnum.FileIdType, + Size: 1, + BaseType: basetype.Enum, + }, + }, + }, + }, + { + name: "fields only with string value", + mesg: &proto.Message{Num: mesgnum.FileId, Fields: []proto.Field{ + {FieldBase: &proto.FieldBase{Num: fieldnum.FileIdProductName, BaseType: basetype.String}, Value: proto.String("FIT SDK Go")}, + }}, + mesgDef: &proto.MessageDefinition{ + Header: proto.MesgDefinitionMask, + MesgNum: mesgnum.FileId, + FieldDefinitions: []proto.FieldDefinition{ + { + Num: fieldnum.FileIdProductName, + Size: 1 * 11, // len("FIT SDK Go") == 10 + '0x00' + BaseType: basetype.String, + }, + }, + }, + }, + { + name: "fields only with array of byte", + mesg: &proto.Message{Num: mesgnum.UserProfile, Fields: []proto.Field{ + {FieldBase: &proto.FieldBase{Num: fieldnum.UserProfileGlobalId, BaseType: basetype.Byte}, Value: proto.SliceUint8([]byte{2, 9})}, + }}, + mesgDef: &proto.MessageDefinition{ + Header: proto.MesgDefinitionMask, + MesgNum: mesgnum.UserProfile, + FieldDefinitions: []proto.FieldDefinition{ + { + Num: fieldnum.UserProfileGlobalId, + Size: 2, + BaseType: basetype.Byte, + }, + }, + }, + }, + + { + name: "developer fields", + mesg: &proto.Message{Num: mesgnum.UserProfile, + Fields: []proto.Field{ + {FieldBase: &proto.FieldBase{Num: fieldnum.UserProfileGlobalId, BaseType: basetype.Byte}, Value: proto.SliceUint8([]byte{2, 9})}, + }, + DeveloperFields: []proto.DeveloperField{ + {Num: 0, DeveloperDataIndex: 0, Value: proto.Uint8(1)}, + }}, + mesgDef: &proto.MessageDefinition{ + Header: proto.MesgDefinitionMask | proto.DevDataMask, + MesgNum: mesgnum.UserProfile, + FieldDefinitions: []proto.FieldDefinition{ + { + Num: fieldnum.UserProfileGlobalId, + Size: 2, + BaseType: basetype.Byte, + }, + }, + DeveloperFieldDefinitions: []proto.DeveloperFieldDefinition{ + { + Num: 0, Size: 1, DeveloperDataIndex: 0, + }, + }, + }, + }, + { + name: "developer fields with string value \"FIT SDK Go\", size should be 11", + mesg: &proto.Message{Num: mesgnum.UserProfile, + Fields: []proto.Field{ + {FieldBase: &proto.FieldBase{Num: fieldnum.UserProfileGlobalId, BaseType: basetype.Byte}, Value: proto.SliceUint8([]byte{2, 9})}, + }, + DeveloperFields: []proto.DeveloperField{ + { + Num: 0, DeveloperDataIndex: 0, Value: proto.String("FIT SDK Go"), + }, + }}, + mesgDef: &proto.MessageDefinition{ + Header: proto.MesgDefinitionMask | proto.DevDataMask, + MesgNum: mesgnum.UserProfile, + FieldDefinitions: []proto.FieldDefinition{ + { + Num: fieldnum.UserProfileGlobalId, + Size: 2, + BaseType: basetype.Byte, + }, + }, + DeveloperFieldDefinitions: []proto.DeveloperFieldDefinition{ + { + Num: 0, Size: 11, DeveloperDataIndex: 0, + }, + }, + }, + }, + { + name: "developer fields with value []uint16{1,2,3}, size should be 3*2 = 6", + mesg: &proto.Message{Num: mesgnum.UserProfile, + Fields: []proto.Field{ + {FieldBase: &proto.FieldBase{Num: fieldnum.UserProfileGlobalId, BaseType: basetype.Byte}, Value: proto.SliceUint8([]byte{2, 9})}, + }, + DeveloperFields: []proto.DeveloperField{ + {Num: 0, DeveloperDataIndex: 0, Value: proto.SliceUint16([]uint16{1, 2, 3})}, + }}, + mesgDef: &proto.MessageDefinition{ + Header: proto.MesgDefinitionMask | proto.DevDataMask, + MesgNum: mesgnum.UserProfile, + FieldDefinitions: []proto.FieldDefinition{ + { + Num: fieldnum.UserProfileGlobalId, + Size: 2, + BaseType: basetype.Byte, + }, + }, + DeveloperFieldDefinitions: []proto.DeveloperFieldDefinition{ + { + Num: 0, Size: 6, DeveloperDataIndex: 0, + }, + }, + }, + }, + } + + for i, tc := range tt { + t.Run(fmt.Sprintf("[%d] %s", i, tc.name), func(t *testing.T) { + enc := New(nil) + enc.options.endianness = tc.arch + mesgDef := enc.newMessageDefinition(tc.mesg) + if diff := cmp.Diff(mesgDef, tc.mesgDef, + cmp.Transformer("DeveloperFieldDefinitions", + func(devFields []proto.DeveloperFieldDefinition) []proto.DeveloperFieldDefinition { + if len(devFields) == 0 { + return nil + } + return devFields + }), + ); diff != "" { + t.Fatal(diff) + } + }) + } +} + // bufferAt wraps bytes.Buffer to enable WriteAt for faster encoding. type bufferAt struct{ *bytes.Buffer } @@ -1387,9 +1572,9 @@ func TestEncodeMessagesWithContext(t *testing.T) { cancel() mesgs := []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(uint8(typedef.FileActivity)), - ), + }}, } enc := New(nil, WithWriteBufferSize(0)) err := enc.encodeMessagesWithContext(ctx, mesgs) diff --git a/encoder/stream_test.go b/encoder/stream_test.go index 8ccef38f..cf86e68c 100644 --- a/encoder/stream_test.go +++ b/encoder/stream_test.go @@ -27,12 +27,12 @@ func TestStreamEncoderOneSequenceHappyFlow(t *testing.T) { t.Fatal(err) } - fileIdMesg := factory.CreateMesgOnly(mesgnum.FileId).WithFields( + fileIdMesg := proto.Message{Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(time.Now())), - ) - recordMesg := factory.CreateMesgOnly(mesgnum.Record).WithFields( + }} + recordMesg := proto.Message{Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordDistance).WithValue(uint32(1000)), - ) + }} err = streamEnc.WriteMessage(&fileIdMesg) if err != nil { @@ -72,9 +72,9 @@ func TestStreamEncoderUnhappyFlow(t *testing.T) { enc := New(mockWriterAt{Writer: fnWriteErr}, WithWriteBufferSize(0)) streamEnc, _ := enc.StreamEncoder() - mesg := factory.CreateMesgOnly(mesgnum.FileId).WithFields( + mesg := proto.Message{Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(time.Now())), - ) + }} err := streamEnc.WriteMessage(&mesg) if !errors.Is(err, io.EOF) { t.Fatalf("expected err: %v, got: %v", io.EOF, err) @@ -155,9 +155,9 @@ func TestStreamEncoderWithoutWriteBuffer(t *testing.T) { t.Fatal(err) } - fileIdMesg := factory.CreateMesgOnly(mesgnum.FileId).WithFields( + fileIdMesg := proto.Message{Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(time.Now())), - ) + }} err = streamEnc.WriteMessage(&fileIdMesg) if err != nil { diff --git a/encoder/validator.go b/encoder/validator.go index a0c2427c..88e233e5 100644 --- a/encoder/validator.go +++ b/encoder/validator.go @@ -59,7 +59,8 @@ func defaultValidatorOptions() validatorOptions { // Factory defines a contract that any Factory containing these method can be used by the Encoder's Validator. type Factory interface { - // CreateField create new field based on defined messages in the factory. If not found, it returns new field with "unknown" name. + // CreateField creates new field based on defined messages in the factory. + // If not found, it returns new field with "unknown" name. CreateField(mesgNum typedef.MesgNum, num byte) proto.Field } @@ -107,8 +108,6 @@ func (v *messageValidator) Reset() { } func (v *messageValidator) Validate(mesg *proto.Message) error { - mesg.Header = proto.MesgNormalHeaderMask // reset default - var valid int for i := range mesg.Fields { field := &mesg.Fields[i] diff --git a/encoder/validator_test.go b/encoder/validator_test.go index efab36d4..91aa676a 100644 --- a/encoder/validator_test.go +++ b/encoder/validator_test.go @@ -106,18 +106,18 @@ func TestMessageValidatorValidate(t *testing.T) { { name: "valid message with developer fields happy flow", mesgs: []proto.Message{ - factory.CreateMesg(mesgnum.DeveloperDataId).WithFieldValues(map[byte]any{ - fieldnum.DeveloperDataIdDeveloperDataIndex: uint8(0), - fieldnum.DeveloperDataIdApplicationId: []byte{0, 1, 2, 3}, - }), - factory.CreateMesg(mesgnum.FieldDescription).WithFieldValues(map[byte]any{ - fieldnum.FieldDescriptionDeveloperDataIndex: uint8(0), - fieldnum.FieldDescriptionFieldDefinitionNumber: uint8(0), - fieldnum.FieldDescriptionFieldName: "Heart Rate", - fieldnum.FieldDescriptionNativeMesgNum: uint16(mesgnum.Record), - fieldnum.FieldDescriptionNativeFieldNum: uint8(fieldnum.RecordHeartRate), - fieldnum.FieldDescriptionFitBaseTypeId: uint8(basetype.Uint8), - }), + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ + factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), + factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdApplicationId).WithValue([]byte{0, 1, 2, 3}), + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("Heart Rate"), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordHeartRate)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint8)), + }}, { Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(time.Now())), @@ -135,7 +135,7 @@ func TestMessageValidatorValidate(t *testing.T) { { name: "mesg contain expanded field", mesgs: []proto.Message{ - factory.CreateMesgOnly(mesgnum.Record).WithFields( + {Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed).WithValue(uint16(1000)), func() proto.Field { field := factory.CreateField(mesgnum.Record, fieldnum.RecordEnhancedSpeed) @@ -143,33 +143,33 @@ func TestMessageValidatorValidate(t *testing.T) { field.Value = proto.Uint32(1000) return field }(), - ), + }}, }, }, { name: "mesg contain field with scaled value", mesgs: []proto.Message{ - factory.CreateMesgOnly(mesgnum.Record).WithFields( + {Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordAltitude).WithValue((float64(37304) / 5) - 500), // 6960.8m - ), + }}, }, }, { name: "mesg contain field value type not align", mesgs: []proto.Message{ - factory.CreateMesgOnly(mesgnum.Record).WithFields( + {Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed).WithValue(uint32(1000)), // should be uint16 - ), + }}, }, errs: []error{ErrValueTypeMismatch}, }, { name: "valid message with developer data index not found in previous message sequence", mesgs: []proto.Message{ - factory.CreateMesg(mesgnum.FieldDescription).WithFieldValues(map[byte]any{ - fieldnum.FieldDescriptionDeveloperDataIndex: uint8(0), - fieldnum.FieldDescriptionFieldDefinitionNumber: uint8(0), - }), + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ + factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), + factory.CreateField(mesgnum.DeveloperDataId, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)), + }}, { Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(time.Now())), @@ -184,10 +184,10 @@ func TestMessageValidatorValidate(t *testing.T) { { name: "valid message with field description not found in previous message sequence", mesgs: []proto.Message{ - factory.CreateMesg(mesgnum.DeveloperDataId).WithFieldValues(map[byte]any{ - fieldnum.DeveloperDataIdDeveloperDataIndex: uint8(0), - fieldnum.DeveloperDataIdApplicationId: []byte{0, 1, 2, 3}, - }), + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ + factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), + factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdApplicationId).WithValue([]byte{0, 1, 2, 3}), + }}, { Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(time.Now())), @@ -202,21 +202,21 @@ func TestMessageValidatorValidate(t *testing.T) { { name: "invalid utf-8 string", mesgs: []proto.Message{ - factory.CreateMesg(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdProductName).WithValue("\xbd"), - ), + }}, }, errs: []error{ErrInvalidUTF8String}, }, { name: "invalid utf-8 []string", mesgs: []proto.Message{ - factory.CreateMesg(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdProductName).WithValue("valid utf-8 string"), - ), - factory.CreateMesg(mesgnum.SegmentFile).WithFields( + }}, + {Num: mesgnum.SegmentFile, Fields: []proto.Field{ factory.CreateField(mesgnum.SegmentFile, fieldnum.SegmentFileLeaderActivityIdString).WithValue([]string{"valid utf-8", "\xbd"}), // valid and invalid string in array - ), + }}, }, errs: []error{nil, ErrInvalidUTF8String}, }, @@ -248,18 +248,18 @@ func TestMessageValidatorValidate(t *testing.T) { { name: "n developer fields exceed allowed", mesgs: []proto.Message{ - factory.CreateMesg(mesgnum.DeveloperDataId).WithFieldValues(map[byte]any{ - fieldnum.DeveloperDataIdDeveloperDataIndex: uint8(0), - fieldnum.DeveloperDataIdApplicationId: []byte{0, 1, 2, 3}, - }), - factory.CreateMesg(mesgnum.FieldDescription).WithFieldValues(map[byte]any{ - fieldnum.FieldDescriptionDeveloperDataIndex: uint8(0), - fieldnum.FieldDescriptionFieldDefinitionNumber: uint8(0), - fieldnum.FieldDescriptionFieldName: "Heart Rate", - fieldnum.FieldDescriptionNativeMesgNum: uint16(mesgnum.Record), - fieldnum.FieldDescriptionNativeFieldNum: uint8(fieldnum.RecordHeartRate), - fieldnum.FieldDescriptionFitBaseTypeId: uint8(basetype.Uint8), - }), + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ + factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), + factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdApplicationId).WithValue([]byte{0, 1, 2, 3}), + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("Heart Rate"), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordHeartRate)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint8)), + }}, { Num: mesgnum.Record, Fields: []proto.Field{ @@ -282,9 +282,9 @@ func TestMessageValidatorValidate(t *testing.T) { { name: "field value size exceed max allowed", mesgs: []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdProductName).WithValue(strings.Repeat("a", 256)), - ), + }}, }, errs: []error{ErrExceedMaxAllowed}, }, @@ -308,31 +308,31 @@ func TestMessageValidatorValidate(t *testing.T) { return mesgValidator }(), mesgs: []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithDeveloperFields( - proto.DeveloperField{ + {Num: mesgnum.FileId, DeveloperFields: []proto.DeveloperField{ + { DeveloperDataIndex: 0, Num: 1, Value: proto.String(strings.Repeat("a", 256)), }, - ), + }}, }, errs: []error{ErrExceedMaxAllowed}, }, { name: "valid message with developer fields invalid value", mesgs: []proto.Message{ - factory.CreateMesg(mesgnum.DeveloperDataId).WithFieldValues(map[byte]any{ - fieldnum.DeveloperDataIdDeveloperDataIndex: uint8(0), - fieldnum.DeveloperDataIdApplicationId: []byte{0, 1, 2, 3}, - }), - factory.CreateMesg(mesgnum.FieldDescription).WithFieldValues(map[byte]any{ - fieldnum.FieldDescriptionDeveloperDataIndex: uint8(0), - fieldnum.FieldDescriptionFieldDefinitionNumber: uint8(0), - fieldnum.FieldDescriptionFieldName: "Heart Rate", - fieldnum.FieldDescriptionNativeMesgNum: uint16(mesgnum.Record), - fieldnum.FieldDescriptionNativeFieldNum: uint8(fieldnum.RecordHeartRate), - fieldnum.FieldDescriptionFitBaseTypeId: uint8(basetype.Uint8), - }), + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ + factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), + factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdApplicationId).WithValue([]byte{0, 1, 2, 3}), + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("Heart Rate"), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordHeartRate)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint8)), + }}, { Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(time.Now())), @@ -350,20 +350,20 @@ func TestMessageValidatorValidate(t *testing.T) { { name: "mesg contain developer field value scaled", mesgs: []proto.Message{ - factory.CreateMesg(mesgnum.DeveloperDataId).WithFieldValues(map[byte]any{ - fieldnum.DeveloperDataIdDeveloperDataIndex: uint8(0), - fieldnum.DeveloperDataIdApplicationId: []byte{0, 1, 2, 3}, - }), - factory.CreateMesg(mesgnum.FieldDescription).WithFieldValues(map[byte]any{ - fieldnum.FieldDescriptionDeveloperDataIndex: uint8(0), - fieldnum.FieldDescriptionFieldDefinitionNumber: uint8(0), - fieldnum.FieldDescriptionFieldName: "Custom Distance", - fieldnum.FieldDescriptionNativeMesgNum: uint16(basetype.Uint16Invalid), - fieldnum.FieldDescriptionNativeFieldNum: uint8(basetype.Uint8Invalid), - fieldnum.FieldDescriptionScale: uint8(100), - fieldnum.FieldDescriptionOffset: int8(0), - fieldnum.FieldDescriptionFitBaseTypeId: uint8(basetype.Uint16), - }), + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ + factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), + factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdApplicationId).WithValue([]byte{0, 1, 2, 3}), + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("Custom Distance"), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(basetype.Uint16Invalid)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(basetype.Uint8Invalid)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionScale).WithValue(uint8(100)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionOffset).WithValue(int8(0)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint16)), + }}, { Num: mesgnum.Record, Fields: []proto.Field{ @@ -382,18 +382,18 @@ func TestMessageValidatorValidate(t *testing.T) { { name: "mesg contain developer field with native value scaled", mesgs: []proto.Message{ - factory.CreateMesg(mesgnum.DeveloperDataId).WithFieldValues(map[byte]any{ - fieldnum.DeveloperDataIdDeveloperDataIndex: uint8(0), - fieldnum.DeveloperDataIdApplicationId: []byte{0, 1, 2, 3}, - }), - factory.CreateMesg(mesgnum.FieldDescription).WithFieldValues(map[byte]any{ - fieldnum.FieldDescriptionDeveloperDataIndex: uint8(0), - fieldnum.FieldDescriptionFieldDefinitionNumber: uint8(0), - fieldnum.FieldDescriptionFieldName: "Altitude", - fieldnum.FieldDescriptionNativeMesgNum: uint16(mesgnum.Record), - fieldnum.FieldDescriptionNativeFieldNum: uint8(fieldnum.RecordAltitude), - fieldnum.FieldDescriptionFitBaseTypeId: uint8(basetype.Uint16), - }), + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ + factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), + factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdApplicationId).WithValue([]byte{0, 1, 2, 3}), + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("Altitude"), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordAltitude)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint16)), + }}, { Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(time.Now())), @@ -411,18 +411,18 @@ func TestMessageValidatorValidate(t *testing.T) { { name: "mesg contain developer field with unknown native", mesgs: []proto.Message{ - factory.CreateMesg(mesgnum.DeveloperDataId).WithFieldValues(map[byte]any{ - fieldnum.DeveloperDataIdDeveloperDataIndex: uint8(0), - fieldnum.DeveloperDataIdApplicationId: []byte{0, 1, 2, 3}, - }), - factory.CreateMesg(mesgnum.FieldDescription).WithFieldValues(map[byte]any{ - fieldnum.FieldDescriptionDeveloperDataIndex: uint8(0), - fieldnum.FieldDescriptionFieldDefinitionNumber: uint8(0), - fieldnum.FieldDescriptionFieldName: "??", - fieldnum.FieldDescriptionNativeMesgNum: uint16(mesgnum.Record), - fieldnum.FieldDescriptionNativeFieldNum: uint8(255), - fieldnum.FieldDescriptionFitBaseTypeId: uint8(basetype.Uint16), - }), + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ + factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), + factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdApplicationId).WithValue([]byte{0, 1, 2, 3}), + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("??"), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(255)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint16)), + }}, { Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(time.Now())), @@ -462,18 +462,18 @@ func TestMessageValidatorValidate(t *testing.T) { { name: "valid message with developer fields has invalid value", mesgs: []proto.Message{ - factory.CreateMesg(mesgnum.DeveloperDataId).WithFieldValues(map[byte]any{ - fieldnum.DeveloperDataIdDeveloperDataIndex: uint8(0), - fieldnum.DeveloperDataIdApplicationId: []byte{0, 1, 2, 3}, - }), - factory.CreateMesg(mesgnum.FieldDescription).WithFieldValues(map[byte]any{ - fieldnum.FieldDescriptionDeveloperDataIndex: uint8(0), - fieldnum.FieldDescriptionFieldDefinitionNumber: uint8(0), - fieldnum.FieldDescriptionFieldName: "Heart Rate", - fieldnum.FieldDescriptionNativeMesgNum: uint16(mesgnum.Record), - fieldnum.FieldDescriptionNativeFieldNum: uint8(fieldnum.RecordHeartRate), - fieldnum.FieldDescriptionFitBaseTypeId: uint8(basetype.Uint8), - }), + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ + factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), + factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdApplicationId).WithValue([]byte{0, 1, 2, 3}), + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("Heart Rate"), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordHeartRate)), + factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint8)), + }}, { Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(time.Now())), @@ -522,7 +522,7 @@ func TestMessageValidatorValidate(t *testing.T) { func BenchmarkValidate(b *testing.B) { b.StopTimer() mesgValidator := NewMessageValidator() - mesg := factory.CreateMesgOnly(mesgnum.Record).WithFields( + mesg := proto.Message{Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed).WithValue(uint16(1000)), factory.CreateField(mesgnum.Record, fieldnum.RecordAltitude).WithValue(uint16(10000)), func() proto.Field { @@ -543,7 +543,7 @@ func BenchmarkValidate(b *testing.B) { field.Value = proto.Uint32(1000) return field }(), - ) + }} b.StartTimer() for i := 0; i < b.N; i++ { diff --git a/factory/exported_gen.go b/factory/exported_gen.go index 89dc5abb..7cd2478a 100755 --- a/factory/exported_gen.go +++ b/factory/exported_gen.go @@ -19,7 +19,7 @@ func StandardFactory() *Factory { return std } // CreateMesg creates new message based on defined messages in the factory. If not found, it returns proto.Message{Num: num}. // // This will create a shallow copy of the Fields, so changing any value declared in Field's FieldBase is prohibited -// (except in case of unknown field). If you want a deep copy of the mesg, use mesg.Clone(). +// (except in case of unknown field). // // NOTE: This method is not used by either the Decoder or the Encoder, and the data will only be populated once upon the first invocation. // Unless you need most of the returned fields, it's recommended create an empty proto.Message{Num: num} then fill only the necessary fields @@ -28,15 +28,10 @@ func CreateMesg(num typedef.MesgNum) proto.Message { return std.CreateMesg(num) } -// CreateMesgOnly is a syntax sugar for creating proto.Message{Num: num}. -func CreateMesgOnly(num typedef.MesgNum) proto.Message { - return std.CreateMesgOnly(num) -} - // CreateField creates new field based on defined messages in the factory. If not found, it returns new field with "unknown" name. // // The returned field contains a pointer reference to FieldBase defined in the factory, so changing any value -// declared in FieldBase is prohibited (except in the case of unknown field). If you want a deep copy, use field.Clone(). +// declared in FieldBase is prohibited (except in the case of unknown field). func CreateField(mesgNum typedef.MesgNum, num byte) proto.Field { return std.CreateField(mesgNum, num) } diff --git a/factory/factory_gen.go b/factory/factory_gen.go index a6cb5973..1cff3147 100755 --- a/factory/factory_gen.go +++ b/factory/factory_gen.go @@ -51,7 +51,7 @@ var ( // cache for CreateMesg method // CreateMesg creates new message based on defined messages in the factory. If not found, it returns proto.Message{Num: num}. // // This will create a shallow copy of the Fields, so changing any value declared in Field's FieldBase is prohibited -// (except in case of unknown field). If you want a deep copy of the mesg, use mesg.Clone(). +// (except in case of unknown field). // // NOTE: This method is not used by either the Decoder or the Encoder, and the data will only be populated once upon the first invocation. // Unless you need most of the returned fields, it's recommended create an empty proto.Message{Num: num} then fill only the necessary fields @@ -118,15 +118,10 @@ func (f *Factory) CreateMesg(num typedef.MesgNum) proto.Message { return mesg } -// CreateMesgOnly is a syntax sugar for creating proto.Message{Num: num}. -func (f *Factory) CreateMesgOnly(num typedef.MesgNum) proto.Message { - return proto.Message{Num: num} -} - // CreateField creates new field based on defined messages in the factory. If not found, it returns new field with "unknown" name. // // The returned field contains a pointer reference to FieldBase defined in the factory, so changing any value -// declared in FieldBase is prohibited (except in the case of unknown field). If you want a deep copy, use field.Clone(). +// declared in FieldBase is prohibited (except in the case of unknown field). func (f *Factory) CreateField(mesgNum typedef.MesgNum, num byte) proto.Field { if mesgNum < 410 && mesgs[mesgNum] != nil { fieldBase := mesgs[mesgNum][num] diff --git a/fuzz_test.go b/fuzz_test.go index 160c9e8c..7b1ff89c 100644 --- a/fuzz_test.go +++ b/fuzz_test.go @@ -134,9 +134,11 @@ func FuzzDecodeEncodeRoundTrip(f *testing.F) { return } - if diff := cmp.Diff(sanitizeOutput(in), sanitizeOutput(encoded)); diff != "" { - fitdump("in", t, in) - fitdump("encoded", t, encoded) + sanitizedIn := sanitizeOutput(in) + sanitizedEncoded := sanitizeOutput(encoded) + if diff := cmp.Diff(sanitizedIn, sanitizedEncoded); diff != "" { + fitdump("in", t, sanitizedIn) + fitdump("encoded", t, sanitizedEncoded) t.Fatal(diff) } }) @@ -236,6 +238,7 @@ func sanitizeOutput(in []byte) []byte { case decoder.RawFlagMesgDef: // the encoder used for test always use localMesgNum 0 b[0] = proto.MesgDefinitionMask + b[1] = 0 // Reserved case decoder.RawFlagMesgData: // the encoder used for test always use localMesgNum 0 b[0] = 0 diff --git a/internal/cmd/benchfit/go.mod b/internal/cmd/benchfit/go.mod index 8bfc7de7..d1da9d2d 100644 --- a/internal/cmd/benchfit/go.mod +++ b/internal/cmd/benchfit/go.mod @@ -17,9 +17,9 @@ require ( golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect golang.org/x/mod v0.17.0 // indirect - golang.org/x/sync v0.7.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/text v0.18.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect honnef.co/go/tools v0.4.2 // indirect mvdan.cc/gofumpt v0.4.0 // indirect diff --git a/internal/cmd/benchfit/go.sum b/internal/cmd/benchfit/go.sum index 9dfc5f19..0ea21db7 100644 --- a/internal/cmd/benchfit/go.sum +++ b/internal/cmd/benchfit/go.sum @@ -69,8 +69,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -97,8 +97,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= diff --git a/internal/cmd/fitgen/factory/factory.tmpl b/internal/cmd/fitgen/factory/factory.tmpl index a87b8f27..4c8f9ab1 100644 --- a/internal/cmd/fitgen/factory/factory.tmpl +++ b/internal/cmd/fitgen/factory/factory.tmpl @@ -120,11 +120,6 @@ func (f *Factory) CreateMesg(num typedef.MesgNum) proto.Message { return mesg } -{{ template "create_mesg_only_doc" -}} -func (f *Factory) CreateMesgOnly(num typedef.MesgNum) proto.Message { - return proto.Message{Num: num} -} - {{ template "create_field_doc" -}} func (f *Factory) CreateField(mesgNum typedef.MesgNum, num byte) proto.Field { if mesgNum < {{ .LenMesgs }} && mesgs[mesgNum] != nil { @@ -191,11 +186,6 @@ func CreateMesg(num typedef.MesgNum) proto.Message { return std.CreateMesg(num) } -{{- template "create_mesg_only_doc" -}} -func CreateMesgOnly(num typedef.MesgNum) proto.Message{ - return std.CreateMesgOnly(num) -} - {{ template "create_field_doc" -}} func CreateField(mesgNum typedef.MesgNum, num byte) proto.Field { return std.CreateField(mesgNum, num) @@ -213,22 +203,18 @@ func RegisterMesg(mesg proto.Message) error { // CreateMesg creates new message based on defined messages in the factory. If not found, it returns proto.Message{Num: num}. // // This will create a shallow copy of the Fields, so changing any value declared in Field's FieldBase is prohibited -// (except in case of unknown field). If you want a deep copy of the mesg, use mesg.Clone(). +// (except in case of unknown field). // // NOTE: This method is not used by either the Decoder or the Encoder, and the data will only be populated once upon the first invocation. // Unless you need most of the returned fields, it's recommended create an empty proto.Message{Num: num} then fill only the necessary fields // using CreateField method. {{ end }} -{{ define "create_mesg_only_doc" }} -// CreateMesgOnly is a syntax sugar for creating proto.Message{Num: num}. -{{ end }} - {{ define "create_field_doc" }} // CreateField creates new field based on defined messages in the factory. If not found, it returns new field with "unknown" name. // // The returned field contains a pointer reference to FieldBase defined in the factory, so changing any value -// declared in FieldBase is prohibited (except in the case of unknown field). If you want a deep copy, use field.Clone(). +// declared in FieldBase is prohibited (except in the case of unknown field). {{ end }} {{ define "register_mesg_doc" }} diff --git a/internal/cmd/testgen/big_activity.go b/internal/cmd/testgen/big_activity.go index 7475b938..212caccc 100644 --- a/internal/cmd/testgen/big_activity.go +++ b/internal/cmd/testgen/big_activity.go @@ -29,63 +29,65 @@ func createBigActivityFile(ctx context.Context) error { defer f.Close() now := datetime.ToTime(uint32(1062766519)) - fit := new(proto.FIT) - fit.Messages = make([]proto.Message, 0, RecordSize) - fit.Messages = append(fit.Messages, - factory.CreateMesg(mesgnum.FileId).WithFieldValues(map[byte]any{ - fieldnum.FileIdType: typedef.FileActivity, - fieldnum.FileIdManufacturer: typedef.ManufacturerBryton, - fieldnum.FileIdProductName: "1901", - fieldnum.FileIdNumber: uint16(0), - fieldnum.FileIdTimeCreated: datetime.ToUint32(now), - fieldnum.FileIdSerialNumber: uint32(5122), - }), - factory.CreateMesg(mesgnum.Sport).WithFieldValues(map[byte]any{ - fieldnum.SportSport: typedef.SportCycling, - fieldnum.SportSubSport: typedef.SubSportRoad, - }), - factory.CreateMesg(mesgnum.Activity).WithFieldValues(map[byte]any{ - fieldnum.ActivityTimestamp: datetime.ToUint32(now), - fieldnum.ActivityType: typedef.ActivityTypeCycling, - fieldnum.ActivityTotalTimerTime: uint32(30877.0 * 1000), - fieldnum.ActivityNumSessions: uint16(1), - fieldnum.ActivityEvent: typedef.EventActivity, - }), - factory.CreateMesg(mesgnum.Session).WithFieldValues(map[byte]any{ - fieldnum.SessionTimestamp: datetime.ToUint32(now), - fieldnum.SessionStartTime: datetime.ToUint32(now), - fieldnum.SessionTotalElapsedTime: uint32(30877.0 * 1000), - fieldnum.SessionTotalDistance: uint32(32172.05 * 100), - fieldnum.SessionSport: typedef.SportCycling, - fieldnum.SessionSubSport: typedef.SubSportRoad, - fieldnum.SessionTotalMovingTime: uint32(22079.0 * 1000), - fieldnum.SessionTotalCalories: uint16(12824), - fieldnum.SessionAvgSpeed: uint16(5.98 * 1000), - fieldnum.SessionMaxSpeed: uint16(13.05 * 1000), - fieldnum.SessionMaxAltitude: uint16((504.0 + 500) * 5), - fieldnum.SessionTotalAscent: uint16(909), - fieldnum.SessionTotalDescent: uint16(901), - fieldnum.SessionSwcLat: int32(0), - fieldnum.SessionSwcLong: int32(0), - fieldnum.SessionNecLat: int32(0), - fieldnum.SessionNecLong: int32(0), - }), - ) + fit := &proto.FIT{ + Messages: []proto.Message{ + {Num: mesgnum.FileId, Fields: []proto.Field{ + factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(typedef.FileActivity), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdManufacturer).WithValue(typedef.ManufacturerBryton), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdProduct).WithValue(uint16(1901)), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdProductName).WithValue("Rider 420"), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdNumber).WithValue(uint16(0)), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), + factory.CreateField(mesgnum.FileId, fieldnum.FileIdSerialNumber).WithValue(uint32(5122)), + }}, + {Num: mesgnum.Sport, Fields: []proto.Field{ + factory.CreateField(mesgnum.Sport, fieldnum.SportSport).WithValue(typedef.SportCycling), + factory.CreateField(mesgnum.Sport, fieldnum.SportSubSport).WithValue(typedef.SubSportRoad), + }}, + {Num: mesgnum.Activity, Fields: []proto.Field{ + factory.CreateField(mesgnum.Activity, fieldnum.ActivityTimestamp).WithValue(datetime.ToUint32(now)), + factory.CreateField(mesgnum.Activity, fieldnum.ActivityType).WithValue(typedef.ActivityTypeCycling), + factory.CreateField(mesgnum.Activity, fieldnum.ActivityTotalTimerTime).WithValue(uint32(30877.0 * 1000)), + factory.CreateField(mesgnum.Activity, fieldnum.ActivityNumSessions).WithValue(uint16(1)), + factory.CreateField(mesgnum.Activity, fieldnum.ActivityEvent).WithValue(typedef.EventActivity), + }}, + {Num: mesgnum.Session, Fields: []proto.Field{ + factory.CreateField(mesgnum.Session, fieldnum.SessionTimestamp).WithValue(datetime.ToUint32(now)), + factory.CreateField(mesgnum.Session, fieldnum.SessionStartTime).WithValue(datetime.ToUint32(now)), + factory.CreateField(mesgnum.Session, fieldnum.SessionTotalElapsedTime).WithValue(uint32(30877.0 * 1000)), + factory.CreateField(mesgnum.Session, fieldnum.SessionTotalDistance).WithValue(uint32(32172.05 * 100)), + factory.CreateField(mesgnum.Session, fieldnum.SessionSport).WithValue(typedef.SportCycling), + factory.CreateField(mesgnum.Session, fieldnum.SessionSubSport).WithValue(typedef.SubSportRoad), + factory.CreateField(mesgnum.Session, fieldnum.SessionTotalMovingTime).WithValue(uint32(22079.0 * 1000)), + factory.CreateField(mesgnum.Session, fieldnum.SessionTotalCalories).WithValue(uint16(12824)), + factory.CreateField(mesgnum.Session, fieldnum.SessionAvgSpeed).WithValue(uint16(5.98 * 1000)), + factory.CreateField(mesgnum.Session, fieldnum.SessionMaxSpeed).WithValue(uint16(13.05 * 1000)), + factory.CreateField(mesgnum.Session, fieldnum.SessionMaxAltitude).WithValue(uint16((504.0 + 500) * 5)), + factory.CreateField(mesgnum.Session, fieldnum.SessionTotalAscent).WithValue(uint16(909)), + factory.CreateField(mesgnum.Session, fieldnum.SessionTotalDescent).WithValue(uint16(901)), + factory.CreateField(mesgnum.Session, fieldnum.SessionSwcLat).WithValue(int32(0)), + factory.CreateField(mesgnum.Session, fieldnum.SessionSwcLong).WithValue(int32(0)), + factory.CreateField(mesgnum.Session, fieldnum.SessionNecLat).WithValue(int32(0)), + factory.CreateField(mesgnum.Session, fieldnum.SessionNecLong).WithValue(int32(0)), + }}, + }, + } n := RecordSize - len(fit.Messages) for i := 0; i < n; i++ { now = now.Add(time.Second) // only time is moving forward - fit.Messages = append(fit.Messages, factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.RecordTimestamp: datetime.ToUint32(now), - fieldnum.RecordPositionLat: int32(-90481372), - fieldnum.RecordPositionLong: int32(1323227263), - fieldnum.RecordSpeed: uint16(8.33 * 1000), - fieldnum.RecordDistance: uint32(405.81 * 100), - fieldnum.RecordHeartRate: uint8(110), - fieldnum.RecordCadence: uint8(85), - fieldnum.RecordAltitude: uint16((166.0 + 500.0) * 5.0), - fieldnum.RecordTemperature: int8(32), - })) + record := proto.Message{Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now)), + factory.CreateField(mesgnum.Record, fieldnum.RecordPositionLat).WithValue(int32(-90481372)), + factory.CreateField(mesgnum.Record, fieldnum.RecordPositionLong).WithValue(int32(1323227263)), + factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed).WithValue(uint16(8.33 * 1000)), + factory.CreateField(mesgnum.Record, fieldnum.RecordDistance).WithValue(uint32(405.81 * 100)), + factory.CreateField(mesgnum.Record, fieldnum.RecordHeartRate).WithValue(uint8(110)), + factory.CreateField(mesgnum.Record, fieldnum.RecordCadence).WithValue(uint8(85)), + factory.CreateField(mesgnum.Record, fieldnum.RecordAltitude).WithValue(uint16((166.0 + 500.0) * 5.0)), + factory.CreateField(mesgnum.Record, fieldnum.RecordTemperature).WithValue(int8(32)), + }} + fit.Messages = append(fit.Messages, record) } enc := encoder.New(f) diff --git a/internal/cmd/testgen/main.go b/internal/cmd/testgen/main.go index 6ba4b28a..641615d8 100644 --- a/internal/cmd/testgen/main.go +++ b/internal/cmd/testgen/main.go @@ -61,26 +61,23 @@ func createValidFitOnlyContainFileId(ctx context.Context) error { type Uint16 uint16 now := datetime.ToTime(uint32(1062766519)) - fit := new(proto.FIT).WithMessages( - factory.CreateMesgOnly(typedef.MesgNumFileId).WithFields( + fit := &proto.FIT{Messages: []proto.Message{ + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(typedef.FileActivity), factory.CreateField(mesgnum.FileId, fieldnum.FileIdProductName).WithValue("something ss"), factory.CreateField(mesgnum.FileId, fieldnum.FileIdManufacturer).WithValue(typedef.ManufacturerBryton), factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), - ), - factory.CreateMesgOnly(typedef.MesgNumActivity).WithFields( - factory.CreateField(typedef.MesgNumActivity, fieldnum.ActivityTimestamp).WithValue(datetime.ToUint32(now)), - ), - factory.CreateMesg(typedef.MesgNumRecord).WithFieldValues(map[byte]any{ - fieldnum.RecordTimestamp: datetime.ToUint32(now), - fieldnum.RecordHeartRate: uint8(112), - fieldnum.RecordCadence: uint8(80), - // fieldnum.RecordAltitude: float64(150), // input scaled value - fieldnum.RecordAltitude: Uint16((150 + 500) * 5), // input scaled value - // fieldnum.RecordAltitude: uint16((150 + 500) * 5), // input scaled value - // fieldnum.RecordAltitude: "something", // input scaled value - }), - ) + }}, + {Num: mesgnum.Activity, Fields: []proto.Field{ + factory.CreateField(mesgnum.Activity, fieldnum.ActivityTimestamp).WithValue(datetime.ToUint32(now)), + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now)), + factory.CreateField(mesgnum.Record, fieldnum.RecordHeartRate).WithValue(uint8(112)), + factory.CreateField(mesgnum.Record, fieldnum.RecordCadence).WithValue(uint8(80)), + factory.CreateField(mesgnum.Record, fieldnum.RecordAltitude).WithValue(Uint16((150 + 500) * 5)), + }}, + }} enc := encoder.New(f) if err := enc.EncodeWithContext(ctx, fit); err != nil { diff --git a/internal/kit/kit.go b/internal/kit/kit.go deleted file mode 100644 index 1b56f1e1..00000000 --- a/internal/kit/kit.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2023 The FIT SDK for Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package kit - -// Ptr returns new pointer of v -func Ptr[T any](v T) *T { return &v } diff --git a/internal/kit/kit_test.go b/internal/kit/kit_test.go deleted file mode 100644 index 0fe4a818..00000000 --- a/internal/kit/kit_test.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2023 The FIT SDK for Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package kit_test - -import ( - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/muktihari/fit/internal/kit" -) - -func TestPtr(t *testing.T) { - val := float64(10) - ptr := kit.Ptr(val) - - if diff := cmp.Diff(val, *ptr); diff != "" { - t.Fatal(diff) - } -} diff --git a/profile/basetype/basetype.go b/profile/basetype/basetype.go index 90d1d964..14000ec3 100644 --- a/profile/basetype/basetype.go +++ b/profile/basetype/basetype.go @@ -9,13 +9,26 @@ import ( "strconv" ) -// BaseTypeNumMask used to get the index/order of the constants (start from 0, Enum). -// Example: (Sint16 & BaseTypeNumMask) -> 3. -const BaseTypeNumMask = 0x1F - // BaseType is the base of all types used in FIT. +// +// Bits layout: +// - 7 : Endian Ability (0: for single byte data; 1: if base type has endianness) +// - 5–6: Reserved +// - 0–4: Base Type Number type BaseType byte +const ( + // BaseTypeNumMask is used to get the Base Type Number. + // + // Example: (Sint16 & BaseTypeNumMask) -> 3. + BaseTypeNumMask = 0b00011111 + + // EndianAbilityMask is used to get the Endian Ability. + // + // Example: (Sint32 & EndianAbilityMask == EndianAbilityMask) -> true + EndianAbilityMask = 0b10000000 +) + const ( Enum BaseType = 0x00 Sint8 BaseType = 0x01 // 2’s complement format @@ -97,29 +110,6 @@ func FromString(s string) BaseType { return 255 } -// List returns all constants. -func List() []BaseType { - return []BaseType{ - Enum, - Sint8, - Uint8, - Sint16, - Uint16, - Sint32, - Uint32, - String, - Float32, - Float64, - Uint8z, - Uint16z, - Uint32z, - Byte, - Sint64, - Uint64, - Uint64z, - } -} - // String returns string representation of t. func (t BaseType) String() string { switch t { @@ -186,6 +176,31 @@ func (t BaseType) Size() byte { return sizes[t] // PERF: use array to optimize speed since this method is frequently used. } +// Valid checks whether BaseType is valid or not. +func (t BaseType) Valid() bool { + switch t { + case Enum, + Sint8, + Uint8, + Sint16, + Uint16, + Sint32, + Uint32, + String, + Float32, + Float64, + Uint8z, + Uint16z, + Uint32z, + Byte, + Sint64, + Uint64, + Uint64z: + return true + } + return false +} + // GoType returns go equivalent type in string. func (t BaseType) GoType() string { switch t { @@ -219,63 +234,6 @@ func (t BaseType) GoType() string { return "invalid(" + strconv.Itoa(int(t)) + ")" } -// EndianAbility return whether t have endianness. -func (t BaseType) EndianAbility() byte { - switch t { - case Enum, Byte, Uint8, Uint8z: - return 0 - case Sint8: - return 0 - case Sint16: - return 1 - case Uint16, Uint16z: - return 1 - case Sint32: - return 1 - case Uint32, Uint32z: - return 1 - case String: - return 0 - case Float32: - return 1 - case Float64: - return 1 - case Sint64: - return 1 - case Uint64, Uint64z: - return 1 - } - return 0 -} - -func (t BaseType) IsInteger() bool { - switch t { - case Enum, Byte, Uint8, Uint8z: - return true - case Sint8: - return true - case Sint16: - return true - case Uint16, Uint16z: - return true - case Sint32: - return true - case Uint32, Uint32z: - return true - case String: - return false - case Float32: - return false - case Float64: - return false - case Sint64: - return true - case Uint64, Uint64z: - return true - } - return false -} - // Invalid returns invalid value of t. e.g. Byte is 255 (its highest value). func (t BaseType) Invalid() any { switch t { @@ -317,10 +275,10 @@ func (t BaseType) Invalid() any { return "invalid" } -// Valid checks whether BaseType is valid or not. -func (t BaseType) Valid() bool { - switch t { - case Enum, +// List returns all constants. +func List() []BaseType { + return []BaseType{ + Enum, Sint8, Uint8, Sint16, @@ -336,8 +294,6 @@ func (t BaseType) Valid() bool { Byte, Sint64, Uint64, - Uint64z: - return true + Uint64z, } - return false } diff --git a/profile/basetype/basetype_test.go b/profile/basetype/basetype_test.go index e44d3d38..1c9ac494 100644 --- a/profile/basetype/basetype_test.go +++ b/profile/basetype/basetype_test.go @@ -156,74 +156,6 @@ func TestGoType(t *testing.T) { } } -func TestEndianAbility(t *testing.T) { - tt := []struct { - baseType basetype.BaseType - endianess byte - }{ - {baseType: basetype.Sint8, endianess: 0}, - {baseType: basetype.Enum, endianess: 0}, - {baseType: basetype.Byte, endianess: 0}, - {baseType: basetype.Uint8, endianess: 0}, - {baseType: basetype.Uint8z, endianess: 0}, - {baseType: basetype.String, endianess: 0}, - {baseType: basetype.Sint16, endianess: 1}, - {baseType: basetype.Uint16, endianess: 1}, - {baseType: basetype.Uint16z, endianess: 1}, - {baseType: basetype.Sint32, endianess: 1}, - {baseType: basetype.Uint32, endianess: 1}, - {baseType: basetype.Uint32z, endianess: 1}, - {baseType: basetype.Float32, endianess: 1}, - {baseType: basetype.Sint64, endianess: 1}, - {baseType: basetype.Uint64, endianess: 1}, - {baseType: basetype.Uint64z, endianess: 1}, - {baseType: basetype.Float64, endianess: 1}, - {baseType: 255, endianess: 0}, - } - for _, tc := range tt { - t.Run(tc.baseType.String(), func(t *testing.T) { - endianess := tc.baseType.EndianAbility() - if endianess != tc.endianess { - t.Fatalf("expected: %d, got: %d", tc.endianess, endianess) - } - }) - } -} - -func TestIsInteger(t *testing.T) { - tt := []struct { - baseType basetype.BaseType - isInteger bool - }{ - {baseType: basetype.Sint8, isInteger: true}, - {baseType: basetype.Enum, isInteger: true}, - {baseType: basetype.Byte, isInteger: true}, - {baseType: basetype.Uint8, isInteger: true}, - {baseType: basetype.Uint8z, isInteger: true}, - {baseType: basetype.String, isInteger: false}, - {baseType: basetype.Sint16, isInteger: true}, - {baseType: basetype.Uint16, isInteger: true}, - {baseType: basetype.Uint16z, isInteger: true}, - {baseType: basetype.Sint32, isInteger: true}, - {baseType: basetype.Uint32, isInteger: true}, - {baseType: basetype.Uint32z, isInteger: true}, - {baseType: basetype.Float32, isInteger: false}, - {baseType: basetype.Sint64, isInteger: true}, - {baseType: basetype.Uint64, isInteger: true}, - {baseType: basetype.Uint64z, isInteger: true}, - {baseType: basetype.Float64, isInteger: false}, - {baseType: 255, isInteger: false}, - } - for _, tc := range tt { - t.Run(tc.baseType.String(), func(t *testing.T) { - isInteger := tc.baseType.IsInteger() - if isInteger != tc.isInteger { - t.Fatalf("expected: %t, got: %t", tc.isInteger, isInteger) - } - }) - } -} - func TestInvalid(t *testing.T) { tt := []struct { baseType basetype.BaseType diff --git a/profile/filedef/activity_summary_test.go b/profile/filedef/activity_summary_test.go index e98b47d6..77d61b47 100644 --- a/profile/filedef/activity_summary_test.go +++ b/profile/filedef/activity_summary_test.go @@ -19,32 +19,32 @@ import ( func newActivitySummaryMessageForTest(now time.Time) []proto.Message { return []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(uint8(typedef.FileActivitySummary)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), - ), - factory.CreateMesgOnly(mesgnum.DeveloperDataId).WithFields( + }}, + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.Lap).WithFields( + }}, + {Num: mesgnum.Lap, Fields: []proto.Field{ factory.CreateField(mesgnum.Lap, fieldnum.LapTimestamp).WithValue(datetime.ToUint32(now)), - ), - factory.CreateMesgOnly(mesgnum.Session).WithFields( + }}, + {Num: mesgnum.Session, Fields: []proto.Field{ factory.CreateField(mesgnum.Session, fieldnum.SessionTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.Activity).WithFields( + }}, + {Num: mesgnum.Activity, Fields: []proto.Field{ factory.CreateField(mesgnum.Activity, fieldnum.ActivityTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, // Unrelated messages - factory.CreateMesgOnly(mesgnum.BarometerData).WithFields( + {Num: mesgnum.BarometerData, Fields: []proto.Field{ factory.CreateField(mesgnum.BarometerData, fieldnum.BarometerDataTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.CoursePoint).WithFields( + }}, + {Num: mesgnum.CoursePoint, Fields: []proto.Field{ factory.CreateField(mesgnum.CoursePoint, fieldnum.CoursePointTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, } } diff --git a/profile/filedef/activity_test.go b/profile/filedef/activity_test.go index bc48b825..17d5ed72 100644 --- a/profile/filedef/activity_test.go +++ b/profile/filedef/activity_test.go @@ -60,93 +60,93 @@ func newActivityMessageForTest(now time.Time) []proto.Message { func newActivityMessagesWithExpectedOrder(now time.Time) (mesgs []proto.Message, ordered []proto.Message) { mesgs = []proto.Message{ - 0: factory.CreateMesgOnly(mesgnum.FileId).WithFields( + 0: {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(uint8(typedef.FileActivity)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), - ), - 1: factory.CreateMesgOnly(mesgnum.DeveloperDataId).WithFields( + }}, + 1: {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), - ), - 2: factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + }}, + 2: {Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue([]string{"Heart Rate"}), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordHeartRate)), factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint8)), - ), - 3: factory.CreateMesgOnly(mesgnum.DeviceInfo).WithFields( + }}, + 3: {Num: mesgnum.DeviceInfo, Fields: []proto.Field{ factory.CreateField(mesgnum.DeviceInfo, fieldnum.DeviceInfoManufacturer).WithValue(uint16(typedef.ManufacturerGarmin)), - ), - 4: factory.CreateMesgOnly(mesgnum.UserProfile).WithFields( + }}, + 4: {Num: mesgnum.UserProfile, Fields: []proto.Field{ factory.CreateField(mesgnum.UserProfile, fieldnum.UserProfileFriendlyName).WithValue("Mary Jane"), factory.CreateField(mesgnum.UserProfile, fieldnum.UserProfileAge).WithValue(uint8(21)), - ), - 5: factory.CreateMesgOnly(mesgnum.Event).WithFields( + }}, + 5: {Num: mesgnum.Event, Fields: []proto.Field{ factory.CreateField(mesgnum.Event, fieldnum.EventTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), factory.CreateField(mesgnum.Event, fieldnum.EventEvent).WithValue(uint8(typedef.EventActivity)), factory.CreateField(mesgnum.Event, fieldnum.EventEventType).WithValue(uint8(typedef.EventTypeStart)), - ), - 6: factory.CreateMesgOnly(mesgnum.Record).WithFields( + }}, + 6: {Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - 7: factory.CreateMesgOnly(mesgnum.Record).WithFields( + }}, + 7: {Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - 8: factory.CreateMesgOnly(mesgnum.Event).WithFields( + }}, + 8: {Num: mesgnum.Event, Fields: []proto.Field{ factory.CreateField(mesgnum.Event, fieldnum.EventTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - 9: factory.CreateMesgOnly(mesgnum.Record).WithFields( + }}, + 9: {Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - 10: factory.CreateMesgOnly(mesgnum.Event).WithFields( + }}, + 10: {Num: mesgnum.Event, Fields: []proto.Field{ // Intentionally using same timestamp as last message. factory.CreateField(mesgnum.Event, fieldnum.EventTimestamp).WithValue(datetime.ToUint32(now)), - ), - 11: factory.CreateMesgOnly(mesgnum.Lap).WithFields( + }}, + 11: {Num: mesgnum.Lap, Fields: []proto.Field{ factory.CreateField(mesgnum.Lap, fieldnum.LapTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - 12: factory.CreateMesgOnly(mesgnum.Session).WithFields( + }}, + 12: {Num: mesgnum.Session, Fields: []proto.Field{ factory.CreateField(mesgnum.Session, fieldnum.SessionTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - 13: factory.CreateMesgOnly(mesgnum.Activity).WithFields( + }}, + 13: {Num: mesgnum.Activity, Fields: []proto.Field{ factory.CreateField(mesgnum.Activity, fieldnum.ActivityTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, // Unordered optional Messages - 14: factory.CreateMesgOnly(mesgnum.Length).WithFields( + 14: {Num: mesgnum.Length, Fields: []proto.Field{ factory.CreateField(mesgnum.Length, fieldnum.LengthAvgSpeed).WithValue(uint16(1000)), - ), - 15: factory.CreateMesgOnly(mesgnum.SegmentLap).WithFields( + }}, + 15: {Num: mesgnum.SegmentLap, Fields: []proto.Field{ factory.CreateField(mesgnum.SegmentLap, fieldnum.SegmentLapAvgCadence).WithValue(uint8(100)), - ), - 16: factory.CreateMesgOnly(mesgnum.ZonesTarget).WithFields( + }}, + 16: {Num: mesgnum.ZonesTarget, Fields: []proto.Field{ factory.CreateField(mesgnum.ZonesTarget, fieldnum.ZonesTargetMaxHeartRate).WithValue(uint8(190)), - ), - 17: factory.CreateMesgOnly(mesgnum.Workout).WithFields( + }}, + 17: {Num: mesgnum.Workout, Fields: []proto.Field{ factory.CreateField(mesgnum.Workout, fieldnum.WorkoutSport).WithValue(uint8(typedef.SportCycling)), - ), - 18: factory.CreateMesgOnly(mesgnum.WorkoutStep).WithFields( + }}, + 18: {Num: mesgnum.WorkoutStep, Fields: []proto.Field{ factory.CreateField(mesgnum.WorkoutStep, fieldnum.WorkoutStepIntensity).WithValue(uint8(typedef.IntensityActive)), - ), - 19: factory.CreateMesgOnly(mesgnum.Hr).WithFields( + }}, + 19: {Num: mesgnum.Hr, Fields: []proto.Field{ factory.CreateField(mesgnum.Hr, fieldnum.HrTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - 20: factory.CreateMesgOnly(mesgnum.Hrv).WithFields( + }}, + 20: {Num: mesgnum.Hrv, Fields: []proto.Field{ factory.CreateField(mesgnum.Hrv, fieldnum.HrvTime).WithValue([]uint16{uint16(1000)}), - ), + }}, // Unrelated messages - 21: factory.CreateMesgOnly(mesgnum.BarometerData).WithFields( + 21: {Num: mesgnum.BarometerData, Fields: []proto.Field{ factory.CreateField(mesgnum.BarometerData, fieldnum.BarometerDataTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, // Special case: // 1. CoursePoint's Timestamp Num is 1 // 2. Set's Timestamp Num is 254 - 22: factory.CreateMesgOnly(mesgnum.CoursePoint).WithFields( + 22: {Num: mesgnum.CoursePoint, Fields: []proto.Field{ factory.CreateField(mesgnum.CoursePoint, fieldnum.CoursePointTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - 23: factory.CreateMesgOnly(mesgnum.Set).WithFields( + }}, + 23: {Num: mesgnum.Set, Fields: []proto.Field{ factory.CreateField(mesgnum.Set, fieldnum.SetTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, } ordered = []proto.Message{ diff --git a/profile/filedef/blood_pressure_test.go b/profile/filedef/blood_pressure_test.go index 59ebfba8..f5e359e9 100644 --- a/profile/filedef/blood_pressure_test.go +++ b/profile/filedef/blood_pressure_test.go @@ -19,35 +19,35 @@ import ( func newBloodPressureMessageForTest(now time.Time) []proto.Message { return []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(uint8(typedef.FileBloodPressure)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), - ), - factory.CreateMesgOnly(mesgnum.DeveloperDataId).WithFields( + }}, + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.UserProfile).WithFields( + }}, + {Num: mesgnum.UserProfile, Fields: []proto.Field{ factory.CreateField(mesgnum.UserProfile, fieldnum.UserProfileAge).WithValue(uint8(27)), - ), - factory.CreateMesgOnly(mesgnum.BloodPressure).WithFields( + }}, + {Num: mesgnum.BloodPressure, Fields: []proto.Field{ factory.CreateField(mesgnum.BloodPressure, fieldnum.BloodPressureTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), factory.CreateField(mesgnum.BloodPressure, fieldnum.BloodPressureSystolicPressure).WithValue(uint16(110)), factory.CreateField(mesgnum.BloodPressure, fieldnum.BloodPressureDiastolicPressure).WithValue(uint16(80)), factory.CreateField(mesgnum.BloodPressure, fieldnum.BloodPressureHeartRate).WithValue(uint8(100)), - ), - factory.CreateMesgOnly(mesgnum.DeviceInfo).WithFields( + }}, + {Num: mesgnum.DeviceInfo, Fields: []proto.Field{ factory.CreateField(mesgnum.DeviceInfo, fieldnum.DeviceInfoTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, // Unrelated messages - factory.CreateMesgOnly(mesgnum.BarometerData).WithFields( + {Num: mesgnum.BarometerData, Fields: []proto.Field{ factory.CreateField(mesgnum.BarometerData, fieldnum.BarometerDataTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.CoursePoint).WithFields( + }}, + {Num: mesgnum.CoursePoint, Fields: []proto.Field{ factory.CreateField(mesgnum.CoursePoint, fieldnum.CoursePointTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, } } diff --git a/profile/filedef/course_test.go b/profile/filedef/course_test.go index 9b7fe5c7..480f8df0 100644 --- a/profile/filedef/course_test.go +++ b/profile/filedef/course_test.go @@ -19,50 +19,50 @@ import ( func newCourseMessageForTest(now time.Time) []proto.Message { return []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(uint8(typedef.FileCourse)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), - ), - factory.CreateMesgOnly(mesgnum.DeveloperDataId).WithFields( + }}, + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.Course).WithFields( + }}, + {Num: mesgnum.Course, Fields: []proto.Field{ factory.CreateField(mesgnum.Course, fieldnum.CourseSport).WithValue(uint8(typedef.SportRunning)), - ), - factory.CreateMesgOnly(mesgnum.Event).WithFields( + }}, + {Num: mesgnum.Event, Fields: []proto.Field{ factory.CreateField(mesgnum.Event, fieldnum.EventTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), factory.CreateField(mesgnum.Event, fieldnum.EventEvent).WithValue(uint8(typedef.EventActivity)), factory.CreateField(mesgnum.Event, fieldnum.EventEventType).WithValue(uint8(typedef.EventTypeStart)), - ), - factory.CreateMesgOnly(mesgnum.Record).WithFields( + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.Record).WithFields( + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.Event).WithFields( + }}, + {Num: mesgnum.Event, Fields: []proto.Field{ factory.CreateField(mesgnum.Event, fieldnum.EventTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.Record).WithFields( + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.Event).WithFields( + }}, + {Num: mesgnum.Event, Fields: []proto.Field{ factory.CreateField(mesgnum.Event, fieldnum.EventTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.Lap).WithFields( + }}, + {Num: mesgnum.Lap, Fields: []proto.Field{ factory.CreateField(mesgnum.Lap, fieldnum.LapTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, // Unordered optional Messages - factory.CreateMesgOnly(mesgnum.CoursePoint).WithFields( + {Num: mesgnum.CoursePoint, Fields: []proto.Field{ factory.CreateField(mesgnum.CoursePoint, fieldnum.CoursePointTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, // Unrelated messages - factory.CreateMesgOnly(mesgnum.BarometerData).WithFields( + {Num: mesgnum.BarometerData, Fields: []proto.Field{ factory.CreateField(mesgnum.BarometerData, fieldnum.BarometerDataTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, } } diff --git a/profile/filedef/device_test.go b/profile/filedef/device_test.go index a3182126..a33ddb5d 100644 --- a/profile/filedef/device_test.go +++ b/profile/filedef/device_test.go @@ -19,42 +19,42 @@ import ( func newDeviceMessageForTest(now time.Time) []proto.Message { return []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(uint8(typedef.FileDevice)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), - ), - factory.CreateMesgOnly(mesgnum.DeveloperDataId).WithFields( + }}, + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.Software).WithFields( + }}, + {Num: mesgnum.Software, Fields: []proto.Field{ factory.CreateField(mesgnum.Software, fieldnum.SoftwareMessageIndex).WithValue(uint16(typedef.MessageIndexReserved)), - ), - factory.CreateMesgOnly(mesgnum.Capabilities).WithFields( + }}, + {Num: mesgnum.Capabilities, Fields: []proto.Field{ factory.CreateField(mesgnum.Capabilities, fieldnum.CapabilitiesSports).WithValue([]uint8{ uint8(typedef.SportBits0Basketball), uint8(typedef.SportBits1AmericanFootball), uint8(typedef.SportBits2Paddling), }), - ), - factory.CreateMesgOnly(mesgnum.FileCapabilities).WithFields( + }}, + {Num: mesgnum.FileCapabilities, Fields: []proto.Field{ factory.CreateField(mesgnum.FileCapabilities, fieldnum.FileCapabilitiesType).WithValue(uint8(typedef.FileActivity)), - ), - factory.CreateMesgOnly(mesgnum.MesgCapabilities).WithFields( + }}, + {Num: mesgnum.MesgCapabilities, Fields: []proto.Field{ factory.CreateField(mesgnum.MesgCapabilities, fieldnum.MesgCapabilitiesFile).WithValue(uint8(typedef.FileActivity)), - ), - factory.CreateMesgOnly(mesgnum.FieldCapabilities).WithFields( + }}, + {Num: mesgnum.FieldCapabilities, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldCapabilities, fieldnum.FieldCapabilitiesFile).WithValue(uint8(typedef.FileActivity)), - ), + }}, // Unrelated messages - factory.CreateMesgOnly(mesgnum.BarometerData).WithFields( + {Num: mesgnum.BarometerData, Fields: []proto.Field{ factory.CreateField(mesgnum.BarometerData, fieldnum.BarometerDataTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.CoursePoint).WithFields( + }}, + {Num: mesgnum.CoursePoint, Fields: []proto.Field{ factory.CreateField(mesgnum.CoursePoint, fieldnum.CoursePointTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, } } diff --git a/profile/filedef/filedef_test.go b/profile/filedef/filedef_test.go index eb4f1fcb..47d7c5ec 100644 --- a/profile/filedef/filedef_test.go +++ b/profile/filedef/filedef_test.go @@ -42,36 +42,36 @@ func TestSortMessagesByTimestamp(t *testing.T) { // 1. CoursePoint's Timestamp Num is 1 // 2. Set's Timestamp Num is 254 messages := []proto.Message{ - 0: factory.CreateMesgOnly(mesgnum.FileId).WithFields( + 0: {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdManufacturer).WithValue(typedef.ManufacturerDevelopment.Uint16()), - ), - 1: factory.CreateMesgOnly(mesgnum.Record).WithFields( + }}, + 1: {Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now)), - ), - 2: factory.CreateMesgOnly(mesgnum.Record).WithFields( + }}, + 2: {Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now.Add(2 * time.Second))), - ), - 3: factory.CreateMesgOnly(mesgnum.Record).WithFields( + }}, + 3: {Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now.Add(1 * time.Second))), - ), - 4: factory.CreateMesgOnly(mesgnum.Event).WithFields( + }}, + 4: {Num: mesgnum.Event, Fields: []proto.Field{ factory.CreateField(mesgnum.Event, fieldnum.EventTimestamp).WithValue(datetime.ToUint32(now.Add(2 * time.Second))), - ), - 5: factory.CreateMesgOnly(mesgnum.Session).WithFields( + }}, + 5: {Num: mesgnum.Session, Fields: []proto.Field{ factory.CreateField(mesgnum.Session, fieldnum.SessionTimestamp).WithValue(datetime.ToUint32(now.Add(2 * time.Second))), - ), - 6: factory.CreateMesgOnly(mesgnum.UserProfile).WithFields( + }}, + 6: {Num: mesgnum.UserProfile, Fields: []proto.Field{ factory.CreateField(mesgnum.UserProfile, fieldnum.UserProfileFriendlyName).WithValue("muktihari"), - ), - 7: factory.CreateMesgOnly(mesgnum.Set).WithFields( + }}, + 7: {Num: mesgnum.Set, Fields: []proto.Field{ factory.CreateField(mesgnum.Set, fieldnum.SetTimestamp).WithValue(datetime.ToUint32(now.Add(4 * time.Second))), - ), - 8: factory.CreateMesgOnly(mesgnum.Record).WithFields( + }}, + 8: {Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(now.Add(2 * time.Second))), - ), - 9: factory.CreateMesgOnly(mesgnum.CoursePoint).WithFields( + }}, + 9: {Num: mesgnum.CoursePoint, Fields: []proto.Field{ factory.CreateField(mesgnum.CoursePoint, fieldnum.CoursePointTimestamp).WithValue(datetime.ToUint32(now.Add(3 * time.Second))), - ), + }}, } expected := []proto.Message{ diff --git a/profile/filedef/goals_test.go b/profile/filedef/goals_test.go index b7af4d44..86985075 100644 --- a/profile/filedef/goals_test.go +++ b/profile/filedef/goals_test.go @@ -19,26 +19,26 @@ import ( func newGoalsMessageForTest(now time.Time) []proto.Message { return []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(uint8(typedef.FileGoals)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), - ), - factory.CreateMesgOnly(mesgnum.DeveloperDataId).WithFields( + }}, + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.Goal).WithFields( + }}, + {Num: mesgnum.Goal, Fields: []proto.Field{ factory.CreateField(mesgnum.Goal, fieldnum.GoalSport).WithValue(uint8(typedef.SportSoccer)), - ), + }}, // Unrelated messages - factory.CreateMesgOnly(mesgnum.BarometerData).WithFields( + {Num: mesgnum.BarometerData, Fields: []proto.Field{ factory.CreateField(mesgnum.BarometerData, fieldnum.BarometerDataTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.CoursePoint).WithFields( + }}, + {Num: mesgnum.CoursePoint, Fields: []proto.Field{ factory.CreateField(mesgnum.CoursePoint, fieldnum.CoursePointTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, } } diff --git a/profile/filedef/listener_test.go b/profile/filedef/listener_test.go index 6b616b98..9065cb32 100644 --- a/profile/filedef/listener_test.go +++ b/profile/filedef/listener_test.go @@ -169,17 +169,17 @@ func TestListenerForSingleFitFile(t *testing.T) { func() table { mesgs := newActivityMessageForTest(now) mesgs = append(mesgs, - factory.CreateMesgOnly(mesgnum.Record). - WithFields( + proto.Message{Num: mesgnum.Record, + Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ). - WithDeveloperFields( - proto.DeveloperField{ + }, + DeveloperFields: []proto.DeveloperField{ + { DeveloperDataIndex: 0, Num: 0, Value: proto.Uint8(100), }, - ), + }}, ) return table{ name: "default listener for activity containing developer fields", diff --git a/profile/filedef/monitoring_ab_test.go b/profile/filedef/monitoring_ab_test.go index 2f43c522..f4cfd6cf 100644 --- a/profile/filedef/monitoring_ab_test.go +++ b/profile/filedef/monitoring_ab_test.go @@ -19,39 +19,39 @@ import ( func newMonitoringAMessageForTest(now time.Time) []proto.Message { return []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(uint8(typedef.FileMonitoringA)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), - ), - factory.CreateMesgOnly(mesgnum.DeveloperDataId).WithFields( + }}, + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.MonitoringInfo).WithFields( + }}, + {Num: mesgnum.MonitoringInfo, Fields: []proto.Field{ factory.CreateField(mesgnum.MonitoringInfo, fieldnum.MonitoringInfoActivityType).WithValue([]uint8{ uint8(typedef.ActivityTypeCycling), uint8(typedef.ActivityTypeRunning), }), - ), - factory.CreateMesgOnly(mesgnum.Monitoring).WithFields( + }}, + {Num: mesgnum.Monitoring, Fields: []proto.Field{ factory.CreateField(mesgnum.Monitoring, fieldnum.MonitoringActivityType).WithValue(uint8(typedef.ActivityTypeCycling)), - ), - factory.CreateMesgOnly(mesgnum.Monitoring).WithFields( + }}, + {Num: mesgnum.Monitoring, Fields: []proto.Field{ factory.CreateField(mesgnum.Monitoring, fieldnum.MonitoringActivityType).WithValue(uint8(typedef.ActivityTypeRunning)), - ), - factory.CreateMesgOnly(mesgnum.DeviceInfo).WithFields( + }}, + {Num: mesgnum.DeviceInfo, Fields: []proto.Field{ factory.CreateField(mesgnum.DeviceInfo, fieldnum.DeviceInfoTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), factory.CreateField(mesgnum.DeviceInfo, fieldnum.DeviceInfoBatteryStatus).WithValue(uint8(typedef.BatteryStatusGood)), - ), + }}, // Unrelated messages - factory.CreateMesgOnly(mesgnum.BarometerData).WithFields( + {Num: mesgnum.BarometerData, Fields: []proto.Field{ factory.CreateField(mesgnum.BarometerData, fieldnum.BarometerDataTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.CoursePoint).WithFields( + }}, + {Num: mesgnum.CoursePoint, Fields: []proto.Field{ factory.CreateField(mesgnum.CoursePoint, fieldnum.CoursePointTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, } } diff --git a/profile/filedef/monitoring_daily_test.go b/profile/filedef/monitoring_daily_test.go index 42f49a4f..01301f98 100644 --- a/profile/filedef/monitoring_daily_test.go +++ b/profile/filedef/monitoring_daily_test.go @@ -19,32 +19,32 @@ import ( func newMonitoringDailyMessageForTest(now time.Time) []proto.Message { return []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(uint8(typedef.FileMonitoringDaily)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), - ), - factory.CreateMesgOnly(mesgnum.DeveloperDataId).WithFields( + }}, + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.MonitoringInfo).WithFields( + }}, + {Num: mesgnum.MonitoringInfo, Fields: []proto.Field{ factory.CreateField(mesgnum.MonitoringInfo, fieldnum.MonitoringInfoTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.Monitoring).WithFields( + }}, + {Num: mesgnum.Monitoring, Fields: []proto.Field{ factory.CreateField(mesgnum.Monitoring, fieldnum.MonitoringIntensity).WithValue(uint8(typedef.IntensityActive)), - ), - factory.CreateMesgOnly(mesgnum.DeviceInfo).WithFields( + }}, + {Num: mesgnum.DeviceInfo, Fields: []proto.Field{ factory.CreateField(mesgnum.DeviceInfo, fieldnum.DeviceInfoTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, // Unrelated messages - factory.CreateMesgOnly(mesgnum.BarometerData).WithFields( + {Num: mesgnum.BarometerData, Fields: []proto.Field{ factory.CreateField(mesgnum.BarometerData, fieldnum.BarometerDataTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.CoursePoint).WithFields( + }}, + {Num: mesgnum.CoursePoint, Fields: []proto.Field{ factory.CreateField(mesgnum.CoursePoint, fieldnum.CoursePointTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, } } diff --git a/profile/filedef/schedules_test.go b/profile/filedef/schedules_test.go index 03677511..e757b8f5 100644 --- a/profile/filedef/schedules_test.go +++ b/profile/filedef/schedules_test.go @@ -19,26 +19,26 @@ import ( func newSchedulesMessageForTest(now time.Time) []proto.Message { return []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(uint8(typedef.FileSchedules)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), - ), - factory.CreateMesgOnly(mesgnum.DeveloperDataId).WithFields( + }}, + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.Schedule).WithFields( + }}, + {Num: mesgnum.Schedule, Fields: []proto.Field{ factory.CreateField(mesgnum.Schedule, fieldnum.ScheduleCompleted).WithValue(true), - ), + }}, // Unrelated messages - factory.CreateMesgOnly(mesgnum.BarometerData).WithFields( + {Num: mesgnum.BarometerData, Fields: []proto.Field{ factory.CreateField(mesgnum.BarometerData, fieldnum.BarometerDataTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.CoursePoint).WithFields( + }}, + {Num: mesgnum.CoursePoint, Fields: []proto.Field{ factory.CreateField(mesgnum.CoursePoint, fieldnum.CoursePointTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, } } diff --git a/profile/filedef/segment_list_test.go b/profile/filedef/segment_list_test.go index d292d682..15712bf3 100644 --- a/profile/filedef/segment_list_test.go +++ b/profile/filedef/segment_list_test.go @@ -19,29 +19,29 @@ import ( func newSegmentListMessageForTest(now time.Time) []proto.Message { return []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(uint8(typedef.FileSegmentList)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), - ), - factory.CreateMesgOnly(mesgnum.DeveloperDataId).WithFields( + }}, + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.FileCreator).WithFields( + }}, + {Num: mesgnum.FileCreator, Fields: []proto.Field{ factory.CreateField(mesgnum.FileCreator, fieldnum.FileCreatorSoftwareVersion).WithValue(uint16(1)), - ), - factory.CreateMesgOnly(mesgnum.SegmentFile).WithFields( + }}, + {Num: mesgnum.SegmentFile, Fields: []proto.Field{ factory.CreateField(mesgnum.SegmentFile, fieldnum.SegmentFileEnabled).WithValue(true), - ), + }}, // Unrelated messages - factory.CreateMesgOnly(mesgnum.BarometerData).WithFields( + {Num: mesgnum.BarometerData, Fields: []proto.Field{ factory.CreateField(mesgnum.BarometerData, fieldnum.BarometerDataTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.CoursePoint).WithFields( + }}, + {Num: mesgnum.CoursePoint, Fields: []proto.Field{ factory.CreateField(mesgnum.CoursePoint, fieldnum.CoursePointTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, } } diff --git a/profile/filedef/segment_test.go b/profile/filedef/segment_test.go index 9a2e9a08..954d824b 100644 --- a/profile/filedef/segment_test.go +++ b/profile/filedef/segment_test.go @@ -19,35 +19,35 @@ import ( func newSegmentMessageForTest(now time.Time) []proto.Message { return []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(uint8(typedef.FileSegment)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), - ), - factory.CreateMesgOnly(mesgnum.DeveloperDataId).WithFields( + }}, + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.SegmentId).WithFields( + }}, + {Num: mesgnum.SegmentId, Fields: []proto.Field{ factory.CreateField(mesgnum.SegmentId, fieldnum.SegmentIdEnabled).WithValue(true), - ), - factory.CreateMesgOnly(mesgnum.SegmentLeaderboardEntry).WithFields( + }}, + {Num: mesgnum.SegmentLeaderboardEntry, Fields: []proto.Field{ factory.CreateField(mesgnum.SegmentLeaderboardEntry, fieldnum.SegmentLeaderboardEntryName).WithValue("entry test"), - ), - factory.CreateMesgOnly(mesgnum.SegmentLap).WithFields( + }}, + {Num: mesgnum.SegmentLap, Fields: []proto.Field{ factory.CreateField(mesgnum.SegmentLap, fieldnum.SegmentLapName).WithValue("lap test"), - ), - factory.CreateMesgOnly(mesgnum.SegmentPoint).WithFields( + }}, + {Num: mesgnum.SegmentPoint, Fields: []proto.Field{ factory.CreateField(mesgnum.SegmentPoint, fieldnum.SegmentPointAltitude).WithValue(uint16(10000)), - ), + }}, // Unrelated messages - factory.CreateMesgOnly(mesgnum.BarometerData).WithFields( + {Num: mesgnum.BarometerData, Fields: []proto.Field{ factory.CreateField(mesgnum.BarometerData, fieldnum.BarometerDataTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.CoursePoint).WithFields( + }}, + {Num: mesgnum.CoursePoint, Fields: []proto.Field{ factory.CreateField(mesgnum.CoursePoint, fieldnum.CoursePointTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, } } diff --git a/profile/filedef/settings_test.go b/profile/filedef/settings_test.go index 039d5d85..7ef2d338 100644 --- a/profile/filedef/settings_test.go +++ b/profile/filedef/settings_test.go @@ -19,38 +19,38 @@ import ( func newSettingsMessageForTest(now time.Time) []proto.Message { return []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(uint8(typedef.FileSettings)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), - ), - factory.CreateMesgOnly(mesgnum.DeveloperDataId).WithFields( + }}, + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.UserProfile).WithFields( + }}, + {Num: mesgnum.UserProfile, Fields: []proto.Field{ factory.CreateField(mesgnum.UserProfile, fieldnum.UserProfileAge).WithValue(uint8(29)), - ), - factory.CreateMesgOnly(mesgnum.HrmProfile).WithFields( + }}, + {Num: mesgnum.HrmProfile, Fields: []proto.Field{ factory.CreateField(mesgnum.HrmProfile, fieldnum.HrmProfileEnabled).WithValue(true), - ), - factory.CreateMesgOnly(mesgnum.SdmProfile).WithFields( + }}, + {Num: mesgnum.SdmProfile, Fields: []proto.Field{ factory.CreateField(mesgnum.SdmProfile, fieldnum.SdmProfileEnabled).WithValue(true), - ), - factory.CreateMesgOnly(mesgnum.BikeProfile).WithFields( + }}, + {Num: mesgnum.BikeProfile, Fields: []proto.Field{ factory.CreateField(mesgnum.BikeProfile, fieldnum.BikeProfileEnabled).WithValue(true), - ), - factory.CreateMesgOnly(mesgnum.DeviceSettings).WithFields( + }}, + {Num: mesgnum.DeviceSettings, Fields: []proto.Field{ factory.CreateField(mesgnum.DeviceSettings, fieldnum.DeviceSettingsBacklightMode).WithValue(uint8(typedef.BacklightModeAutoBrightness)), - ), + }}, // Unrelated messages - factory.CreateMesgOnly(mesgnum.BarometerData).WithFields( + {Num: mesgnum.BarometerData, Fields: []proto.Field{ factory.CreateField(mesgnum.BarometerData, fieldnum.BarometerDataTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.CoursePoint).WithFields( + }}, + {Num: mesgnum.CoursePoint, Fields: []proto.Field{ factory.CreateField(mesgnum.CoursePoint, fieldnum.CoursePointTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, } } diff --git a/profile/filedef/sport_test.go b/profile/filedef/sport_test.go index b9daceb9..08930004 100644 --- a/profile/filedef/sport_test.go +++ b/profile/filedef/sport_test.go @@ -19,44 +19,44 @@ import ( func newSportMessageForTest(now time.Time) []proto.Message { return []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(uint8(typedef.FileSport)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), - ), - factory.CreateMesgOnly(mesgnum.DeveloperDataId).WithFields( + }}, + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.ZonesTarget).WithFields( + }}, + {Num: mesgnum.ZonesTarget, Fields: []proto.Field{ factory.CreateField(mesgnum.ZonesTarget, fieldnum.ZonesTargetMaxHeartRate).WithValue(uint8(190)), - ), - factory.CreateMesgOnly(mesgnum.Sport).WithFields( + }}, + {Num: mesgnum.Sport, Fields: []proto.Field{ factory.CreateField(mesgnum.Sport, fieldnum.SportSport).WithValue(uint8(typedef.SportAmericanFootball)), - ), - factory.CreateMesgOnly(mesgnum.HrZone).WithFields( + }}, + {Num: mesgnum.HrZone, Fields: []proto.Field{ factory.CreateField(mesgnum.HrZone, fieldnum.HrZoneHighBpm).WithValue(uint8(177)), - ), - factory.CreateMesgOnly(mesgnum.PowerZone).WithFields( + }}, + {Num: mesgnum.PowerZone, Fields: []proto.Field{ factory.CreateField(mesgnum.PowerZone, fieldnum.PowerZoneHighValue).WithValue(uint16(200)), - ), - factory.CreateMesgOnly(mesgnum.MetZone).WithFields( + }}, + {Num: mesgnum.MetZone, Fields: []proto.Field{ factory.CreateField(mesgnum.MetZone, fieldnum.MetZoneHighBpm).WithValue(uint8(178)), - ), - factory.CreateMesgOnly(mesgnum.SpeedZone).WithFields( + }}, + {Num: mesgnum.SpeedZone, Fields: []proto.Field{ factory.CreateField(mesgnum.SpeedZone, fieldnum.SpeedZoneHighValue).WithValue(uint16(10000)), - ), - factory.CreateMesgOnly(mesgnum.CadenceZone).WithFields( + }}, + {Num: mesgnum.CadenceZone, Fields: []proto.Field{ factory.CreateField(mesgnum.CadenceZone, fieldnum.CadenceZoneHighValue).WithValue(uint8(100)), - ), + }}, // Unrelated messages - factory.CreateMesgOnly(mesgnum.BarometerData).WithFields( + {Num: mesgnum.BarometerData, Fields: []proto.Field{ factory.CreateField(mesgnum.BarometerData, fieldnum.BarometerDataTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.CoursePoint).WithFields( + }}, + {Num: mesgnum.CoursePoint, Fields: []proto.Field{ factory.CreateField(mesgnum.CoursePoint, fieldnum.CoursePointTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, } } diff --git a/profile/filedef/totals_test.go b/profile/filedef/totals_test.go index f44518f2..309974e2 100644 --- a/profile/filedef/totals_test.go +++ b/profile/filedef/totals_test.go @@ -19,26 +19,26 @@ import ( func newTotalsMessageForTest(now time.Time) []proto.Message { return []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(uint8(typedef.FileTotals)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), - ), - factory.CreateMesgOnly(mesgnum.DeveloperDataId).WithFields( + }}, + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.Totals).WithFields( + }}, + {Num: mesgnum.Totals, Fields: []proto.Field{ factory.CreateField(mesgnum.Totals, fieldnum.TotalsSport).WithValue(uint8(typedef.SportSoccer)), - ), + }}, // Unrelated messages - factory.CreateMesgOnly(mesgnum.BarometerData).WithFields( + {Num: mesgnum.BarometerData, Fields: []proto.Field{ factory.CreateField(mesgnum.BarometerData, fieldnum.BarometerDataTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.CoursePoint).WithFields( + }}, + {Num: mesgnum.CoursePoint, Fields: []proto.Field{ factory.CreateField(mesgnum.CoursePoint, fieldnum.CoursePointTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, } } diff --git a/profile/filedef/weight_test.go b/profile/filedef/weight_test.go index 694674e8..5a1adfd4 100644 --- a/profile/filedef/weight_test.go +++ b/profile/filedef/weight_test.go @@ -19,32 +19,32 @@ import ( func newWeightMessageForTest(now time.Time) []proto.Message { return []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(uint8(typedef.FileWeight)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), - ), - factory.CreateMesgOnly(mesgnum.DeveloperDataId).WithFields( + }}, + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.UserProfile).WithFields( + }}, + {Num: mesgnum.UserProfile, Fields: []proto.Field{ factory.CreateField(mesgnum.UserProfile, fieldnum.UserProfileAge).WithValue(uint8(27)), - ), - factory.CreateMesgOnly(mesgnum.WeightScale).WithFields( + }}, + {Num: mesgnum.WeightScale, Fields: []proto.Field{ factory.CreateField(mesgnum.WeightScale, fieldnum.WeightScaleBmi).WithValue(uint16(1000)), - ), - factory.CreateMesgOnly(mesgnum.DeviceInfo).WithFields( + }}, + {Num: mesgnum.DeviceInfo, Fields: []proto.Field{ factory.CreateField(mesgnum.DeviceInfo, fieldnum.DeviceInfoTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, // Unrelated messages - factory.CreateMesgOnly(mesgnum.BarometerData).WithFields( + {Num: mesgnum.BarometerData, Fields: []proto.Field{ factory.CreateField(mesgnum.BarometerData, fieldnum.BarometerDataTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.CoursePoint).WithFields( + }}, + {Num: mesgnum.CoursePoint, Fields: []proto.Field{ factory.CreateField(mesgnum.CoursePoint, fieldnum.CoursePointTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, } } diff --git a/profile/filedef/workout_test.go b/profile/filedef/workout_test.go index c50c6199..5721005a 100644 --- a/profile/filedef/workout_test.go +++ b/profile/filedef/workout_test.go @@ -19,32 +19,32 @@ import ( func newWorkoutMessageForTest(now time.Time) []proto.Message { return []proto.Message{ - factory.CreateMesgOnly(mesgnum.FileId).WithFields( + {Num: mesgnum.FileId, Fields: []proto.Field{ factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(uint8(typedef.FileWorkout)), factory.CreateField(mesgnum.FileId, fieldnum.FileIdTimeCreated).WithValue(datetime.ToUint32(now)), - ), - factory.CreateMesgOnly(mesgnum.DeveloperDataId).WithFields( + }}, + {Num: mesgnum.DeveloperDataId, Fields: []proto.Field{ factory.CreateField(mesgnum.DeveloperDataId, fieldnum.DeveloperDataIdDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields( + }}, + {Num: mesgnum.FieldDescription, Fields: []proto.Field{ factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)), - ), - factory.CreateMesgOnly(mesgnum.Workout).WithFields( + }}, + {Num: mesgnum.Workout, Fields: []proto.Field{ factory.CreateField(mesgnum.Workout, fieldnum.WorkoutSport).WithValue(uint8(typedef.SportSwimming)), - ), - factory.CreateMesgOnly(mesgnum.WorkoutStep).WithFields( + }}, + {Num: mesgnum.WorkoutStep, Fields: []proto.Field{ factory.CreateField(mesgnum.WorkoutStep, fieldnum.WorkoutStepEquipment).WithValue(uint8(typedef.WorkoutEquipmentSwimFins)), - ), - factory.CreateMesgOnly(mesgnum.WorkoutStep).WithFields( + }}, + {Num: mesgnum.WorkoutStep, Fields: []proto.Field{ factory.CreateField(mesgnum.WorkoutStep, fieldnum.WorkoutStepEquipment).WithValue(uint8(typedef.WorkoutEquipmentSwimSnorkel)), - ), + }}, // Unrelated messages - factory.CreateMesgOnly(mesgnum.CoursePoint).WithFields( + {Num: mesgnum.CoursePoint, Fields: []proto.Field{ factory.CreateField(mesgnum.CoursePoint, fieldnum.CoursePointTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), - factory.CreateMesgOnly(mesgnum.BarometerData).WithFields( + }}, + {Num: mesgnum.BarometerData, Fields: []proto.Field{ factory.CreateField(mesgnum.BarometerData, fieldnum.BarometerDataTimestamp).WithValue(datetime.ToUint32(incrementSecond(&now))), - ), + }}, } } diff --git a/profile/mesgdef/mesgdef.go b/profile/mesgdef/mesgdef.go index 4b1cd29c..a285c81a 100644 --- a/profile/mesgdef/mesgdef.go +++ b/profile/mesgdef/mesgdef.go @@ -12,7 +12,8 @@ import ( // Factory defines a contract that any Factory containing these method can be used by mesgdef's structs. type Factory interface { - // CreateField create new field based on defined messages in the factory. If not found, it returns new field with "unknown" name. + // CreateField creates new field based on defined messages in the factory. + // If not found, it returns new field with "unknown" name. CreateField(mesgNum typedef.MesgNum, num byte) proto.Field } diff --git a/profile/mesgdef/mesgdef_test.go b/profile/mesgdef/mesgdef_test.go index 481c6934..45e29a44 100644 --- a/profile/mesgdef/mesgdef_test.go +++ b/profile/mesgdef/mesgdef_test.go @@ -13,6 +13,7 @@ import ( "github.com/muktihari/fit/profile/typedef" "github.com/muktihari/fit/profile/untyped/fieldnum" "github.com/muktihari/fit/profile/untyped/mesgnum" + "github.com/muktihari/fit/proto" ) func TestDefaultOptions(t *testing.T) { @@ -34,9 +35,13 @@ func TestUnsafeCast(t *testing.T) { typedef.AttitudeValidityHwFail, typedef.AttitudeValiditySolutionCoasting, } - mesg := factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.AviationAttitudeValidity: attitudeValidities, - }) + mesg := factory.CreateMesg(mesgnum.Record) + for i := range mesg.Fields { + if mesg.Fields[i].Num == fieldnum.AviationAttitudeValidity { + mesg.Fields[i].Value = proto.SliceUint16(attitudeValidities) + break + } + } aviationAttitude := NewAviationAttitude(&mesg) newMesg := aviationAttitude.ToMesg(nil) diff --git a/profile/mesgdef/record_gen_test.go b/profile/mesgdef/record_gen_test.go index 3c9ad4a5..336bb63f 100644 --- a/profile/mesgdef/record_gen_test.go +++ b/profile/mesgdef/record_gen_test.go @@ -28,9 +28,13 @@ func BenchmarkNewRecord(b *testing.B) { } func BenchmarkRecordToMesg(b *testing.B) { - mesg := factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.RecordTimestamp: datetime.ToUint32(time.Now()), - }) + mesg := factory.CreateMesg(mesgnum.Record) + for i := range mesg.Fields { + if mesg.Fields[i].Num == fieldnum.RecordTimestamp { + mesg.Fields[i].Value = proto.Uint32(datetime.ToUint32(time.Now())) + break + } + } record := NewRecord(&mesg) b.ResetTimer() diff --git a/proto/proto.go b/proto/proto.go index b48e1e41..941570e4 100644 --- a/proto/proto.go +++ b/proto/proto.go @@ -5,6 +5,8 @@ package proto import ( + "fmt" + "github.com/muktihari/fit/profile" "github.com/muktihari/fit/profile/basetype" "github.com/muktihari/fit/profile/typedef" @@ -27,6 +29,9 @@ const ( // header is 1 byte -> 0bxxxxxxxx CompressedBitShift = 5 // Used for right-shifting the 5 least significant bits (lsb) of compressed time. + LittleEndian = 0 + BigEndian = 1 + DefaultFileHeaderSize byte = 14 // The preferred size is 14 DataTypeFIT string = ".FIT" // FIT is a constant string ".FIT" @@ -44,63 +49,6 @@ const ( // header is 1 byte -> 0bxxxxxxxx FieldNumTimestamp = 253 ) -// LocalMesgNum extracts LocalMesgNum from message header. -func LocalMesgNum(header byte) byte { - if (header & MesgCompressedHeaderMask) == MesgCompressedHeaderMask { - return (header & CompressedLocalMesgNumMask) >> CompressedBitShift - } - return header & LocalMesgNumMask -} - -// CreateMessageDefinition creates new MessageDefinition base on given Message. -// It will panic if mesg is nil. And mesg must be validated first, for instance -// if field.Value's size is more than 255 bytes, overflow will occurs. -func CreateMessageDefinition(mesg *Message) (mesgDef MessageDefinition) { - CreateMessageDefinitionTo(&mesgDef, mesg) - return -} - -// CreateMessageDefinitionTo create MessageDefinition base on given Message and put it at target object to avoid allocation. -// It will panic if either target or mesg is nil. And mesg must be validated first, for instance -// if field.Value's size is more than 255 bytes, overflow will occurs. -func CreateMessageDefinitionTo(target *MessageDefinition, mesg *Message) { - target.Header = MesgDefinitionMask - target.Reserved = mesg.Reserved - target.Architecture = mesg.Architecture - target.MesgNum = mesg.Num - - target.FieldDefinitions = target.FieldDefinitions[:0] - if cap(target.FieldDefinitions) < len(mesg.Fields) { - target.FieldDefinitions = make([]FieldDefinition, 0, len(mesg.Fields)) - } - - for i := range mesg.Fields { - target.FieldDefinitions = append(target.FieldDefinitions, FieldDefinition{ - Num: mesg.Fields[i].Num, - Size: byte(Sizeof(mesg.Fields[i].Value)), - BaseType: mesg.Fields[i].BaseType, - }) - } - - if len(mesg.DeveloperFields) == 0 { - return - } - - target.Header |= DevDataMask - - target.DeveloperFieldDefinitions = target.DeveloperFieldDefinitions[:0] - if cap(target.DeveloperFieldDefinitions) < len(mesg.DeveloperFields) { - target.DeveloperFieldDefinitions = make([]DeveloperFieldDefinition, 0, len(mesg.DeveloperFields)) - } - for i := range mesg.DeveloperFields { - target.DeveloperFieldDefinitions = append(target.DeveloperFieldDefinitions, DeveloperFieldDefinition{ - Num: mesg.DeveloperFields[i].Num, - Size: byte(Sizeof(mesg.DeveloperFields[i].Value)), - DeveloperDataIndex: mesg.DeveloperFields[i].DeveloperDataIndex, - }) - } -} - // FIT represents a structure for FIT Files. type FIT struct { FileHeader FileHeader // File Header contains either 12 or 14 bytes @@ -108,20 +56,14 @@ type FIT struct { CRC uint16 // Cyclic Redundancy Check 16-bit value to ensure the integrity of the messages. } -// WithMessages set Messages and return the pointer to the FIT. -func (f *FIT) WithMessages(messages ...Message) *FIT { - f.Messages = messages - return f -} - // FileHeader is a FIT's FileHeader with either 12 bytes size without CRC or a 14 bytes size with CRC, while 14 bytes size is the preferred size. type FileHeader struct { - Size byte // File header size either 12 (legacy) or 14. - ProtocolVersion byte // The FIT Protocol version which is being used to encode the FIT file. - ProfileVersion uint16 // The FIT Profile Version (associated with data defined in Global FIT Profile). - DataSize uint32 // The size of the messages in bytes (this field will be automatically updated by the encoder) - DataType string // ".FIT" (a string constant) - CRC uint16 // Cyclic Redundancy Check 16-bit value to ensure the integrity of the file header. (this field will be automatically updated by the encoder) + Size byte // File header size either 12 (legacy) or 14. + ProtocolVersion Version // The FIT Protocol version which is being used to encode the FIT file. + ProfileVersion uint16 // The FIT Profile Version (associated with data defined in Global FIT Profile). + DataSize uint32 // The size of the messages in bytes (this field will be automatically updated by the encoder) + DataType string // ".FIT" (a string constant) + CRC uint16 // Cyclic Redundancy Check 16-bit value to ensure the integrity of the file header. (this field will be automatically updated by the encoder) } // MessageDefinition is the definition of the upcoming data messages. @@ -134,13 +76,6 @@ type MessageDefinition struct { DeveloperFieldDefinitions []DeveloperFieldDefinition // List of the developer field definition (only if Developer Data Flag is set in Header) } -// Clone clones MessageDefinition -func (m MessageDefinition) Clone() MessageDefinition { - m.FieldDefinitions = append(m.FieldDefinitions[:0:0], m.FieldDefinitions...) - m.DeveloperFieldDefinitions = append(m.DeveloperFieldDefinitions[:0:0], m.DeveloperFieldDefinitions...) - return m -} - // FieldDefinition is the definition of the upcoming field within the message's structure. type FieldDefinition struct { Num byte // The field definition number @@ -159,40 +94,10 @@ type DeveloperFieldDefinition struct { // 3 bits type Message struct { Header byte // Message Header serves to distinguish whether the message is a Normal Data or a Compressed Timestamp Data. Unlike MessageDefinition, Message's Header should not contain Developer Data Flag. Num typedef.MesgNum // Global Message Number defined in Global FIT Profile, except number within range 0xFF00 - 0xFFFE are manufacturer specific number. - Reserved byte // Currently undetermined; the default value is 0. - Architecture byte // Architecture type / Endianness. Fields []Field // List of Field DeveloperFields []DeveloperField // List of DeveloperField } -// WithFields puts the provided fields into the message's fields. -func (m Message) WithFields(fields ...Field) Message { - m.Fields = fields - return m -} - -// WithFieldValues assigns the values of the targeted fields with the given map, -// where map[byte]any represents the field numbers and their respective values. -// If the Message does not have a corresponding field number match in the Fields, no value will be assigned or added. -func (m Message) WithFieldValues(fieldNumValues map[byte]any) Message { - for i := range m.Fields { - value, ok := fieldNumValues[m.Fields[i].Num] - if !ok { - continue - } - if value != nil { // only accept non-nil value. - m.Fields[i].Value = Any(value) - } - } - return m -} - -// WithFields puts the provided fields into the message's fields. -func (m Message) WithDeveloperFields(developerFields ...DeveloperField) Message { - m.DeveloperFields = developerFields - return m -} - // FieldByNum returns a pointer to the Field in a Message, if not found return nil. func (m *Message) FieldByNum(num byte) *Field { for i := range m.Fields { @@ -223,19 +128,6 @@ func (m *Message) RemoveFieldByNum(num byte) { } } -// Clone clones Message. -func (m Message) Clone() Message { - m.Fields = append(m.Fields[:0:0], m.Fields...) - for i := range m.Fields { - m.Fields[i] = m.Fields[i].Clone() - } - m.DeveloperFields = append(m.DeveloperFields[:0:0], m.DeveloperFields...) - for i := range m.DeveloperFields { - m.DeveloperFields[i] = m.DeveloperFields[i].Clone() - } - return m -} - // FieldBase acts as a fundamental representation of a field as defined in the Global FIT Profile. // The value of this representation should not be altered, except in the case of an unknown field. type FieldBase struct { @@ -315,23 +207,6 @@ func convertToInt64(val Value) (int64, bool) { return 0, false } -// Clone clones Field -func (f Field) Clone() Field { - if f.FieldBase == nil { - return f - } - - fieldBase := *f.FieldBase // also include FieldBase, clone is meant to be a deep copy - fieldBase.Components = append(fieldBase.Components[:0:0], fieldBase.Components...) - fieldBase.SubFields = append(fieldBase.SubFields[:0:0], fieldBase.SubFields...) - for i := range fieldBase.SubFields { - fieldBase.SubFields[i] = fieldBase.SubFields[i].Clone() - } - f.FieldBase = &fieldBase - - return f -} - // DeveloperField is a way to add custom data fields to existing messages. Developer Data Fields can be added // to any message at runtime by providing a self-describing FieldDefinition messages prior to that message. // The combination of the DeveloperDataIndex and FieldDefinitionNumber create a unique id for each FieldDescription. @@ -346,11 +221,6 @@ type DeveloperField struct { Value Value } -// Clone clones DeveloperField -func (f DeveloperField) Clone() DeveloperField { - return f -} - // Component is a way of compressing one or more fields into a bit field expressed in a single containing field. // The component can be expanded as a main Field in a Message or to update the value of the destination main Field. type Component struct { @@ -372,13 +242,6 @@ type SubField struct { Components []Component } -// Clone clones SubField -func (s SubField) Clone() SubField { - s.Components = append(s.Components[:0:0], s.Components...) - s.Maps = append(s.Maps[:0:0], s.Maps...) - return s -} - // SubFieldMap is the mapping between SubField and the corresponding main Field in a Message. // When any Field in a Message has Field.Num == RefFieldNum and Field.Value == RefFieldValue, then the SubField containing // this mapping can be interpreted as the main Field's properties (name, scale, type etc.) @@ -386,3 +249,70 @@ type SubFieldMap struct { RefFieldNum byte RefFieldValue int64 } + +// LocalMesgNum extracts LocalMesgNum from message header. +func LocalMesgNum(header byte) byte { + if (header & MesgCompressedHeaderMask) == MesgCompressedHeaderMask { + return (header & CompressedLocalMesgNumMask) >> CompressedBitShift + } + return header & LocalMesgNumMask +} + +const ( + errNilMesg = errorString("mesg is nil") + errValueSizeExceed255 = errorString("value's size exceed 255") +) + +// NewMessageDefinition returns a new MessageDefinition based on the given Message or an error if one occurs. +// This will set Reserved and Architecture with 0 value. It's up to the caller to change the returning value. +// +// This serves as a testing helper and is for documentation purposes only. +func NewMessageDefinition(mesg *Message) (*MessageDefinition, error) { + if mesg == nil { + return nil, errNilMesg + } + + const maxValueSize = 255 + + mesgDef := &MessageDefinition{ + Header: MesgDefinitionMask, + Reserved: 0, + Architecture: LittleEndian, + MesgNum: mesg.Num, + FieldDefinitions: make([]FieldDefinition, 0, len(mesg.Fields)), + } + + for i := range mesg.Fields { + size := Sizeof(mesg.Fields[i].Value) + if size > maxValueSize { + return nil, fmt.Errorf("Fields[%d].Value's size should be <= %d: %w", + i, maxValueSize, errValueSizeExceed255) + } + mesgDef.FieldDefinitions = append(mesgDef.FieldDefinitions, FieldDefinition{ + Num: mesg.Fields[i].Num, + Size: byte(size), + BaseType: mesg.Fields[i].BaseType, + }) + } + + if len(mesg.DeveloperFields) == 0 { + return mesgDef, nil + } + + mesgDef.DeveloperFieldDefinitions = make([]DeveloperFieldDefinition, 0, len(mesg.DeveloperFields)) + mesgDef.Header |= DevDataMask + for i := range mesg.DeveloperFields { + size := Sizeof(mesg.DeveloperFields[i].Value) + if size > maxValueSize { + return nil, fmt.Errorf("Fields[%d].Value's size should be <= %d: %w", + i, maxValueSize, errValueSizeExceed255) + } + mesgDef.DeveloperFieldDefinitions = append(mesgDef.DeveloperFieldDefinitions, DeveloperFieldDefinition{ + Num: mesg.DeveloperFields[i].Num, + Size: byte(size), + DeveloperDataIndex: mesg.DeveloperFields[i].DeveloperDataIndex, + }) + } + + return mesgDef, nil +} diff --git a/proto/proto_internal_test.go b/proto/proto_internal_test.go index ab1db004..c3e187d9 100644 --- a/proto/proto_internal_test.go +++ b/proto/proto_internal_test.go @@ -5,10 +5,194 @@ package proto import ( + "errors" "fmt" "testing" + + "github.com/google/go-cmp/cmp" + "github.com/muktihari/fit/profile/basetype" + "github.com/muktihari/fit/profile/typedef" + "github.com/muktihari/fit/profile/untyped/fieldnum" + "github.com/muktihari/fit/profile/untyped/mesgnum" ) +func TestNewMessageDefinition(t *testing.T) { + tt := []struct { + name string + mesg *Message + mesgDef *MessageDefinition + err error + }{ + {name: "nil mesg", err: errNilMesg}, + { + name: "field value exceed max 255", + mesg: &Message{Num: mesgnum.FileId, Fields: []Field{ + { + FieldBase: &FieldBase{Num: fieldnum.FileIdProductName, BaseType: basetype.String}, + Value: String(string(make([]byte, 256))), + }, + }}, + err: errValueSizeExceed255, + }, + { + name: "developerField value exceed max 255", + mesg: &Message{Num: mesgnum.FileId, Fields: []Field{}, + DeveloperFields: []DeveloperField{ + {Value: String(string(make([]byte, 256)))}, + }, + }, + err: errValueSizeExceed255, + }, + { + name: "fields only with non-array values", + mesg: &Message{Num: mesgnum.FileId, Fields: []Field{ + {FieldBase: &FieldBase{Num: fieldnum.FileIdType, BaseType: basetype.Enum}, Value: Uint8(typedef.FileActivity.Byte())}, + }}, + mesgDef: &MessageDefinition{ + Header: MesgDefinitionMask, + MesgNum: mesgnum.FileId, + FieldDefinitions: []FieldDefinition{ + { + Num: fieldnum.FileIdType, + Size: 1, + BaseType: basetype.Enum, + }, + }, + }, + }, + { + name: "fields only with string value", + mesg: &Message{Num: mesgnum.FileId, Fields: []Field{ + {FieldBase: &FieldBase{Num: fieldnum.FileIdProductName, BaseType: basetype.String}, Value: String("FIT SDK Go")}, + }}, + mesgDef: &MessageDefinition{ + Header: MesgDefinitionMask, + MesgNum: mesgnum.FileId, + FieldDefinitions: []FieldDefinition{ + { + Num: fieldnum.FileIdProductName, + Size: 1 * 11, // len("FIT SDK Go") == 10 + '0x00' + BaseType: basetype.String, + }, + }, + }, + }, + { + name: "fields only with array of byte", + mesg: &Message{Num: mesgnum.UserProfile, Fields: []Field{ + {FieldBase: &FieldBase{Num: fieldnum.UserProfileGlobalId, BaseType: basetype.Byte}, Value: SliceUint8([]byte{2, 9})}, + }}, + mesgDef: &MessageDefinition{ + Header: MesgDefinitionMask, + MesgNum: mesgnum.UserProfile, + FieldDefinitions: []FieldDefinition{ + { + Num: fieldnum.UserProfileGlobalId, + Size: 2, + BaseType: basetype.Byte, + }, + }, + }, + }, + + { + name: "developer fields", + mesg: &Message{Num: mesgnum.UserProfile, + Fields: []Field{ + {FieldBase: &FieldBase{Num: fieldnum.UserProfileGlobalId, BaseType: basetype.Byte}, Value: SliceUint8([]byte{2, 9})}, + }, + DeveloperFields: []DeveloperField{ + {Num: 0, DeveloperDataIndex: 0, Value: Uint8(1)}, + }}, + mesgDef: &MessageDefinition{ + Header: MesgDefinitionMask | DevDataMask, + MesgNum: mesgnum.UserProfile, + FieldDefinitions: []FieldDefinition{ + { + Num: fieldnum.UserProfileGlobalId, + Size: 2, + BaseType: basetype.Byte, + }, + }, + DeveloperFieldDefinitions: []DeveloperFieldDefinition{ + { + Num: 0, Size: 1, DeveloperDataIndex: 0, + }, + }, + }, + }, + { + name: "developer fields with string value \"FIT SDK Go\", size should be 11", + mesg: &Message{Num: mesgnum.UserProfile, + Fields: []Field{ + {FieldBase: &FieldBase{Num: fieldnum.UserProfileGlobalId, BaseType: basetype.Byte}, Value: SliceUint8([]byte{2, 9})}, + }, + DeveloperFields: []DeveloperField{ + { + Num: 0, DeveloperDataIndex: 0, Value: String("FIT SDK Go"), + }, + }}, + mesgDef: &MessageDefinition{ + Header: MesgDefinitionMask | DevDataMask, + MesgNum: mesgnum.UserProfile, + FieldDefinitions: []FieldDefinition{ + { + Num: fieldnum.UserProfileGlobalId, + Size: 2, + BaseType: basetype.Byte, + }, + }, + DeveloperFieldDefinitions: []DeveloperFieldDefinition{ + { + Num: 0, Size: 11, DeveloperDataIndex: 0, + }, + }, + }, + }, + { + name: "developer fields with value []uint16{1,2,3}, size should be 3*2 = 6", + mesg: &Message{Num: mesgnum.UserProfile, + Fields: []Field{ + {FieldBase: &FieldBase{Num: fieldnum.UserProfileGlobalId, BaseType: basetype.Byte}, Value: SliceUint8([]byte{2, 9})}, + }, + DeveloperFields: []DeveloperField{ + {Num: 0, DeveloperDataIndex: 0, Value: SliceUint16([]uint16{1, 2, 3})}, + }}, + mesgDef: &MessageDefinition{ + Header: MesgDefinitionMask | DevDataMask, + MesgNum: mesgnum.UserProfile, + FieldDefinitions: []FieldDefinition{ + { + Num: fieldnum.UserProfileGlobalId, + Size: 2, + BaseType: basetype.Byte, + }, + }, + DeveloperFieldDefinitions: []DeveloperFieldDefinition{ + { + Num: 0, Size: 6, DeveloperDataIndex: 0, + }, + }, + }, + }, + } + + for i, tc := range tt { + t.Run(fmt.Sprintf("[%d] %s", i, tc.name), func(t *testing.T) { + mesgDef, err := NewMessageDefinition(tc.mesg) + if !errors.Is(err, tc.err) { + t.Fatalf("expected error: %v, got: %v", tc.err, err) + } + if err != nil { + return + } + if diff := cmp.Diff(mesgDef, tc.mesgDef); diff != "" { + t.Fatal(diff) + } + }) + } +} + func TestIsValueEqualTo(t *testing.T) { tt := []struct { field Field diff --git a/proto/proto_marshal.go b/proto/proto_marshal.go index cf70eda6..5ff49057 100644 --- a/proto/proto_marshal.go +++ b/proto/proto_marshal.go @@ -5,44 +5,15 @@ package proto import ( - "encoding" "encoding/binary" "fmt" - "sync" ) -const littleEndian = 0 - // Marshaler should only do one thing: marshaling to its bytes representation, any validation should be done outside. -// Header + ((max n Fields) * (n value)) + ((max n DeveloperFields) * (n value)) -const MaxBytesPerMessage = 1 + (255*255)*2 - -// Header + Reserved + Architecture + MesgNum (2 bytes) + n Fields + (Max n Fields * 3) + n DevFields + (Max n DevFields * 3). -const MaxBytesPerMessageDefinition = 5 + 1 + (255 * 3) + 1 + (255 * 3) - -var pool = sync.Pool{New: func() any { return new([MaxBytesPerMessage]byte) }} - -var ( - _ encoding.BinaryMarshaler = (*FileHeader)(nil) - _ encoding.BinaryMarshaler = (*MessageDefinition)(nil) - _ encoding.BinaryMarshaler = (*Message)(nil) -) - -// MarshalBinary returns the FIT format encoding of FileHeader and nil error. -func (h FileHeader) MarshalBinary() ([]byte, error) { - arr := pool.Get().(*[MaxBytesPerMessage]byte) - defer pool.Put(arr) - b := arr[:0] - - b, _ = h.MarshalAppend(b) - - return append([]byte{}, b...), nil -} - // MarshalAppend appends the FIT format encoding of FileHeader to b, returning the result. -func (h FileHeader) MarshalAppend(b []byte) ([]byte, error) { - b = append(b, h.Size, h.ProtocolVersion) +func (h *FileHeader) MarshalAppend(b []byte) ([]byte, error) { + b = append(b, h.Size, byte(h.ProtocolVersion)) b = binary.LittleEndian.AppendUint16(b, h.ProfileVersion) b = binary.LittleEndian.AppendUint32(b, h.DataSize) b = append(b, h.DataType[:4]...) @@ -52,24 +23,13 @@ func (h FileHeader) MarshalAppend(b []byte) ([]byte, error) { return b, nil } -// MarshalBinary returns the FIT format encoding of MessageDefinition and nil error. -func (m MessageDefinition) MarshalBinary() ([]byte, error) { - arr := pool.Get().(*[MaxBytesPerMessage]byte) - defer pool.Put(arr) - b := arr[:0] - - b, _ = m.MarshalAppend(b) - - return append([]byte{}, b...), nil -} - // MarshalAppend appends the FIT format encoding of MessageDefinition to b, returning the result. -func (m MessageDefinition) MarshalAppend(b []byte) ([]byte, error) { +func (m *MessageDefinition) MarshalAppend(b []byte) ([]byte, error) { b = append(b, m.Header) b = append(b, m.Reserved) b = append(b, m.Architecture) - if m.Architecture == littleEndian { + if m.Architecture == LittleEndian { b = binary.LittleEndian.AppendUint16(b, uint16(m.MesgNum)) } else { b = binary.BigEndian.AppendUint16(b, uint16(m.MesgNum)) @@ -98,27 +58,13 @@ func (m MessageDefinition) MarshalAppend(b []byte) ([]byte, error) { return b, nil } -// MarshalBinary returns the FIT format encoding of Message and any error encountered during marshal. -func (m Message) MarshalBinary() ([]byte, error) { - arr := pool.Get().(*[MaxBytesPerMessage]byte) - defer pool.Put(arr) - b := arr[:0] - - b, err := m.MarshalAppend(b) - if err != nil { - return nil, err - } - - return append([]byte{}, b...), nil -} - // MarshalAppend appends the FIT format encoding of Message to b, returning the result. -func (m Message) MarshalAppend(b []byte) ([]byte, error) { +func (m *Message) MarshalAppend(b []byte, arch byte) ([]byte, error) { b = append(b, m.Header) var err error for i := range m.Fields { - b, err = m.Fields[i].Value.MarshalAppend(b, m.Architecture) + b, err = m.Fields[i].Value.MarshalAppend(b, arch) if err != nil { return nil, fmt.Errorf("field: [num: %d, value: %v]: %w", m.Fields[i].Num, m.Fields[i].Value.Any(), err) @@ -126,7 +72,7 @@ func (m Message) MarshalAppend(b []byte) ([]byte, error) { } for i := range m.DeveloperFields { - b, err = m.DeveloperFields[i].Value.MarshalAppend(b, m.Architecture) + b, err = m.DeveloperFields[i].Value.MarshalAppend(b, arch) if err != nil { return nil, fmt.Errorf("developer field: [num: %d, value: %v]: %w", m.DeveloperFields[i].Num, m.DeveloperFields[i].Value.Any(), err) diff --git a/proto/proto_marshal_test.go b/proto/proto_marshal_test.go index 3fc7dc5e..51426039 100644 --- a/proto/proto_marshal_test.go +++ b/proto/proto_marshal_test.go @@ -64,7 +64,7 @@ func TestHeaderMarshaler(t *testing.T) { } for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { - b, err := tc.fileHeader.MarshalBinary() + b, err := tc.fileHeader.MarshalAppend(nil) if !errors.Is(err, tc.err) { t.Fatalf("expected err: %v, got: %v", tc.err, err) } @@ -148,7 +148,7 @@ func TestMessageDefinitionMarshaler(t *testing.T) { for i, tc := range tt { t.Run(fmt.Sprintf("[%d] %s", i, tc.name), func(t *testing.T) { - b, _ := tc.mesgdef.MarshalBinary() + b, _ := tc.mesgdef.MarshalAppend(nil) if diff := cmp.Diff(b, tc.b); diff != "" { t.Fatal(diff) } @@ -243,7 +243,7 @@ func TestMessageMarshaler(t *testing.T) { for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { - b, err := tc.mesg.MarshalBinary() + b, err := tc.mesg.MarshalAppend(nil, proto.LittleEndian) if !errors.Is(err, tc.err) { t.Fatalf("expected err: %v, got: %v", tc.err, err) } @@ -254,7 +254,7 @@ func TestMessageMarshaler(t *testing.T) { } } -func BenchmarkHeaderMarshalBinary(b *testing.B) { +func BenchmarkFileHeaderMarshalAppend(b *testing.B) { b.StopTimer() header := proto.FileHeader{ Size: 14, @@ -267,76 +267,58 @@ func BenchmarkHeaderMarshalBinary(b *testing.B) { b.StartTimer() for i := 0; i < b.N; i++ { - _, _ = header.MarshalBinary() - } -} - -func BenchmarkHeaderMarshalAppend(b *testing.B) { - b.StopTimer() - header := proto.FileHeader{ - Size: 14, - ProtocolVersion: 32, - ProfileVersion: 2132, - DataSize: 642262, - DataType: ".FIT", - CRC: 12856, - } - arr := [proto.MaxBytesPerMessageDefinition]byte{} - b.StartTimer() - - for i := 0; i < b.N; i++ { - _, _ = header.MarshalAppend(arr[:0]) - } -} - -func BenchmarkMessageDefinitionMarshalBinary(b *testing.B) { - b.StopTimer() - mesg := factory.CreateMesg(mesgnum.Record) - mesgDef := proto.CreateMessageDefinition(&mesg) - b.StartTimer() - - for i := 0; i < b.N; i++ { - _, _ = mesgDef.MarshalBinary() + _, _ = header.MarshalAppend(make([]byte, 0, 14)) } } func BenchmarkMessageDefinitionMarshalAppend(b *testing.B) { b.StopTimer() - mesg := factory.CreateMesg(mesgnum.Record) - mesgDef := proto.CreateMessageDefinition(&mesg) - arr := [proto.MaxBytesPerMessageDefinition]byte{} - b.StartTimer() - - for i := 0; i < b.N; i++ { - _, _ = mesgDef.MarshalAppend(arr[:0]) + mesg := proto.Message{Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(uint32(1000)), + factory.CreateField(mesgnum.Record, fieldnum.RecordDistance).WithValue(uint32(1000)), + factory.CreateField(mesgnum.Record, fieldnum.RecordPositionLat).WithValue(int32(1000)), + factory.CreateField(mesgnum.Record, fieldnum.RecordPositionLong).WithValue(int32(1000)), + factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed).WithValue(uint16(1000)), + factory.CreateField(mesgnum.Record, fieldnum.RecordHeartRate).WithValue(uint8(70)), + factory.CreateField(mesgnum.Record, fieldnum.RecordAltitude).WithValue(uint16(300*5 - 500)), + factory.CreateField(mesgnum.Record, fieldnum.RecordPower).WithValue(uint16(300)), + }} + mesgDef, err := proto.NewMessageDefinition(&mesg) + if err != nil { + b.Fatal(err) } -} - -func BenchmarkMessageMarshalBinary(b *testing.B) { - b.StopTimer() - mesg := factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.RecordPositionLat: proto.Int32(1000), - fieldnum.RecordPositionLong: proto.Int32(1000), - fieldnum.RecordSpeed: proto.Uint16(1000), - }) + buf := make([]byte, 6+len(mesg.Fields)*3+len(mesg.DeveloperFields)*3) b.StartTimer() for i := 0; i < b.N; i++ { - _, _ = mesg.MarshalBinary() + _, _ = mesgDef.MarshalAppend(buf[:0]) } } func BenchmarkMessageMarshalAppend(b *testing.B) { b.StopTimer() - mesg := factory.CreateMesg(mesgnum.Record).WithFieldValues(map[byte]any{ - fieldnum.RecordPositionLat: proto.Int32(1000), - fieldnum.RecordPositionLong: proto.Int32(1000), - fieldnum.RecordSpeed: proto.Uint16(1000), - }) - arr := [proto.MaxBytesPerMessage]byte{} + mesg := proto.Message{Num: mesgnum.Record, Fields: []proto.Field{ + factory.CreateField(mesgnum.Record, fieldnum.RecordTimestamp).WithValue(uint32(1000)), + factory.CreateField(mesgnum.Record, fieldnum.RecordDistance).WithValue(uint32(1000)), + factory.CreateField(mesgnum.Record, fieldnum.RecordPositionLat).WithValue(int32(1000)), + factory.CreateField(mesgnum.Record, fieldnum.RecordPositionLong).WithValue(int32(1000)), + factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed).WithValue(uint16(1000)), + factory.CreateField(mesgnum.Record, fieldnum.RecordHeartRate).WithValue(uint8(70)), + factory.CreateField(mesgnum.Record, fieldnum.RecordAltitude).WithValue(uint16(300*5 - 500)), + factory.CreateField(mesgnum.Record, fieldnum.RecordPower).WithValue(uint16(300)), + }} + + var size = 1 + for i := range mesg.Fields { + size += proto.Sizeof(mesg.Fields[i].Value) + } + buf := make([]byte, size) b.StartTimer() for i := 0; i < b.N; i++ { - _, _ = mesg.MarshalAppend(arr[:0]) + _, err := mesg.MarshalAppend(buf[:0], proto.LittleEndian) + if err != nil { + b.Fatal(err) + } } } diff --git a/proto/proto_test.go b/proto/proto_test.go index 7c77826d..d50da935 100644 --- a/proto/proto_test.go +++ b/proto/proto_test.go @@ -10,7 +10,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/muktihari/fit/factory" - "github.com/muktihari/fit/profile/basetype" "github.com/muktihari/fit/profile/typedef" "github.com/muktihari/fit/profile/untyped/fieldnum" "github.com/muktihari/fit/profile/untyped/mesgnum" @@ -50,23 +49,23 @@ func TestFitWithMessages(t *testing.T) { { name: "withMessages", messages: []proto.Message{ - factory.CreateMesg(mesgnum.Record).WithFields( + {Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed), factory.CreateField(mesgnum.Record, fieldnum.RecordCadence), factory.CreateField(mesgnum.Record, fieldnum.RecordHeartRate), - ), - factory.CreateMesg(mesgnum.Record).WithFields( + }}, + {Num: mesgnum.Record, Fields: []proto.Field{ factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed), factory.CreateField(mesgnum.Record, fieldnum.RecordCadence), factory.CreateField(mesgnum.Record, fieldnum.RecordHeartRate), - ), + }}, }, }, } for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { - fit := new(proto.FIT).WithMessages(tc.messages...) + fit := &proto.FIT{Messages: tc.messages} if diff := cmp.Diff(fit.Messages, tc.messages, cmp.Transformer("Value", func(v proto.Value) any { return v.Any() @@ -78,55 +77,6 @@ func TestFitWithMessages(t *testing.T) { } } -func TestMessageDefinitionClone(t *testing.T) { - mesgDef := proto.MessageDefinition{ - FieldDefinitions: []proto.FieldDefinition{ - {Num: fieldnum.RecordCadence, Size: 1, BaseType: basetype.Uint8}, - {Num: fieldnum.RecordHeartRate, Size: 1, BaseType: basetype.Uint8}, - }, - DeveloperFieldDefinitions: []proto.DeveloperFieldDefinition{ - {Num: 0, DeveloperDataIndex: 0, Size: 1}, - }, - } - - cloned := mesgDef.Clone() - cloned.FieldDefinitions[0].Num = 100 - cloned.DeveloperFieldDefinitions[0].Num = 100 - - if diff := cmp.Diff(mesgDef, cloned); diff == "" { - t.Fatalf("expected deep cloned, but some data still being referenced.") - } -} - -func TestMessageWithFieldValues(t *testing.T) { - tt := []struct { - name string - fieldValues map[byte]any - }{ - { - name: "withFieldValues", - fieldValues: map[byte]any{ - fieldnum.RecordSpeed: uint16(1000), - fieldnum.RecordCadence: uint16(100), - }, - }, - } - - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - mesg := factory.CreateMesg(mesgnum.Record) - mesg.WithFieldValues(tc.fieldValues) - for i := range mesg.Fields { - if value, ok := tc.fieldValues[mesg.Fields[i].Num]; ok { - if mesg.Fields[i].Value.Any() != value { - t.Errorf("expected %T(%v), got: %T(%v)", value, value, mesg.Fields[i].Value, mesg.Fields[i].Value) - } - } - } - }) - } -} - func TestMessageFieldByNum(t *testing.T) { sharedField := factory.CreateField(mesgnum.Event, fieldnum.EventEventType).WithValue(typedef.EventTypeStart) @@ -138,17 +88,17 @@ func TestMessageFieldByNum(t *testing.T) { }{ { name: "FieldByNum found", - mesg: proto.Message{Num: mesgnum.Event}.WithFields( + mesg: proto.Message{Num: mesgnum.Event, Fields: []proto.Field{ sharedField, - ), + }}, fieldNum: fieldnum.EventEventType, field: &sharedField, }, { name: "FieldByNum not found", - mesg: proto.Message{Num: mesgnum.Event}.WithFields( + mesg: proto.Message{Num: mesgnum.Event, Fields: []proto.Field{ sharedField, - ), + }}, fieldNum: fieldnum.EventData, field: nil, }, @@ -177,17 +127,17 @@ func TestMessageFieldValueByNum(t *testing.T) { }{ { name: "FieldValueByNum found", - mesg: proto.Message{Num: mesgnum.Event}.WithFields( + mesg: proto.Message{Num: mesgnum.Event, Fields: []proto.Field{ factory.CreateField(mesgnum.Event, fieldnum.EventEventType).WithValue(typedef.EventTypeStart), - ), + }}, fieldNum: fieldnum.EventEventType, value: proto.Uint8(uint8(typedef.EventTypeStart)), }, { name: "FieldValueByNum not found", - mesg: proto.Message{Num: mesgnum.Event}.WithFields( + mesg: proto.Message{Num: mesgnum.Event, Fields: []proto.Field{ factory.CreateField(mesgnum.Event, fieldnum.EventEventType).WithValue(typedef.EventTypeStart), - ), + }}, fieldNum: fieldnum.EventData, value: proto.Value{}, }, @@ -213,18 +163,18 @@ func TestMessageRemoveFieldByNum(t *testing.T) { }{ { name: "remove existing field", - mesg: proto.Message{Num: mesgnum.Event}.WithFields( + mesg: proto.Message{Num: mesgnum.Event, Fields: []proto.Field{ factory.CreateField(mesgnum.Event, fieldnum.EventEventType).WithValue(typedef.EventTypeStart), - ), + }}, fieldNum: fieldnum.EventEventType, field: nil, size: 0, }, { name: "remove field that is not exist", - mesg: proto.Message{Num: mesgnum.Event}.WithFields( + mesg: proto.Message{Num: mesgnum.Event, Fields: []proto.Field{ factory.CreateField(mesgnum.Event, fieldnum.EventEventType).WithValue(typedef.EventTypeStart), - ), + }}, fieldNum: fieldnum.EventData, field: nil, size: 1, @@ -245,32 +195,6 @@ func TestMessageRemoveFieldByNum(t *testing.T) { } } -func TestMessageClone(t *testing.T) { - mesg := factory.CreateMesg(mesgnum.Session).WithFieldValues(map[byte]any{ - fieldnum.SessionAvgAltitude: proto.Uint16(1000), - fieldnum.SessionAvgSpeed: proto.Uint16(1000), - }).WithDeveloperFields( - proto.DeveloperField{ - Num: 0, - DeveloperDataIndex: 0, - Value: proto.Uint8(1), - }, - proto.DeveloperField{}, - ) - - cloned := mesg.Clone() - cloned.Fields[0].Num = 100 - cloned.DeveloperFields[0].Num = 100 - - if diff := cmp.Diff(mesg, cloned, - cmp.Transformer("Value", func(v proto.Value) any { - return v.Any() - }), - ); diff == "" { - t.Fatalf("expected deep cloned, but some data still being referenced.") - } -} - func TestFieldSubFieldSubtitution(t *testing.T) { tt := []struct { name string @@ -281,26 +205,26 @@ func TestFieldSubFieldSubtitution(t *testing.T) { }{ { name: "SubFieldSubtitution ok, main field can be interpreted.", - mesg: factory.CreateMesg(mesgnum.Event).WithFields( + mesg: proto.Message{Num: mesgnum.Event, Fields: []proto.Field{ factory.CreateField(mesgnum.Event, fieldnum.EventEvent).WithValue(uint8(10)), - ), + }}, field: factory.CreateField(mesgnum.Event, fieldnum.EventData), subfieldName: "course_point_index", ok: true, }, { name: "SubFieldSubtitution not ok, can't interpret main field.", - mesg: factory.CreateMesg(mesgnum.Event).WithFields( + mesg: proto.Message{Num: mesgnum.Event, Fields: []proto.Field{ factory.CreateField(mesgnum.Event, fieldnum.EventEvent).WithValue(uint8(100)), - ), + }}, field: factory.CreateField(mesgnum.Event, fieldnum.EventData), ok: false, }, { name: "SubFieldSubtitution field reference not found", - mesg: factory.CreateMesg(mesgnum.Event).WithFields( + mesg: proto.Message{Num: mesgnum.Event, Fields: []proto.Field{ factory.CreateField(mesgnum.Event, fieldnum.EventActivityType).WithValue(uint8(10)), - ), + }}, field: factory.CreateField(mesgnum.Event, fieldnum.EventData), ok: false, }, @@ -321,202 +245,3 @@ func TestFieldSubFieldSubtitution(t *testing.T) { }) } } - -func TestFieldClone(t *testing.T) { - field := factory.CreateField(mesgnum.Record, fieldnum.RecordSpeed) - - cloned := field.Clone() - cloned.Components[0].Scale = 777 - - if diff := cmp.Diff(field, cloned, - cmp.Transformer("Value", func(v proto.Value) any { - return v.Any() - }), - ); diff == "" { - t.Fatalf("expected deep cloned, but some data still being referenced.") - } - - field = proto.Field{} - cloned = field.Clone() - - if diff := cmp.Diff(field, cloned, - cmp.Transformer("Value", func(v proto.Value) any { - return v.Any() - }), - ); diff != "" { - t.Fatalf("should not changed") - } -} - -func TestCreateMessageDefinition(t *testing.T) { - tt := []struct { - name string - mesg proto.Message - mesgDef proto.MessageDefinition - }{ - { - name: "fields only with non-array values", - mesg: proto.Message{Num: mesgnum.FileId}.WithFields( - factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(typedef.FileActivity), - ), - mesgDef: proto.MessageDefinition{ - Header: proto.MesgDefinitionMask, - MesgNum: mesgnum.FileId, - FieldDefinitions: []proto.FieldDefinition{ - { - Num: fieldnum.FileIdType, - Size: 1, - BaseType: basetype.Enum, - }, - }, - }, - }, - { - name: "fields only with mesg architecture big-endian", - mesg: func() proto.Message { - mesg := proto.Message{Num: mesgnum.FileId}.WithFields( - factory.CreateField(mesgnum.FileId, fieldnum.FileIdType).WithValue(typedef.FileActivity), - ) - mesg.Architecture = 1 // big-endian - return mesg - }(), - mesgDef: proto.MessageDefinition{ - Header: proto.MesgDefinitionMask, - Architecture: 1, // big-endian - MesgNum: mesgnum.FileId, - FieldDefinitions: []proto.FieldDefinition{ - { - Num: fieldnum.FileIdType, - Size: 1, - BaseType: basetype.Enum, - }, - }, - }, - }, - { - name: "fields only with string value", - mesg: proto.Message{Num: mesgnum.FileId}.WithFields( - factory.CreateField(mesgnum.FileId, fieldnum.FileIdProductName).WithValue("FIT SDK Go"), - ), - mesgDef: proto.MessageDefinition{ - Header: proto.MesgDefinitionMask, - MesgNum: mesgnum.FileId, - FieldDefinitions: []proto.FieldDefinition{ - { - Num: fieldnum.FileIdProductName, - Size: 1 * 11, // len("FIT SDK Go") == 10 + '0x00' - BaseType: basetype.String, - }, - }, - }, - }, - { - name: "fields only with array of byte", - mesg: proto.Message{Num: mesgnum.UserProfile}.WithFields( - factory.CreateField(mesgnum.UserProfile, fieldnum.UserProfileGlobalId).WithValue([]byte{2, 9}), - ), - mesgDef: proto.MessageDefinition{ - Header: proto.MesgDefinitionMask, - MesgNum: mesgnum.UserProfile, - FieldDefinitions: []proto.FieldDefinition{ - { - Num: fieldnum.UserProfileGlobalId, - Size: 2, - BaseType: basetype.Byte, - }, - }, - }, - }, - - { - name: "developer fields", - mesg: proto.Message{Num: mesgnum.UserProfile}. - WithFields( - factory.CreateField(mesgnum.UserProfile, fieldnum.UserProfileGlobalId).WithValue([]byte{byte(2), byte(9)})). - WithDeveloperFields( - proto.DeveloperField{ - Num: 0, DeveloperDataIndex: 0, Value: proto.Uint8(1), - }, - ), - mesgDef: proto.MessageDefinition{ - Header: proto.MesgDefinitionMask | proto.DevDataMask, - MesgNum: mesgnum.UserProfile, - FieldDefinitions: []proto.FieldDefinition{ - { - Num: fieldnum.UserProfileGlobalId, - Size: 2, - BaseType: basetype.Byte, - }, - }, - DeveloperFieldDefinitions: []proto.DeveloperFieldDefinition{ - { - Num: 0, Size: 1, DeveloperDataIndex: 0, - }, - }, - }, - }, - { - name: "developer fields with string value \"FIT SDK Go\", size should be 11", - mesg: proto.Message{Num: mesgnum.UserProfile}. - WithFields( - factory.CreateField(mesgnum.UserProfile, fieldnum.UserProfileGlobalId).WithValue([]byte{byte(2), byte(9)})). - WithDeveloperFields( - proto.DeveloperField{ - Num: 0, DeveloperDataIndex: 0, Value: proto.String("FIT SDK Go"), - }, - ), - mesgDef: proto.MessageDefinition{ - Header: proto.MesgDefinitionMask | proto.DevDataMask, - MesgNum: mesgnum.UserProfile, - FieldDefinitions: []proto.FieldDefinition{ - { - Num: fieldnum.UserProfileGlobalId, - Size: 2, - BaseType: basetype.Byte, - }, - }, - DeveloperFieldDefinitions: []proto.DeveloperFieldDefinition{ - { - Num: 0, Size: 11, DeveloperDataIndex: 0, - }, - }, - }, - }, - { - name: "developer fields with value []uint16{1,2,3}, size should be 3*2 = 6", - mesg: proto.Message{Num: mesgnum.UserProfile}. - WithFields( - factory.CreateField(mesgnum.UserProfile, fieldnum.UserProfileGlobalId).WithValue([]byte{byte(2), byte(9)})). - WithDeveloperFields( - proto.DeveloperField{ - Num: 0, DeveloperDataIndex: 0, Value: proto.SliceUint16([]uint16{1, 2, 3}), - }, - ), - mesgDef: proto.MessageDefinition{ - Header: proto.MesgDefinitionMask | proto.DevDataMask, - MesgNum: mesgnum.UserProfile, - FieldDefinitions: []proto.FieldDefinition{ - { - Num: fieldnum.UserProfileGlobalId, - Size: 2, - BaseType: basetype.Byte, - }, - }, - DeveloperFieldDefinitions: []proto.DeveloperFieldDefinition{ - { - Num: 0, Size: 6, DeveloperDataIndex: 0, - }, - }, - }, - }, - } - - for i, tc := range tt { - t.Run(fmt.Sprintf("[%d] %s", i, tc.name), func(t *testing.T) { - mesgDef := proto.CreateMessageDefinition(&tc.mesg) - if diff := cmp.Diff(mesgDef, tc.mesgDef); diff != "" { - t.Fatal(diff) - } - }) - } -} diff --git a/proto/value_marshal.go b/proto/value_marshal.go index 47a877a5..1f87f079 100644 --- a/proto/value_marshal.go +++ b/proto/value_marshal.go @@ -50,7 +50,7 @@ func (v Value) MarshalAppend(b []byte, arch byte) ([]byte, error) { b = append(b, v.SliceUint8()...) return b, nil case TypeInt16: - if arch == littleEndian { + if arch == LittleEndian { b = binary.LittleEndian.AppendUint16(b, uint16(v.num)) } else { b = binary.BigEndian.AppendUint16(b, uint16(v.num)) @@ -58,7 +58,7 @@ func (v Value) MarshalAppend(b []byte, arch byte) ([]byte, error) { return b, nil case TypeSliceInt16: vals := v.SliceInt16() - if arch == littleEndian { + if arch == LittleEndian { for i := range vals { b = binary.LittleEndian.AppendUint16(b, uint16(vals[i])) } @@ -69,7 +69,7 @@ func (v Value) MarshalAppend(b []byte, arch byte) ([]byte, error) { } return b, nil case TypeUint16: - if arch == littleEndian { + if arch == LittleEndian { b = binary.LittleEndian.AppendUint16(b, uint16(v.num)) } else { b = binary.BigEndian.AppendUint16(b, uint16(v.num)) @@ -77,7 +77,7 @@ func (v Value) MarshalAppend(b []byte, arch byte) ([]byte, error) { return b, nil case TypeSliceUint16: vals := v.SliceUint16() - if arch == littleEndian { + if arch == LittleEndian { for i := range vals { b = binary.LittleEndian.AppendUint16(b, vals[i]) } @@ -88,7 +88,7 @@ func (v Value) MarshalAppend(b []byte, arch byte) ([]byte, error) { } return b, nil case TypeInt32: - if arch == littleEndian { + if arch == LittleEndian { b = binary.LittleEndian.AppendUint32(b, uint32(v.num)) } else { b = binary.BigEndian.AppendUint32(b, uint32(v.num)) @@ -96,7 +96,7 @@ func (v Value) MarshalAppend(b []byte, arch byte) ([]byte, error) { return b, nil case TypeSliceInt32: vals := v.SliceInt32() - if arch == littleEndian { + if arch == LittleEndian { for i := range vals { b = binary.LittleEndian.AppendUint32(b, uint32(vals[i])) } @@ -107,7 +107,7 @@ func (v Value) MarshalAppend(b []byte, arch byte) ([]byte, error) { } return b, nil case TypeUint32: - if arch == littleEndian { + if arch == LittleEndian { b = binary.LittleEndian.AppendUint32(b, uint32(v.num)) } else { b = binary.BigEndian.AppendUint32(b, uint32(v.num)) @@ -115,7 +115,7 @@ func (v Value) MarshalAppend(b []byte, arch byte) ([]byte, error) { return b, nil case TypeSliceUint32: vals := v.SliceUint32() - if arch == littleEndian { + if arch == LittleEndian { for i := range vals { b = binary.LittleEndian.AppendUint32(b, vals[i]) } @@ -126,7 +126,7 @@ func (v Value) MarshalAppend(b []byte, arch byte) ([]byte, error) { } return b, nil case TypeInt64: - if arch == littleEndian { + if arch == LittleEndian { b = binary.LittleEndian.AppendUint64(b, uint64(v.num)) } else { b = binary.BigEndian.AppendUint64(b, uint64(v.num)) @@ -134,7 +134,7 @@ func (v Value) MarshalAppend(b []byte, arch byte) ([]byte, error) { return b, nil case TypeSliceInt64: vals := v.SliceInt64() - if arch == littleEndian { + if arch == LittleEndian { for i := range vals { b = binary.LittleEndian.AppendUint64(b, uint64(vals[i])) } @@ -145,7 +145,7 @@ func (v Value) MarshalAppend(b []byte, arch byte) ([]byte, error) { } return b, nil case TypeUint64: - if arch == littleEndian { + if arch == LittleEndian { b = binary.LittleEndian.AppendUint64(b, v.num) } else { b = binary.BigEndian.AppendUint64(b, v.num) @@ -153,7 +153,7 @@ func (v Value) MarshalAppend(b []byte, arch byte) ([]byte, error) { return b, nil case TypeSliceUint64: vals := v.SliceUint64() - if arch == littleEndian { + if arch == LittleEndian { for i := range vals { b = binary.LittleEndian.AppendUint64(b, vals[i]) } @@ -164,7 +164,7 @@ func (v Value) MarshalAppend(b []byte, arch byte) ([]byte, error) { } return b, nil case TypeFloat32: - if arch == littleEndian { + if arch == LittleEndian { b = binary.LittleEndian.AppendUint32(b, uint32(v.num)) } else { b = binary.BigEndian.AppendUint32(b, uint32(v.num)) @@ -172,7 +172,7 @@ func (v Value) MarshalAppend(b []byte, arch byte) ([]byte, error) { return b, nil case TypeSliceFloat32: vals := v.SliceFloat32() - if arch == littleEndian { + if arch == LittleEndian { for i := range vals { b = binary.LittleEndian.AppendUint32(b, math.Float32bits(vals[i])) } @@ -183,7 +183,7 @@ func (v Value) MarshalAppend(b []byte, arch byte) ([]byte, error) { } return b, nil case TypeFloat64: - if arch == littleEndian { + if arch == LittleEndian { b = binary.LittleEndian.AppendUint64(b, v.num) } else { b = binary.BigEndian.AppendUint64(b, v.num) @@ -191,7 +191,7 @@ func (v Value) MarshalAppend(b []byte, arch byte) ([]byte, error) { return b, nil case TypeSliceFloat64: vals := v.SliceFloat64() - if arch == littleEndian { + if arch == LittleEndian { for i := range vals { b = binary.LittleEndian.AppendUint64(b, math.Float64bits(vals[i])) } diff --git a/proto/value_marshal_test.go b/proto/value_marshal_test.go index 4b2fa9e3..81493cab 100644 --- a/proto/value_marshal_test.go +++ b/proto/value_marshal_test.go @@ -14,70 +14,63 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/muktihari/fit/internal/kit" ) func TestValueMarshalAppend(t *testing.T) { tt := []struct { - b *[]byte value Value err error }{ - {b: kit.Ptr([]byte{}), value: Bool(false)}, - {b: kit.Ptr([]byte{}), value: Bool(true)}, - {b: kit.Ptr([]byte{}), value: Int8(-19)}, - {b: kit.Ptr([]byte{}), value: Uint8(129)}, - {b: kit.Ptr([]byte{}), value: Int16(1429)}, - {b: kit.Ptr([]byte{}), value: Int16(-429)}, - {b: kit.Ptr([]byte{}), value: Uint16(9929)}, - {b: kit.Ptr([]byte{}), value: Int32(819293429)}, - {b: kit.Ptr([]byte{}), value: Int32(-8979123)}, - {b: kit.Ptr([]byte{}), value: Uint32(9929)}, - {b: kit.Ptr([]byte{}), value: Int64(819293429)}, - {b: kit.Ptr([]byte{}), value: Int64(-8979123)}, - {b: kit.Ptr([]byte{}), value: Uint64(9929)}, - {b: kit.Ptr([]byte{}), value: Float32(819293429.192321)}, - {b: kit.Ptr([]byte{}), value: Float32(-8979123.546734)}, - {b: kit.Ptr([]byte{}), value: Float64(8192934298908979.192321)}, - {b: kit.Ptr([]byte{}), value: Float64(-897912398989898.546734)}, - {b: kit.Ptr([]byte{}), value: String("FIT SDK")}, - {b: kit.Ptr([]byte{}), value: String("")}, - {b: kit.Ptr([]byte{}), value: SliceBool([]bool{true, false})}, - {b: kit.Ptr([]byte{}), value: SliceUint8([]byte{1, 2})}, - {b: kit.Ptr([]byte{}), value: SliceUint8([]uint8{1, 2})}, - {b: kit.Ptr([]byte{}), value: SliceInt8([]int8{-19})}, - {b: kit.Ptr([]byte{}), value: SliceUint8([]uint8{129})}, - {b: kit.Ptr([]byte{}), value: SliceInt16([]int16{1429})}, - {b: kit.Ptr([]byte{}), value: SliceInt16([]int16{-429})}, - {b: kit.Ptr([]byte{}), value: SliceUint16([]uint16{9929})}, - {b: kit.Ptr([]byte{}), value: SliceInt32([]int32{819293429})}, - {b: kit.Ptr([]byte{}), value: SliceInt32([]int32{-8979123})}, - {b: kit.Ptr([]byte{}), value: SliceUint32([]uint32{9929})}, - {b: kit.Ptr([]byte{}), value: SliceString([]string{"supported"})}, - {b: kit.Ptr([]byte{}), value: SliceString([]string{})}, - {b: kit.Ptr([]byte{}), value: SliceString([]string{""})}, - {b: kit.Ptr([]byte{}), value: SliceString([]string{"\x00"})}, - {b: kit.Ptr([]byte{}), value: SliceString([]string{"\x00", "\x00"})}, - {b: kit.Ptr([]byte{}), value: SliceString([]string{string([]byte{'\x00'})})}, - {b: kit.Ptr([]byte{}), value: SliceInt64([]int64{819293429})}, - {b: kit.Ptr([]byte{}), value: SliceInt64([]int64{-8979123})}, - {b: kit.Ptr([]byte{}), value: SliceUint64([]uint64{9929})}, - {b: kit.Ptr([]byte{}), value: SliceFloat32([]float32{819293429.192321})}, - {b: kit.Ptr([]byte{}), value: SliceFloat32([]float32{-8979123.546734})}, - {b: kit.Ptr([]byte{}), value: SliceFloat64([]float64{8192934298908979.192321})}, - {b: kit.Ptr([]byte{}), value: SliceFloat64([]float64{-897912398989898.546734})}, - {b: kit.Ptr([]byte{}), value: Value{}, err: ErrTypeNotSupported}, + {value: Bool(false)}, + {value: Bool(true)}, + {value: Int8(-19)}, + {value: Uint8(129)}, + {value: Int16(1429)}, + {value: Int16(-429)}, + {value: Uint16(9929)}, + {value: Int32(819293429)}, + {value: Int32(-8979123)}, + {value: Uint32(9929)}, + {value: Int64(819293429)}, + {value: Int64(-8979123)}, + {value: Uint64(9929)}, + {value: Float32(819293429.192321)}, + {value: Float32(-8979123.546734)}, + {value: Float64(8192934298908979.192321)}, + {value: Float64(-897912398989898.546734)}, + {value: String("FIT SDK")}, + {value: String("")}, + {value: SliceBool([]bool{true, false})}, + {value: SliceUint8([]byte{1, 2})}, + {value: SliceUint8([]uint8{1, 2})}, + {value: SliceInt8([]int8{-19})}, + {value: SliceUint8([]uint8{129})}, + {value: SliceInt16([]int16{1429})}, + {value: SliceInt16([]int16{-429})}, + {value: SliceUint16([]uint16{9929})}, + {value: SliceInt32([]int32{819293429})}, + {value: SliceInt32([]int32{-8979123})}, + {value: SliceUint32([]uint32{9929})}, + {value: SliceString([]string{"supported"})}, + {value: SliceString([]string{})}, + {value: SliceString([]string{""})}, + {value: SliceString([]string{"\x00"})}, + {value: SliceString([]string{"\x00", "\x00"})}, + {value: SliceString([]string{string([]byte{'\x00'})})}, + {value: SliceInt64([]int64{819293429})}, + {value: SliceInt64([]int64{-8979123})}, + {value: SliceUint64([]uint64{9929})}, + {value: SliceFloat32([]float32{819293429.192321})}, + {value: SliceFloat32([]float32{-8979123.546734})}, + {value: SliceFloat64([]float64{8192934298908979.192321})}, + {value: SliceFloat64([]float64{-897912398989898.546734})}, + {value: Value{}, err: ErrTypeNotSupported}, } for i, tc := range tt { for arch := byte(0); arch <= 1; arch++ { t.Run(fmt.Sprintf("[%d] %T(%v))", i, tc.value.Any(), tc.value.Any()), func(t *testing.T) { - arr := pool.Get().(*[MaxBytesPerMessage]byte) - defer pool.Put(arr) - b := arr[:0] - - var err error - *tc.b, err = tc.value.MarshalAppend(b, arch) + b, err := tc.value.MarshalAppend(nil, arch) if !errors.Is(err, tc.err) { t.Fatalf("expected err: %v, got: %v", tc.err, err) } @@ -90,12 +83,12 @@ func TestValueMarshalAppend(t *testing.T) { t.Fatalf("marshalWithReflectionForTest: %v", err) } - if len(*tc.b) == 0 && len(buf.Bytes()) == 0 { + if len(b) == 0 && len(buf.Bytes()) == 0 { return } - if diff := cmp.Diff(*tc.b, buf.Bytes()); diff != "" { - fmt.Printf("value: %v, b: %v, buf: %v\n", tc.value.Any(), *tc.b, buf.Bytes()) + if diff := cmp.Diff(b, buf.Bytes()); diff != "" { + fmt.Printf("value: %v, b: %v, buf: %v\n", tc.value.Any(), b, buf.Bytes()) t.Fatal(diff) } @@ -162,6 +155,6 @@ func BenchmarkValueMarshalAppend(b *testing.B) { b.StartTimer() for i := 0; i < b.N; i++ { - _, _ = value.MarshalAppend(buf, littleEndian) + _, _ = value.MarshalAppend(buf, LittleEndian) } } diff --git a/proto/value_unmarshal.go b/proto/value_unmarshal.go index ca0d31e0..0cc5e17d 100755 --- a/proto/value_unmarshal.go +++ b/proto/value_unmarshal.go @@ -49,7 +49,7 @@ func UnmarshalValue(b []byte, arch byte, baseType basetype.BaseType, profileType if isArray { const n = 2 vals := make([]int16, 0, len(b)/n) - if arch == littleEndian { + if arch == LittleEndian { for ; len(b) >= n; b = b[n:] { vals = append(vals, int16(binary.LittleEndian.Uint16(b[:n]))) } @@ -60,7 +60,7 @@ func UnmarshalValue(b []byte, arch byte, baseType basetype.BaseType, profileType } return SliceInt16(vals), nil } - if arch == littleEndian { + if arch == LittleEndian { return Int16(int16(binary.LittleEndian.Uint16(b))), nil } return Int16(int16(binary.BigEndian.Uint16(b))), nil @@ -68,7 +68,7 @@ func UnmarshalValue(b []byte, arch byte, baseType basetype.BaseType, profileType if isArray { const n = 2 vals := make([]uint16, 0, len(b)/n) - if arch == littleEndian { + if arch == LittleEndian { for ; len(b) >= n; b = b[n:] { vals = append(vals, binary.LittleEndian.Uint16(b[:n])) } @@ -79,7 +79,7 @@ func UnmarshalValue(b []byte, arch byte, baseType basetype.BaseType, profileType } return SliceUint16(vals), nil } - if arch == littleEndian { + if arch == LittleEndian { return Uint16(binary.LittleEndian.Uint16(b)), nil } return Uint16(binary.BigEndian.Uint16(b)), nil @@ -87,7 +87,7 @@ func UnmarshalValue(b []byte, arch byte, baseType basetype.BaseType, profileType if isArray { const n = 4 vals := make([]int32, 0, len(b)/n) - if arch == littleEndian { + if arch == LittleEndian { for ; len(b) >= n; b = b[n:] { vals = append(vals, int32(binary.LittleEndian.Uint32(b[:n]))) } @@ -98,7 +98,7 @@ func UnmarshalValue(b []byte, arch byte, baseType basetype.BaseType, profileType } return SliceInt32(vals), nil } - if arch == littleEndian { + if arch == LittleEndian { return Int32(int32(binary.LittleEndian.Uint32(b))), nil } return Int32(int32(binary.BigEndian.Uint32(b))), nil @@ -106,7 +106,7 @@ func UnmarshalValue(b []byte, arch byte, baseType basetype.BaseType, profileType if isArray { const n = 4 vals := make([]uint32, 0, len(b)/n) - if arch == littleEndian { + if arch == LittleEndian { for ; len(b) >= n; b = b[n:] { vals = append(vals, binary.LittleEndian.Uint32(b[:n])) } @@ -117,7 +117,7 @@ func UnmarshalValue(b []byte, arch byte, baseType basetype.BaseType, profileType } return SliceUint32(vals), nil } - if arch == littleEndian { + if arch == LittleEndian { return Uint32(binary.LittleEndian.Uint32(b)), nil } return Uint32(binary.BigEndian.Uint32(b)), nil @@ -125,7 +125,7 @@ func UnmarshalValue(b []byte, arch byte, baseType basetype.BaseType, profileType if isArray { const n = 8 vals := make([]int64, 0, len(b)/n) - if arch == littleEndian { + if arch == LittleEndian { for ; len(b) >= n; b = b[n:] { vals = append(vals, int64(binary.LittleEndian.Uint64(b[:n]))) } @@ -136,7 +136,7 @@ func UnmarshalValue(b []byte, arch byte, baseType basetype.BaseType, profileType } return SliceInt64(vals), nil } - if arch == littleEndian { + if arch == LittleEndian { return Int64(int64(binary.LittleEndian.Uint64(b))), nil } return Int64(int64(binary.BigEndian.Uint64(b))), nil @@ -144,7 +144,7 @@ func UnmarshalValue(b []byte, arch byte, baseType basetype.BaseType, profileType if isArray { const n = 8 vals := make([]uint64, 0, len(b)/n) - if arch == littleEndian { + if arch == LittleEndian { for ; len(b) >= n; b = b[n:] { vals = append(vals, binary.LittleEndian.Uint64(b[:n])) } @@ -155,7 +155,7 @@ func UnmarshalValue(b []byte, arch byte, baseType basetype.BaseType, profileType } return SliceUint64(vals), nil } - if arch == littleEndian { + if arch == LittleEndian { return Uint64(binary.LittleEndian.Uint64(b)), nil } return Uint64(binary.BigEndian.Uint64(b)), nil @@ -163,7 +163,7 @@ func UnmarshalValue(b []byte, arch byte, baseType basetype.BaseType, profileType if isArray { const n = 4 vals := make([]float32, 0, len(b)/n) - if arch == littleEndian { + if arch == LittleEndian { for ; len(b) >= n; b = b[n:] { vals = append(vals, math.Float32frombits(binary.LittleEndian.Uint32(b[:n]))) } @@ -174,7 +174,7 @@ func UnmarshalValue(b []byte, arch byte, baseType basetype.BaseType, profileType } return SliceFloat32(vals), nil } - if arch == littleEndian { + if arch == LittleEndian { return Float32(math.Float32frombits(binary.LittleEndian.Uint32(b))), nil } return Float32(math.Float32frombits(binary.BigEndian.Uint32(b))), nil @@ -182,7 +182,7 @@ func UnmarshalValue(b []byte, arch byte, baseType basetype.BaseType, profileType if isArray { const n = 8 vals := make([]float64, 0, len(b)/n) - if arch == littleEndian { + if arch == LittleEndian { for ; len(b) >= n; b = b[n:] { vals = append(vals, math.Float64frombits(binary.LittleEndian.Uint64(b[:n]))) } @@ -193,7 +193,7 @@ func UnmarshalValue(b []byte, arch byte, baseType basetype.BaseType, profileType } return SliceFloat64(vals), nil } - if arch == littleEndian { + if arch == LittleEndian { return Float64(math.Float64frombits(binary.LittleEndian.Uint64(b))), nil } return Float64(math.Float64frombits(binary.BigEndian.Uint64(b))), nil diff --git a/proto/version.go b/proto/version.go index 6ff10b76..9f7dea2d 100644 --- a/proto/version.go +++ b/proto/version.go @@ -29,19 +29,19 @@ func CreateVersion(major, minor byte) (Version, bool) { } // Validate checks whether given version is a valid version. -func Validate(version byte) error { - if VersionMajor(version) > VersionMajor(byte(Vmax)) { +func Validate(version Version) error { + if VersionMajor(version) > VersionMajor(Vmax) { return ErrProtocolVersionNotSupported } return nil } // VersionMajor returns major value of given version -func VersionMajor(version byte) byte { - return version >> MajorVersionShift +func VersionMajor(version Version) byte { + return byte(version >> MajorVersionShift) } // VersionMinor returns minor value of given version -func VersionMinor(version byte) byte { - return version & MinorVersionMask +func VersionMinor(version Version) byte { + return byte(version & MinorVersionMask) } diff --git a/proto/version_test.go b/proto/version_test.go index ae2e83e4..9180ed38 100644 --- a/proto/version_test.go +++ b/proto/version_test.go @@ -52,7 +52,7 @@ func TestCreateVersion(t *testing.T) { func TestValidateVersion(t *testing.T) { tt := []struct { - version byte + version proto.Version err error }{ {version: 32, err: nil},