From 6fc1baa342d7496e2e8dcbdc7209f8f12e4c4c49 Mon Sep 17 00:00:00 2001 From: Eduardo Gonzalez Date: Thu, 27 Aug 2026 12:18:00 +0200 Subject: [PATCH 1/5] gconnman_serv_test.cpp: Run also if the manager has services It could be that the manager fill the services before the onServicesChanged is emitted. Check if there are already services in the manager and run the tests on them. Run only once. The latter make the test more robust. Fix typos on the logs. Signed-off-by: Eduardo Gonzalez --- tests/gconnman_serv_test.cpp | 66 +++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/tests/gconnman_serv_test.cpp b/tests/gconnman_serv_test.cpp index 0f9e5e4..b87ad6c 100644 --- a/tests/gconnman_serv_test.cpp +++ b/tests/gconnman_serv_test.cpp @@ -71,7 +71,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 +81,47 @@ 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) { - 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"; + 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) << "TechnologiesChanged callback was never called"; + ASSERT_TRUE(called) << "ServicesChanged callback was never called"; } TEST(Connman, ForgetAndDisconnectService) { From 287fa3b7643dd54b2e739d259a3ca14ca1561d55 Mon Sep 17 00:00:00 2001 From: Eduardo Gonzalez Date: Thu, 27 Aug 2026 11:57:12 +0200 Subject: [PATCH 2/5] connman_service: Parse and expose IPv4.Configuration Add IPv4.Configuration property string. Add public constructor of IPv4 that can be used by the user to set the IPv4.Configuration. Reuse the IPv4 parser for the service's IPv4.Configuration property and add a getter. Add a virtual method to the base class of IPv4 for creating the gvariant. Add the latter virtual method overwrite for IPv4 class. Implement part of #50. Signed-off-by: Eduardo Gonzalez --- include/amarula/dbus/connman/gservice.hpp | 16 ++++++++ src/dbus/gconnman_private.hpp | 1 + src/dbus/gconnman_service.cpp | 47 +++++++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/include/amarula/dbus/connman/gservice.hpp b/include/amarula/dbus/connman/gservice.hpp index dd4d9e9..682d6a1 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 { @@ -204,6 +214,9 @@ 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 getEthernet() const { return ethernet_; } [[nodiscard]] auto getProvider() const { return provider_; } @@ -230,6 +243,7 @@ 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 ethernet_{std::nullopt}; std::optional provider_{std::nullopt}; @@ -255,6 +269,8 @@ 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); friend class Manager; }; diff --git a/src/dbus/gconnman_private.hpp b/src/dbus/gconnman_private.hpp index 62a6d8b..86aa115 100644 --- a/src/dbus/gconnman_private.hpp +++ b/src/dbus/gconnman_private.hpp @@ -62,6 +62,7 @@ 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"; // 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..e41a8d0 100644 --- a/src/dbus/gconnman_service.cpp +++ b/src/dbus/gconnman_service.cpp @@ -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,30 @@ 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}; +} + void IPv6::update(const gchar* key, GVariant* value) { if (g_strcmp0(key, METHOD_STR) == 0U) { method_ = @@ -241,6 +279,10 @@ 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)) @@ -346,6 +388,11 @@ 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(); } From d14585a53e78e0adea00230e134090857471e233 Mon Sep 17 00:00:00 2001 From: Eduardo Gonzalez Date: Thu, 27 Aug 2026 11:58:42 +0200 Subject: [PATCH 3/5] gconnman_serv_test.cpp: Add setIPv4Configuration test Mirrors the existing setNameServers test, setting a manual IPv4 configuration on every discovered service and latter putt it to dhcp. Implement part of #50 Signed-off-by: Eduardo Gonzalez --- tests/gconnman_serv_test.cpp | 64 ++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/gconnman_serv_test.cpp b/tests/gconnman_serv_test.cpp index b87ad6c..7fbad1a 100644 --- a/tests/gconnman_serv_test.cpp +++ b/tests/gconnman_serv_test.cpp @@ -15,6 +15,7 @@ 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; TEST(Connman, getServs) { bool called = false; @@ -124,6 +125,69 @@ TEST(Connman, setNameServers) { 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 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) << "ServicesChanged callback was never called"; +} + TEST(Connman, ForgetAndDisconnectService) { bool called = false; From 0e60cf0e071a0bfeed306a56a1018197c39194aa Mon Sep 17 00:00:00 2001 From: Eduardo Gonzalez Date: Thu, 27 Aug 2026 16:30:40 +0200 Subject: [PATCH 4/5] connman_service: Parse and expose IPv6.Configuration Add IPv6.Configuration property string. Add public constructor of IPv6 that can be used by the user to set the IPv6.Configuration. Reuse the IPv6 parser for the service's IPv6.Configuration property and add a getter. Implement part of #50. Signed-off-by: Eduardo Gonzalez --- include/amarula/dbus/connman/gservice.hpp | 12 +++++ src/dbus/gconnman_private.hpp | 1 + src/dbus/gconnman_service.cpp | 57 ++++++++++++++++++++++- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/include/amarula/dbus/connman/gservice.hpp b/include/amarula/dbus/connman/gservice.hpp index 682d6a1..1a50a15 100644 --- a/include/amarula/dbus/connman/gservice.hpp +++ b/include/amarula/dbus/connman/gservice.hpp @@ -76,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_; } @@ -92,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 { @@ -218,6 +224,9 @@ struct ServProperties { 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_; } @@ -245,6 +254,7 @@ struct ServProperties { 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}; @@ -271,6 +281,8 @@ class Service : public DBusProxy { 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 86aa115..015ea06 100644 --- a/src/dbus/gconnman_private.hpp +++ b/src/dbus/gconnman_private.hpp @@ -63,6 +63,7 @@ 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 e41a8d0..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) {} @@ -174,6 +174,22 @@ auto IPv4::getVariant() const -> VariantPtr { &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_ = @@ -192,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; @@ -287,6 +331,10 @@ void ServProperties::update(const gchar* key, GVariant* value) { 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)) @@ -397,6 +445,11 @@ auto operator<<(std::ostream& ost, const ServProperties& obj) -> std::ostream& { ost << obj.ipv6_.value(); } + if (obj.ipv6_configuration_) { + ost << "Configuration "; + ost << obj.ipv6_configuration_.value(); + } + if (obj.ethernet_) { ost << obj.ethernet_.value(); } From e633b88418a14409e6331f85a91db26d067b1ec3 Mon Sep 17 00:00:00 2001 From: Eduardo Gonzalez Date: Thu, 27 Aug 2026 16:33:00 +0200 Subject: [PATCH 5/5] gconnman_serv_test.cpp: Add setIPv6Configuration test Mirrors the existing setIPv4 test, setting a manual IPv6 configuration on every discovered service and latter putt it to auto. Implement part of #50. Signed-off-by: Eduardo Gonzalez --- tests/gconnman_serv_test.cpp | 64 ++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/gconnman_serv_test.cpp b/tests/gconnman_serv_test.cpp index 7fbad1a..f76e27f 100644 --- a/tests/gconnman_serv_test.cpp +++ b/tests/gconnman_serv_test.cpp @@ -16,6 +16,7 @@ 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; @@ -188,6 +189,69 @@ TEST(Connman, setIPv4Configuration) { 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) { bool called = false;