Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: tcx decode unexpected error due to improper HR parse handling #46

Merged
merged 1 commit into from
Nov 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/wasm/activity-service/activity/tcx/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ func NewRecord(trackpoint *schema.Trackpoint, prevRec *activity.Record) *activit
rec.PositionLong = &trackpoint.Position.LongitudeDegrees
}

if trackpoint.Extensions != nil {
if trackpoint.Extensions.Speed != nil {
rec.Speed = trackpoint.Extensions.Speed
}
}

var pointDistance float64
if prevRec != nil && prevRec.Distance != nil && rec.Distance != nil {
pointDistance = *rec.Distance - *prevRec.Distance
Expand Down
9 changes: 5 additions & 4 deletions src/wasm/activity-service/activity/tcx/schema/activity.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package schema

import (
"encoding/xml"
"fmt"
"time"
)

Expand All @@ -24,7 +25,7 @@ func (a *ActivityList) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error
case "Activity":
var activity Activity
if err := activity.UnmarshalXML(dec, elem); err != nil {
return err
return fmt.Errorf("unmarshal Activity: %w", err)
}
a.Activity = &activity
}
Expand Down Expand Up @@ -69,13 +70,13 @@ func (a *Activity) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error {
case "Lap":
var activityLap ActivityLap
if err := activityLap.UnmarshalXML(dec, elem); err != nil {
return err
return fmt.Errorf("unmarshal Lap: %w", err)
}
a.Laps = append(a.Laps, activityLap)
case "Creator":
var device Device
if err := device.UnmarshalXML(dec, elem); err != nil {
return err
return fmt.Errorf("unmarshal Creator: %w", err)
}
a.Creator = &device
default:
Expand All @@ -86,7 +87,7 @@ func (a *Activity) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error {
case "Id":
t, err := time.Parse(time.RFC3339, string(elem))
if err != nil {
return err
return fmt.Errorf("parse Id: %w", err)
}
a.ID = t
case "Notes":
Expand Down
23 changes: 12 additions & 11 deletions src/wasm/activity-service/activity/tcx/schema/author.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package schema

import (
"encoding/xml"
"fmt"
"strconv"
)

Expand All @@ -28,7 +29,7 @@ func (a *Application) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error
case "Build":
var build Build
if err := build.UnmarshalXML(dec, elem); err != nil {
return err
return fmt.Errorf("unmarshal Build: %w", err)
}
a.Build = &build

Expand Down Expand Up @@ -74,7 +75,7 @@ func (b *Build) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error {
case "Version":
var version Version
if err := version.UnmarshalXML(dec, elem); err != nil {
return err
return fmt.Errorf("unmarshal Version: %w", err)
}
b.Version = &version
default:
Expand Down Expand Up @@ -106,7 +107,7 @@ const (
type Device struct {
Name string `xml:"Name"`
UnitId uint32 `xml:"UnitId"`
ProductID uint16 `xml:"ProductID"`
ProductID uint16 `xml:"ProductId"`
Version *Version `xml:"Version"`
}

Expand All @@ -126,7 +127,7 @@ func (d *Device) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error {
case "Version":
var version Version
if err := version.UnmarshalXML(dec, elem); err != nil {
return err
return fmt.Errorf("unmarshal Version: %w", err)
}
d.Version = &version

Expand All @@ -140,13 +141,13 @@ func (d *Device) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error {
case "UnitId":
u, err := strconv.ParseUint(string(elem), 10, 32)
if err != nil {
return err
return fmt.Errorf("parse UnitId: %w", err)
}
d.UnitId = uint32(u)
case "ProductID":
case "ProductId":
u, err := strconv.ParseUint(string(elem), 10, 16)
if err != nil {
return err
return fmt.Errorf("parse ProductId: %w", err)
}
d.ProductID = uint16(u)
}
Expand Down Expand Up @@ -184,25 +185,25 @@ func (v *Version) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error {
case "VersionMajor":
u, err := strconv.ParseUint(string(elem), 10, 16)
if err != nil {
return err
return fmt.Errorf("parse VersionMajor: %w", err)
}
v.VersionMajor = uint16(u)
case "VersionMinor":
u, err := strconv.ParseUint(string(elem), 10, 16)
if err != nil {
return err
return fmt.Errorf("parse VersionMinor: %w", err)
}
v.VersionMinor = uint16(u)
case "BuildMajor":
u, err := strconv.ParseUint(string(elem), 10, 16)
if err != nil {
return err
return fmt.Errorf("parse BuildMajor: %w", err)
}
v.BuildMajor = uint16(u)
case "BuildMinor":
u, err := strconv.ParseUint(string(elem), 10, 16)
if err != nil {
return err
return fmt.Errorf("parse BuildMinor: %w", err)
}
v.BuildMinor = uint16(u)
}
Expand Down
25 changes: 16 additions & 9 deletions src/wasm/activity-service/activity/tcx/schema/lap.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package schema

import (
"encoding/xml"
"fmt"
"strconv"
"time"

Expand Down Expand Up @@ -33,7 +34,7 @@ func (a *ActivityLap) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error
case "StartTime":
t, err := time.Parse(time.RFC3339, attr.Value)
if err != nil {
return err
return fmt.Errorf("parse StartTime: %w", err)
}
a.StartTime = t
}
Expand All @@ -52,9 +53,11 @@ func (a *ActivityLap) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error
case "Track":
var track Track
if err := track.UnmarshalXML(dec, elem); err != nil {
return err
return fmt.Errorf("unmarshal Track: %w", err)
}
a.Tracks = append(a.Tracks, track)
case "Value":
targetCharData = targetCharData + "Value"
default:
targetCharData = elem.Name.Local
}
Expand All @@ -63,45 +66,49 @@ func (a *ActivityLap) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error
case "TotalTimeSeconds":
f, err := strconv.ParseFloat(string(elem), 64)
if err != nil {
return err
return fmt.Errorf("parse TotalTimeSeconds: %w", err)
}
a.TotalTimeSeconds = f
case "DistanceMeters":
f, err := strconv.ParseFloat(string(elem), 64)
if err != nil {
return err
return fmt.Errorf("parse DistanceMeters: %w", err)
}
a.DistanceMeters = f
case "MaximumSpeed":
f, err := strconv.ParseFloat(string(elem), 64)
if err != nil {
return err
return fmt.Errorf("parse MaximumSpeed: %w", err)
}
a.MaximumSpeed = &f
case "Calories":
u, err := strconv.ParseUint(string(elem), 10, 16)
if err != nil {
return err
return fmt.Errorf("parse Calories: %w", err)
}
a.Calories = uint16(u)
case "AverageHeartRateBpm":
continue
case "AverageHeartRateBpmValue":
u, err := strconv.ParseUint(string(elem), 10, 8)
if err != nil {
return err
return fmt.Errorf("parse AverageHeartRateBpm: %w", err)
}
a.AverageHeartRateBpm = kit.Ptr(uint8(u))
case "MaximumHeartRateBpm":
continue
case "MaximumHeartRateBpmValue":
u, err := strconv.ParseUint(string(elem), 10, 8)
if err != nil {
return err
return fmt.Errorf("parse MaximumHeartRateBpm: %w", err)
}
a.MaximumHeartRateBpm = kit.Ptr(uint8(u))
case "Intensity":
a.Intensity = Intensity(elem)
case "Cadence":
u, err := strconv.ParseUint(string(elem), 10, 8)
if err != nil {
return err
return fmt.Errorf("parse Cadence: %w", err)
}
a.Cadence = kit.Ptr(uint8(u))
case "TriggerMethod":
Expand Down
20 changes: 18 additions & 2 deletions src/wasm/activity-service/activity/tcx/schema/tcx.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,24 @@ package schema

import (
"encoding/xml"
"fmt"
)

// TODO: for implementing xml.Marshaler
const (
xmlns = "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2"
xmlnsxsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlnsns5 = "http://www.garmin.com/xmlschemas/ActivityGoals/v1"
xmlnsns3 = "http://www.garmin.com/xmlschemas/ActivityExtension/v2"
xmlnsns2 = "http://www.garmin.com/xmlschemas/UserProfile/v2"
xmlnstpx = "http://www.garmin.com/xmlschemas/GpxExtensions/v3"
)

var schemaLocations = [...]string{
"http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2",
"http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd",
}

// TCX simplified schema.
type TCX struct {
Activities []ActivityList `xml:"Activities,omitempty"`
Expand All @@ -25,13 +41,13 @@ func (t *TCX) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error {
case "Activities":
var al ActivityList
if err := al.UnmarshalXML(dec, elem); err != nil {
return err
return fmt.Errorf("unmarshal Activities: %w", err)
}
t.Activities = append(t.Activities, al)
case "Author":
var application Application
if err := application.UnmarshalXML(dec, elem); err != nil {
return err
return fmt.Errorf("unmarshal Author: %w", err)
}
t.Author = &application
}
Expand Down
66 changes: 53 additions & 13 deletions src/wasm/activity-service/activity/tcx/schema/track.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (t *Track) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error {
case "Trackpoint":
var trackpoint Trackpoint
if err := trackpoint.UnmarshalXML(dec, elem); err != nil {
return err
return fmt.Errorf("unmarshal Trackpoint: %w", err)
}
t.Trackpoints = append(t.Trackpoints, trackpoint)
}
Expand All @@ -41,13 +41,14 @@ func (t *Track) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error {
}

type Trackpoint struct {
Time time.Time `xml:"Time"`
Position *Position `xml:"Position,omitempty"`
AltitudeMeters *float64 `xml:"AltitudeMeters,omitempty"`
DistanceMeters *float64 `xml:"DistanceMeters,omitempty"`
HeartRateBpm *uint8 `xml:"HeartRateBpm,omitempty"`
Cadence *uint8 `xml:"Cadence,omitempty"`
SensorState SensorState `xml:"SensorState,omitempty"`
Time time.Time `xml:"Time"`
Position *Position `xml:"Position,omitempty"`
AltitudeMeters *float64 `xml:"AltitudeMeters,omitempty"`
DistanceMeters *float64 `xml:"DistanceMeters,omitempty"`
HeartRateBpm *uint8 `xml:"HeartRateBpm,omitempty"`
Cadence *uint8 `xml:"Cadence,omitempty"`
SensorState SensorState `xml:"SensorState,omitempty"`
Extensions *TrackpointExtension `xml:"Extensions>TPX,omitempty"`
}

var _ xml.Unmarshaler = &Trackpoint{}
Expand All @@ -66,13 +67,17 @@ func (tp *Trackpoint) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error
case "Position":
var position Position
if err := position.UnmarshalXML(dec, elem); err != nil {
return err
return fmt.Errorf("unmarshal Position: %w", err)
}
tp.Position = &position
case "Value":
if targetCharData == "HeartRateBpm" { // check prev value
targetCharData = "HeartRateBpmValue"
targetCharData = targetCharData + "Value"
case "Extensions":
var trackpointExtension TrackpointExtension
if err := trackpointExtension.UnmarshalXML(dec, elem); err != nil {
return fmt.Errorf("unmarshal Extensions: %w", err)
}
tp.Extensions = &trackpointExtension
default:
targetCharData = elem.Name.Local
}
Expand All @@ -96,6 +101,8 @@ func (tp *Trackpoint) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error
return fmt.Errorf("parse DistanceMeters %q: %w", elem, err)
}
tp.DistanceMeters = &f
case "HeartRateBpm":
continue
case "HeartRateBpmValue":
u, err := strconv.ParseUint(string(elem), 10, 8)
if err != nil {
Expand Down Expand Up @@ -143,13 +150,13 @@ func (p *Position) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error {
case "LatitudeDegrees":
f, err := strconv.ParseFloat(string(elem), 64)
if err != nil {
return err
return fmt.Errorf("parse LatitudeDegrees: %w", err)
}
p.LatitudeDegrees = f
case "LongitudeDegrees":
f, err := strconv.ParseFloat(string(elem), 64)
if err != nil {
return err
return fmt.Errorf("parse LongitudeDegrees: %w", err)
}
p.LongitudeDegrees = f
}
Expand All @@ -168,3 +175,36 @@ const (
SensorStatePresent SensorState = "Present"
SensorStateAbsent SensorState = "Absent"
)

type TrackpointExtension struct {
Speed *float64 `xml:"Speed,omitempty"`
}

func (tpe *TrackpointExtension) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error {
var targetCharData string
for {
token, err := dec.Token()
if err != nil {
return err
}

switch elem := token.(type) {
case xml.StartElement:
targetCharData = elem.Name.Local
case xml.CharData:
switch targetCharData {
case "Speed":
f, err := strconv.ParseFloat(string(elem), 64)
if err != nil {
return fmt.Errorf("parse Speed: %w", err)
}
tpe.Speed = &f
}
targetCharData = ""
case xml.EndElement:
if elem == se.End() {
return nil
}
}
}
}
Loading