Skip to content

Commit

Permalink
Merge pull request #143 from pranavishere2/uriSerializer
Browse files Browse the repository at this point in the history
Added implementation of UUri serializer and implemented unit tests.
  • Loading branch information
gregmedd authored Jun 12, 2024
2 parents 555ebd9 + 36e3d7a commit 2e74a93
Show file tree
Hide file tree
Showing 3 changed files with 312 additions and 8 deletions.
4 changes: 2 additions & 2 deletions include/up-cpp/datamodel/serializer/UUri.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ namespace uprotocol::datamodel::serializer::uri {
/// @brief Converts to and from a human-readable string representation of UUri
/// according to the UUri spec.
struct AsString {
[[nodiscard]] std::string serialize(const v1::UUri&);
[[nodiscard]] v1::UUri deserialize(const std::string&);
[[nodiscard]] static std::string serialize(const v1::UUri&);
[[nodiscard]] static v1::UUri deserialize(const std::string&);
};

} // namespace uprotocol::datamodel::serializer::uri
Expand Down
108 changes: 107 additions & 1 deletion src/datamodel/serializer/UUri.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,111 @@
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: Apache-2.0

#include "up-cpp/datamodel/serializer/UUri.h"

#include <charconv>
#include <sstream>

#include "up-cpp/datamodel/validator/UUri.h"

namespace uprotocol::datamodel::serializer::uri {

std::string AsString::serialize(const v1::UUri& uri) {
using namespace uprotocol::datamodel::validator::uri;
auto [valid, reason] = isValid(uri);
if (!valid) {
throw std::invalid_argument("Invalid UUri For Serialization | " +
std::string(message(*reason)));
}
std::stringstream ss;
ss << std::hex << std::uppercase;

if (!isLocal(uri)) {
ss << "//" << uri.authority_name();
}
ss << "/" << uri.ue_id() << "/" << uri.ue_version_major() << "/"
<< uri.resource_id();

return std::move(ss).str();
}

std::string_view extractSegment(std::string_view& uriView) {
constexpr std::string_view segment_separator = "/";
const auto end = uriView.find(segment_separator);
if (end == uriView.npos) {
throw std::invalid_argument("Could not extract segment from '" +
std::string(uriView) +
"' with separator '" + "/" + "'");
}
auto segment = uriView.substr(0, end);
uriView = uriView.substr(end + 1);
return segment;
}

uint32_t segmentToUint32(const std::string_view& segment) {
uint32_t value = 0;
auto [end, ec] = std::from_chars(
segment.data(), segment.data() + segment.size(), value, 16);
const bool convert_ok =
(ec == std::errc{}) && (end == segment.data() + segment.size());
if (!convert_ok) {
throw std::invalid_argument("Failed to convert segment to number: " +
std::string(segment));
}
return value;
}

uprotocol::v1::UUri AsString::deserialize(const std::string& uriAsString) {
if (uriAsString.empty()) {
throw std::invalid_argument("Cannot deserialize empty string");
}

constexpr std::string_view schema_prefix = "up://";
constexpr std::string_view remote_prefix = "//";
constexpr std::string_view segment_separator = "/";

// Operating on a string view to avoid copies and reallocations
std::string_view uriView(uriAsString);

// Extract and convert the rest of the URI string
v1::UUri uri;

// Verify start and extract Authority, if present
// With up:// schema
if (uriView.substr(0, schema_prefix.size()) == schema_prefix) {
// Advance past the prefix
uriView = uriView.substr(schema_prefix.size());
uri.set_authority_name(std::string(extractSegment(uriView)));
// with // remote prefix
} else if (uriView.substr(0, remote_prefix.size()) == remote_prefix) {
// Advance past the prefix
uriView = uriView.substr(remote_prefix.size());
uri.set_authority_name(std::string(extractSegment(uriView)));

// with / local prefix
} else if (uriView.substr(0, segment_separator.size()) ==
segment_separator) {
// Advance past the prefix
uriView = uriView.substr(segment_separator.size());

// Missing required prefix
} else {
throw std::invalid_argument(
"Did not find expected URI start in string: '" +
std::string(uriView) + "'");
}
uri.set_ue_id(segmentToUint32(extractSegment(uriView)));
uri.set_ue_version_major(segmentToUint32(extractSegment(uriView)));
uri.set_resource_id(segmentToUint32(uriView));

{
using namespace uprotocol::datamodel::validator::uri;
auto [valid, reason] = isValid(uri);
if (!valid) {
throw std::invalid_argument("Invalid UUri For DeSerialization | " +
std::string(message(*reason)));
}
}
return uri;
}
} // namespace uprotocol::datamodel::serializer::uri
208 changes: 203 additions & 5 deletions test/coverage/datamodel/UUriSerializerTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
#include <up-cpp/datamodel/serializer/UUri.h>

