1f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved.
2f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
3f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)// found in the LICENSE file.
4f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
5f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "base/message_loop/message_loop.h"
6f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "chrome/browser/local_discovery/privet_local_printer_lister.h"
7f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "chrome/browser/local_discovery/test_service_discovery_client.h"
8f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "net/url_request/test_url_fetcher_factory.h"
9f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "net/url_request/url_request_test_util.h"
10f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
11f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "testing/gmock/include/gmock/gmock.h"
12f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "testing/gtest/include/gtest/gtest.h"
13f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
14f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)using testing::StrictMock;
1503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)using testing::AtLeast;
16f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)using testing::_;
17f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
18f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)namespace local_discovery {
19f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
20f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)namespace {
21f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
22f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)const uint8 kAnnouncePacket[] = {
23a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // Header
24a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x00, 0x00,  // ID is zeroed out
25a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x80, 0x00,  // Standard query response, no error
26a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x00, 0x00,  // No questions (for simplicity)
27a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x00, 0x05,  // 5 RR (answers)
28a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x00, 0x00,  // 0 authority RRs
29a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x00, 0x00,  // 0 additional RRs
30a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x07, '_',  'p',  'r',  'i',  'v',  'e',  't',  0x04, '_',
31a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    't',  'c',  'p',  0x05, 'l',  'o',  'c',  'a',  'l',  0x00,
32a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x00, 0x0c,              // TYPE is PTR.
33a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x00, 0x01,              // CLASS is IN.
34a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x00, 0x00,              // TTL (4 bytes) is 32768 second.
35a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x10, 0x00, 0x00, 0x0c,  // RDLENGTH is 12 bytes.
36a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x09, 'm',  'y',  'S',  'e',  'r',  'v',  'i',  'c',  'e',
37a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0xc0, 0x0c, 0x09, 'm',  'y',  'S',  'e',  'r',  'v',  'i',
38a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    'c',  'e',  0xc0, 0x0c, 0x00, 0x10,  // TYPE is TXT.
39a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x00, 0x01,                          // CLASS is IN.
40a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x00, 0x00,                          // TTL (4 bytes) is 32768 seconds.
41a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x01, 0x00, 0x00, 0x44,              // RDLENGTH is 55 bytes.
42a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x06, 'i',  'd',  '=',  'r',  'e',  'g',  0x10, 't',  'y',
43a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    '=',  'S',  'a',  'm',  'p',  'l',  'e',  ' ',  'd',  'e',
44a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    'v',  'i',  'c',  'e',  0x1e, 'n',  'o',  't',  'e',  '=',
45a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    'S',  'a',  'm',  'p',  'l',  'e',  ' ',  'd',  'e',  'v',
46a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    'i',  'c',  'e',  ' ',  'd',  'e',  's',  'c',  'r',  'i',
47a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    'p',  't',  'i',  'o',  'n',  0x0c, 't',  'y',  'p',  'e',
48a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    '=',  'p',  'r',  'i',  'n',  't',  'e',  'r',  0x09, 'm',
49a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    'y',  'S',  'e',  'r',  'v',  'i',  'c',  'e',  0xc0, 0x0c,
50a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x00, 0x21,                          // Type is SRV
51a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x00, 0x01,                          // CLASS is IN
52a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x00, 0x00,                          // TTL (4 bytes) is 32768 second.
53a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x10, 0x00, 0x00, 0x17,              // RDLENGTH is 23
54a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x00, 0x00, 0x00, 0x00, 0x22, 0xb8,  // port 8888
55a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x09, 'm',  'y',  'S',  'e',  'r',  'v',  'i',  'c',  'e',
56a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x05, 'l',  'o',  'c',  'a',  'l',  0x00, 0x09, 'm',  'y',
57a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    'S',  'e',  'r',  'v',  'i',  'c',  'e',  0x05, 'l',  'o',
58a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    'c',  'a',  'l',  0x00, 0x00, 0x01,  // Type is A
59a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x00, 0x01,                          // CLASS is IN
60a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x00, 0x00,                          // TTL (4 bytes) is 32768 second.
61a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x10, 0x00, 0x00, 0x04,              // RDLENGTH is 4
62a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x01, 0x02, 0x03, 0x04,              // 1.2.3.4
63a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x09, 'm',  'y',  'S',  'e',  'r',  'v',  'i',  'c',  'e',
64a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x05, 'l',  'o',  'c',  'a',  'l',  0x00, 0x00, 0x1C,  // Type is AAAA
65a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x00, 0x01,                                            // CLASS is IN
66a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x00, 0x00,              // TTL (4 bytes) is 32768 second.
67a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x10, 0x00, 0x00, 0x10,  // RDLENGTH is 16
68a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x01, 0x02, 0x03, 0x04,  // 1.2.3.4
69a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02,
70a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    0x03, 0x04, };
71f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
72f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)const char kInfoIsLocalPrinter[] = "{"
73f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    "\"api\" : [ \"/privet/printer/submitdoc\" ],"
74f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    "\"x-privet-token\" : \"sample\""
75f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    "}";
76f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
77f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)const char kInfoIsNotLocalPrinter[] = "{"
78f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    "\"api\" : [ \"/privet/register\" ],"
79f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    "\"x-privet-token\" : \"sample\""
80f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    "}";
81f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
82a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)const char kServiceName[] = "myService._privet._tcp.local";
83a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
84a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)const char kPrivetInfoURL[] = "http://1.2.3.4:8888/privet/info";
85f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
86f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)class MockLocalPrinterListerDelegate
87f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    : public PrivetLocalPrinterLister::Delegate {
88f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) public:
89f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  MockLocalPrinterListerDelegate() {
90f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  }
91f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
92f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  virtual ~MockLocalPrinterListerDelegate() {
93f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  }
94f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
95a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)  MOCK_METHOD4(LocalPrinterChanged, void(bool added,
96f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                         const std::string& name,
97a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)                                         bool has_local_printing,
98f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                         const DeviceDescription& description));
99f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
100f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  MOCK_METHOD1(LocalPrinterRemoved, void(const std::string& name));
101f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
102f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  MOCK_METHOD0(LocalPrinterCacheFlushed, void());
103f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)};
104f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
105f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)class PrivetLocalPrinterListerTest : public testing::Test {
106f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) public:
107f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  PrivetLocalPrinterListerTest() {
108f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    test_service_discovery_client_ = new TestServiceDiscoveryClient();
109f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    test_service_discovery_client_->Start();
110f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    url_request_context_ = new net::TestURLRequestContextGetter(
111f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        base::MessageLoopProxy::current());
112f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    local_printer_lister_.reset(new PrivetLocalPrinterLister(
113f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        test_service_discovery_client_.get(),
114f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        url_request_context_.get(),
115f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        &delegate_));
116f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  }
117f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
118f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  ~PrivetLocalPrinterListerTest() {
119f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  }
120f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
121f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  bool SuccessfulResponseToURL(const GURL& url,
122f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                               const std::string& response) {
123f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    net::TestURLFetcher* fetcher = fetcher_factory_.GetFetcherByID(0);
124f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    EXPECT_TRUE(fetcher);
125f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    EXPECT_EQ(url, fetcher->GetOriginalURL());
126f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
127f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    if (!fetcher || url != fetcher->GetOriginalURL())
128f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      return false;
129f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
130f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    fetcher->SetResponseString(response);
131f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    fetcher->set_status(net::URLRequestStatus(net::URLRequestStatus::SUCCESS,
132f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                              net::OK));
133f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    fetcher->set_response_code(200);
134f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    fetcher->delegate()->OnURLFetchComplete(fetcher);
135f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return true;
136f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  }
137f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
138f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  void SimulateReceive(const uint8* packet, size_t size) {
139f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    test_service_discovery_client_->SimulateReceive(packet, size);
140f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    message_loop_.RunUntilIdle();
141f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  }
142f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
143f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  void ExpectAnyPacket() {
1441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    EXPECT_CALL(*test_service_discovery_client_.get(), OnSendTo(_))
1451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        .Times(AtLeast(2));
146f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  }
147f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
148f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) protected:
149f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  base::MessageLoop message_loop_;
150f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  scoped_refptr<TestServiceDiscoveryClient> test_service_discovery_client_;
151f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  scoped_refptr<net::TestURLRequestContextGetter> url_request_context_;
152f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  scoped_ptr<PrivetLocalPrinterLister> local_printer_lister_;
153f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  net::TestURLFetcherFactory fetcher_factory_;
154f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  StrictMock<MockLocalPrinterListerDelegate> delegate_;
155f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)};
156f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
157f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)TEST_F(PrivetLocalPrinterListerTest, PrinterAddedTest) {
158f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  ExpectAnyPacket();
159f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
160f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  local_printer_lister_->Start();
161f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
162f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  SimulateReceive(kAnnouncePacket, sizeof(kAnnouncePacket));
163f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
164a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)  EXPECT_CALL(delegate_, LocalPrinterChanged(true, kServiceName, true, _));
165f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
166f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  EXPECT_TRUE(SuccessfulResponseToURL(
167a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      GURL(kPrivetInfoURL),
168f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      std::string(kInfoIsLocalPrinter)));
169f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)};
170f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
171f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)TEST_F(PrivetLocalPrinterListerTest, NonPrinterAddedTest) {
172f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  ExpectAnyPacket();
173f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
174f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  local_printer_lister_->Start();
175f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
176f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  SimulateReceive(kAnnouncePacket, sizeof(kAnnouncePacket));
177f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
178a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)  EXPECT_CALL(delegate_, LocalPrinterChanged(true, kServiceName, false, _));
179a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
180f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  EXPECT_TRUE(SuccessfulResponseToURL(
181a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      GURL(kPrivetInfoURL),
182f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      std::string(kInfoIsNotLocalPrinter)));
183f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)};
184f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
185f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)}  // namespace
186f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
187f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)}  // namespace local_discovery
188