| /* | |
| * | |
| * Copyright (c) 2026 Project CHIP Authors | |
| * All rights reserved. | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| /** | |
| * @file | |
| * Seeded FuzzTest harness for chip::bdx::ParseURI / chip::bdx::MakeURI. | |
| * BDX "bdx://" URIs are parsed during OTA Provider/Requestor exchanges, so | |
| * ParseURI is exercised here on arbitrary input. | |
| * | |
| * A raw-bytes mutator would have to discover the "bdx://" scheme + | |
| * 16-hex-char node id structure from random bytes, so we use a std::string | |
| * domain seeded with real valid and edge-case URIs so the mutator starts | |
| * near the legal grammar and exercises the scheme / hex / separator / | |
| * file-designator boundaries. | |
| * | |
| * Three properties under test: | |
| * - ParseURI on arbitrary strings: no crash. | |
| * - parse -> make -> parse round-trip: once a URI parses, re-encoding the | |
| * (nodeId, fileDesignator) and re-parsing must recover the same nodeId | |
| * and a byte-equal file designator (MakeURI emits canonical UPPERCASE | |
| * hex, so the second parse is the fixed point). | |
| * - make -> parse round-trip: starting from a valid operational NodeId and | |
| * a non-empty file designator, MakeURI then ParseURI must recover both | |
| * inputs exactly. | |
| */ | |
| #include <string> | |
| #include <vector> | |
| #include <fuzztest/fuzztest_core.h> | |
| #include <gtest/gtest.h> | |
| #include <lib/core/CHIPError.h> | |
| #include <lib/core/DataModelTypes.h> | |
| #include <lib/core/NodeId.h> | |
| #include <lib/support/CHIPMem.h> | |
| #include <lib/support/CodeUtils.h> | |
| #include <lib/support/Span.h> | |
| #include <protocols/bdx/BdxUri.h> | |
| namespace { | |
| using namespace chip; | |
| using namespace fuzztest; | |
| void EnsureInitialized() | |
| { | |
| static const bool sInitialized = [] { | |
| VerifyOrDie(Platform::MemoryInit() == CHIP_NO_ERROR); | |
| return true; | |
| }(); | |
| (void) sInitialized; | |
| } | |
| // kScheme ("bdx://") is the only grammar constant exported by BdxUri.h. The | |
| // node-id field is always sizeof(NodeId)*2 hex chars; '+1' for the '/' separator | |
| // and '+1' for MakeURI's reserved NUL terminator. | |
| constexpr size_t kSchemeLen = sizeof(bdx::kScheme) - 1; | |
| constexpr size_t kEncodeOverhead = kSchemeLen + sizeof(NodeId) * 2 + 1 /* '/' */ + 1 /* NUL */; | |
| // A mix of valid (operational node id, non-empty file designator) URIs and the | |
| // malformed forms ParseURI must reject without crashing. Drives the scheme, | |
| // hex-decode, operational-node-id, separator and file-designator branches. | |
| std::vector<std::string> UriStringSeeds() | |
| { | |
| return { | |
| // ==== Valid, parseable ==== | |
| "bdx://0000000000000001/the-file", // smallest operational node id | |
| "bdx://1234567890ABCDEF/file_name", // canonical (matches TestBdxUri) | |
| "bdx://1234567890abcdef/file_name", // lowercase hex (HexToBytes accepts it) | |
| "bdx://FEDCBA9876543210/firmware.bin", // upper-half operational id | |
| "bdx://1234567890ABCDEF/file/na/me", // slashes are part of the designator | |
| "bdx://0000000000000001/a", // 1-char file designator (min length) | |
| "bdx://FFFFFFEFFFFFFFFF/ota.bin", // largest operational node id | |
| "bdx://0000000000000001/dir/sub/img.ota", // path-like designator | |
| "bdx://1234567890ABCDEF/%2e%2e%2ffirmware", // percent-encodings (NOT decoded by ParseURI) | |
| "bdx://1234567890ABCDEF/file with spaces", // spaces in designator | |
| // ==== Malformed: scheme ==== | |
| "", // empty | |
| "bdx:/", // truncated scheme | |
| "bdx://", // scheme only, nothing after | |
| "bdy://1234567890ABCDEF/file_name", // wrong scheme byte | |
| "BDX://1234567890ABCDEF/file_name", // wrong-case scheme | |
| "http://1234567890ABCDEF/file_name", // unrelated scheme | |
| "1234567890ABCDEF/file_name", // missing scheme entirely | |
| // ==== Malformed: node id ==== | |
| "bdx://1234567890ABCDE/file_name", // node id too short (15 hex) | |
| "bdx://1234567890ABCDEFEF/file_name", // node id too long (18 hex) | |
| "bdx://1234567890ABCDEG/file_name", // non-hex digit 'G' | |
| "bdx://12345678 0ABCDEF/file_name", // space inside node id | |
| "bdx://0000000000000000/file_name", // kUndefinedNodeId (not operational) | |
| "bdx://FFFFFFFFFFFFFFFF/file_name", // group id range (not operational) | |
| "bdx://FFFFFFFE00000000/file_name", // temporary-local id (not operational) | |
| "bdx://FFFFFFFD00000000/file_name", // CASE auth tag range (not operational) | |
| // ==== Malformed: separator / file designator ==== | |
| "bdx://1234567890ABCDEF.file_name", // wrong separator (no '/') | |
| "bdx://1234567890ABCDEF/", // empty file designator | |
| "bdx://1234567890ABCDEF", // no separator, no designator | |
| // ==== Edge / overflow shapes ==== | |
| std::string("bdx://1234567890ABCDEF/") + std::string(4096, 'A'), // very long designator | |
| std::string("bdx://1234567890ABCDEF/") + std::string("a\0b", 3), // embedded NUL in designator | |
| }; | |
| } | |
| // Operational node ids only (1 .. kMaxOperationalNodeId), so MakeURI/ParseURI | |
| // agree. kUndefinedNodeId (0) and the carved-out high ranges are excluded. | |
| auto AnyOperationalNodeId() | |
| { | |
| return InRange<NodeId>(1, kMaxOperationalNodeId); | |
| } | |
| // Non-empty file designators. MakeURI rejects an empty designator, so the | |
| // make->parse oracle only makes sense for size >= 1. | |
| auto AnyFileDesignator() | |
| { | |
| return Arbitrary<std::string>().WithMinSize(1).WithMaxSize(512); | |
| } | |
| // Property 1: ParseURI on arbitrary bytes must never crash. | |
| void ParseUriNoCrash(const std::string & uriStr) | |
| { | |
| EnsureInitialized(); | |
| CharSpan uri(uriStr.data(), uriStr.size()); | |
| NodeId nodeId = 0; | |
| CharSpan fileDesignator; | |
| (void) bdx::ParseURI(uri, nodeId, fileDesignator); | |
| } | |
| FUZZ_TEST(BdxUriPW, ParseUriNoCrash).WithDomains(Arbitrary<std::string>().WithSeeds(UriStringSeeds()).WithMaxSize(8192)); | |
| // Property 2: parse -> make -> parse is a fixed point. Once a URI parses, the | |
| // canonical re-encoding of (nodeId, fileDesignator) must parse back to the same | |
| // nodeId and a byte-equal designator. | |
| void ParseMakeRoundtrip(const std::string & uriStr) | |
| { | |
| EnsureInitialized(); | |
| CharSpan uri(uriStr.data(), uriStr.size()); | |
| NodeId nodeId = 0; | |
| CharSpan fileDesignator; | |
| if (bdx::ParseURI(uri, nodeId, fileDesignator) != CHIP_NO_ERROR) | |
| return; | |
| // The parse succeeded, so nodeId is operational and the designator is non-empty. | |
| // Re-encode into a buffer large enough for scheme + node-id hex + '/' + designator + NUL. | |
| std::vector<char> buffer(kEncodeOverhead + fileDesignator.size()); | |
| MutableCharSpan encoded(buffer.data(), buffer.size()); | |
| ASSERT_EQ(bdx::MakeURI(nodeId, fileDesignator, encoded), CHIP_NO_ERROR); | |
| NodeId nodeId2 = 0; | |
| CharSpan fileDesignator2; | |
| ASSERT_EQ(bdx::ParseURI(encoded, nodeId2, fileDesignator2), CHIP_NO_ERROR); | |
| ASSERT_EQ(nodeId2, nodeId); | |
| ASSERT_TRUE(fileDesignator2.data_equal(fileDesignator)); | |
| } | |
| FUZZ_TEST(BdxUriPW, ParseMakeRoundtrip).WithDomains(Arbitrary<std::string>().WithSeeds(UriStringSeeds()).WithMaxSize(8192)); | |
| // Property 3: make -> parse recovers structured input exactly. Starting from a | |
| // valid operational NodeId and a non-empty file designator, MakeURI then | |
| // ParseURI must yield the same nodeId and a byte-equal designator. | |
| void MakeParseRoundtrip(NodeId nodeId, const std::string & fileDesignatorStr) | |
| { | |
| EnsureInitialized(); | |
| CharSpan fileDesignator(fileDesignatorStr.data(), fileDesignatorStr.size()); | |
| std::vector<char> buffer(kEncodeOverhead + fileDesignator.size()); | |
| MutableCharSpan encoded(buffer.data(), buffer.size()); | |
| ASSERT_EQ(bdx::MakeURI(nodeId, fileDesignator, encoded), CHIP_NO_ERROR); | |
| NodeId parsedNodeId = 0; | |
| CharSpan parsedFileDesignator; | |
| ASSERT_EQ(bdx::ParseURI(encoded, parsedNodeId, parsedFileDesignator), CHIP_NO_ERROR); | |
| ASSERT_EQ(parsedNodeId, nodeId); | |
| ASSERT_TRUE(parsedFileDesignator.data_equal(fileDesignator)); | |
| } | |
| FUZZ_TEST(BdxUriPW, MakeParseRoundtrip).WithDomains(AnyOperationalNodeId(), AnyFileDesignator()); | |
| } // namespace |