Chromium Code Reviews| Index: chrome/browser/local_discovery/service_discovery_client_impl.cc |
| diff --git a/chrome/browser/local_discovery/service_discovery_client_impl.cc b/chrome/browser/local_discovery/service_discovery_client_impl.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..22e4439cb700b15e40010b31a12c806bcfbdcf78 |
| --- /dev/null |
| +++ b/chrome/browser/local_discovery/service_discovery_client_impl.cc |
| @@ -0,0 +1,433 @@ |
| +// Copyright (c) 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. |
| + |
| +#include <utility> |
| + |
| +#include "base/logging.h" |
| +#include "base/message_loop_proxy.h" |
| +#include "chrome/browser/local_discovery/service_discovery_client_impl.h" |
| +#include "net/dns/dns_protocol.h" |
| +#include "net/dns/record_rdata.h" |
| + |
| +namespace local_discovery { |
| + |
| +ServiceDiscoveryClientImpl::ServiceDiscoveryClientImpl() { |
| +} |
| + |
| +ServiceDiscoveryClientImpl::~ServiceDiscoveryClientImpl() { |
| +} |
| + |
| +scoped_ptr<ServiceTypeWatcher> |
| +ServiceDiscoveryClientImpl::CreateServiceTypeWatcher( |
| + const std::string& service_type, |
| + bool active, |
| + bool alert_existing_services, |
| + ServiceTypeWatcher::Delegate* delegate) { |
| + return scoped_ptr<ServiceTypeWatcher>(new ServiceTypeWatcherImpl( |
| + service_type, active, alert_existing_services, delegate)); |
| +} |
| + |
| +scoped_ptr<ServiceReader> ServiceDiscoveryClientImpl::CreateServiceReader( |
| + const std::string& service_name, |
| + ServiceReader::Delegate* delegate) { |
| + return scoped_ptr<ServiceReader>(new ServiceReaderImpl( |
| + service_name, delegate)); |
| +} |
| + |
| +ServiceTypeWatcherImpl::ServiceTypeWatcherImpl( |
| + const std::string& service_type, |
| + bool active, |
| + bool alert_existing_services, |
| + ServiceTypeWatcher::Delegate* delegate) |
| + : service_type_(service_type), active_(active), |
| + alert_existing_services_(alert_existing_services), delegate_(delegate), |
| + started_(false) { |
| +} |
| + |
| +bool ServiceTypeWatcherImpl::Start() { |
| + DCHECK(!started_); |
| + listener_ = net::MDnsClient::GetInstance()->CreateListener( |
| + net::dns_protocol::kTypePTR, service_type_, this); |
| + if (!listener_->Start()) return false; |
|
Vitaly Buka (NO REVIEWS)
2013/06/15 00:22:30
Chromium coding styles requires line break after c
Noam Samuel
2013/06/18 22:46:03
Done.
|
| + if (!CreateTransaction(active_, alert_existing_services_, false)) |
| + return false; |
| + |
| + started_ = true; |
| + return true; |
| +} |
| + |
| +ServiceTypeWatcherImpl::~ServiceTypeWatcherImpl() { |
| +} |
| + |
| +void ServiceTypeWatcherImpl::GetAvailableServices( |
| + std::vector<std::string>* services) const { |
| + DCHECK(started_); |
| + DCHECK(services); |
| + services->insert(services->begin(), services_.begin(), services_.end()); |
| +} |
| + |
| +void ServiceTypeWatcherImpl::DiscoverNewServices() { |
| + DCHECK(started_); |
| + CreateTransaction(true, false, false); |
| +} |
| + |
| +void ServiceTypeWatcherImpl::ForceUpdateServices() { |
| + DCHECK(started_); |
| + CreateTransaction(true, false, true); |
| +} |
| + |
| +bool ServiceTypeWatcherImpl::CreateTransaction( |
| + bool active, bool alert_existing_services, bool force_refresh) { |
| + int transaction_flags = 0; |
| + if (active) transaction_flags |= net::MDnsTransaction::QUERY_NETWORK; |
|
Vitaly Buka (NO REVIEWS)
2013/06/15 00:22:30
same
Noam Samuel
2013/06/18 22:46:03
Done.
|
| + |
| + if (alert_existing_services) { |
| + transaction_flags |= net::MDnsTransaction::QUERY_CACHE; |
| + } |
| + |
| + // TODO(noamsml): Add flag for force_refresh when support. |
| + |
| + if (transaction_flags) { |
| + transaction_ = net::MDnsClient::GetInstance()->CreateTransaction( |
| + net::dns_protocol::kTypePTR, service_type_, transaction_flags, |
| + base::Bind(&ServiceTypeWatcherImpl::OnTransactionResponse, |
| + base::Unretained(this))); |
| + return transaction_->Start(); |
| + } |
| + |
| + return true; |
| +} |
| + |
| +std::string ServiceTypeWatcherImpl::GetServiceType() const { |
| + return listener_->GetName(); |
| +} |
| + |
| +bool ServiceTypeWatcherImpl::IsActive() const { |
| + return active_; |
| +} |
| + |
| +void ServiceTypeWatcherImpl::OnRecordUpdate( |
| + net::MDnsListener::UpdateType update, |
| + const net::RecordParsed* record) { |
| + DCHECK(started_); |
| + DCHECK(record->name() == GetServiceType()); |
| + DCHECK(record->type() == net::dns_protocol::kTypePTR); |
| + |
| + const net::PtrRecordRdata* rdata = record->rdata<net::PtrRecordRdata>(); |
| + |
| + switch (update) { |
| + case net::MDnsListener::RECORD_ADDED: |
| + AddService(rdata->ptrdomain()); |
| + break; |
| + case net::MDnsListener::RECORD_CHANGED: |
| + NOTREACHED(); |
| + break; |
| + case net::MDnsListener::RECORD_REMOVED: |
| + RemoveService(rdata->ptrdomain()); |
| + break; |
| + } |
| +} |
| + |
| +void ServiceTypeWatcherImpl::OnCachePurged() { |
| + // Not yet implemented. |
| +} |
| + |
| +void ServiceTypeWatcherImpl::OnTransactionResponse( |
| + net::MDnsTransaction::Result result, |
| + const net::RecordParsed* record) { |
| + DCHECK(started_); |
| + if (result == net::MDnsTransaction::RESULT_RECORD) { |
| + const net::PtrRecordRdata* rdata = record->rdata<net::PtrRecordRdata>(); |
| + DCHECK(rdata); |
| + AddService(rdata->ptrdomain()); |
| + } else if (result == net::MDnsTransaction::RESULT_DONE) { |
| + transaction_.reset(); |
| + } |
| + |
| + // Do nothing for NSEC records. It is an error for hosts to broadcast an NSEC |
| + // record for PTR records on any name. |
| +} |
| + |
| +void ServiceTypeWatcherImpl::AddService(const std::string& service) { |
| + DCHECK(started_); |
| + std::pair<std::set<std::string>::iterator, bool> found = |
| + services_.insert(service); |
| + if (found.second) { // Newly inserted. |
| + delegate_->OnServiceStatusChanged(true, service); |
| + } |
| +} |
| + |
| +void ServiceTypeWatcherImpl::RemoveService(const std::string& service) { |
| + DCHECK(started_); |
| + if (services_.erase(service)) { |
| + delegate_->OnServiceStatusChanged(false, service); |
| + } |
| +} |
| + |
| +void ServiceTypeWatcherImpl::OnNsecRecord(const std::string& name, |
| + unsigned rrtype) { |
| + // Do nothing. It is an error for hosts to broadcast an NSEC record for PTR |
| + // on any name. |
| +} |
| + |
| +ServiceReaderImpl::ServiceReaderImpl( |
| + const std::string& service_name, |
| + ServiceReader::Delegate* delegate) |
| + : service_name_(service_name), delegate_(delegate), |
| + has_address_(false), has_metadata_(false), started_(false) { |
| + srv_listener_ = net::MDnsClient::GetInstance()->CreateListener( |
| + net::dns_protocol::kTypeSRV, service_name, this); |
| + txt_listener_ = net::MDnsClient::GetInstance()->CreateListener( |
| + net::dns_protocol::kTypeTXT, service_name, this); |
| +} |
| + |
| +bool ServiceReaderImpl::Start() { |
| + if (!srv_listener_->Start()) return false; |
|
Vitaly Buka (NO REVIEWS)
2013/06/15 00:22:30
again
Noam Samuel
2013/06/18 22:46:03
Done.
|
| + if (!txt_listener_->Start()) return false; |
| + |
| + // Update initial values |
| + if (!CreateTxtTransaction()) return false; |
| + if (!CreateSrvTransaction()) return false; |
| + started_ = true; |
| + return true; |
| +} |
| + |
| +ServiceReaderImpl::~ServiceReaderImpl() { |
| +} |
| + |
| +bool ServiceReaderImpl::GetMetadata( |
| + std::vector<std::string>* metadata) const { |
| + DCHECK(started_); |
| + if (has_metadata_) { |
| + *metadata = metadata_; |
| + return true; |
| + } |
| + return false; |
| +} |
| + |
| +void ServiceReaderImpl::ReadMetadata(const MetadataCallback& callback, |
| + bool force_refresh) { |
| + DCHECK(started_); |
| + metadata_callbacks_.push_back(callback); |
| + if (!txt_transaction_.get()) { |
| + CreateTxtTransaction(); |
| + } |
| +} |
| + |
| +bool ServiceReaderImpl::CreateTxtTransaction() { |
| + txt_transaction_ = net::MDnsClient::GetInstance()->CreateTransaction( |
| + net::dns_protocol::kTypeTXT, service_name_, |
| + net::MDnsTransaction::SINGLE_RESULT | net::MDnsTransaction::QUERY_CACHE | |
| + net::MDnsTransaction::QUERY_NETWORK, |
| + base::Bind(&ServiceReaderImpl::TxtRecordTransactionResponse, |
| + AsWeakPtr())); |
|
Vitaly Buka (NO REVIEWS)
2013/06/15 00:22:30
don't need to break bind line
Noam Samuel
2013/06/18 22:46:03
It doesn't fit by a couple of characters (at least
|
| + return txt_transaction_->Start(); |
| +} |
| + |
| +bool ServiceReaderImpl::GetAddress(net::HostPortPair* address) const { |
| + DCHECK(started_); |
| + if (has_address_) { |
| + *address = address_; |
| + return true; |
| + } |
| + return false; |
| +} |
| + |
| +void ServiceReaderImpl::ReadAddress(const AddressCallback& callback, |
| + bool force_refresh) { |
| + DCHECK(started_); |
| + address_callbacks_.push_back(callback); |
| + if (!srv_transaction_.get()) { |
| + CreateSrvTransaction(); |
| + } |
| +} |
| + |
| +bool ServiceReaderImpl::CreateSrvTransaction() { |
| + srv_transaction_ = net::MDnsClient::GetInstance()->CreateTransaction( |
| + net::dns_protocol::kTypeSRV, service_name_, |
| + net::MDnsTransaction::SINGLE_RESULT | net::MDnsTransaction::QUERY_CACHE | |
| + net::MDnsTransaction::QUERY_NETWORK, |
| + base::Bind(&ServiceReaderImpl::SrvRecordTransactionResponse, |
| + AsWeakPtr())); |
| + return srv_transaction_->Start(); |
| +} |
| + |
| +std::string ServiceReaderImpl::GetHumanReadableName() const { |
| + // TODO(noamsml): Once we have escaping working, get this to |
| + // parse escaped domains. |
| + size_t first_period = service_name_.find_first_of('.'); |
| + return service_name_.substr(0, first_period); |
| +} |
| + |
| +std::string ServiceReaderImpl::GetType() const { |
| + // TODO(noamsml): Once we have escaping working, get this to |
| + // parse escaped domains. |
| + size_t first_period = service_name_.find_first_of('.'); |
| + if (first_period == std::string::npos) return ""; |
|
Vitaly Buka (NO REVIEWS)
2013/06/15 00:22:30
more
Noam Samuel
2013/06/18 22:46:03
Done.
|
| + return service_name_.substr(first_period+1); |
| +} |
| + |
| +std::string ServiceReaderImpl::GetName() const { |
| + return service_name_; |
| +} |
| + |
| +bool ServiceReaderImpl::IsAvailable() const { |
| + DCHECK(started_); |
| + // TODO(noamsml): This should ideally be based on the PTR record. |
| + return has_address_; |
| +} |
| + |
| +void ServiceReaderImpl::ReadLastSeen( |
| + const ServiceReader::LastSeenCallback& callback) { |
| + DCHECK(started_); |
| + // HACK(noamsml): Because cache-based MDns transactions are currently |
| + // synchronous, there can only be one last seen callback at a time, so |
| + // no need to keep a callback list. |
| + |
| + last_seen_transaction_ = net::MDnsClient::GetInstance()->CreateTransaction( |
| + net::dns_protocol::kTypeSRV, service_name_, |
| + net::MDnsTransaction::SINGLE_RESULT | net::MDnsTransaction::QUERY_CACHE, |
| + base::Bind(&ServiceReaderImpl::LastSeenTransactionResponse, |
| + base::Unretained(this), callback)); |
| + last_seen_transaction_->Start(); |
| +} |
| + |
| +void ServiceReaderImpl::LastSeenTransactionResponse( |
| + const ServiceReader::LastSeenCallback& callback, |
| + net::MDnsTransaction::Result status, |
| + const net::RecordParsed* record) { |
| + last_seen_transaction_.reset(); |
| + DCHECK(started_); |
| + if (status == net::MDnsTransaction::RESULT_RECORD) { |
| + DCHECK(record); |
| + callback.Run(record->time_created()); |
| + } else { |
| + callback.Run(base::Time()); |
| + } |
| +} |
| + |
| +void ServiceReaderImpl::OnRecordUpdate(net::MDnsListener::UpdateType update, |
| + const net::RecordParsed* record) { |
| + DCHECK(started_); |
| + switch (record->type()) { |
| + case net::dns_protocol::kTypeSRV: { |
| + if (update == net::MDnsListener::RECORD_REMOVED) { |
| + has_address_ = false; |
| + address_ = net::HostPortPair(); |
|
Vitaly Buka (NO REVIEWS)
2013/06/15 00:22:30
same question as for has_metadata_ below
Noam Samuel
2013/06/18 22:46:03
Code path removed.
|
| + } else { |
| + has_address_ = true; |
| + address_ = RecordToAddress(record); |
| + } |
| + |
| + if (delegate_) { |
| + delegate_->OnAddressChanged(service_name_, |
| + address_); |
| + } |
| + break; |
| + } |
| + case net::dns_protocol::kTypeTXT: { |
| + if (update == net::MDnsListener::RECORD_REMOVED) { |
| + has_metadata_ = false; |
|
Vitaly Buka (NO REVIEWS)
2013/06/15 00:22:30
can you just use metadata_.empty() instead of has_
Noam Samuel
2013/06/15 00:42:06
I think so. Technically, a service could have empt
|
| + metadata_ = std::vector<std::string>(); |
| + } else { |
| + has_metadata_ = true; |
| + metadata_ = RecordToMetadata(record); |
| + } |
| + |
| + if (delegate_) { |
| + delegate_->OnMetadataChanged(service_name_, |
| + metadata_); |
|
Vitaly Buka (NO REVIEWS)
2013/06/15 00:22:30
one line
Noam Samuel
2013/06/18 22:46:03
Deleted.
|
| + } |
| + break; |
| + } |
| + default: |
| + NOTREACHED(); |
| + } |
| +} |
| + |
| +void ServiceReaderImpl::OnCachePurged() { |
| + // Not yet implemented. |
| +} |
| + |
| +void ServiceReaderImpl::SrvRecordTransactionResponse( |
| + net::MDnsTransaction::Result status, const net::RecordParsed* record) { |
| + DCHECK(started_); |
| + srv_transaction_.reset(); |
| + |
| + if (record) { |
| + address_ = RecordToAddress(record); |
| + has_metadata_ = true; |
| + } |
| + |
| + std::vector<ServiceReader::AddressCallback> address_callbacks; |
| + address_callbacks.swap(address_callbacks_); |
| + |
| + ServiceReader::RequestStatus output_status = MDnsStatusToRequestStatus( |
| + status); |
| + |
| + for (std::vector<ServiceReader::AddressCallback>::iterator i = |
|
Vitaly Buka (NO REVIEWS)
2013/06/15 00:22:30
SrvRecordTransactionResponse and TxtRecordTransact
Noam Samuel
2013/06/15 00:42:06
Since they contain slightly different logic and ty
Noam Samuel
2013/06/18 22:46:03
Deleted.
|
| + address_callbacks.begin(); i != address_callbacks.end(); ++i) { |
| + i->Run(output_status, address_); |
| + } |
| +} |
| + |
| +void ServiceReaderImpl::TxtRecordTransactionResponse( |
| + net::MDnsTransaction::Result status, const net::RecordParsed* record) { |
| + DCHECK(started_); |
| + txt_transaction_.reset(); |
| + |
| + std::vector<ServiceReader::MetadataCallback> metadata_callbacks; |
| + metadata_callbacks.swap(metadata_callbacks_); |
| + |
| + if (record) { |
| + metadata_ = RecordToMetadata(record); |
| + has_metadata_ = true; |
| + } |
| + |
| + ServiceReader::RequestStatus output_status = MDnsStatusToRequestStatus( |
| + status); |
| + |
| + for (std::vector<ServiceReader::MetadataCallback>::iterator i = |
| + metadata_callbacks.begin(); i != metadata_callbacks.end(); ++i) { |
| + i->Run(output_status, metadata_); |
| + } |
| +} |
| + |
| +void ServiceReaderImpl::OnNsecRecord(const std::string& name, |
| + unsigned rrtype) { |
| + // TODO(noamsml): Logic NSEC in listener case. |
| +} |
| + |
| +ServiceReader::RequestStatus ServiceReaderImpl::MDnsStatusToRequestStatus( |
| + net::MDnsTransaction::Result status) const { |
| + switch (status) { |
| + case net::MDnsTransaction::RESULT_RECORD: |
| + return ServiceReader::STATUS_SUCCESS; |
| + case net::MDnsTransaction::RESULT_NO_RESULTS: |
| + return ServiceReader::STATUS_TIMEOUT; |
| + case net::MDnsTransaction::RESULT_NSEC: |
| + return ServiceReader::STATUS_KNOWN_NONEXISTENT; |
| + case net::MDnsTransaction::RESULT_DONE: |
| + NOTREACHED(); |
| + return ServiceReader::STATUS_TIMEOUT; |
| + } |
| +} |
| + |
| +const std::vector<std::string>& ServiceReaderImpl::RecordToMetadata( |
| + const net::RecordParsed* record) const { |
| + DCHECK(record->type() == net::dns_protocol::kTypeTXT); |
| + const net::TxtRecordRdata* txt_rdata = record->rdata<net::TxtRecordRdata>(); |
| + DCHECK(txt_rdata); |
| + return txt_rdata->texts(); |
| +} |
| + |
| +net::HostPortPair ServiceReaderImpl::RecordToAddress( |
| + const net::RecordParsed* record) const { |
| + DCHECK(record->type() == net::dns_protocol::kTypeSRV); |
| + const net::SrvRecordRdata* srv_rdata = record->rdata<net::SrvRecordRdata>(); |
| + DCHECK(srv_rdata); |
| + return net::HostPortPair(srv_rdata->target(), srv_rdata->port()); |
| +} |
| + |
| +} // namespace local_discovery |