wimax_unittest.cc revision 0bc1acaf481a6ebf33a1b973f15f800919b676ee
16b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 26b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine// Use of this source code is governed by a BSD-style license that can be 36b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine// found in the LICENSE file. 46b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine 56b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine#include "shill/wimax.h" 66b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine 76b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine#include <string> 86b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine 96b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine#include "shill/event_dispatcher.h" 106b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine#include "shill/mock_dhcp_config.h" 116b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine#include "shill/mock_dhcp_provider.h" 126b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine#include "shill/mock_manager.h" 136b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine#include "shill/mock_metrics.h" 146b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine#include "shill/mock_proxy_factory.h" 156b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine#include "shill/mock_wimax_device_proxy.h" 166b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine#include "shill/mock_wimax_provider.h" 176b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine#include "shill/mock_wimax_service.h" 186b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine#include "shill/nice_mock_control.h" 196b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine#include "shill/testing.h" 206b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine 216b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavineusing base::Bind; 226b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavineusing base::Unretained; 236b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavineusing std::string; 246b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavineusing testing::_; 256b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavineusing testing::NiceMock; 266b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavineusing testing::Return; 276b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine 286b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavinenamespace shill { 296b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine 306b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavinenamespace { 316b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine 326b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavineconst char kTestLinkName[] = "wm0"; 336b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavineconst char kTestAddress[] = "01:23:45:67:89:ab"; 346b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavineconst int kTestInterfaceIndex = 5; 356b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavineconst char kTestPath[] = "/org/chromium/WiMaxManager/Device/6"; 366b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine 376b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine} // namespace 386b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine 396b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavineclass WiMaxTest : public testing::Test { 406b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine public: 416b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine WiMaxTest() 426b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine : proxy_(new MockWiMaxDeviceProxy()), 436b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine metrics_(&dispatcher_), 446b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine manager_(&control_, &dispatcher_, &metrics_, NULL), 456b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine dhcp_config_(new MockDHCPConfig(&control_, 466b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine kTestLinkName)), 476b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine device_(new WiMax(&control_, &dispatcher_, &metrics_, &manager_, 486b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine kTestLinkName, kTestAddress, kTestInterfaceIndex, 496b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine kTestPath)) {} 506b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine 516b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine virtual ~WiMaxTest() {} 526b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine 536b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine protected: 546b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine class Target { 556b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine public: 566b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine virtual ~Target() {} 576b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine 586b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine MOCK_METHOD1(EnabledStateChanged, void(const Error &error)); 596b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine }; 606b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine 616b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine virtual void SetUp() { 626b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine device_->proxy_factory_ = &proxy_factory_; 636b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine device_->set_dhcp_provider(&dhcp_provider_); 646b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine } 656b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine 666b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine virtual void TearDown() { 676b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine device_->SelectService(NULL); 686b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine device_->pending_service_ = NULL; 696b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine device_->proxy_factory_ = NULL; 706b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine } 716b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine 726b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine scoped_ptr<MockWiMaxDeviceProxy> proxy_; 736b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine MockProxyFactory proxy_factory_; 746b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine NiceMockControl control_; 756b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine EventDispatcher dispatcher_; 766b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine NiceMock<MockMetrics> metrics_; 776b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine MockManager manager_; 786b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine MockDHCPProvider dhcp_provider_; 796b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine scoped_refptr<MockDHCPConfig> dhcp_config_; 806b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine WiMaxRefPtr device_; 816b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine}; 826b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine 836b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim SiniavineTEST_F(WiMaxTest, Constructor) { 846b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine EXPECT_EQ(kTestPath, device_->path()); 856b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine EXPECT_FALSE(device_->scanning()); 866b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine} 876b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine 886b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim SiniavineTEST_F(WiMaxTest, StartStop) { 896b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine EXPECT_FALSE(device_->proxy_.get()); 906b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine EXPECT_CALL(proxy_factory_, CreateWiMaxDeviceProxy(_)) 916b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine .WillOnce(ReturnAndReleasePointee(&proxy_)); 926b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine EXPECT_CALL(*proxy_, Enable(_, _, _)); 936b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine EXPECT_CALL(*proxy_, set_networks_changed_callback(_)); 946b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine EXPECT_CALL(*proxy_, set_status_changed_callback(_)); 956b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine EXPECT_CALL(*proxy_, Disable(_, _, _)); 966b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine device_->Start(NULL, EnabledStateChangedCallback()); 976b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine ASSERT_TRUE(device_->proxy_.get()); 986b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine 996b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine scoped_refptr<MockWiMaxService> service( 1006b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine new MockWiMaxService(&control_, NULL, &metrics_, &manager_)); 1016b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine device_->pending_service_ = service; 1026b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine EXPECT_CALL(*service, SetState(Service::kStateIdle)); 1036b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine device_->networks_.insert("path"); 1046b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine MockWiMaxProvider provider; 1056b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine EXPECT_CALL(manager_, wimax_provider()).WillOnce(Return(&provider)); 1066b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine EXPECT_CALL(provider, OnNetworksChanged()); 1076b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine device_->StartConnectTimeout(); 1086b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine device_->Stop(NULL, EnabledStateChangedCallback()); 1096b1e88e5d09a88dd77e631918aa7122a95a7ff4bMaxim Siniavine EXPECT_TRUE(device_->networks_.empty()); 110 EXPECT_FALSE(device_->IsConnectTimeoutStarted()); 111 EXPECT_FALSE(device_->pending_service_); 112} 113 114TEST_F(WiMaxTest, OnServiceStopped) { 115 scoped_refptr<NiceMock<MockWiMaxService> > service0( 116 new NiceMock<MockWiMaxService>( 117 &control_, 118 reinterpret_cast<EventDispatcher *>(NULL), 119 &metrics_, 120 &manager_)); 121 scoped_refptr<MockWiMaxService> service1( 122 new MockWiMaxService(&control_, NULL, &metrics_, &manager_)); 123 device_->SelectService(service0); 124 device_->pending_service_ = service1; 125 126 device_->OnServiceStopped(NULL); 127 EXPECT_TRUE(device_->selected_service()); 128 EXPECT_TRUE(device_->pending_service_); 129 130 device_->OnServiceStopped(service0); 131 EXPECT_FALSE(device_->selected_service()); 132 EXPECT_TRUE(device_->pending_service_); 133 134 device_->OnServiceStopped(service1); 135 EXPECT_FALSE(device_->selected_service()); 136 EXPECT_FALSE(device_->pending_service_); 137} 138 139TEST_F(WiMaxTest, OnNetworksChanged) { 140 MockWiMaxProvider provider; 141 EXPECT_CALL(manager_, wimax_provider()).WillOnce(Return(&provider)); 142 EXPECT_CALL(provider, OnNetworksChanged()); 143 device_->networks_.insert("foo"); 144 RpcIdentifiers networks; 145 networks.push_back("bar"); 146 networks.push_back("zoo"); 147 networks.push_back("bar"); 148 device_->OnNetworksChanged(networks); 149 EXPECT_EQ(2, device_->networks_.size()); 150 EXPECT_TRUE(ContainsKey(device_->networks_, "bar")); 151 EXPECT_TRUE(ContainsKey(device_->networks_, "zoo")); 152} 153 154TEST_F(WiMaxTest, OnConnectComplete) { 155 scoped_refptr<MockWiMaxService> service( 156 new MockWiMaxService(&control_, NULL, &metrics_, &manager_)); 157 device_->pending_service_ = service; 158 EXPECT_CALL(*service, SetState(_)).Times(0); 159 EXPECT_TRUE(device_->pending_service_); 160 EXPECT_CALL(*service, SetState(Service::kStateFailure)); 161 device_->OnConnectComplete(Error(Error::kOperationFailed)); 162 EXPECT_FALSE(device_->pending_service_); 163} 164 165TEST_F(WiMaxTest, OnStatusChanged) { 166 scoped_refptr<MockWiMaxService> service( 167 new MockWiMaxService(&control_, NULL, &metrics_, &manager_)); 168 169 EXPECT_EQ(wimax_manager::kDeviceStatusUninitialized, device_->status_); 170 device_->pending_service_ = service; 171 EXPECT_CALL(*service, SetState(_)).Times(0); 172 EXPECT_CALL(*service, ClearPassphrase()).Times(0); 173 device_->OnStatusChanged(wimax_manager::kDeviceStatusScanning); 174 EXPECT_TRUE(device_->pending_service_); 175 EXPECT_EQ(wimax_manager::kDeviceStatusScanning, device_->status_); 176 177 device_->status_ = wimax_manager::kDeviceStatusConnecting; 178 EXPECT_CALL(*service, SetState(Service::kStateFailure)); 179 EXPECT_CALL(*service, ClearPassphrase()).Times(0); 180 device_->OnStatusChanged(wimax_manager::kDeviceStatusScanning); 181 EXPECT_FALSE(device_->pending_service_); 182 183 device_->status_ = wimax_manager::kDeviceStatusConnecting; 184 device_->SelectService(service); 185 EXPECT_CALL(*service, SetState(Service::kStateFailure)); 186 EXPECT_CALL(*service, SetState(Service::kStateIdle)); 187 EXPECT_CALL(*service, ClearPassphrase()).Times(0); 188 device_->OnStatusChanged(wimax_manager::kDeviceStatusScanning); 189 EXPECT_FALSE(device_->selected_service()); 190 191 device_->pending_service_ = service; 192 device_->SelectService(service); 193 EXPECT_CALL(*service, SetState(_)).Times(0); 194 EXPECT_CALL(*service, ClearPassphrase()).Times(0); 195 device_->OnStatusChanged(wimax_manager::kDeviceStatusConnecting); 196 EXPECT_TRUE(device_->pending_service_); 197 EXPECT_TRUE(device_->selected_service()); 198 EXPECT_EQ(wimax_manager::kDeviceStatusConnecting, device_->status_); 199 200 EXPECT_CALL(*service, SetState(Service::kStateIdle)); 201 device_->SelectService(NULL); 202} 203 204TEST_F(WiMaxTest, UseNoArpGateway) { 205 EXPECT_CALL(dhcp_provider_, CreateConfig(kTestLinkName, _, _, false, false)) 206 .WillOnce(Return(dhcp_config_)); 207 device_->AcquireIPConfig(); 208} 209 210TEST_F(WiMaxTest, DropService) { 211 scoped_refptr<NiceMock<MockWiMaxService> > service0( 212 new NiceMock<MockWiMaxService>( 213 &control_, 214 reinterpret_cast<EventDispatcher *>(NULL), 215 &metrics_, 216 &manager_)); 217 scoped_refptr<MockWiMaxService> service1( 218 new MockWiMaxService(&control_, NULL, &metrics_, &manager_)); 219 device_->SelectService(service0); 220 device_->pending_service_ = service1; 221 device_->StartConnectTimeout(); 222 223 EXPECT_CALL(*service0, SetState(Service::kStateIdle)).Times(2); 224 EXPECT_CALL(*service1, SetState(Service::kStateIdle)); 225 device_->DropService(Service::kStateIdle); 226 EXPECT_FALSE(device_->selected_service()); 227 EXPECT_FALSE(device_->pending_service_); 228 EXPECT_FALSE(device_->IsConnectTimeoutStarted()); 229 230 // Expect no crash. 231 device_->DropService(Service::kStateFailure); 232} 233 234TEST_F(WiMaxTest, OnDeviceVanished) { 235 device_->proxy_.reset(proxy_.release()); 236 scoped_refptr<MockWiMaxService> service( 237 new MockWiMaxService(&control_, NULL, &metrics_, &manager_)); 238 device_->pending_service_ = service; 239 EXPECT_CALL(*service, SetState(Service::kStateIdle)); 240 device_->OnDeviceVanished(); 241 EXPECT_FALSE(device_->proxy_.get()); 242 EXPECT_FALSE(device_->pending_service_); 243} 244 245TEST_F(WiMaxTest, OnEnableComplete) { 246 MockWiMaxProvider provider; 247 EXPECT_CALL(manager_, wimax_provider()).WillOnce(Return(&provider)); 248 RpcIdentifiers networks(1, "path"); 249 EXPECT_CALL(*proxy_, Networks(_)).WillOnce(Return(networks)); 250 device_->proxy_.reset(proxy_.release()); 251 EXPECT_CALL(provider, OnNetworksChanged()); 252 Target target; 253 EXPECT_CALL(target, EnabledStateChanged(_)); 254 EnabledStateChangedCallback callback( 255 Bind(&Target::EnabledStateChanged, Unretained(&target))); 256 Error error; 257 device_->OnEnableComplete(callback, error); 258 EXPECT_EQ(1, device_->networks_.size()); 259 EXPECT_TRUE(ContainsKey(device_->networks_, "path")); 260 261 EXPECT_TRUE(device_->proxy_.get()); 262 error.Populate(Error::kOperationFailed); 263 EXPECT_CALL(target, EnabledStateChanged(_)); 264 device_->OnEnableComplete(callback, error); 265 EXPECT_FALSE(device_->proxy_.get()); 266} 267 268TEST_F(WiMaxTest, ConnectTimeout) { 269 EXPECT_EQ(&dispatcher_, device_->dispatcher()); 270 EXPECT_TRUE(device_->connect_timeout_callback_.IsCancelled()); 271 EXPECT_FALSE(device_->IsConnectTimeoutStarted()); 272 EXPECT_EQ(WiMax::kDefaultConnectTimeoutSeconds, 273 device_->connect_timeout_seconds_); 274 device_->connect_timeout_seconds_ = 0; 275 device_->StartConnectTimeout(); 276 EXPECT_FALSE(device_->connect_timeout_callback_.IsCancelled()); 277 EXPECT_TRUE(device_->IsConnectTimeoutStarted()); 278 device_->dispatcher_ = NULL; 279 device_->StartConnectTimeout(); // Expect no crash. 280 scoped_refptr<MockWiMaxService> service( 281 new MockWiMaxService(&control_, NULL, &metrics_, &manager_)); 282 device_->pending_service_ = service; 283 EXPECT_CALL(*service, SetState(Service::kStateFailure)); 284 dispatcher_.DispatchPendingEvents(); 285 EXPECT_TRUE(device_->connect_timeout_callback_.IsCancelled()); 286 EXPECT_FALSE(device_->IsConnectTimeoutStarted()); 287 EXPECT_FALSE(device_->pending_service_); 288} 289 290TEST_F(WiMaxTest, ConnectTo) { 291 static const char kPath[] = "/network/path"; 292 scoped_refptr<MockWiMaxService> service( 293 new MockWiMaxService(&control_, NULL, &metrics_, &manager_)); 294 EXPECT_CALL(*service, SetState(Service::kStateAssociating)); 295 device_->status_ = wimax_manager::kDeviceStatusScanning; 296 EXPECT_CALL(*service, GetNetworkObjectPath()).WillOnce(Return(kPath)); 297 EXPECT_CALL(*proxy_, Connect(kPath, _, _, _, _)); 298 device_->proxy_.reset(proxy_.release()); 299 Error error; 300 device_->ConnectTo(service, &error); 301 EXPECT_TRUE(error.IsSuccess()); 302 EXPECT_EQ(service.get(), device_->pending_service_.get()); 303 EXPECT_EQ(wimax_manager::kDeviceStatusUninitialized, device_->status_); 304 EXPECT_TRUE(device_->IsConnectTimeoutStarted()); 305 306 device_->ConnectTo(service, &error); 307 EXPECT_EQ(Error::kInProgress, error.type()); 308 309 device_->pending_service_ = NULL; 310} 311 312TEST_F(WiMaxTest, IsIdle) { 313 EXPECT_TRUE(device_->IsIdle()); 314 scoped_refptr<NiceMock<MockWiMaxService> > service( 315 new NiceMock<MockWiMaxService>( 316 &control_, 317 reinterpret_cast<EventDispatcher *>(NULL), 318 &metrics_, 319 &manager_)); 320 device_->pending_service_ = service; 321 EXPECT_FALSE(device_->IsIdle()); 322 device_->pending_service_ = NULL; 323 device_->SelectService(service); 324 EXPECT_FALSE(device_->IsIdle()); 325} 326 327} // namespace shill 328