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

Add ParseTagBlock func #112

Merged
merged 1 commit into from
Jul 10, 2024
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
30 changes: 7 additions & 23 deletions sentence.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,32 +110,16 @@ 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, tagBlockLen, err := ParseTagBlock(raw)
if err != nil {
return BaseSentence{}, err
}
if tagBlockLen > 0 && 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:]
}
raw = raw[tagBlockLen:]

startIndex := strings.IndexAny(raw, SentenceStart+SentenceStartEncapsulated)
if startIndex != 0 {
Expand Down
49 changes: 31 additions & 18 deletions tagblock.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,26 @@ 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 is the length of the tag block prefix.
// See: https://gpsd.gitlab.io/gpsd/AIVDM.html#_nmea_tag_blocks
func ParseTagBlock(raw string) (TagBlock, int, error) {
startOfTagBlock := strings.IndexByte(raw, TagBlockSep)
if startOfTagBlock == -1 {
return TagBlock{}, 0, 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{}, 0, 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{}, 0, fmt.Errorf("nmea: tagblock does not contain checksum separator")
}

var (
Expand All @@ -43,22 +49,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{}, 0, 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{}, 0, 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{}, 0, err
}
case "d": // Destination ID
tagBlock.Destination = value
Expand All @@ -67,18 +72,26 @@ func parseTagBlock(tags string) (TagBlock, error) {
case "n": // Line count
tagBlock.LineCount, err = parseInt64(value)
if err != nil {
return TagBlock{}, err
return TagBlock{}, 0, err
}
case "r": // Relative time
tagBlock.RelativeTime, err = parseInt64(value)
if err != nil {
return TagBlock{}, err
return TagBlock{}, 0, err
}
case "s": // Source ID
tagBlock.Source = value
case "t": // Text string
tagBlock.Text = value
}
}
return tagBlock, nil
return tagBlock, endOfTagBlock + 1, 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
}
63 changes: 37 additions & 26 deletions tagblock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,60 +7,66 @@ import (
)

var tagblocktests = []struct {
name string
raw string
err string
msg TagBlock
name string
raw string
err string
block TagBlock
len int
}{
{

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",
},
len: 30,
},
{

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",
},
len: 28,
},
{

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: "",
},
len: 28,
},
{
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: "",
},
len: 28,
},
{

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: "",
},
len: 31,
},
{

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 +75,61 @@ var tagblocktests = []struct {
Text: "helloworld",
LineCount: 13,
},
len: 72,
},
{

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",
},
}

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, n, 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.len, n)
})
}
}
Loading