namespace {
using namespace uprotocol::datamodel::serializer::uri;
using namespace uprotocol;

class TestFixture : public testing::Test {
class TestUUriSerializer : public testing::Test {
protected:
// Run once per TEST_F.
// Used to set up clean environments per test.
Expand All @@ -23,16 +25,212 @@ class TestFixture : public testing::Test {

// Run once per execution of the test application.
// Used for setup of all tests. Has access to this instance.
TestFixture() = default;
~TestFixture() = default;
TestUUriSerializer() = default;
~TestUUriSerializer() = default;

// Run once per execution of the test application.
// Used only for global setup outside of tests.
static void SetUpTestSuite() {}
static void TearDownTestSuite() {}
};

// TODO replace
TEST_F(TestFixture, SomeTestName) {}
v1::UUri buildValidTestURI(const std::string& authority = "192.168.1.10") {
v1::UUri uri;
uri.set_authority_name(authority);
uri.set_ue_id(0x10010001);
uri.set_ue_version_major(0xFE);
uri.set_resource_id(0x7500);
return uri;
}

// Positive test case - test serialization of UUri to string
TEST_F(TestUUriSerializer, SerializeUUriToString) {
auto testUUri = buildValidTestURI();
const std::string expectedUUri = "//192.168.1.10/10010001/FE/7500";
const std::string actualUUri = AsString::serialize(testUUri);
ASSERT_EQ(expectedUUri, actualUUri);
}

// Positive test case - test serialization of UUri with no authority to string
TEST_F(TestUUriSerializer, SerializeUUriWithNoAuthorityToString) {
auto testUUri = buildValidTestURI("");
const std::string expectedUUri = "/10010001/FE/7500";
const std::string actualUUri = AsString::serialize(testUUri);
ASSERT_EQ(expectedUUri, actualUUri);
}

// Test Service ID in uEID field as a 0xFFFF to see if it thorws an exception
// for using wildcard
TEST_F(TestUUriSerializer, SerializeUUriToStringWithServiceIDWildCard) {
v1::UUri testUUri;
testUUri.set_authority_name("testAuthority");
testUUri.set_ue_id(0xFFFF); // Wildcard
testUUri.set_ue_version_major(0xFE);
testUUri.set_resource_id(0x7500);
ASSERT_THROW(AsString::serialize(testUUri), std::invalid_argument);
}

// Test Instance ID in uEID field as a 0x0 to see if it thorws an exception for
// using wildcard
TEST_F(TestUUriSerializer, SerializeUUriToStringWithInstanceIDWildCard) {
v1::UUri testUUri;
testUUri.set_authority_name("testAuthority");
testUUri.set_ue_id(0x00001234); // Wildcard
testUUri.set_ue_version_major(0xFE);
testUUri.set_resource_id(0x7500);
ASSERT_THROW(AsString::serialize(testUUri), std::invalid_argument);
}

// Test major version as 0xFF to see if it thorws an exception for using
// wildcard
TEST_F(TestUUriSerializer, SerializeUUriToStringWithMajorVersionWildCard) {
v1::UUri testUUri;
testUUri.set_authority_name("testAuthority");
testUUri.set_ue_id(0x12340000);
testUUri.set_ue_version_major(0xFF); // Wildcard
testUUri.set_resource_id(0x7500);
ASSERT_THROW(AsString::serialize(testUUri), std::invalid_argument);
}

// Test resource id as 0xFFFF to see if it thorws an exception for using
// wildcard
TEST_F(TestUUriSerializer, SerializeUUriToStringWithResourceIDWildCard) {
v1::UUri testUUri;
testUUri.set_authority_name("testAuthority");
testUUri.set_ue_id(0x12340000);
testUUri.set_ue_version_major(0xFE);
testUUri.set_resource_id(0xFFFF); // Wildcard
ASSERT_THROW(AsString::serialize(testUUri), std::invalid_argument);
}

// Test deserialize by providing scheme "up:" which is allowed to have as per
// spec
TEST_F(TestUUriSerializer, DeSerializeUUriStringWithScheme) {
const std::string uuriAsString = "up://192.168.1.10/10010001/FE/7500";
const std::string expectedAuthority = "192.168.1.10";
const uint32_t expectedUEID = 0x10010001;
const uint32_t expectedMajorVersion = 0xFE;
const uint32_t expectedResourceID = 0x7500;

auto uri = AsString::deserialize(uuriAsString);
ASSERT_EQ(expectedAuthority, uri.authority_name());
ASSERT_EQ(expectedUEID, uri.ue_id());
ASSERT_EQ(expectedMajorVersion, uri.ue_version_major());
ASSERT_EQ(expectedResourceID, uri.resource_id());
}

// Test deserialize by providing incorrect scheme "uprotocol:"
TEST_F(TestUUriSerializer, DeSerializeUUriStringWithIncorrectScheme) {
const std::string uuriAsString =
"uprotocol://192.168.1.10/10010001/FE/7500";
ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument);
}

// Test deserialize without providing scheme "up:"
TEST_F(TestUUriSerializer, DeSerializeUUriStringWithoutScheme) {
const std::string uuriAsString = "//192.168.1.10/10010001/FE/7500";
const std::string expectedAuthority = "192.168.1.10";
const uint32_t expectedUEID = 0x10010001;
const uint32_t expectedMajorVersion = 0xFE;
const uint32_t expectedResourceID = 0x7500;

auto uri = AsString::deserialize(uuriAsString);
ASSERT_EQ(expectedAuthority, uri.authority_name());
ASSERT_EQ(expectedUEID, uri.ue_id());
ASSERT_EQ(expectedMajorVersion, uri.ue_version_major());
ASSERT_EQ(expectedResourceID, uri.resource_id());
}

// Test deserializing empty string to check if it thorws an exception
TEST_F(TestUUriSerializer, DeSerializeEmptyUUriString) {
const std::string uuriAsString = "";
ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument);
}

// Test deserializing string with no authority
TEST_F(TestUUriSerializer, DeSerializeUUriStringWithNoAuthority) {
const std::string uuriAsString = "/10010001/FE/7500";
const std::string expectedAuthority = "";
const uint32_t expectedUEID = 0x10010001;
const uint32_t expectedMajorVersion = 0xFE;
const uint32_t expectedResourceID = 0x7500;

auto uri = AsString::deserialize(uuriAsString);
ASSERT_EQ(expectedAuthority, uri.authority_name());
ASSERT_EQ(expectedUEID, uri.ue_id());
ASSERT_EQ(expectedMajorVersion, uri.ue_version_major());
ASSERT_EQ(expectedResourceID, uri.resource_id());
}

// Test deserializing string with invalid number of arguments
TEST_F(TestUUriSerializer, DeSerializeUUriStringWithInvalidNumberOfArgument) {
// Provided 5 arguments instead of 4 when authority exist
std::string uuriAsString = "//192.168.1.10/10010001/FE/FE/7500";

ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument);

// UE ID is missing. Provided 3 arguments instead of 4 when authority exist.
uuriAsString = "//192.168.1.10/FE/7500";
ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument);

