Skip to content

Commit 65fb477

Browse files
authored
add ParseTagBlock func (#112)
1 parent a60cdb4 commit 65fb477

File tree

3 files changed

+75
-67
lines changed

3 files changed

+75
-67
lines changed

sentence.go

+7-23
Original file line numberDiff line numberDiff line change
@@ -110,32 +110,16 @@ func (p *SentenceParser) parseBaseSentence(raw string) (BaseSentence, error) {
110110
if raw == "" {
111111
return BaseSentence{}, errors.New("nmea: can not parse empty input")
112112
}
113-
114-
var (
115-
tagBlock TagBlock
116-
err error
117-
)
118-
119-
if startOfTagBlock := strings.IndexByte(raw, TagBlockSep); startOfTagBlock != -1 {
120-
// tag block is always at the start of line (unless IEC 61162-450). Starts with `\` and ends with `\` and has valid sentence
121-
// following or <CR><LF>
122-
//
123-
// Note: tag block group can span multiple lines but we only parse ones that have sentence
124-
endOfTagBlock := strings.LastIndexByte(raw, TagBlockSep)
125-
if endOfTagBlock <= startOfTagBlock {
126-
return BaseSentence{}, fmt.Errorf("nmea: sentence tag block is missing '\\' at the end")
127-
}
128-
tagBlock, err = parseTagBlock(raw[startOfTagBlock+1 : endOfTagBlock])
129-
if err != nil {
113+
tagBlock, tagBlockLen, err := ParseTagBlock(raw)
114+
if err != nil {
115+
return BaseSentence{}, err
116+
}
117+
if tagBlockLen > 0 && p.OnTagBlock != nil {
118+
if err := p.OnTagBlock(tagBlock); err != nil {
130119
return BaseSentence{}, err
131120
}
132-
if p.OnTagBlock != nil {
133-
if err := p.OnTagBlock(tagBlock); err != nil {
134-
return BaseSentence{}, err
135-
}
136-
}
137-
raw = raw[endOfTagBlock+1:]
138121
}
122+
raw = raw[tagBlockLen:]
139123

140124
startIndex := strings.IndexAny(raw, SentenceStart+SentenceStartEncapsulated)
141125
if startIndex != 0 {

tagblock.go

+31-18
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,26 @@ type TagBlock struct {
1717
Text string // TypeTextString valid character string, parameter -t
1818
}
1919

20-
func parseInt64(raw string) (int64, error) {
21-
i, err := strconv.ParseInt(raw, 10, 64)
22-
if err != nil {
23-
return 0, fmt.Errorf("nmea: tagblock unable to parse uint64 [%s]", raw)
20+
// ParseTagBlock parses tag blocks from a sentence string.
21+
// The second return value is the length of the tag block prefix.
22+
// See: https://gpsd.gitlab.io/gpsd/AIVDM.html#_nmea_tag_blocks
23+
func ParseTagBlock(raw string) (TagBlock, int, error) {
24+
startOfTagBlock := strings.IndexByte(raw, TagBlockSep)
25+
if startOfTagBlock == -1 {
26+
return TagBlock{}, 0, nil
2427
}
25-
return i, nil
26-
}
27-
28-
// parseTagBlock adds support for tagblocks
29-
// https://gpsd.gitlab.io/gpsd/AIVDM.html#_nmea_tag_blocks
30-
func parseTagBlock(tags string) (TagBlock, error) {
28+
// tag block is always at the start of line (unless IEC 61162-450). Starts with `\` and ends with `\` and has valid sentence
29+
// following or <CR><LF>
30+
//
31+
// Note: tag block group can span multiple lines but we only parse ones that have sentence
32+
endOfTagBlock := strings.LastIndexByte(raw, TagBlockSep)
33+
if endOfTagBlock <= startOfTagBlock {
34+
return TagBlock{}, 0, fmt.Errorf("nmea: sentence tag block is missing '\\' at the end")
35+
}
36+
tags := raw[startOfTagBlock+1 : endOfTagBlock]
3137
sumSepIndex := strings.Index(tags, ChecksumSep)
3238
if sumSepIndex == -1 {
33-
return TagBlock{}, fmt.Errorf("nmea: tagblock does not contain checksum separator")
39+
return TagBlock{}, 0, fmt.Errorf("nmea: tagblock does not contain checksum separator")
3440
}
3541

3642
var (
@@ -43,22 +49,21 @@ func parseTagBlock(tags string) (TagBlock, error) {
4349

4450
// Validate the checksum
4551
if checksum != checksumRaw {
46-
return TagBlock{}, fmt.Errorf("nmea: tagblock checksum mismatch [%s != %s]", checksum, checksumRaw)
52+
return TagBlock{}, 0, fmt.Errorf("nmea: tagblock checksum mismatch [%s != %s]", checksum, checksumRaw)
4753
}
4854

4955
items := strings.Split(tags[:sumSepIndex], ",")
5056
for _, item := range items {
5157
parts := strings.SplitN(item, ":", 2)
5258
if len(parts) != 2 {
53-
return TagBlock{},
54-
fmt.Errorf("nmea: tagblock field is malformed (should be <key>:<value>) [%s]", item)
59+
return TagBlock{}, 0, fmt.Errorf("nmea: tagblock field is malformed (should be <key>:<value>) [%s]", item)
5560
}
5661
key, value := parts[0], parts[1]
5762
switch key {
5863
case "c": // UNIX timestamp
5964
tagBlock.Time, err = parseInt64(value)
6065
if err != nil {
61-
return TagBlock{}, err
66+
return TagBlock{}, 0, err
6267
}
6368
case "d": // Destination ID
6469
tagBlock.Destination = value
@@ -67,18 +72,26 @@ func parseTagBlock(tags string) (TagBlock, error) {
6772
case "n": // Line count
6873
tagBlock.LineCount, err = parseInt64(value)
6974
if err != nil {
70-
return TagBlock{}, err
75+
return TagBlock{}, 0, err
7176
}
7277
case "r": // Relative time
7378
tagBlock.RelativeTime, err = parseInt64(value)
7479
if err != nil {
75-
return TagBlock{}, err
80+
return TagBlock{}, 0, err
7681
}
7782
case "s": // Source ID
7883
tagBlock.Source = value
7984
case "t": // Text string
8085
tagBlock.Text = value
8186
}
8287
}
83-
return tagBlock, nil
88+
return tagBlock, endOfTagBlock + 1, nil
89+
}
90+
91+
func parseInt64(raw string) (int64, error) {
92+
i, err := strconv.ParseInt(raw, 10, 64)
93+
if err != nil {
94+
return 0, fmt.Errorf("nmea: tagblock unable to parse uint64 [%s]", raw)
95+
}
96+
return i, nil
8497
}

tagblock_test.go

+37-26
Original file line numberDiff line numberDiff line change
@@ -7,60 +7,66 @@ import (
77
)
88

99
var tagblocktests = []struct {
10-
name string
11-
raw string
12-
err string
13-
msg TagBlock
10+
name string
11+
raw string
12+
err string
13+
block TagBlock
14+
len int
1415
}{
1516
{
1617

1718
name: "Test NMEA tag block",
18-
raw: "s:Satelite_1,c:1553390539*62",
19-
msg: TagBlock{
19+
raw: "\\s:Satelite_1,c:1553390539*62\\!AIVDM,1,2,3",
20+
block: TagBlock{
2021
Time: 1553390539,
2122
Source: "Satelite_1",
2223
},
24+
len: 30,
2325
},
2426
{
2527

2628
name: "Test NMEA tag block with head",
27-
raw: "s:satelite,c:1564827317*25",
28-
msg: TagBlock{
29+
raw: "\\s:satelite,c:1564827317*25\\!AIVDM,1,2,3",
30+
block: TagBlock{
2931
Time: 1564827317,
3032
Source: "satelite",
3133
},
34+
len: 28,
3235
},
3336
{
3437

3538
name: "Test unknown tag",
36-
raw: "x:NorSat_1,c:1564827317*42",
37-
msg: TagBlock{
39+
raw: "\\x:NorSat_1,c:1564827317*42\\!AIVDM,1,2,3",
40+
block: TagBlock{
3841
Time: 1564827317,
3942
Source: "",
4043
},
44+
len: 28,
4145
},
4246
{
4347
name: "Test unix timestamp",
44-
raw: "x:NorSat_1,c:1564827317*42",
45-
msg: TagBlock{
48+
raw: "\\x:NorSat_1,c:1564827317*42\\!AIVDM,1,2,3",
49+
block: TagBlock{
4650
Time: 1564827317,
4751
Source: "",
4852
},
53+
len: 28,
4954
},
5055
{
5156

5257
name: "Test milliseconds timestamp",
53-
raw: "x:NorSat_1,c:1564827317000*72",
54-
msg: TagBlock{
58+
raw: "\\x:NorSat_1,c:1564827317000*72\\!AIVDM,1,2,3",
59+
block: TagBlock{
5560
Time: 1564827317000,
5661
Source: "",
5762
},
63+
len: 31,
5864
},
5965
{
6066

6167
name: "Test all input types",
62-
raw: "s:satelite,c:1564827317,r:1553390539,d:ara,g:bulk,n:13,t:helloworld*3F",
63-
msg: TagBlock{
68+
raw: "\\s:satelite,c:1564827317,r:1553390539,d:ara,g:bulk,n:13,t:helloworld*3F\\!AIVDM,1,2,3",
69+
block: TagBlock{
6470
Time: 1564827317,
6571
RelativeTime: 1553390539,
6672
Destination: "ara",
@@ -69,56 +75,61 @@ var tagblocktests = []struct {
6975
Text: "helloworld",
7076
LineCount: 13,
7177
},
78+
len: 72,
7279
},
7380
{
7481

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

8188
name: "Test Invalid checksum",
82-
raw: "s:satelite,c:1564827317*49",
89+
raw: "\\s:satelite,c:1564827317*49\\!AIVDM,1,2,3",
8390
err: "nmea: tagblock checksum mismatch [25 != 49]",
8491
},
8592
{
8693

8794
name: "Test no checksum",
88-
raw: "s:satelite,c:156482731749",
95+
raw: "\\s:satelite,c:156482731749\\!AIVDM,1,2,3",
8996
err: "nmea: tagblock does not contain checksum separator",
9097
},
9198
{
9299

93100
name: "Test invalid timestamp",
94-
raw: "s:satelite,c:gjadslkg*30",
101+
raw: "\\s:satelite,c:gjadslkg*30\\!AIVDM,1,2,3",
95102
err: "nmea: tagblock unable to parse uint64 [gjadslkg]",
96103
},
97104
{
98105

99106
name: "Test invalid linecount",
100-
raw: "s:satelite,n:gjadslkg*3D",
107+
raw: "\\s:satelite,n:gjadslkg*3D\\!AIVDM,1,2,3",
101108
err: "nmea: tagblock unable to parse uint64 [gjadslkg]",
102109
},
103110
{
104111

105112
name: "Test invalid relative time",
106-
raw: "s:satelite,r:gjadslkg*21",
113+
raw: "\\s:satelite,r:gjadslkg*21\\!AIVDM,1,2,3",
107114
err: "nmea: tagblock unable to parse uint64 [gjadslkg]",
108115
},
116+
{
117+
name: "Test no tagblock",
118+
raw: "!AIVDM,1,2,3",
119+
},
109120
}
110121

111-
func TestTagBlock(t *testing.T) {
122+
func TestParseTagBlock(t *testing.T) {
112123
for _, tt := range tagblocktests {
113124
t.Run(tt.name, func(t *testing.T) {
114-
m, err := parseTagBlock(tt.raw)
125+
b, n, err := ParseTagBlock(tt.raw)
115126
if tt.err != "" {
116127
assert.Error(t, err)
117-
assert.EqualError(t, err, tt.err)
118128
} else {
119129
assert.NoError(t, err)
120-
assert.Equal(t, tt.msg, m)
121130
}
131+
assert.Equal(t, tt.block, b)
132+
assert.Equal(t, tt.len, n)
122133
})
123134
}
124135
}

0 commit comments

Comments
 (0)