diff --git a/include/radproto/attribute.h b/include/radproto/attribute.h index b00410a..9934d19 100644 --- a/include/radproto/attribute.h +++ b/include/radproto/attribute.h @@ -16,6 +16,7 @@ namespace RadProto virtual std::string toString() const = 0; virtual std::vector toVector(const std::string& secret, const std::array& auth) const = 0; virtual Attribute* clone() const = 0; + static Attribute* make(uint8_t code, const std::string& type, const std::string& data); private: uint8_t m_code; }; diff --git a/include/radproto/error.h b/include/radproto/error.h index b9c4ef2..affb4f9 100644 --- a/include/radproto/error.h +++ b/include/radproto/error.h @@ -21,10 +21,14 @@ namespace RadProto eapMessageAttributeError, invalidAttributeCode, invalidAttributeSize, + invalidAttributeType, + invalidValueType, invalidVendorSpecificAttributeId, suchAttributeNameAlreadyExists, suchAttributeCodeAlreadyExists, - suchAttributeNameWithAnotherTypeAlreadyExists + suchAttributeNameWithAnotherTypeAlreadyExists, + typeIsNotSupported, + invalidHexStringLength }; class Exception: public std::runtime_error diff --git a/src/attribute.cpp b/src/attribute.cpp index 043f8af..6126a0d 100644 --- a/src/attribute.cpp +++ b/src/attribute.cpp @@ -2,16 +2,94 @@ #include "attribute.h" #include "utils.h" #include "error.h" +#include #include #include -#include using Attribute = RadProto::Attribute; +namespace +{ + enum class ValueType : uint8_t + { + String, + Integer, + IpAddress, + Encrypted, + Bytes, + VendorSpecific, + ChapPassword + }; + + ValueType parseValueType(const std::string& type) + { + if (type == "string") + return ValueType::String; + else if (type == "integer" || type == "date") + return ValueType::Integer; + else if (type == "ipaddr") + return ValueType::IpAddress; + else if (type == "encrypted") + return ValueType::Encrypted; + else if (type == "octets") + return ValueType::Bytes; + else if (type == "vsa") + throw RadProto::Exception(RadProto::Error::typeIsNotSupported); + else + throw RadProto::Exception(RadProto::Error::invalidValueType); + } +} + Attribute::Attribute(uint8_t code) : m_code(code) { } +Attribute* Attribute::make(uint8_t code, const std::string& type, const std::string& data) +{ + ValueType valueType = parseValueType(type); + + switch (valueType) + { + case ValueType::String: + return new String(code, data); + case ValueType::Integer: + return new Integer(code, std::stoul(data)); + case ValueType::Encrypted: + return new Encrypted(code, data); + case ValueType::IpAddress: + { + using tokenizer = boost::tokenizer>; + boost::char_separator sep("."); + tokenizer tok(data, sep); + + std::array ipAddr; + size_t i = 0; + for (const auto& t : tok) + { + ipAddr[i] = static_cast(std::stoul(t)); + ++i; + } + return new IpAddress(code, ipAddr); + } + case ValueType::Bytes: + { + if (data.length() % 2 != 0) + throw RadProto::Exception(RadProto::Error::invalidHexStringLength); + + std::vector bytes; + + for (size_t i = 0; i < data.length(); i += 2) + { + auto byte = static_cast(std::stoi(data.substr(i, 2), nullptr, 16)); + bytes.push_back(byte); + } + return new Bytes(code, bytes); + } + default: + throw RadProto::Exception(RadProto::Error::invalidValueType); + } +} + using String = RadProto::String; String::String(uint8_t code, const uint8_t* data, size_t size) : Attribute(code), diff --git a/src/error.cpp b/src/error.cpp index b31d423..5720916 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -37,6 +37,10 @@ std::string ErrorCategory::message(int ev) const noexcept return "Invalid attribute code"; case Error::invalidAttributeSize: return "Invalid attribute size"; + case Error::invalidAttributeType: + return "Invalid attribute type"; + case Error::invalidValueType: + return "Invalid type of enum ValueType"; case Error::invalidVendorSpecificAttributeId: return "Invalid Vendor Specific attribute Id"; case Error::suchAttributeNameAlreadyExists: @@ -45,7 +49,11 @@ std::string ErrorCategory::message(int ev) const noexcept return "Such attribute code already exists"; case Error::suchAttributeNameWithAnotherTypeAlreadyExists: return "Such attribute name with another type already exists"; - default: + case Error::typeIsNotSupported: + return "Type 'vsa' is not supported in this class"; + case Error::invalidHexStringLength: + return "Invalid length of hex string"; + default: return "(Unrecognized error)"; } } diff --git a/tests/attribute_tests.cpp b/tests/attribute_tests.cpp index b929788..db00861 100644 --- a/tests/attribute_tests.cpp +++ b/tests/attribute_tests.cpp @@ -19,6 +19,113 @@ BOOST_AUTO_TEST_SUITE(AttributeTests) +BOOST_AUTO_TEST_SUITE(AttributeMakeTests) + +BOOST_AUTO_TEST_CASE(TypeString) +{ + RadProto::Attribute* attribute = RadProto::Attribute::make(1, "string", "User"); + RadProto::String* str = dynamic_cast(attribute); + + BOOST_REQUIRE(str); + + BOOST_CHECK_EQUAL(str->toString(), "User"); + + std::vector values = str->toVector({}, {}); + std::vector expected {1, 6, 'U', 's', 'e', 'r'}; + + BOOST_TEST(values == expected, boost::test_tools::per_element()); + + BOOST_CHECK_EQUAL(str->code(), 1); +} + +BOOST_AUTO_TEST_CASE(TypeInteger) +{ + RadProto::Attribute* attribute = RadProto::Attribute::make(5, "integer", "169090600"); + RadProto::Integer* intg = dynamic_cast(attribute); + + BOOST_REQUIRE(intg); + + BOOST_CHECK_EQUAL(intg->toString(), "169090600"); + + std::vector values = intg->toVector({}, {}); + std::vector expected {5, 6, 10, 20, 30, 40}; + + BOOST_TEST(values == expected, boost::test_tools::per_element()); + + BOOST_CHECK_EQUAL(intg->code(), 5); +} + +BOOST_AUTO_TEST_CASE(TypeDateToInteger) +{ + RadProto::Attribute* attribute = RadProto::Attribute::make(75, "date", "169090600"); + RadProto::Integer* intg = dynamic_cast(attribute); + + BOOST_REQUIRE(intg); + + BOOST_CHECK_EQUAL(intg->toString(), "169090600"); + + std::vector values = intg->toVector({}, {}); + std::vector expected {75, 6, 10, 20, 30, 40}; + + BOOST_TEST(values == expected, boost::test_tools::per_element()); + + BOOST_CHECK_EQUAL(intg->code(), 75); +} + +BOOST_AUTO_TEST_CASE(TypeIpaddr) +{ + RadProto::Attribute* attribute = RadProto::Attribute::make(4, "ipaddr", "127.104.22.17"); + RadProto::IpAddress* ipadr = dynamic_cast(attribute); + + BOOST_REQUIRE(ipadr); + + BOOST_CHECK_EQUAL(ipadr->toString(), "127.104.22.17"); + + std::vector values = ipadr->toVector({}, {}); + std::vector expected {4, 6, 127, 104, 22, 17}; + + BOOST_TEST(values == expected, boost::test_tools::per_element()); + + BOOST_CHECK_EQUAL(ipadr->code(), 4); +} + +BOOST_AUTO_TEST_CASE(TypeEncrypted) +{ + RadProto::Attribute* attribute = RadProto::Attribute::make(2, "encrypted", "123456"); + RadProto::Encrypted* encrypt = dynamic_cast(attribute); + + BOOST_REQUIRE(encrypt); + + BOOST_CHECK_EQUAL(encrypt->toString(), "123456"); + + std::array auth {0x92, 0xfa, 0xa1, 0xed, 0x98, 0x9b, 0xb4, 0x79, 0xfe, 0x20, 0xe2, 0xf4, 0x7f, 0x4a, 0x5a, 0x70}; + std::vector values = encrypt->toVector("secret", auth); + std::vector expected {0x02, 0x12, 0x25, 0x38, 0x58, 0x18, 0xae, 0x97, 0xeb, 0xeb, 0xbd, 0x46, 0xfd, 0xb9, 0xd1, 0x17, 0x84, 0xeb}; + + BOOST_TEST(values == expected, boost::test_tools::per_element()); + + BOOST_CHECK_EQUAL(encrypt->code(), 2); +} + +BOOST_AUTO_TEST_CASE(TypeOctets) +{ + RadProto::Attribute* attribute = RadProto::Attribute::make(3, "octets", "313233616263"); + RadProto::Bytes* bts = dynamic_cast(attribute); + + BOOST_REQUIRE(bts); + + BOOST_CHECK_EQUAL(bts->toString(), "313233616263"); + + std::vector values = bts->toVector({}, {}); + std::vector expected({3, 8, '1', '2', '3', 'a', 'b', 'c'}); + + BOOST_TEST(values == expected, boost::test_tools::per_element()); + + BOOST_CHECK_EQUAL(bts->code(), 3); +} + +BOOST_AUTO_TEST_SUITE_END() + BOOST_AUTO_TEST_CASE(StringDataConstructor) { std::vector d {'t', 'e', 's', 't'};