// Provided 4 arguments instead of 3 when authority does not exist.
uuriAsString = "/1102/FE/FE/7500";
ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument);

// UE ID is missing. Provided 2 arguments instead of 3 when authority does
// not exist.
uuriAsString = "/FE/7500";
ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument);

// Valid Uri but no leading /
uuriAsString = "192.168.1.10/1102/FE/7500";
ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument);

// Valid Uri but no leading /
uuriAsString = "1102/FE/7500";
ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument);

// Valid Uri but leading /// .
uuriAsString = "///192.168.1.10/1102/FE/7500";
ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument);

// Valid Uri but additional trailing /
uuriAsString = "//192.168.1.10/1102/FE/7500/";
ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument);
}

// Test deserializing string with invalid arguments
TEST_F(TestUUriSerializer, DeSerializeUUriStringWithInvalidArgument) {
// UE ID provided is invalid. It should be hex numeric
std::string uuriAsString = "//192.168.1.10/testUE/FE/7500";
ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument);

// Major Version provided is invalid. It should be hex numeric
uuriAsString = "//192.168.1.10/10010001/^%/7500";
ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument);

// Resource ID provided is invalid. It should be hex numeric
uuriAsString = "//192.168.1.10/10010001/FE/xyz";
ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument);
}

// Test deserializing string with eildcard arguments to see if throws exception
TEST_F(TestUUriSerializer, DeSerializeUUriStringWithWildcardArgument) {
// Service ID provided in ueID is wildcard as 0xFFFF
std::string uuriAsString = "//192.168.1.10/FFFF/FE/7500";
ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument);

// Instance ID provided in ueID is wildcard as 0x0
uuriAsString = "//192.168.1.10/00001234/FE/7500";
ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument);

// Major Version provided is wildcard as 0xFF
uuriAsString = "//192.168.1.10/10010001/FF/7500";
ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument);

// Resource ID provided is wildcard as 0xFFFF
uuriAsString = "//192.168.1.10/10010001/FE/FFFF";
ASSERT_THROW(AsString::deserialize(uuriAsString), std::invalid_argument);
}

} // namespace

0 comments on commit 2e74a93

Please sign in to comment.