diff --git a/common/Makefile.am b/common/Makefile.am index 5ec4e232..d77ea310 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -62,7 +62,8 @@ libswsscommon_la_SOURCES = \ exec.cpp \ subscriberstatetable.cpp \ timestamp.cpp \ - warm_restart.cpp + warm_restart.cpp \ + redisutility.cpp libswsscommon_la_CXXFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(LIBNL_CFLAGS) libswsscommon_la_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(LIBNL_CPPFLAGS) diff --git a/common/boolean.h b/common/boolean.h new file mode 100644 index 00000000..12219f51 --- /dev/null +++ b/common/boolean.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +namespace swss +{ +class Boolean +{ +public: + Boolean() = default; + Boolean(bool boolean) : m_boolean(boolean) + { + } + operator bool() const + { + return m_boolean; + } + operator bool&() + { + return m_boolean; + } +protected: + bool m_boolean; +}; + +class AlphaBoolean : public Boolean +{ +public: + AlphaBoolean() = default; + AlphaBoolean(bool boolean) : Boolean(boolean) + { + } +}; + +static inline std::ostream &operator<<(std::ostream &out, const AlphaBoolean &b) +{ + return out << std::boolalpha << (bool)(b); +} + +static inline std::istream &operator>>(std::istream &in, AlphaBoolean &b) +{ + return in >> std::boolalpha >> (bool &)(b); +} + +static inline std::ostream &operator<<(std::ostream &out, const Boolean &b) +{ + return out << (bool)(b); +} + +static inline std::istream &operator>>(std::istream &in, Boolean &b) +{ + return in >> (bool &)(b); +} + +} diff --git a/common/redisutility.cpp b/common/redisutility.cpp new file mode 100644 index 00000000..1d1d0ece --- /dev/null +++ b/common/redisutility.cpp @@ -0,0 +1,33 @@ +#include "redisutility.h" +#include "stringutility.h" + +#include + + +boost::optional swss::fvsGetValue( + const std::vector &fvt, + const std::string &field, + bool case_insensitive) +{ + boost::optional ret; + + for (auto itr = fvt.begin(); itr != fvt.end(); itr++) + { + bool is_equal = false; + if (case_insensitive) + { + is_equal = boost::iequals(fvField(*itr), field); + } + else + { + is_equal = (fvField(*itr) == field); + } + if (is_equal) + { + ret = fvValue(*itr); + break; + } + } + + return ret; +} \ No newline at end of file diff --git a/common/redisutility.h b/common/redisutility.h new file mode 100644 index 00000000..3da0ac66 --- /dev/null +++ b/common/redisutility.h @@ -0,0 +1,16 @@ +#pragma once + +#include "rediscommand.h" + +#include + +#include +#include + +namespace swss +{ +boost::optional fvsGetValue( + const std::vector &fvt, + const std::string &field, + bool case_insensitive = false); +} diff --git a/common/stringutility.h b/common/stringutility.h new file mode 100644 index 00000000..c569fbe6 --- /dev/null +++ b/common/stringutility.h @@ -0,0 +1,136 @@ +#pragma once + +#include "logger.h" +#include "tokenize.h" +#include "schema.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace swss { + +template +static inline void lexical_convert(const std::string &str, T &t) +{ + t = boost::lexical_cast(str); +} + +namespace lexical_convert_detail +{ + + template + void lexical_convert( + std::vector::const_iterator begin, + std::vector::const_iterator end, + T &t) + { + if (begin == end) + { + SWSS_LOG_THROW("Insufficient corpus"); + } + auto cur_itr = begin++; + if (begin != end) + { + SWSS_LOG_THROW("Too much corpus"); + } + swss::lexical_convert(*cur_itr, t); + } + + template + void lexical_convert( + std::vector::const_iterator begin, + std::vector::const_iterator end, + T &t, + Args &... args) + { + if (begin == end) + { + SWSS_LOG_THROW("Insufficient corpus"); + } + swss::lexical_convert(*(begin++), t); + return lexical_convert(begin, end, args...); + } + +} + +template +void lexical_convert(const std::vector &strs, T &t, Args &... args) +{ + lexical_convert_detail::lexical_convert(strs.begin(), strs.end(), t, args...); +} + +namespace join_detail +{ + + template + void join(std::ostringstream &ostream, char, const T &t) + { + ostream << t; + } + + template + void join(std::ostringstream &ostream, char delimiter, const T &t, const Args &... args) + { + ostream << t << delimiter; + join(ostream, delimiter, args...); + } + +} + +template +static inline std::string join(char delimiter, const T &t, const Args &... args) +{ + std::ostringstream ostream; + join_detail::join(ostream, delimiter, t, args...); + return ostream.str(); +} + +static inline bool hex_to_binary(const std::string &hex_str, std::uint8_t *buffer, size_t buffer_length) +{ + if (hex_str.length() != (buffer_length * 2)) + { + SWSS_LOG_DEBUG("Buffer length isn't sufficient"); + return false; + } + + try + { + boost::algorithm::unhex(hex_str, buffer); + } + catch(const boost::algorithm::non_hex_input &e) + { + SWSS_LOG_DEBUG("Invalid hex string %s", hex_str.c_str()); + return false; + } + + return true; +} + +template +static inline void hex_to_binary(const std::string &s, T &value) +{ + return hex_to_binary(s, &value, sizeof(T)); +} + +static inline std::string binary_to_hex(const void *buffer, size_t length) +{ + std::string s; + auto buf = static_cast(buffer); + + boost::algorithm::hex( + buf, + buf + length, + std::back_inserter(s)); + + return s; +} + +} diff --git a/tests/Makefile.am b/tests/Makefile.am index 9fbf48ea..25827bab 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -30,6 +30,9 @@ tests_SOURCES = redis_ut.cpp \ logger_ut.cpp \ redis_multi_ns_ut.cpp \ fdb_flush.cpp \ + stringutility_ut.cpp \ + redisutility_ut.cpp \ + boolean_ut.cpp \ main.cpp tests_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(LIBNL_CFLAGS) diff --git a/tests/boolean_ut.cpp b/tests/boolean_ut.cpp new file mode 100644 index 00000000..c8a214ad --- /dev/null +++ b/tests/boolean_ut.cpp @@ -0,0 +1,80 @@ +#include "common/boolean.h" + +#include "gtest/gtest.h" + +#include + +TEST(BOOLEAN, boolean) +{ + swss::Boolean b; + + b = true; + std::ostringstream ost; + ost << b; + EXPECT_EQ(ost.str(), "1"); + + b = false; + std::ostringstream osf; + osf << b; + EXPECT_EQ(osf.str(), "0"); + + b = false; + std::istringstream ist("1"); + ist >> b; + EXPECT_TRUE(b); + EXPECT_FALSE(ist.fail()); + + b = true; + std::istringstream isf("0"); + isf >> b; + EXPECT_FALSE(b); + EXPECT_FALSE(isf.fail()); +} + +TEST(BOOLEAN, alpha_boolean) +{ + swss::AlphaBoolean bt(true); + swss::AlphaBoolean bf(false); + + EXPECT_TRUE(bt); + EXPECT_FALSE(bf); + + std::ostringstream ost; + ost << bt; + EXPECT_EQ(ost.str(), "true"); + + std::ostringstream osf; + osf << bf; + EXPECT_EQ(osf.str(), "false"); + + std::istringstream is; + + bt = false; + is.str("true"); + is >> bt; + EXPECT_TRUE(bt); + EXPECT_FALSE(is.fail()); + is.clear(); + + bt = false; + is.str("true123"); + int i; + is >> bt >> i; + EXPECT_TRUE(bt); + EXPECT_EQ(i, 123); + EXPECT_FALSE(is.fail()); + is.clear(); + + bf = true; + is.str("false"); + is >> bf; + EXPECT_FALSE(bf); + EXPECT_FALSE(is.fail()); + is.clear(); + + bf = true; + is.str("abdef"); + EXPECT_FALSE(is >> bt); + EXPECT_TRUE(is.fail()); + is.clear(); +} diff --git a/tests/redisutility_ut.cpp b/tests/redisutility_ut.cpp new file mode 100644 index 00000000..6257580d --- /dev/null +++ b/tests/redisutility_ut.cpp @@ -0,0 +1,32 @@ +#include "common/redisutility.h" +#include "common/stringutility.h" +#include "common/boolean.h" + +#include "gtest/gtest.h" + +TEST(REDISUTILITY, fvsGetValue) +{ + std::vector fvt; + fvt.push_back(std::make_pair("int", "123")); + fvt.push_back(std::make_pair("bool", "true")); + fvt.push_back(std::make_pair("string", "name")); + + auto si = swss::fvsGetValue(fvt, "int"); + EXPECT_TRUE(si); + auto sb = swss::fvsGetValue(fvt, "bool"); + EXPECT_TRUE(sb); + auto ss = swss::fvsGetValue(fvt, "string"); + EXPECT_TRUE(ss); + + int i; + swss::AlphaBoolean b; + std::string s; + ASSERT_NO_THROW(swss::lexical_convert({*si, *sb, *ss}, i, b, s)); + EXPECT_EQ(i, 123); + EXPECT_EQ(b, true); + EXPECT_EQ(s, "name"); + + EXPECT_FALSE(swss::fvsGetValue(fvt, "Int")); + EXPECT_TRUE(swss::fvsGetValue(fvt, "Int", true)); + EXPECT_FALSE(swss::fvsGetValue(fvt, "double")); +} diff --git a/tests/stringutility_ut.cpp b/tests/stringutility_ut.cpp new file mode 100644 index 00000000..5f217c8e --- /dev/null +++ b/tests/stringutility_ut.cpp @@ -0,0 +1,93 @@ +#include "common/stringutility.h" +#include "common/boolean.h" + +#include "gtest/gtest.h" + +#include +#include + +TEST(STRINGUTILITY, cast_int) +{ + int i; + + EXPECT_NO_THROW(swss::lexical_convert("123", i)); + EXPECT_EQ(i, 123); + + EXPECT_NO_THROW(swss::lexical_convert("0", i)); + EXPECT_EQ(i, 0); + + EXPECT_NO_THROW(swss::lexical_convert("-123", i)); + EXPECT_EQ(i, -123); + + EXPECT_THROW(swss::lexical_convert("123:", i), boost::bad_lexical_cast); +} + +TEST(STRINGUTILITY, cast_alpha_bool) +{ + swss::AlphaBoolean b; + + EXPECT_NO_THROW(swss::lexical_convert("true", b)); + EXPECT_EQ(b, true); + + EXPECT_NO_THROW(swss::lexical_convert("false", b)); + EXPECT_EQ(b, false); + + EXPECT_THROW(swss::lexical_convert("True", b), boost::bad_lexical_cast); + + EXPECT_THROW(swss::lexical_convert("abcdefg", b), boost::bad_lexical_cast); +} + +TEST(STRINGUTILITY, cast_mix) +{ + int i; + swss::AlphaBoolean b; + std::string s; + + EXPECT_NO_THROW(swss::lexical_convert(swss::tokenize("123:true:name", ':'), i, b, s)); + EXPECT_EQ(i, 123); + EXPECT_EQ(b, true); + EXPECT_EQ("name", s); + + EXPECT_NO_THROW(swss::lexical_convert({"123", "true", "name"}, i, b, s)); + EXPECT_EQ(i, 123); + EXPECT_EQ(b, true); + EXPECT_EQ("name", s); + + std::vector attrs{"123", "true", "name"}; + EXPECT_NO_THROW(swss::lexical_convert(attrs, i, b, s)); + + EXPECT_THROW(swss::lexical_convert({"123"}, i, b), std::runtime_error); + EXPECT_THROW(swss::lexical_convert(attrs, i, i, i), boost::bad_lexical_cast); +} + +TEST(STRINGUTILITY, join) +{ + EXPECT_EQ(swss::join(':', 123, true, std::string("name")), "123:1:name"); + + EXPECT_EQ(swss::join(':', 123, swss::AlphaBoolean(true), std::string("name")), "123:true:name"); + + EXPECT_EQ(swss::join('|', 123, swss::AlphaBoolean(false), std::string("name")), "123|false|name"); +} + +TEST(STRINGUTILITY, hex_to_binary) +{ + std::array a; + + EXPECT_TRUE(swss::hex_to_binary("01020aff05", a.data(), a.size())); + EXPECT_EQ(a, (std::array{0x1, 0x2, 0x0a, 0xff, 0x5})); + + EXPECT_TRUE(swss::hex_to_binary("01020AFF05", a.data(), a.size())); + EXPECT_EQ(a, (std::array{0x1, 0x2, 0x0A, 0xFF, 0x5})); + + EXPECT_FALSE(swss::hex_to_binary("01020", a.data(), a.size())); + EXPECT_FALSE(swss::hex_to_binary("0101010101010101", a.data(), a.size())); + EXPECT_FALSE(swss::hex_to_binary("xxxx", a.data(), a.size())); +} + +TEST(STRINGUTILITY, binary_to_hex) +{ + std::array a{0x1, 0x2, 0x0a, 0xff, 0x5}; + EXPECT_EQ(swss::binary_to_hex(a.data(), a.size()), "01020AFF05"); + + EXPECT_EQ(swss::binary_to_hex(nullptr, 0), ""); +}