Skip to content

Commit

Permalink
add ParseTagBlock func
Browse files Browse the repository at this point in the history
  • Loading branch information
icholy committed Jul 6, 2024
1 parent a60cdb4 commit 7295c89
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 67 deletions.
29 changes: 6 additions & 23 deletions sentence.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,31 +110,14 @@ func (p *SentenceParser) parseBaseSentence(raw string) (BaseSentence, error) {
if raw == "" {
return BaseSentence{}, errors.New("nmea: can not parse empty input")
}

var (
tagBlock TagBlock
err error
)

if startOfTagBlock := strings.IndexByte(raw, TagBlockSep); startOfTagBlock != -1 {
// tag block is always at the start of line (unless IEC 61162-450). Starts with `\` and ends with `\` and has valid sentence
// following or <CR><LF>
//
// Note: tag block group can span multiple lines but we only parse ones that have sentence
endOfTagBlock := strings.LastIndexByte(raw, TagBlockSep)
if endOfTagBlock <= startOfTagBlock {
return BaseSentence{}, fmt.Errorf("nmea: sentence tag block is missing '\\' at the end")
}
tagBlock, err = parseTagBlock(raw[startOfTagBlock+1 : endOfTagBlock])
if err != nil {
tagBlock, raw, ok, err := ParseTagBlock(raw)
if err != nil {
return BaseSentence{}, err
}
if ok && p.OnTagBlock != nil {
if err := p.OnTagBlock(tagBlock); err != nil {
return BaseSentence{}, err
}
if p.OnTagBlock != nil {
if err := p.OnTagBlock(tagBlock); err != nil {
return BaseSentence{}, err
}
}
raw = raw[endOfTagBlock+1:]
}

startIndex := strings.IndexAny(raw, SentenceStart+SentenceStartEncapsulated)
Expand Down
50 changes: 32 additions & 18 deletions tagblock.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,27 @@ type TagBlock struct {
Text string // TypeTextString valid character string, parameter -t
}

func parseInt64(raw string) (int64, error) {
i, err := strconv.ParseInt(raw, 10, 64)
if err != nil {
return 0, fmt.Errorf("nmea: tagblock unable to parse uint64 [%s]", raw)
// ParseTagBlock parses tag blocks from a sentence string.
// The second return value contains the sentence with tag block removed.
// The third return value indicates if a tag block was found.
// See: https://gpsd.gitlab.io/gpsd/AIVDM.html#_nmea_tag_blocks
func ParseTagBlock(raw string) (TagBlock, string, bool, error) {
startOfTagBlock := strings.IndexByte(raw, TagBlockSep)
if startOfTagBlock == -1 {
return TagBlock{}, raw, false, nil
}
return i, nil
}

// parseTagBlock adds support for tagblocks
// https://gpsd.gitlab.io/gpsd/AIVDM.html#_nmea_tag_blocks
func parseTagBlock(tags string) (TagBlock, error) {
// tag block is always at the start of line (unless IEC 61162-450). Starts with `\` and ends with `\` and has valid sentence
// following or <CR><LF>
//
// Note: tag block group can span multiple lines but we only parse ones that have sentence
endOfTagBlock := strings.LastIndexByte(raw, TagBlockSep)
if endOfTagBlock <= startOfTagBlock {
return TagBlock{}, "", false, fmt.Errorf("nmea: sentence tag block is missing '\\' at the end")
}
tags := raw[startOfTagBlock+1 : endOfTagBlock]
sumSepIndex := strings.Index(tags, ChecksumSep)
if sumSepIndex == -1 {
return TagBlock{}, fmt.Errorf("nmea: tagblock does not contain checksum separator")
return TagBlock{}, "", false, fmt.Errorf("nmea: tagblock does not contain checksum separator")
}

var (
Expand All @@ -43,22 +50,21 @@ func parseTagBlock(tags string) (TagBlock, error) {

// Validate the checksum
if checksum != checksumRaw {
return TagBlock{}, fmt.Errorf("nmea: tagblock checksum mismatch [%s != %s]", checksum, checksumRaw)
return TagBlock{}, "", false, fmt.Errorf("nmea: tagblock checksum mismatch [%s != %s]", checksum, checksumRaw)
}

items := strings.Split(tags[:sumSepIndex], ",")
for _, item := range items {
parts := strings.SplitN(item, ":", 2)
if len(parts) != 2 {
return TagBlock{},
fmt.Errorf("nmea: tagblock field is malformed (should be <key>:<value>) [%s]", item)
return TagBlock{}, "", false, fmt.Errorf("nmea: tagblock field is malformed (should be <key>:<value>) [%s]", item)
}
key, value := parts[0], parts[1]
switch key {
case "c": // UNIX timestamp
tagBlock.Time, err = parseInt64(value)
if err != nil {
return TagBlock{}, err
return TagBlock{}, "", false, err
}
case "d": // Destination ID
tagBlock.Destination = value
Expand All @@ -67,18 +73,26 @@ func parseTagBlock(tags string) (TagBlock, error) {
case "n": // Line count
tagBlock.LineCount, err = parseInt64(value)
if err != nil {
return TagBlock{}, err
return TagBlock{}, "", false, err
}
case "r": // Relative time
tagBlock.RelativeTime, err = parseInt64(value)
if err != nil {
return TagBlock{}, err
return TagBlock{}, "", false, err
}
case "s": // Source ID
tagBlock.Source = value
case "t": // Text string
tagBlock.Text = value
}
}
return tagBlock, nil
return tagBlock, raw[endOfTagBlock+1:], true, nil
}

func parseInt64(raw string) (int64, error) {
i, err := strconv.ParseInt(raw, 10, 64)
if err != nil {
return 0, fmt.Errorf("nmea: tagblock unable to parse uint64 [%s]", raw)
}
return i, nil
}
72 changes: 46 additions & 26 deletions tagblock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,60 +7,72 @@ import (
)

var tagblocktests = []struct {
name string
raw string
err string
msg TagBlock
name string
raw string
err string
block TagBlock
ok bool
remaining string
}{
{

name: "Test NMEA tag block",
raw: "s:Satelite_1,c:1553390539*62",
msg: TagBlock{
raw: "\\s:Satelite_1,c:1553390539*62\\!AIVDM,1,2,3",
block: TagBlock{
Time: 1553390539,
Source: "Satelite_1",
},
ok: true,
remaining: "!AIVDM,1,2,3",
},
{

name: "Test NMEA tag block with head",
raw: "s:satelite,c:1564827317*25",
msg: TagBlock{
raw: "\\s:satelite,c:1564827317*25\\!AIVDM,1,2,3",
block: TagBlock{
Time: 1564827317,
Source: "satelite",
},
ok: true,
remaining: "!AIVDM,1,2,3",
},
{

name: "Test unknown tag",
raw: "x:NorSat_1,c:1564827317*42",
msg: TagBlock{
raw: "\\x:NorSat_1,c:1564827317*42\\!AIVDM,1,2,3",
block: TagBlock{
Time: 1564827317,
Source: "",
},
ok: true,
remaining: "!AIVDM,1,2,3",
},
{
name: "Test unix timestamp",
raw: "x:NorSat_1,c:1564827317*42",
msg: TagBlock{
raw: "\\x:NorSat_1,c:1564827317*42\\!AIVDM,1,2,3",
block: TagBlock{
Time: 1564827317,
Source: "",
},
ok: true,
remaining: "!AIVDM,1,2,3",
},
{

name: "Test milliseconds timestamp",
raw: "x:NorSat_1,c:1564827317000*72",
msg: TagBlock{
raw: "\\x:NorSat_1,c:1564827317000*72\\!AIVDM,1,2,3",
block: TagBlock{
Time: 1564827317000,
Source: "",
},
ok: true,
remaining: "!AIVDM,1,2,3",
},
{

name: "Test all input types",
raw: "s:satelite,c:1564827317,r:1553390539,d:ara,g:bulk,n:13,t:helloworld*3F",
msg: TagBlock{
raw: "\\s:satelite,c:1564827317,r:1553390539,d:ara,g:bulk,n:13,t:helloworld*3F\\!AIVDM,1,2,3",
block: TagBlock{
Time: 1564827317,
RelativeTime: 1553390539,
Destination: "ara",
Expand All @@ -69,56 +81,64 @@ var tagblocktests = []struct {
Text: "helloworld",
LineCount: 13,
},
ok: true,
remaining: "!AIVDM,1,2,3",
},
{

name: "Test empty tag in tagblock",
raw: "s:satelite,,r:1553390539,d:ara,g:bulk,n:13,t:helloworld*68",
raw: "\\s:satelite,,r:1553390539,d:ara,g:bulk,n:13,t:helloworld*68\\!AIVDM,1,2,3",
err: "nmea: tagblock field is malformed (should be <key>:<value>) []",
},
{

name: "Test Invalid checksum",
raw: "s:satelite,c:1564827317*49",
raw: "\\s:satelite,c:1564827317*49\\!AIVDM,1,2,3",
err: "nmea: tagblock checksum mismatch [25 != 49]",
},
{

name: "Test no checksum",
raw: "s:satelite,c:156482731749",
raw: "\\s:satelite,c:156482731749\\!AIVDM,1,2,3",
err: "nmea: tagblock does not contain checksum separator",
},
{

name: "Test invalid timestamp",
raw: "s:satelite,c:gjadslkg*30",
raw: "\\s:satelite,c:gjadslkg*30\\!AIVDM,1,2,3",
err: "nmea: tagblock unable to parse uint64 [gjadslkg]",
},
{

name: "Test invalid linecount",
raw: "s:satelite,n:gjadslkg*3D",
raw: "\\s:satelite,n:gjadslkg*3D\\!AIVDM,1,2,3",
err: "nmea: tagblock unable to parse uint64 [gjadslkg]",
},
{

name: "Test invalid relative time",
raw: "s:satelite,r:gjadslkg*21",
raw: "\\s:satelite,r:gjadslkg*21\\!AIVDM,1,2,3",
err: "nmea: tagblock unable to parse uint64 [gjadslkg]",
},
{
name: "Test no tagblock",
raw: "!AIVDM,1,2,3",
remaining: "!AIVDM,1,2,3",
},
}

func TestTagBlock(t *testing.T) {
func TestParseTagBlock(t *testing.T) {
for _, tt := range tagblocktests {
t.Run(tt.name, func(t *testing.T) {
m, err := parseTagBlock(tt.raw)
b, remaining, ok, err := ParseTagBlock(tt.raw)
if tt.err != "" {
assert.Error(t, err)
assert.EqualError(t, err, tt.err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.msg, m)
}
assert.Equal(t, tt.block, b)
assert.Equal(t, tt.remaining, remaining)
assert.Equal(t, tt.ok, ok)
})
}
}

0 comments on commit 7295c89

Please sign in to comment.