blob: a30595785d44387710f0f88e3bc44d61b94d7a3e [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Cocoa/Cocoa.h>
#include <stdint.h>
#include "base/bind.h"
#include "base/callback.h"
#include "base/mac/scoped_nsobject.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "chrome/browser/local_discovery/service_discovery_client.h"
#include "chrome/browser/local_discovery/service_discovery_client_mac.h"
#import "chrome/browser/ui/cocoa/cocoa_test_helper.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "net/base/ip_endpoint.h"
#include "net/base/sockaddr_storage.h"
#include "testing/gtest_mac.h"
@interface TestNSNetService : NSNetService {
@private
base::scoped_nsobject<NSData> data_;
base::scoped_nsobject<NSArray> addresses_;
}
- (id)initWithData:(NSData*)data;
- (void)setAddresses:(NSArray*)addresses;
@end
@implementation TestNSNetService
- (id)initWithData:(NSData*)data {
if ((self = [super init])) {
data_.reset([data retain]);
}
return self;
}
- (void)setAddresses:(NSArray*)addresses {
addresses_.reset([addresses copy]);
}
- (NSArray*)addresses {
return addresses_;
}
- (NSData*)TXTRecordData {
return data_;
}
@end
namespace local_discovery {
class ServiceDiscoveryClientMacTest : public CocoaTest {
public:
ServiceDiscoveryClientMacTest()
: client_(new ServiceDiscoveryClientMac()),
num_updates_(0),
num_resolves_(0) {
}
void OnServiceUpdated(
ServiceWatcher::UpdateType update,
const std::string& service_name) {
last_update_ = update;
last_service_name_ = service_name;
num_updates_++;
}
void OnResolveComplete(
ServiceResolver::RequestStatus status,
const ServiceDescription& service_description) {
last_status_ = status;
last_service_description_ = service_description;
num_resolves_++;
}
ServiceDiscoveryClient* client() { return client_.get(); }
protected:
content::TestBrowserThreadBundle thread_bundle_;
scoped_refptr<ServiceDiscoveryClientMac> client_;
ServiceWatcher::UpdateType last_update_;
std::string last_service_name_;
int num_updates_;
ServiceResolver::RequestStatus last_status_;
ServiceDescription last_service_description_;
int num_resolves_;
};
TEST_F(ServiceDiscoveryClientMacTest, ServiceWatcher) {
const std::string test_service_type = "_testing._tcp.local";
const std::string test_service_name = "Test.123";
std::unique_ptr<ServiceWatcher> watcher = client()->CreateServiceWatcher(
test_service_type,
base::Bind(&ServiceDiscoveryClientMacTest::OnServiceUpdated,
base::Unretained(this)));
watcher->Start();
// Weak pointer to implementation class.
ServiceWatcherImplMac* watcher_impl =
static_cast<ServiceWatcherImplMac*>(watcher.get());
// Simulate service update events.
watcher_impl->OnServicesUpdate(
ServiceWatcher::UPDATE_ADDED, test_service_name);
watcher_impl->OnServicesUpdate(
ServiceWatcher::UPDATE_CHANGED, test_service_name);
watcher_impl->OnServicesUpdate(
ServiceWatcher::UPDATE_REMOVED, test_service_name);
EXPECT_EQ(last_service_name_, test_service_name + "." + test_service_type);
EXPECT_EQ(num_updates_, 3);
}
TEST_F(ServiceDiscoveryClientMacTest, ServiceResolver) {
const std::string test_service_name = "Test.123._testing._tcp.local";
std::unique_ptr<ServiceResolver> resolver = client()->CreateServiceResolver(
test_service_name,
base::Bind(&ServiceDiscoveryClientMacTest::OnResolveComplete,
base::Unretained(this)));
const uint8_t record_bytes[] = {2, 'a', 'b', 3, 'd', '=', 'e'};
base::scoped_nsobject<TestNSNetService> test_service([[TestNSNetService alloc]
initWithData:[NSData dataWithBytes:record_bytes
length:arraysize(record_bytes)]]);
const std::string kIp = "2001:4860:4860::8844";
const uint16_t kPort = 4321;
net::IPAddress ip_address;
ASSERT_TRUE(ip_address.AssignFromIPLiteral(kIp));
net::IPEndPoint endpoint(ip_address, kPort);
net::SockaddrStorage storage;
ASSERT_TRUE(endpoint.ToSockAddr(storage.addr, &storage.addr_len));
NSData* discoveryHost =
[NSData dataWithBytes:storage.addr length:storage.addr_len];
NSArray* addresses = @[ discoveryHost ];
[test_service setAddresses:addresses];
ServiceResolverImplMac* resolver_impl =
static_cast<ServiceResolverImplMac*>(resolver.get());
resolver_impl->GetContainerForTesting()->SetServiceForTesting(
base::scoped_nsobject<NSNetService>(test_service));
resolver->StartResolving();
resolver_impl->GetContainerForTesting()->OnResolveUpdate(
ServiceResolver::STATUS_SUCCESS);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, num_resolves_);
const std::vector<std::string>& metadata =
last_service_description_.metadata;
EXPECT_EQ(2u, metadata.size());
EXPECT_NE(metadata.end(),
std::find(metadata.begin(), metadata.end(), "ab"));
EXPECT_NE(metadata.end(),
std::find(metadata.begin(), metadata.end(), "d=e"));
EXPECT_EQ(ip_address, last_service_description_.ip_address);
EXPECT_EQ(kPort, last_service_description_.address.port());
EXPECT_EQ(kIp, last_service_description_.address.host());
}
// https://crbug.com/586628
TEST_F(ServiceDiscoveryClientMacTest, ResolveInvalidUnicodeRecord) {
const std::string test_service_name = "Test.123._testing._tcp.local";
std::unique_ptr<ServiceResolver> resolver = client()->CreateServiceResolver(
test_service_name,
base::Bind(&ServiceDiscoveryClientMacTest::OnResolveComplete,
base::Unretained(this)));
const uint8_t record_bytes[] = {
3, 'a', '=', 'b',
// The bytes after name= are the UTF-8 encoded representation of
// U+1F4A9, with the first two bytes of the code unit sequence transposed.
9, 'n', 'a', 'm', 'e', '=', 0x9F, 0xF0, 0x92, 0xA9,
5, 'c', 'd', '=', 'e', '9',
};
base::scoped_nsobject<TestNSNetService> test_service([[TestNSNetService alloc]
initWithData:[NSData dataWithBytes:record_bytes
length:arraysize(record_bytes)]]);
const std::string kIp = "2001:4860:4860::8844";
const uint16_t kPort = 4321;
net::IPAddress ip_address;
ASSERT_TRUE(ip_address.AssignFromIPLiteral(kIp));
net::IPEndPoint endpoint(ip_address, kPort);
net::SockaddrStorage storage;
ASSERT_TRUE(endpoint.ToSockAddr(storage.addr, &storage.addr_len));
NSData* discovery_host =
[NSData dataWithBytes:storage.addr length:storage.addr_len];
NSArray* addresses = @[ discovery_host ];
[test_service setAddresses:addresses];
ServiceResolverImplMac* resolver_impl =
static_cast<ServiceResolverImplMac*>(resolver.get());
resolver_impl->GetContainerForTesting()->SetServiceForTesting(
base::scoped_nsobject<NSNetService>(test_service));
resolver->StartResolving();
resolver_impl->GetContainerForTesting()->OnResolveUpdate(
ServiceResolver::STATUS_SUCCESS);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, num_resolves_);
const std::vector<std::string>& metadata =
last_service_description_.metadata;
EXPECT_EQ(2u, metadata.size());
EXPECT_NE(metadata.end(),
std::find(metadata.begin(), metadata.end(), "a=b"));
EXPECT_NE(metadata.end(),
std::find(metadata.begin(), metadata.end(), "cd=e9"));
EXPECT_EQ(ip_address, last_service_description_.ip_address);
EXPECT_EQ(kPort, last_service_description_.address.port());
EXPECT_EQ(kIp, last_service_description_.address.host());
}
TEST_F(ServiceDiscoveryClientMacTest, ResolveInvalidServiceName) {
base::RunLoop run_loop;
// This is the same invalid U+1F4A9 code unit sequence as in
// ResolveInvalidUnicodeRecord.
const std::string test_service_name =
"Test\x9F\xF0\x92\xA9.123._testing._tcp.local";
std::unique_ptr<ServiceResolver> resolver = client()->CreateServiceResolver(
test_service_name,
base::Bind(
[](ServiceDiscoveryClientMacTest* test,
base::Closure quit_closure,
ServiceResolver::RequestStatus status,
const ServiceDescription& service_description) {
test->OnResolveComplete(status, service_description);
quit_closure.Run();
},
base::Unretained(this), run_loop.QuitClosure()));
resolver->StartResolving();
run_loop.Run();
EXPECT_EQ(1, num_resolves_);
EXPECT_EQ(ServiceResolver::STATUS_KNOWN_NONEXISTENT, last_status_);
}
} // namespace local_discovery