diff --git a/src/drivers/gnss/septentrio/CMakeLists.txt b/src/drivers/gnss/septentrio/CMakeLists.txt index 62451fc9b1..6971f57e28 100644 --- a/src/drivers/gnss/septentrio/CMakeLists.txt +++ b/src/drivers/gnss/septentrio/CMakeLists.txt @@ -48,3 +48,15 @@ px4_add_module( MODULE_CONFIG module.yaml ) + +px4_add_functional_gtest(SRC septentrio_fuzz_tests.cpp + LINKLIBS + driver__septentrio + COMPILE_FLAGS + # There warnings come from within fuzztest + -Wno-float-equal + -Wno-sign-compare + -Wno-shadow + -Wno-extra + -Wno-non-template-friend +) diff --git a/src/drivers/gnss/septentrio/sbf/decoder.cpp b/src/drivers/gnss/septentrio/sbf/decoder.cpp index 3e742bcd3c..7f9720bd82 100644 --- a/src/drivers/gnss/septentrio/sbf/decoder.cpp +++ b/src/drivers/gnss/septentrio/sbf/decoder.cpp @@ -262,8 +262,14 @@ bool Decoder::done() const bool Decoder::can_parse() const { - return done() && _message.header.length <= sizeof(_message) && _message.header.length > 4 - && _message.header.crc == buffer_crc16(reinterpret_cast(&_message) + 4, _message.header.length - 4); + const bool precondition = done() && _message.header.length <= sizeof(_message) && _message.header.length > 4; +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + // When fuzzing, disable the CRC check as it is hard for the fuzzer to find msgs with valid CRC. + return precondition; +#else + return precondition && + _message.header.crc == buffer_crc16(reinterpret_cast(&_message) + 4, _message.header.length - 4); +#endif } } // namespace sbf diff --git a/src/drivers/gnss/septentrio/sbf/decoder.h b/src/drivers/gnss/septentrio/sbf/decoder.h index 4c5b73c943..2b7e8e3995 100644 --- a/src/drivers/gnss/septentrio/sbf/decoder.h +++ b/src/drivers/gnss/septentrio/sbf/decoder.h @@ -206,6 +206,13 @@ public: * become ready for the next message. */ void reset(); + + /** + * @brief Check whether a full message has been received and is valid. + * + * @return `true` if the data can be parsed, `false` if the message is incomplete or not valid. + */ + bool can_parse() const; private: /** * @brief Check whether a full message has been received. @@ -216,13 +223,6 @@ private: */ bool done() const; - /** - * @brief Check whether a full message has been received and is valid. - * - * @return `true` if the data can be parsed, `false` if the message is incomplete or not valid. - */ - bool can_parse() const; - State _state{State::SearchingSync1}; uint16_t _current_index{0}; message_t _message; diff --git a/src/drivers/gnss/septentrio/septentrio.h b/src/drivers/gnss/septentrio/septentrio.h index 41abaa361f..d4f86a2a15 100644 --- a/src/drivers/gnss/septentrio/septentrio.h +++ b/src/drivers/gnss/septentrio/septentrio.h @@ -282,6 +282,13 @@ public: * Default baud rate, used when the user requested an invalid baud rate. */ static uint32_t k_default_baud_rate; + + /** + * @brief Parse the next byte of a received message from the receiver. + * + * @return 0 = decoding, 1 = message handled, 2 = sat info message handled + */ + int parse_char(const uint8_t byte); private: enum class State { OpeningSerialPort, @@ -407,13 +414,6 @@ private: */ ConfigureResult configure(); - /** - * @brief Parse the next byte of a received message from the receiver. - * - * @return 0 = decoding, 1 = message handled, 2 = sat info message handled - */ - int parse_char(const uint8_t byte); - /** * @brief Process a fully received message from the receiver. * diff --git a/src/drivers/gnss/septentrio/septentrio_fuzz_tests.cpp b/src/drivers/gnss/septentrio/septentrio_fuzz_tests.cpp new file mode 100644 index 0000000000..94f3c6b592 --- /dev/null +++ b/src/drivers/gnss/septentrio/septentrio_fuzz_tests.cpp @@ -0,0 +1,151 @@ +/**************************************************************************** + * + * Copyright (c) 2025 PX4 Development Team. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name PX4 nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + + +#include "septentrio.h" + +#include +#include + +#include "util.h" + +TEST(SeptentrioTestDecoding, decodeMessage) +{ + // Check if a single message can be decoded + septentrio::sbf::message_t m{}; + m.header.sync1 = '$'; + m.header.sync2 = '@'; + m.header.id_number = septentrio::sbf::BlockID::PVTGeodetic; + m.header.length = sizeof(m.header) + sizeof(septentrio::sbf::PVTGeodetic); + septentrio::sbf::PVTGeodetic pvt{}; + pvt.latitude = 123; + pvt.longitude = 456; + pvt.height = 789; + memcpy(&m.payload, &pvt, sizeof(pvt)); + m.header.crc = septentrio::buffer_crc16((uint8_t *)&m + 4, m.header.length - 4); + + septentrio::sbf::Decoder decoder; + const uint8_t *buffer = (const uint8_t *)&m; + + for (int i = 0; i < m.header.length; ++i) { + decoder.add_byte(buffer[i]); + } + + ASSERT_EQ(decoder.id(), septentrio::sbf::BlockID::PVTGeodetic); + + septentrio::sbf::Header header; + ASSERT_EQ(decoder.parse(&header), 0); +} + + +class SeptentrioTestDecoder +{ +public: + SeptentrioTestDecoder() + { + // This object will be reused for each fuzzing iteration call + } + ~SeptentrioTestDecoder() = default; + + void runTest(const std::vector &data) + { + ++_num_runs; + + for (int i = 0; i < data.size(); i++) { + if (_decoder.add_byte(data[i]) == septentrio::sbf::Decoder::State::Done) { + ++_num_msgs; + + if (_num_runs % 100'000 == 0) { + printf("Message: %i / %i / %i (%.6f)\n", _num_headers, _num_msgs, _num_runs, (double)_num_headers / _num_runs); + } + + septentrio::sbf::Header message; + + if (_decoder.parse(&message) == 0) { + ++_num_headers; + } + + _decoder.reset(); + } + } + + } +private: + septentrio::sbf::Decoder _decoder; + int _num_runs{0}; + int _num_msgs{0}; + int _num_headers{0}; +}; + +FUZZ_TEST_F(SeptentrioTestDecoder, runTest); + +class SeptentrioTest +{ +public: + SeptentrioTest() + : _driver("", septentrio::Instance::Main, 0) + { + // This object will be reused for each fuzzing iteration call + } + + ~SeptentrioTest() = default; + + void runTest(const std::vector &data) + { + ++_num_runs; + + for (int i = 0; i < data.size(); i++) { + if (_driver.parse_char(data[i]) > 0) { + ++_num_msgs; + } + } + + if (_num_runs % 100'000 == 0) { + // Print some stats (note that a high _num_msgs could also mean the fuzzer sends always the same message) + printf("Parsed messages: %i / %i iterations (Rate: %.6f)\n", _num_msgs, _num_runs, (double)_num_msgs / _num_runs); + } + } + + std::vector>> seeds() { return _seeds; } + +private: + septentrio::SeptentrioDriver _driver; + + int _num_runs{0}; + int _num_msgs{0}; + + // Provide a valid PVTGeodetic message as seed + const std::vector>> _seeds = {{{0x24, 0x40, 0xe8, 0x88, 0xa7, 0x0f, 0x5e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}; +}; + +FUZZ_TEST_F(SeptentrioTest, runTest).WithSeeds(&SeptentrioTest::seeds);