• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

fliuzzi02 / nmealib / 23060599635

13 Mar 2026 04:33PM UTC coverage: 92.6% (+0.1%) from 92.49%
23060599635

push

github

web-flow
Merge pull request #71 from fliuzzi02/patch/ai-slop-cleanup

48 of 53 new or added lines in 13 files covered. (90.57%)

1777 of 1919 relevant lines covered (92.6%)

19.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.92
/src/nmea0183.cpp
1
#include "nmealib/nmea0183.h"
2

3
#include <sstream>
4
#include <iomanip>
5
#include <algorithm>
6
#include <cctype>
7
#include <cstdlib>
8
#include <numeric>
9
#include <cmath>
10

11
namespace nmealib {
12
namespace nmea0183 {
13

14
Message0183::Message0183(std::string raw,
104✔
15
                        TimePoint ts,
16
                        char startChar,
17
                        std::string talker,
18
                        std::string sentenceType,
19
                        std::string payload) noexcept
104✔
20
    : Message(std::move(raw), Type::NMEA0183, ts),
21
    startChar_(startChar), 
104✔
22
    talker_(std::move(talker)), 
104✔
23
    sentenceType_(std::move(sentenceType)), 
104✔
24
    payload_(std::move(payload)) {
312✔
25
    calculatedChecksumStr_ = computeChecksum(payload_);
104✔
26
}
104✔
27

28
Message0183::Message0183(std::string raw,
36✔
29
                        TimePoint ts,
30
                        char startChar,
31
                        std::string talker,
32
                        std::string sentenceType,
33
                        std::string payload,
34
                        std::string checksumStr) noexcept
36✔
35
    : Message(std::move(raw), Type::NMEA0183, ts),
36
    startChar_(startChar), 
36✔
37
    talker_(std::move(talker)), 
36✔
38
    sentenceType_(std::move(sentenceType)), 
36✔
39
    payload_(std::move(payload)),
36✔
40
    checksumStr_(std::move(checksumStr)) {
108✔
41
    calculatedChecksumStr_ = computeChecksum(payload_);
36✔
42
}
36✔
43

44
std::unique_ptr<Message0183> Message0183::create(const std::string& raw, TimePoint ts) {
142✔
45
    std::string context = "Message0183::create";
142✔
46
    validateFormat(context, raw);
142✔
47

48
    char startChar = raw[0];
140✔
49

50
    bool hasCRLF = raw.size() >= 2 && raw.substr(raw.size() - 2) == "\r\n";
142✔
51

52
    // Extract talker and sentence type from the raw sentence.
53
    std::string talker = raw.substr(1, 2);
140✔
54
    std::string sentenceType = raw.substr(3, 3);
140✔
55
    bool hasChecksum = raw.find('*') != std::string::npos;
140✔
56
    if(hasChecksum) {
140✔
57
        size_t asteriskPos = raw.find('*');
58
        std::string payload = raw.substr(1, asteriskPos - 1); // Exclude start char and checksum part
36✔
59
        std::string checksumStr = raw.substr(asteriskPos + 1, 2);
36✔
60
        return std::unique_ptr<Message0183>(new Message0183(raw, ts, startChar, talker, sentenceType, payload, checksumStr));
144✔
61
    } else {
62
        if (hasCRLF) {
104✔
63
            std::string payload = raw.substr(1, raw.size() - 3); // Exclude start char and CRLF
104✔
64
            return std::unique_ptr<Message0183>(new Message0183(raw, ts, startChar, talker, sentenceType, payload));
416✔
65
        } else {
66
            std::string payload = raw.substr(1); // Exclude start char only
×
67
            return std::unique_ptr<Message0183>(new Message0183(raw, ts, startChar, talker, sentenceType, payload));
×
68
        }
69
    }
70
}
71

72
void Message0183::validateFormat(const std::string& context, const std::string& raw) {
142✔
73
    // TODO: I have to check that it correspons to the minimum sentence: $XXXXX*ZZ<CR><LF>
74
    // and also without checksum: $XXXXX<CR><LF>
75
    // and also without CRLF: $XXXXX*ZZ and $XXXXX
76
    if(raw.size() > 82) {
142✔
77
        throw TooLongSentenceException(context, "Input string length: " + std::to_string(raw.size()));
3✔
78
    }
79
    if(raw.empty() || (raw[0] != '$' && raw[0] != '!')) {
141✔
80
        throw InvalidStartCharacterException("NMEA 0183 sentence must start with '$' or '!'");
2✔
81
    }
82

83
    bool hasChecksum = raw.find('*') != std::string::npos;
140✔
84
    if (hasChecksum) {
140✔
85
        // If it has a checksum, find the position of '*' and extract payload and checksum accordingly.
86
        size_t asteriskPos = raw.find('*');
87
        std::string checksumStr = raw.substr(asteriskPos + 1, 2);
36✔
88
        if(!isHexByte(checksumStr)) {
36✔
89
            throw NoChecksumException(context, "Invalid checksum format: " + checksumStr);
×
90
        }
91
    }
92
}
140✔
93

94
std::unique_ptr<nmealib::Message> Message0183::clone() const {
×
95
    return std::unique_ptr<Message0183>(new Message0183(*this));
×
96
}
97

98
std::string Message0183::getPayload() const noexcept {
89✔
99
    return payload_;
89✔
100
}
101

102
char Message0183::getStartChar() const noexcept {
1✔
103
    return startChar_;
1✔
104
}
105

106
std::string Message0183::getTalker() const noexcept {
32✔
107
    return talker_;
32✔
108
}
109

110
std::string Message0183::getSentenceType() const noexcept {
208✔
111
    return sentenceType_;
208✔
112
}
113

114
std::string Message0183::getChecksumStr() const {
30✔
115
    if (checksumStr_.empty()) {
30✔
116
        throw NoChecksumException("Message0183::getChecksumStr", "This sentence does not contain a checksum");
52✔
117
    }
118
    return checksumStr_;
4✔
119
}
120

121
std::string Message0183::getCalculatedChecksumStr() const noexcept {
2✔
122
    return calculatedChecksumStr_;
2✔
123
}
124

125
std::string Message0183::getStringContent(bool verbose) const noexcept {
×
126
    std::stringstream ss;
×
127

NEW
128
    ss << this->toString(verbose);
×
129

NEW
130
    if (verbose) ss << "\tUnimplemented sentence type";
×
NEW
131
    else ss << "Unimplemented sentence type";
×
132

NEW
133
    return ss.str();
×
NEW
134
}
×
135

136
std::string Message0183::toString(bool verbose) const noexcept {
24✔
137
    std::stringstream ss;
24✔
138
    std::string validity = "KO";
24✔
139
    if(validate()) {
24✔
140
        validity = "OK";
141
    }
142

143
    if (verbose) {
24✔
144
        ss << "--------------------------------\n";
12✔
145
        ss << "Protocol: " << typeToString(type_) << "\n";
36✔
146
        ss << "Talker: " << getTalker() << "\n";
24✔
147
        ss << "Sentence Type: " << getSentenceType() << "\n";
24✔
148
        ss << "Checksum: " << (checksumStr_.empty() ? "None" : validity) << "\n";
24✔
149
        ss << "Fields:\n";
12✔
150
    } else {
151
        ss << "[" << validity << "] " << typeToString(type_) << " " << getTalker() << " " << getSentenceType() << ": ";
36✔
152
    }
153

154
    return ss.str();
24✔
155
}
24✔
156

157
std::string Message0183::serialize() const {
26✔
158
    return rawData_;
26✔
159
}
160

161
bool Message0183::validate() const noexcept{
27✔
162
    bool hasChecksum = true;
163
    bool valid = true;
164
    // Try to get checksum, if i get an exception it does not have one
165
    try {
166
        valid = getChecksumStr() == getCalculatedChecksumStr();
27✔
167
    } catch (const NoChecksumException&) {
25✔
168
        hasChecksum = false;
169
    }
25✔
170
    
171
    return !hasChecksum || valid;
27✔
172
}
173

174
std::string Message0183::computeChecksum(const std::string& payload) noexcept {
140✔
175
    uint8_t checksum = std::accumulate(payload.begin(), payload.end(), static_cast<uint8_t>(0),
176
        [](uint8_t acc, char c) { return acc ^ static_cast<uint8_t>(c); });
5,164✔
177
    std::stringstream ss;
140✔
178
    ss << std::uppercase << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(checksum);
140✔
179
    return ss.str();
140✔
180
}
140✔
181

182
bool Message0183::isHexByte(const std::string& s) noexcept {
36✔
183
    if (s.size() != 2) return false;
36✔
184
    return std::isxdigit(static_cast<unsigned char>(s[0])) &&
36✔
185
           std::isxdigit(static_cast<unsigned char>(s[1]));
36✔
186
}
187

188
double Message0183::convertNmeaCoordinateToDecimalDegrees(const std::string& nmeaCoordinate) {
60✔
189
    if (nmeaCoordinate.empty()) {
60✔
190
        return 0.0;
191
    }
192

193
    const long double raw = std::stold(nmeaCoordinate);
194
    const long double degrees = std::floor(raw / 100.0L);
57✔
195
    const long double minutes = raw - (degrees * 100.0L);
57✔
196
    return static_cast<double>(degrees + (minutes / 60.0L));
57✔
197
}
198

199
bool Message0183::operator==(const Message0183& other) const noexcept {
13✔
200
    return startChar_ == other.startChar_ &&
26✔
201
           talker_ == other.talker_ &&
13✔
202
           sentenceType_ == other.sentenceType_ &&
13✔
203
           payload_ == other.payload_ &&
13✔
204
           checksumStr_ == other.checksumStr_ &&
13✔
205
           calculatedChecksumStr_ == other.calculatedChecksumStr_ &&
39✔
206
           Message::operator==(other);
13✔
207
}
208

209
bool Message0183::hasEqualContent(const Message0183& other) const noexcept {
14✔
210
    return startChar_ == other.startChar_ &&
28✔
211
           talker_ == other.talker_ &&
14✔
212
           sentenceType_ == other.sentenceType_ &&
14✔
213
           payload_ == other.payload_ &&
14✔
214
           checksumStr_ == other.checksumStr_ &&
28✔
215
           calculatedChecksumStr_ == other.calculatedChecksumStr_;
14✔
216
}
217

218
} // namespace nmea0183
219
} // namespace nmealib
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc