diff --git a/include/amarula/dbus/connman/gservice.hpp b/include/amarula/dbus/connman/gservice.hpp index dd4d9e9..1a50a15 100644 --- a/include/amarula/dbus/connman/gservice.hpp +++ b/include/amarula/dbus/connman/gservice.hpp @@ -12,6 +12,8 @@ namespace Amarula::DBus::G::Connman { class Manager; struct ServProperties; +using VariantPtr = std::unique_ptr; + class GVariantParser { public: GVariantParser() = default; @@ -26,6 +28,9 @@ class GVariantParser { void parse(GVariant* variant); virtual void update(const gchar* /*key*/, GVariant* /*value*/) {}; + [[nodiscard]] virtual auto getVariant() const -> VariantPtr { + return {nullptr, &g_variant_unref}; + } }; class IPv4 : public GVariantParser { @@ -37,6 +42,9 @@ class IPv4 : public GVariantParser { Auto, }; + explicit IPv4(IPv4::Method method, std::string address = "", + std::string netmask = "", std::string gateway = ""); + friend auto operator<<(std::ostream& ostr, const IPv4& object) -> std::ostream&; @@ -52,8 +60,10 @@ class IPv4 : public GVariantParser { std::string gateway_; explicit IPv4(GVariant* variant) { parse(variant); }; void update(const gchar* key, GVariant* value) override; + [[nodiscard]] auto getVariant() const -> VariantPtr override; friend class ServProperties; + friend class Service; }; struct IPv6 : public GVariantParser { @@ -66,6 +76,10 @@ struct IPv6 : public GVariantParser { Auto, }; enum class Privacy : uint8_t { Disabled = 0, Enabled, Preferred }; + + explicit IPv6(IPv6::Method method, std::string address = "", + uint8_t prefix_length = 0U, std::string gateway = "", + IPv6::Privacy privacy = IPv6::Privacy::Disabled); friend auto operator<<(std::ostream& ostr, const IPv6& object) -> std::ostream&; [[nodiscard]] auto getMethod() const { return method_; } @@ -82,8 +96,10 @@ struct IPv6 : public GVariantParser { uint8_t prefix_length_{0U}; explicit IPv6(GVariant* variant) { parse(variant); }; void update(const gchar* key, GVariant* value) override; + [[nodiscard]] auto getVariant() const -> VariantPtr override; friend class ServProperties; + friend class Service; }; struct Ethernet : public GVariantParser { @@ -204,7 +220,13 @@ struct ServProperties { [[nodiscard]] auto isImmutable() const { return immutable_; } [[nodiscard]] auto isRoaming() const { return roaming_; } [[nodiscard]] auto getIPv4() const { return ipv4_; } + [[nodiscard]] auto getIPv4Configuration() const { + return ipv4_configuration_; + } [[nodiscard]] auto getIPv6() const { return ipv6_; } + [[nodiscard]] auto getIPv6Configuration() const { + return ipv6_configuration_; + } [[nodiscard]] auto getEthernet() const { return ethernet_; } [[nodiscard]] auto getProvider() const { return provider_; } [[nodiscard]] auto getProxy() const { return proxy_; } @@ -230,7 +252,9 @@ struct ServProperties { bool roaming_{false}; uint8_t strength_{0U}; std::optional ipv4_{std::nullopt}; + std::optional ipv4_configuration_{std::nullopt}; std::optional ipv6_{std::nullopt}; + std::optional ipv6_configuration_{std::nullopt}; std::optional ethernet_{std::nullopt}; std::optional provider_{std::nullopt}; std::optional proxy_{std::nullopt}; @@ -255,6 +279,10 @@ class Service : public DBusProxy { PropertiesSetCallback callback = nullptr); void setNameServers(const std::vector& name_servers, PropertiesSetCallback callback = nullptr); + void setIPv4(const IPv4& ipv4_configuration, + PropertiesSetCallback callback = nullptr); + void setIPv6(const IPv6& ipv6_configuration, + PropertiesSetCallback callback = nullptr); friend class Manager; }; diff --git a/src/dbus/gconnman_private.hpp b/src/dbus/gconnman_private.hpp index 62a6d8b..015ea06 100644 --- a/src/dbus/gconnman_private.hpp +++ b/src/dbus/gconnman_private.hpp @@ -62,6 +62,8 @@ constexpr auto REMOVE_STR = "Remove"; constexpr auto INTERFACE_STR = "Interface"; constexpr auto MTU_STR = "MTU"; constexpr auto NAMESERVERS_CONFIGURATION_STR = "Nameservers.Configuration"; +constexpr auto IPV4_CONFIGURATION_STR = "IPv4.Configuration"; +constexpr auto IPV6_CONFIGURATION_STR = "IPv6.Configuration"; // Manager interface constexpr auto MANAGER_INTERFACE = "net.connman.Manager"; diff --git a/src/dbus/gconnman_service.cpp b/src/dbus/gconnman_service.cpp index 2da369b..f1efc4c 100644 --- a/src/dbus/gconnman_service.cpp +++ b/src/dbus/gconnman_service.cpp @@ -81,8 +81,8 @@ static constexpr EnumStringMap IPV6_METHOD_MAP{ static constexpr EnumStringMap IPV6_PRIVACY_MAP{ {{{IPv6::Privacy::Disabled, "disabled"}, {IPv6::Privacy::Enabled, "enabled"}, - {IPv6::Privacy::Preferred, "preferred"}, - {IPv6::Privacy::Preferred, "prefered"}}}}; + {IPv6::Privacy::Preferred, "prefered"}, + {IPv6::Privacy::Preferred, "preferred"}}}}; Service::Service(DBus* dbus, const gchar* obj_path) : DBusProxy(dbus, SERVICE, obj_path, SERVICE_INTERFACE) {} @@ -121,6 +121,20 @@ void Service::setNameServers(const std::vector& name_servers, &Service::finishAsyncCall, data.release()); } +IPv4::IPv4(const IPv4::Method method, std::string address, std::string netmask, + std::string gateway) + : method_(method), + address_(std::move(address)), + netmask_(std::move(netmask)), + gateway_(std::move(gateway)) {} + +void Service::setIPv4(const IPv4& ipv4_configuration, + PropertiesSetCallback callback) { + auto data = prepareCallback(std::move(callback)); + setProperty(IPV4_CONFIGURATION_STR, ipv4_configuration.getVariant().get(), + nullptr, &Service::finishAsyncCall, data.release()); +} + void IPv4::update(const gchar* key, GVariant* value) { if (g_strcmp0(key, METHOD_STR) == 0U) { method_ = @@ -136,6 +150,46 @@ void IPv4::update(const gchar* key, GVariant* value) { } } +auto IPv4::getVariant() const -> VariantPtr { + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); + g_variant_builder_add( + &builder, "{sv}", METHOD_STR, + g_variant_new_string( + std::string(IPV4_METHOD_MAP.toString(method_)).c_str())); + if (!address_.empty()) { + g_variant_builder_add(&builder, "{sv}", ADDRESS_STR, + g_variant_new_string(address_.c_str())); + } + if (!netmask_.empty()) { + g_variant_builder_add(&builder, "{sv}", NETMASK_STR, + g_variant_new_string(netmask_.c_str())); + } + if (!gateway_.empty()) { + g_variant_builder_add(&builder, "{sv}", GATEWAY_STR, + g_variant_new_string(gateway_.c_str())); + } + + return VariantPtr{g_variant_ref_sink(g_variant_builder_end(&builder)), + &g_variant_unref}; +} + +IPv6::IPv6(const IPv6::Method method, std::string address, + uint8_t prefix_length, std::string gateway, + const IPv6::Privacy privacy) + : method_(method), + address_(std::move(address)), + gateway_(std::move(gateway)), + privacy_(privacy), + prefix_length_(prefix_length) {} + +void Service::setIPv6(const IPv6& ipv6_configuration, + PropertiesSetCallback callback) { + auto data = prepareCallback(std::move(callback)); + setProperty(IPV6_CONFIGURATION_STR, ipv6_configuration.getVariant().get(), + nullptr, &Service::finishAsyncCall, data.release()); +} + void IPv6::update(const gchar* key, GVariant* value) { if (g_strcmp0(key, METHOD_STR) == 0U) { method_ = @@ -154,6 +208,34 @@ void IPv6::update(const gchar* key, GVariant* value) { } } +auto IPv6::getVariant() const -> VariantPtr { + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); + g_variant_builder_add( + &builder, "{sv}", METHOD_STR, + g_variant_new_string( + std::string(IPV6_METHOD_MAP.toString(method_)).c_str())); + if (!address_.empty()) { + g_variant_builder_add(&builder, "{sv}", ADDRESS_STR, + g_variant_new_string(address_.c_str())); + } + if (prefix_length_ != 0U) { + g_variant_builder_add(&builder, "{sv}", PREFIXLENGTH_STR, + g_variant_new_byte(prefix_length_)); + } + if (!gateway_.empty()) { + g_variant_builder_add(&builder, "{sv}", GATEWAY_STR, + g_variant_new_string(gateway_.c_str())); + } + g_variant_builder_add( + &builder, "{sv}", PRIVACY_STR, + g_variant_new_string( + std::string(IPV6_PRIVACY_MAP.toString(privacy_)).c_str())); + + return VariantPtr{g_variant_ref_sink(g_variant_builder_end(&builder)), + &g_variant_unref}; +} + void GVariantParser::parse(GVariant* variant) { GVariantIter* iter = g_variant_iter_new(variant); GVariant* prop = nullptr; @@ -241,10 +323,18 @@ void ServProperties::update(const gchar* key, GVariant* value) { ipv4_ = (g_variant_n_children(value) != 0) ? std::optional(IPv4(value)) : std::nullopt; + } else if (g_strcmp0(key, IPV4_CONFIGURATION_STR) == 0U) { + ipv4_configuration_ = (g_variant_n_children(value) != 0) + ? std::optional(IPv4(value)) + : std::nullopt; } else if (g_strcmp0(key, IPV6_STR) == 0U) { ipv6_ = (g_variant_n_children(value) != 0) ? std::optional(IPv6(value)) : std::nullopt; + } else if (g_strcmp0(key, IPV6_CONFIGURATION_STR) == 0U) { + ipv6_configuration_ = (g_variant_n_children(value) != 0) + ? std::optional(IPv6(value)) + : std::nullopt; } else if (g_strcmp0(key, ETHERNET_STR) == 0U) { ethernet_ = (g_variant_n_children(value) != 0) ? std::optional(Ethernet(value)) @@ -346,10 +436,20 @@ auto operator<<(std::ostream& ost, const ServProperties& obj) -> std::ostream& { ost << obj.ipv4_.value(); } + if (obj.ipv4_configuration_) { + ost << "Configuration "; + ost << obj.ipv4_configuration_.value(); + } + if (obj.ipv6_) { ost << obj.ipv6_.value(); } + if (obj.ipv6_configuration_) { + ost << "Configuration "; + ost << obj.ipv6_configuration_.value(); + } + if (obj.ethernet_) { ost << obj.ethernet_.value(); } diff --git a/tests/gconnman_serv_test.cpp b/tests/gconnman_serv_test.cpp index 0f9e5e4..f76e27f 100644 --- a/tests/gconnman_serv_test.cpp +++ b/tests/gconnman_serv_test.cpp @@ -15,6 +15,8 @@ using Error = Amarula::DBus::G::Connman::ServProperties::Error; using State = Amarula::DBus::G::Connman::ServProperties::State; using Type = Amarula::DBus::G::Connman::TechProperties::Type; using ServType = Amarula::DBus::G::Connman::ServProperties::Type; +using IPv4 = Amarula::DBus::G::Connman::IPv4; +using IPv6 = Amarula::DBus::G::Connman::IPv6; TEST(Connman, getServs) { bool called = false; @@ -71,7 +73,7 @@ TEST(Connman, getServs) { } }); } - ASSERT_TRUE(called) << "TechnologiesChanged callback was never called"; + ASSERT_TRUE(called) << "ServicesChanged callback was never called"; } TEST(Connman, setNameServers) { @@ -81,35 +83,173 @@ TEST(Connman, setNameServers) { const Connman connman; const auto manager = connman.manager(); - manager->onServicesChanged([&called, main_tid = thread_bundle.main_tid, - loop_tid = thread_bundle.loop_tid]( - const auto& services) { - called = true; - const auto callback_tid = std::this_thread::get_id(); - EXPECT_NE(callback_tid, main_tid); - EXPECT_NE(callback_tid, loop_tid); - ASSERT_FALSE(services.empty()); - for (const auto& serv : services) { - const auto props = serv->properties(); - const auto name = props.getName(); - std::cout << props; - serv->onPropertyChanged([](const auto& properties) { - std::cout << "onPropertyChange:\n"; - std::cout << properties; - }); - serv->setNameServers( - {"8.8.8.8", "4.4.4.4"}, - [name, main_tid, loop_tid](auto success) { + auto do_on_servs = [&called, main_tid = thread_bundle.main_tid, + loop_tid = thread_bundle.loop_tid]( + const auto& services, + const bool check_thread_id = true) { + if (!called) { + called = true; + if (check_thread_id) { + const auto callback_tid = std::this_thread::get_id(); + EXPECT_NE(callback_tid, main_tid); + EXPECT_NE(callback_tid, loop_tid); + } + ASSERT_FALSE(services.empty()); + for (const auto& serv : services) { + const auto props = serv->properties(); + const auto name = props.getName(); + std::cout << props; + serv->onPropertyChanged([](const auto& properties) { + std::cout << "onPropertyChange:\n"; + std::cout << properties; + }); + serv->setNameServers( + {"8.8.8.8", "4.4.4.4"}, + [name, main_tid, loop_tid](auto success) { + const auto callback_tid = + std::this_thread::get_id(); + EXPECT_NE(callback_tid, main_tid); + EXPECT_NE(callback_tid, loop_tid); + EXPECT_TRUE(success) << "Set setNameServers for " + << name << " did not succeed"; + }); + } + } + }; + + if (manager->services().empty()) { + manager->onServicesChanged(do_on_servs); + } else { + do_on_servs(manager->services(), false); + } + } + ASSERT_TRUE(called) << "ServicesChanged callback was never called"; +} + +TEST(Connman, setIPv4Configuration) { + bool called = false; + { + const ThreadBundle thread_bundle; + const Connman connman; + const auto manager = connman.manager(); + + auto do_on_servs = [&called, main_tid = thread_bundle.main_tid, + loop_tid = thread_bundle.loop_tid]( + const auto& services, + const bool check_thread_id = true) { + if (!called) { + called = true; + if (check_thread_id) { + const auto callback_tid = std::this_thread::get_id(); + EXPECT_NE(callback_tid, main_tid); + EXPECT_NE(callback_tid, loop_tid); + } + ASSERT_FALSE(services.empty()); + for (const auto& serv : services) { + const auto props = serv->properties(); + const auto name = props.getName(); + std::cout << props; + serv->onPropertyChanged([](const auto& properties) { + std::cout << "onPropertyChange:\n"; + std::cout << properties; + }); + const auto ipv4_config = + IPv4(IPv4::Method::Manual, "192.168.1.100", + "255.255.255.0", "192.168.1.1"); + serv->setIPv4(ipv4_config, [serv, name, main_tid, + loop_tid](auto success) { const auto callback_tid = std::this_thread::get_id(); EXPECT_NE(callback_tid, main_tid); EXPECT_NE(callback_tid, loop_tid); - EXPECT_TRUE(success) << "Set setNameServers for " - << name << " did not succeed"; + EXPECT_TRUE(success) + << "Set setIPv4Configuration manual for " << name + << " did not succeed"; + const auto ipv4_config = IPv4(IPv4::Method::Dhcp); + serv->setIPv4(ipv4_config, [name, main_tid, + loop_tid](auto success) { + const auto callback_tid = + std::this_thread::get_id(); + EXPECT_NE(callback_tid, main_tid); + EXPECT_NE(callback_tid, loop_tid); + EXPECT_TRUE(success) + << "Set setIPv4Configuration dhcp for " << name + << " did not succeed"; + }); }); + } } - }); + }; + + if (manager->services().empty()) { + manager->onServicesChanged(do_on_servs); + } else { + do_on_servs(manager->services(), false); + } } - ASSERT_TRUE(called) << "TechnologiesChanged callback was never called"; + ASSERT_TRUE(called) << "ServicesChanged callback was never called"; +} + +TEST(Connman, setIPv6Configuration) { + bool called = false; + { + const ThreadBundle thread_bundle; + const Connman connman; + const auto manager = connman.manager(); + + auto do_on_servs = [&called, main_tid = thread_bundle.main_tid, + loop_tid = thread_bundle.loop_tid]( + const auto& services, + const bool check_thread_id = true) { + if (!called) { + called = true; + if (check_thread_id) { + const auto callback_tid = std::this_thread::get_id(); + EXPECT_NE(callback_tid, main_tid); + EXPECT_NE(callback_tid, loop_tid); + } + ASSERT_FALSE(services.empty()); + for (const auto& serv : services) { + const auto props = serv->properties(); + const auto name = props.getName(); + std::cout << props; + serv->onPropertyChanged([](const auto& properties) { + std::cout << "onPropertyChange:\n"; + std::cout << properties; + }); + const auto ipv6_config = + IPv6(IPv6::Method::Manual, "2001:db8::100", 64, + "2001:db8::1", IPv6::Privacy::Preferred); + serv->setIPv6(ipv6_config, [serv, name, main_tid, + loop_tid](auto success) { + const auto callback_tid = std::this_thread::get_id(); + EXPECT_NE(callback_tid, main_tid); + EXPECT_NE(callback_tid, loop_tid); + EXPECT_TRUE(success) + << "Set setIPv6Configuration manual for " << name + << " did not succeed"; + const auto ipv6_config = IPv6(IPv6::Method::Auto); + serv->setIPv6(ipv6_config, [name, main_tid, + loop_tid](auto success) { + const auto callback_tid = + std::this_thread::get_id(); + EXPECT_NE(callback_tid, main_tid); + EXPECT_NE(callback_tid, loop_tid); + EXPECT_TRUE(success) + << "Set setIPv6Configuration auto for " << name + << " did not succeed"; + }); + }); + } + } + }; + + if (manager->services().empty()) { + manager->onServicesChanged(do_on_servs); + } else { + do_on_servs(manager->services(), false); + } + } + ASSERT_TRUE(called) << "ServicesChanged callback was never called"; } TEST(Connman, ForgetAndDisconnectService) {