summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cloud_print/gcp20/prototype/dns_packet_parser.cc38
-rw-r--r--cloud_print/gcp20/prototype/dns_packet_parser.h78
-rw-r--r--cloud_print/gcp20/prototype/dns_response_builder.cc153
-rw-r--r--cloud_print/gcp20/prototype/dns_response_builder.h68
-rw-r--r--cloud_print/gcp20/prototype/dns_sd_server.cc247
-rw-r--r--cloud_print/gcp20/prototype/dns_sd_server.h76
-rw-r--r--cloud_print/gcp20/prototype/gcp20_device.cc43
-rw-r--r--cloud_print/gcp20/prototype/gcp20_device.gyp23
-rw-r--r--cloud_print/gcp20/prototype/printer.cc131
-rw-r--r--cloud_print/gcp20/prototype/printer.h42
-rw-r--r--cloud_print/gcp20/prototype/service_parameters.cc22
-rw-r--r--cloud_print/gcp20/prototype/service_parameters.h32
12 files changed, 885 insertions, 68 deletions
diff --git a/cloud_print/gcp20/prototype/dns_packet_parser.cc b/cloud_print/gcp20/prototype/dns_packet_parser.cc
new file mode 100644
index 0000000..f85ffc7
--- /dev/null
+++ b/cloud_print/gcp20/prototype/dns_packet_parser.cc
@@ -0,0 +1,38 @@
+// 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.
+
+#include "cloud_print/gcp20/prototype/dns_packet_parser.h"
+
+#include "net/base/big_endian.h"
+
+DnsPacketParser::DnsPacketParser(const char* packet, size_t length)
+ : packet_(packet),
+ length_(length),
+ record_parser_(packet, length, sizeof(header_)) {
+ net::BigEndianReader reader(packet, length);
+ is_header_read_ = reader.ReadU16(&header_.id) &&
+ reader.ReadU16(&header_.flags) &&
+ reader.ReadU16(&header_.qdcount) &&
+ reader.ReadU16(&header_.ancount) &&
+ reader.ReadU16(&header_.nscount) &&
+ reader.ReadU16(&header_.arcount);
+}
+
+bool DnsPacketParser::ReadRecord(DnsQueryRecord* out) {
+ DCHECK(packet_);
+ DnsQueryRecord result;
+ size_t consumed = ReadName(&result.qname);
+ if (!consumed)
+ return false;
+ net::BigEndianReader reader(packet_ + GetOffset() + consumed,
+ length_ - (GetOffset() + consumed));
+ if (reader.ReadU16(&result.qtype) && reader.ReadU16(&result.qclass) &&
+ record_parser_.SkipQuestion()) { // instead of |cur_ = reader.ptr();|
+ *out = result;
+ return true;
+ }
+
+ return false;
+}
+
diff --git a/cloud_print/gcp20/prototype/dns_packet_parser.h b/cloud_print/gcp20/prototype/dns_packet_parser.h
new file mode 100644
index 0000000..88c6a03
--- /dev/null
+++ b/cloud_print/gcp20/prototype/dns_packet_parser.h
@@ -0,0 +1,78 @@
+// 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.
+
+#ifndef CLOUD_PRINT_GCP20_PROTOTYPE_DNS_PACKET_PARSER_H_
+#define CLOUD_PRINT_GCP20_PROTOTYPE_DNS_PACKET_PARSER_H_
+
+#include <string>
+
+#include "net/dns/dns_protocol.h"
+#include "net/dns/dns_response.h"
+
+// Parsed response record.
+struct DnsQueryRecord {
+ DnsQueryRecord() : qtype(0), qclass(0) {}
+ ~DnsQueryRecord() {}
+
+ std::string qname; // in dotted form
+ uint16 qtype;
+ uint16 qclass;
+};
+
+// Iterator to walk over records of the DNS response packet. Encapsulates
+// |DnsRecordParser| object for using its functionality.
+class DnsPacketParser {
+ public:
+ // Constructs an iterator to process the |packet| of given |length|.
+ DnsPacketParser(const char* packet, size_t length);
+
+ // Destroys the object.
+ ~DnsPacketParser() {}
+
+ bool IsValid() const { return record_parser_.IsValid() && is_header_read_; }
+
+ // Returns |true| if no more bytes remain in the packet.
+ bool AtEnd() const { return record_parser_.AtEnd(); }
+
+ // Returns header of DNS packet.
+ const net::dns_protocol::Header& header() const { return header_; }
+
+ // Parses the next query record into |record|. Returns true if succeeded.
+ bool ReadRecord(DnsQueryRecord* record);
+
+ // Parses the next resource record into |record|. Returns true if succeeded.
+ bool ReadRecord(net::DnsResourceRecord* record) {
+ return record_parser_.ReadRecord(record);
+ }
+
+ private:
+ // Returns current offset into the packet.
+ size_t GetOffset() const { return record_parser_.GetOffset(); }
+
+ // Parses a (possibly compressed) DNS name from the packet starting at
+ // |pos|. Stores output (even partial) in |out| unless |out| is NULL. |out|
+ // is stored in the dotted form, e.g., "example.com". Returns number of bytes
+ // consumed or 0 on failure.
+ // This is exposed to allow parsing compressed names within RRDATA for TYPEs
+ // such as NS, CNAME, PTR, MX, SOA.
+ // See RFC 1035 section 4.1.4.
+ unsigned ReadName(std::string* out) const {
+ return record_parser_.ReadName(packet_ + GetOffset(), out);
+ }
+
+ const char* packet_;
+ size_t length_;
+
+ // Contents parsed header_;
+ net::dns_protocol::Header header_;
+ bool is_header_read_;
+
+ // Encapsulated parser.
+ net::DnsRecordParser record_parser_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsPacketParser);
+};
+
+#endif // CLOUD_PRINT_GCP20_PROTOTYPE_DNS_PACKET_PARSER_H_
+
diff --git a/cloud_print/gcp20/prototype/dns_response_builder.cc b/cloud_print/gcp20/prototype/dns_response_builder.cc
new file mode 100644
index 0000000..2bcd43f
--- /dev/null
+++ b/cloud_print/gcp20/prototype/dns_response_builder.cc
@@ -0,0 +1,153 @@
+// 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.
+
+#include "cloud_print/gcp20/prototype/dns_response_builder.h"
+
+#include "base/logging.h"
+#include "net/base/big_endian.h"
+#include "net/base/dns_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/dns/dns_response.h"
+
+namespace {
+
+uint16 klass = net::dns_protocol::kClassIN;
+
+} // namespace
+
+DnsResponseRecord::DnsResponseRecord() : type(0), klass(0), ttl(0) {
+}
+
+DnsResponseRecord::~DnsResponseRecord() {
+}
+
+DnsResponseBuilder::DnsResponseBuilder(uint16 id) {
+ header_.id = id;
+ // TODO(maksymb): Check do we need AA flag enabled.
+ header_.flags = net::dns_protocol::kFlagResponse |
+ net::dns_protocol::kFlagAA;
+ header_.qdcount = 0;
+ header_.ancount = 0;
+ header_.nscount = 0;
+ header_.arcount = 0;
+}
+
+void DnsResponseBuilder::AppendPtr(const std::string& service_type, uint32 ttl,
+ const std::string& service_name) {
+ std::string rdata;
+ bool success = net::DNSDomainFromDot(service_name, &rdata);
+ DCHECK(success);
+
+ AddResponse(service_type, net::dns_protocol::kTypePTR, ttl, rdata);
+}
+
+void DnsResponseBuilder::AppendSrv(const std::string& service_name, uint32 ttl,
+ uint16 priority, uint16 weight,
+ uint16 http_port,
+ const std::string& service_domain_name) {
+ std::string domain_name;
+ bool success = net::DNSDomainFromDot(service_domain_name, &domain_name);
+ DCHECK(success);
+
+ std::vector<uint8> rdata(2 + 2 + 2 + domain_name.size());
+
+ net::BigEndianWriter writer(rdata.data(), rdata.size());
+ success = writer.WriteU16(priority) &&
+ writer.WriteU16(weight) &&
+ writer.WriteU16(http_port) &&
+ writer.WriteBytes(domain_name.data(), domain_name.size());
+ DCHECK(success);
+ DCHECK_EQ(writer.remaining(), 0); // For warranty of correct size allocation.
+
+ AddResponse(service_name, net::dns_protocol::kTypeSRV, ttl,
+ std::string(rdata.begin(), rdata.end()));
+}
+
+void DnsResponseBuilder::AppendA(const std::string& service_domain_name,
+ uint32 ttl, net::IPAddressNumber http_ipv4) {
+ // TODO(maksymb): IP to send must depends on interface from where query was
+ // received.
+ if (http_ipv4.empty()) {
+ LOG(ERROR) << "Invalid IP";
+ return;
+ }
+
+ AddResponse(service_domain_name, net::dns_protocol::kTypeA, ttl,
+ std::string(http_ipv4.begin(), http_ipv4.end()));
+}
+
+void DnsResponseBuilder::AppendTxt(const std::string& service_name, uint32 ttl,
+ const std::vector<std::string>& metadata) {
+ std::string rdata;
+ for (std::vector<std::string>::const_iterator str = metadata.begin();
+ str != metadata.end(); ++str) {
+ int len = static_cast<int>(str->size());
+ DCHECK_LT(len, 256);
+ rdata += static_cast<char>(len); // Set length byte.
+ rdata += *str;
+ }
+
+ AddResponse(service_name, net::dns_protocol::kTypeTXT, ttl, rdata);
+}
+
+scoped_refptr<net::IOBufferWithSize> DnsResponseBuilder::Build() {
+ size_t size = sizeof(header_);
+ for (std::vector<DnsResponseRecord>::const_iterator iter = responses_.begin();
+ iter != responses_.end(); ++iter) {
+ size += iter->name.size() + 2 + // Two dots: first and last.
+ sizeof(iter->type) + sizeof(iter->klass) + sizeof(iter->ttl) +
+ 2 + // sizeof(RDLENGTH)
+ iter->rdata.size();
+ }
+
+ if (responses_.empty())
+ return NULL; // No answer.
+
+ header_.ancount = static_cast<uint16>(responses_.size());
+
+ scoped_refptr<net::IOBufferWithSize> message(
+ new net::IOBufferWithSize(static_cast<int>(size)));
+ net::BigEndianWriter writer(message->data(), message->size());
+ bool success = writer.WriteU16(header_.id) &&
+ writer.WriteU16(header_.flags) &&
+ writer.WriteU16(header_.qdcount) &&
+ writer.WriteU16(header_.ancount) &&
+ writer.WriteU16(header_.nscount) &&
+ writer.WriteU16(header_.arcount);
+ DCHECK(success);
+
+ std::string name_in_dns_format;
+ for (std::vector<DnsResponseRecord>::const_iterator iter = responses_.begin();
+ iter != responses_.end(); ++iter) {
+ success = net::DNSDomainFromDot(iter->name, &name_in_dns_format);
+ DCHECK(success);
+ DCHECK_EQ(name_in_dns_format.size(), iter->name.size() + 2);
+
+ success = writer.WriteBytes(name_in_dns_format.data(),
+ name_in_dns_format.size()) &&
+ writer.WriteU16(iter->type) &&
+ writer.WriteU16(iter->klass) &&
+ writer.WriteU32(iter->ttl) &&
+ writer.WriteU16(static_cast<uint16>(iter->rdata.size())) &&
+ writer.WriteBytes(iter->rdata.data(), iter->rdata.size());
+ DCHECK(success);
+ }
+
+ DCHECK_EQ(writer.remaining(), 0); // For warranty of correct size allocation.
+
+ return message;
+}
+
+void DnsResponseBuilder::AddResponse(const std::string& name, uint16 type,
+ uint32 ttl, const std::string& rdata) {
+ DnsResponseRecord response;
+ response.name = name;
+ response.klass = klass;
+ response.ttl = ttl;
+ response.type = type;
+ response.rdata = rdata;
+ responses_.push_back(response);
+}
+
diff --git a/cloud_print/gcp20/prototype/dns_response_builder.h b/cloud_print/gcp20/prototype/dns_response_builder.h
new file mode 100644
index 0000000..430a7e1
--- /dev/null
+++ b/cloud_print/gcp20/prototype/dns_response_builder.h
@@ -0,0 +1,68 @@
+// 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.
+
+#ifndef CLOUD_PRINT_GCP20_PROTOTYPE_DNS_RESPONSE_BUILDER_H_
+#define CLOUD_PRINT_GCP20_PROTOTYPE_DNS_RESPONSE_BUILDER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "net/base/net_util.h"
+#include "net/dns/dns_protocol.h"
+
+namespace net {
+ class IOBufferWithSize;
+}
+
+// Record for storing response data.
+struct DnsResponseRecord {
+ DnsResponseRecord();
+ ~DnsResponseRecord();
+
+ std::string name; // in dotted form
+ uint16 type;
+ uint16 klass;
+ uint32 ttl;
+ std::string rdata;
+};
+
+// Class for building service-specified responses.
+class DnsResponseBuilder {
+ public:
+ // Initializes builder.
+ explicit DnsResponseBuilder(uint16 id);
+
+ // Destroys the object.
+ ~DnsResponseBuilder() {}
+
+ // Methods for appending different types of responses to packet.
+ void AppendPtr(const std::string& service_type, uint32 ttl,
+ const std::string& service_name);
+ void AppendSrv(const std::string& service_name, uint32 ttl, uint16 priority,
+ uint16 weight, uint16 http_port,
+ const std::string& service_domain_name);
+ void AppendA(const std::string& service_domain_name, uint32 ttl,
+ net::IPAddressNumber http_ipv4);
+ void AppendTxt(const std::string& service_name, uint32 ttl,
+ const std::vector<std::string>& metadata);
+
+ // Serializes packet to byte sequence.
+ scoped_refptr<net::IOBufferWithSize> Build();
+
+ private:
+ // Appends response to packet.
+ void AddResponse(const std::string& name, uint16 type, uint32 ttl,
+ const std::string& rdata);
+
+ std::vector<DnsResponseRecord> responses_;
+
+ // Header of response package.
+ net::dns_protocol::Header header_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsResponseBuilder);
+};
+
+#endif // CLOUD_PRINT_GCP20_PROTOTYPE_DNS_RESPONSE_BUILDER_H_
+
diff --git a/cloud_print/gcp20/prototype/dns_sd_server.cc b/cloud_print/gcp20/prototype/dns_sd_server.cc
index 0ca8b43..2d2716b 100644
--- a/cloud_print/gcp20/prototype/dns_sd_server.cc
+++ b/cloud_print/gcp20/prototype/dns_sd_server.cc
@@ -7,7 +7,15 @@
#include <string.h>
#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/message_loop.h"
+#include "base/strings/stringprintf.h"
+#include "cloud_print/gcp20/prototype/dns_packet_parser.h"
+#include "cloud_print/gcp20/prototype/dns_response_builder.h"
#include "net/base/big_endian.h"
+#include "net/base/dns_util.h"
+#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "net/dns/dns_protocol.h"
@@ -16,53 +24,91 @@ namespace {
const char* kDefaultIpAddressMulticast = "224.0.0.251";
const uint16 kDefaultPortMulticast = 5353;
-// TODO(maksymb): Add possibility to set constants via command line arguments
-const uint32 kDefaultTTL = 60*60; // in seconds
+const double kTimeToNextAnnouncement = 0.8; // relatively to TTL
+const int kDnsBufSize = 65537;
+
+const uint16 kSrvPriority = 0;
+const uint16 kSrvWeight = 0;
+
+void DoNothingAfterSendToSocket(int /*val*/) {
+ NOTREACHED();
+ // TODO(maksymb): Delete this function once empty callback for SendTo() method
+ // will be allowed.
+}
} // namespace
-DnsSdServer::DnsSdServer() : is_online_(false) {
- // Do nothing
+DnsSdServer::DnsSdServer()
+ : recv_buf_(new net::IOBufferWithSize(kDnsBufSize)),
+ full_ttl_(0) {
}
DnsSdServer::~DnsSdServer() {
Shutdown();
}
-bool DnsSdServer::Start() {
- if (is_online_)
+bool DnsSdServer::Start(const ServiceParameters& serv_params, uint32 full_ttl,
+ const std::vector<std::string>& metadata) {
+ if (IsOnline())
return true;
if (!CreateSocket())
return false;
+ // Initializing server with parameters from arguments.
+ serv_params_ = serv_params;
+ full_ttl_ = full_ttl;
+ metadata_ = metadata;
+
LOG(INFO) << "DNS server started";
+ LOG(WARNING) << "DNS server does not support probing";
- SendAnnouncement(kDefaultTTL);
+ SendAnnouncement(full_ttl_);
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&DnsSdServer::OnDatagramReceived, base::Unretained(this)));
- is_online_ = true;
return true;
}
void DnsSdServer::Update() {
- if (!is_online_)
+ if (!IsOnline())
return;
- SendAnnouncement(kDefaultTTL);
+ SendAnnouncement(full_ttl_);
}
void DnsSdServer::Shutdown() {
- if (!is_online_)
+ if (!IsOnline())
return;
- SendAnnouncement(0); // ttl is 0
+ SendAnnouncement(0); // TTL is 0
socket_->Close();
- is_online_ = false;
+ socket_.reset(NULL);
LOG(INFO) << "DNS server stopped";
}
-void DnsSdServer::ProcessMessages() {
- NOTIMPLEMENTED(); // implement this
+void DnsSdServer::UpdateMetadata(const std::vector<std::string>& metadata) {
+ if (!IsOnline())
+ return;
+
+ metadata_ = metadata;
+
+ // TODO(maksymb): If less than 20% of full TTL left before next announcement
+ // then send it now.
+
+ uint32 current_ttl = GetCurrentTLL();
+ if (!CommandLine::ForCurrentProcess()->HasSwitch("no-announcement")) {
+ DnsResponseBuilder builder(current_ttl);
+
+ builder.AppendTxt(serv_params_.service_name_, current_ttl, metadata_);
+ scoped_refptr<net::IOBufferWithSize> buffer(builder.Build());
+
+ DCHECK(buffer.get() != NULL);
+
+ socket_->SendTo(buffer.get(), buffer.get()->size(), multicast_address_,
+ base::Bind(&DoNothingAfterSendToSocket));
+ }
}
bool DnsSdServer::CreateSocket() {
@@ -77,8 +123,7 @@ bool DnsSdServer::CreateSocket() {
socket_.reset(new net::UDPSocket(net::DatagramSocket::DEFAULT_BIND,
- net::RandIntCallback(),
- NULL,
+ net::RandIntCallback(), NULL,
net::NetLog::Source()));
net::IPEndPoint local_address = net::IPEndPoint(local_ip_any,
@@ -103,35 +148,153 @@ bool DnsSdServer::CreateSocket() {
return true;
}
-bool DnsSdServer::CheckPendingQueries() {
- NOTIMPLEMENTED(); // implement this
- return false;
+void DnsSdServer::ProcessMessage(int len, net::IOBufferWithSize* buf) {
+ VLOG(1) << "Received new message with length: " << len;
+
+ // Parse the message.
+ DnsPacketParser parser(buf->data(), len);
+
+ if (!parser.IsValid()) // Was unable to parse header.
+ return;
+
+ // TODO(maksymb): Handle truncated messages.
+ if (parser.header().flags & net::dns_protocol::kFlagResponse) // Not a query.
+ return;
+
+ DnsResponseBuilder builder(parser.header().id);
+
+ uint32 current_ttl = GetCurrentTLL();
+
+ DnsQueryRecord query;
+ // TODO(maksymb): Check known answers.
+ for (int query_idx = 0; query_idx < parser.header().qdcount; ++query_idx) {
+ bool success = parser.ReadRecord(&query);
+ if (success) {
+ ProccessQuery(current_ttl, query, &builder);
+ } else { // if (success)
+ LOG(INFO) << "Broken package";
+ break;
+ }
+ }
+
+ scoped_refptr<net::IOBufferWithSize> buffer(builder.Build());
+ if (buffer.get() == NULL)
+ return; // No answers.
+
+ VLOG(1) << "Current TTL for respond: " << current_ttl;
+
+ bool multicast_respond =
+ CommandLine::ForCurrentProcess()->HasSwitch("multicast-respond");
+ socket_->SendTo(buffer.get(), buffer.get()->size(),
+ multicast_respond ? multicast_address_ : recv_address_,
+ base::Bind(&DoNothingAfterSendToSocket));
+ VLOG(1) << "Responded to "
+ << (multicast_respond ? multicast_address_ : recv_address_).ToString();
}
-void DoNothing(int /*var*/) {
- // Do nothing
+void DnsSdServer::ProccessQuery(uint32 current_ttl, const DnsQueryRecord& query,
+ DnsResponseBuilder* builder) const {
+ std::string log;
+ bool responded = false;
+ switch (query.qtype) {
+ // TODO(maksymb): Add IPv6 support.
+ case net::dns_protocol::kTypePTR:
+ log = "Processing PTR query";
+ if (query.qname == serv_params_.service_type_) {
+ builder->AppendPtr(serv_params_.service_type_, current_ttl,
+ serv_params_.service_name_);
+ responded = true;
+ }
+ break;
+ case net::dns_protocol::kTypeSRV:
+ log = "Processing SRV query";
+ if (query.qname == serv_params_.service_name_) {
+ builder->AppendSrv(serv_params_.service_name_, current_ttl,
+ kSrvPriority, kSrvWeight, serv_params_.http_port_,
+ serv_params_.service_domain_name_);
+ responded = true;
+ }
+ break;
+ case net::dns_protocol::kTypeA:
+ log = "Processing A query";
+ if (query.qname == serv_params_.service_domain_name_) {
+ builder->AppendA(serv_params_.service_domain_name_, current_ttl,
+ serv_params_.http_ipv4_);
+ responded = true;
+ }
+ break;
+ case net::dns_protocol::kTypeTXT:
+ log = "Processing TXT query";
+ if (query.qname == serv_params_.service_name_) {
+ builder->AppendTxt(serv_params_.service_name_, current_ttl, metadata_);
+ responded = true;
+ }
+ break;
+ default:
+ base::SStringPrintf(&log, "Unknown query type (%d)", query.qtype);
+ }
+ log += responded ? ": responded" : ": ignored";
+ VLOG(1) << log;
+}
+
+void DnsSdServer::DoLoop(int rv) {
+ // TODO(maksymb): Check what happened if buffer will be overflowed
+ do {
+ if (rv > 0)
+ ProcessMessage(rv, recv_buf_);
+ rv = socket_->RecvFrom(recv_buf_, recv_buf_->size(), &recv_address_,
+ base::Bind(&DnsSdServer::DoLoop,
+ base::Unretained(this)));
+ } while (rv > 0);
+
+ // TODO(maksymb): Add handler for errors
+ DCHECK(rv == net::ERR_IO_PENDING);
+}
+
+void DnsSdServer::OnDatagramReceived() {
+ DoLoop(0);
}
void DnsSdServer::SendAnnouncement(uint32 ttl) {
- // Create a message with allocated space for header.
- // DNS header is temporary empty.
- scoped_ptr<std::vector<uint8> > message(
- new std::vector<uint8>(sizeof(net::dns_protocol::Header), 0)); // all is 0
-
- // TODO(maksymb): Create and implement DnsResponse class
-
- // Preparing for sending
- scoped_refptr<net::IOBufferWithSize> buffer =
- new net::IOBufferWithSize(static_cast<int>(message.get()->size()));
- memcpy(buffer.get()->data(), message.get()->data(), message.get()->size());
-
- // Create empty callback (we don't need it at all) and send packet
- net::CompletionCallback callback = base::Bind(DoNothing);
- socket_->SendTo(buffer.get(),
- buffer.get()->size(),
- multicast_address_,
- callback);
-
- LOG(INFO) << "Announcement was sent with TTL: " << ttl;
+ if (!CommandLine::ForCurrentProcess()->HasSwitch("no-announcement")) {
+ DnsResponseBuilder builder(ttl);
+
+ builder.AppendPtr(serv_params_.service_type_, ttl,
+ serv_params_.service_name_);
+ builder.AppendSrv(serv_params_.service_name_, ttl, kSrvPriority, kSrvWeight,
+ serv_params_.http_port_,
+ serv_params_.service_domain_name_);
+ builder.AppendA(serv_params_.service_domain_name_, ttl,
+ serv_params_.http_ipv4_);
+ builder.AppendTxt(serv_params_.service_name_, ttl, metadata_);
+ scoped_refptr<net::IOBufferWithSize> buffer(builder.Build());
+
+ DCHECK(buffer.get() != NULL);
+
+ socket_->SendTo(buffer.get(), buffer.get()->size(), multicast_address_,
+ base::Bind(&DoNothingAfterSendToSocket));
+
+ VLOG(1) << "Announcement was sent with TTL: " << ttl;
+ }
+
+ time_until_live_ = base::Time::Now() +
+ base::TimeDelta::FromSeconds(full_ttl_);
+
+ // Schedule next announcement.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&DnsSdServer::Update, base::Unretained(this)),
+ base::TimeDelta::FromSeconds(static_cast<int64>(
+ kTimeToNextAnnouncement*full_ttl_)));
+}
+
+uint32 DnsSdServer::GetCurrentTLL() const {
+ uint32 current_ttl = (time_until_live_ - base::Time::Now()).InSeconds();
+ if (time_until_live_ < base::Time::Now() || current_ttl == 0) {
+ // This should not be reachable. But still we don't need to fail.
+ current_ttl = 1; // Service is still alive.
+ LOG(ERROR) << "|current_ttl| was equal to zero.";
+ }
+ return current_ttl;
}
diff --git a/cloud_print/gcp20/prototype/dns_sd_server.h b/cloud_print/gcp20/prototype/dns_sd_server.h
index 9c8ed96..d5ef793 100644
--- a/cloud_print/gcp20/prototype/dns_sd_server.h
+++ b/cloud_print/gcp20/prototype/dns_sd_server.h
@@ -2,27 +2,40 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#ifndef GCP20_PROTOTYPE_DNS_SD_H_
-#define GCP20_PROTOTYPE_DNS_SD_H_
+#ifndef CLOUD_PRINT_GCP20_PROTOTYPE_DNS_SD_SERVER_H_
+#define CLOUD_PRINT_GCP20_PROTOTYPE_DNS_SD_SERVER_H_
+#include <string>
#include <vector>
-#include "net/dns/dns_protocol.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "cloud_print/gcp20/prototype/service_parameters.h"
+#include "net/base/ip_endpoint.h"
#include "net/udp/udp_socket.h"
+namespace net {
+ class IOBufferWithSize;
+}
+
+struct DnsQueryRecord;
+class DnsResponseBuilder;
+
// Class for sending multicast announcements, receiving queries and answering on
-// them. Client should call |ProccessMessages| periodically to make server work.
+// them.
+// TODO(maksymb): Implement probing.
class DnsSdServer {
public:
- // Constructs unstarted server.
+ // Constructor does not start server.
DnsSdServer();
- // Stops server.
+ // Stops the server and destroys the object.
~DnsSdServer();
// Starts the server. Returns |true| if server works. Also sends
// announcement.
- bool Start();
+ bool Start(const ServiceParameters& serv_params, uint32 full_ttl,
+ const std::vector<std::string>& metadata) WARN_UNUSED_RESULT;
// Sends announcement if server works.
void Update();
@@ -30,24 +43,35 @@ class DnsSdServer {
// Stops server with announcement.
void Shutdown();
- // Process pending queries for the server.
- void ProcessMessages();
-
// Returns |true| if server works.
- bool is_online() { return is_online_; }
+ bool IsOnline() { return !!socket_; }
+
+ // Updates data for TXT respond.
+ void UpdateMetadata(const std::vector<std::string>& metadata);
private:
// Binds a socket to multicast address. Returns |true| on success.
bool CreateSocket();
+ // Processes single query.
+ void ProccessQuery(uint32 current_ttl, const DnsQueryRecord& query,
+ DnsResponseBuilder* builder) const;
+
+ // Processes DNS message.
+ void ProcessMessage(int len, net::IOBufferWithSize* buf);
+
+ // CompletionCallback for receiving data from DNS.
+ void DoLoop(int rv);
+
+ // Function to start listening to socket (delegate to DoLoop function).
+ void OnDatagramReceived();
+
// Sends announcement.
void SendAnnouncement(uint32 ttl);
- // Returns |true| if server received some questions.
- bool CheckPendingQueries();
-
- // Stores |true| if server was started.
- bool is_online_;
+ // Calculates and returns current TTL (with accordance to last send
+ // announcement time.
+ uint32 GetCurrentTLL() const;
// Stores socket to multicast address.
scoped_ptr<net::UDPSocket> socket_;
@@ -55,8 +79,26 @@ class DnsSdServer {
// Stores multicast address end point.
net::IPEndPoint multicast_address_;
+ // Stores time until last announcement is live.
+ base::Time time_until_live_;
+
+ // Stores service parameters (like service-name and service-type etc.)
+ ServiceParameters serv_params_;
+
+ // Stores the buffer for receiving messages.
+ scoped_refptr<net::IOBufferWithSize> recv_buf_;
+
+ // Stores address from where last message was sent.
+ net::IPEndPoint recv_address_;
+
+ // Stores information for TXT respond.
+ std::vector<std::string> metadata_;
+
+ // TTL for announcements
+ uint32 full_ttl_;
+
DISALLOW_COPY_AND_ASSIGN(DnsSdServer);
};
-#endif // GCP20_PROTOTYPE_DNS_SD_H_
+#endif // CLOUD_PRINT_GCP20_PROTOTYPE_DNS_SD_SERVER_H_
diff --git a/cloud_print/gcp20/prototype/gcp20_device.cc b/cloud_print/gcp20/prototype/gcp20_device.cc
index 2ff1a2d..4eab248 100644
--- a/cloud_print/gcp20/prototype/gcp20_device.cc
+++ b/cloud_print/gcp20/prototype/gcp20_device.cc
@@ -2,12 +2,40 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include <stdio.h>
+#include <signal.h>
+#include "base/at_exit.h"
+#include "base/bind.h"
#include "base/command_line.h"
#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/run_loop.h"
#include "base/threading/platform_thread.h"
-#include "cloud_print/gcp20/prototype/dns_sd_server.h"
+#include "base/time.h"
+#include "cloud_print/gcp20/prototype/printer.h"
+
+namespace {
+
+Printer printer;
+base::AtExitManager at_exit;
+
+void StopPrinter() {
+ printer.Stop();
+}
+
+void StartPrinter() {
+ bool success = printer.Start();
+ DCHECK(success);
+
+ at_exit.RegisterTask(base::Bind(&StopPrinter));
+}
+
+void OnAbort(int val) {
+ StopPrinter();
+ base::MessageLoop::current()->Quit();
+}
+
+} // namespace
int main(int argc, char* argv[]) {
CommandLine::Init(argc, argv);
@@ -16,11 +44,12 @@ int main(int argc, char* argv[]) {
settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
logging::InitLogging(settings);
- DnsSdServer dns_sd_server;
- dns_sd_server.Start();
- base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(2));
- dns_sd_server.Shutdown();
+ signal(SIGINT, OnAbort); // Handle Ctrl+C signal.
+
+ base::MessageLoop loop(base::MessageLoop::TYPE_IO);
+ base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(&StartPrinter));
+ base::RunLoop runner;
+ runner.Run();
return 0;
}
-
diff --git a/cloud_print/gcp20/prototype/gcp20_device.gyp b/cloud_print/gcp20/prototype/gcp20_device.gyp
index 7946960..8324b8b 100644
--- a/cloud_print/gcp20/prototype/gcp20_device.gyp
+++ b/cloud_print/gcp20/prototype/gcp20_device.gyp
@@ -16,14 +16,33 @@
},
'targets': [
{
- 'target_name': 'gcp20_device',
- 'type': 'executable',
+ 'target_name': 'gcp20_device_lib',
+ 'type': 'static_library',
'dependencies': [
'<(DEPTH)/base/base.gyp:base',
'<(DEPTH)/net/net.gyp:net',
],
'sources': [
+ 'dns_packet_parser.cc',
+ 'dns_packet_parser.h',
+ 'dns_response_builder.cc',
+ 'dns_response_builder.h',
'dns_sd_server.cc',
+ 'dns_sd_server.h',
+ 'gcp20_device.cc',
+ 'printer.cc',
+ 'printer.h',
+ 'service_parameters.cc',
+ 'service_parameters.h',
+ ],
+ },
+ {
+ 'target_name': 'gcp20_device',
+ 'type': 'executable',
+ 'dependencies': [
+ 'gcp20_device.gyp:gcp20_device_lib',
+ ],
+ 'sources': [
'gcp20_device.cc',
],
'msvs_settings': {
diff --git a/cloud_print/gcp20/prototype/printer.cc b/cloud_print/gcp20/prototype/printer.cc
new file mode 100644
index 0000000..4d39fe8
--- /dev/null
+++ b/cloud_print/gcp20/prototype/printer.cc
@@ -0,0 +1,131 @@
+// 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.
+
+#include "cloud_print/gcp20/prototype/printer.h"
+
+#include <string>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/strings/string_number_conversions.h"
+#include "cloud_print/gcp20/prototype/service_parameters.h"
+#include "net/base/net_util.h"
+
+namespace {
+
+const char* kServiceType = "_privet._tcp.local";
+const char* kServiceNamePrefix = "first_gcp20_device";
+const char* kServiceDomainName = "my-privet-device.local";
+
+const uint16 kDefaultTTL = 60*60;
+const uint16 kDefaultHttpPort = 10101;
+
+uint16 ReadHttpPortFromCommandLine() {
+ uint32 http_port_tmp = kDefaultHttpPort;
+
+ std::string http_port_string_tmp =
+ CommandLine::ForCurrentProcess()->GetSwitchValueASCII("http-port");
+ base::StringToUint(http_port_string_tmp, &http_port_tmp);
+
+ if (http_port_tmp > kuint16max) {
+ LOG(ERROR) << "Port " << http_port_tmp << " is too large (maximum is " <<
+ kDefaultHttpPort << "). Using default port.";
+
+ http_port_tmp = kDefaultHttpPort;
+ }
+
+ VLOG(1) << "HTTP port for responses: " << http_port_tmp;
+ return static_cast<uint16>(http_port_tmp);
+}
+
+uint16 ReadTtlFromCommandLine() {
+ uint32 ttl = kDefaultTTL;
+
+ base::StringToUint(
+ CommandLine::ForCurrentProcess()->GetSwitchValueASCII("ttl"), &ttl);
+
+ VLOG(1) << "TTL for announcements: " << ttl;
+ return ttl;
+}
+
+// Returns local IP address number of first interface found (except loopback).
+// Return value is empty if no interface found. Possible interfaces names are
+// "eth0", "wlan0" etc. If interface name is empty, function will return IP
+// address of first interface found.
+net::IPAddressNumber GetLocalIp(const std::string& interface_name,
+ bool return_ipv6_number) {
+ net::NetworkInterfaceList interfaces;
+ bool success = net::GetNetworkList(&interfaces);
+ DCHECK(success);
+
+ size_t expected_address_size = return_ipv6_number ? net::kIPv6AddressSize
+ : net::kIPv4AddressSize;
+
+ for (net::NetworkInterfaceList::iterator iter = interfaces.begin();
+ iter != interfaces.end(); ++iter) {
+ if (iter->address.size() == expected_address_size &&
+ (interface_name.empty() || interface_name == iter->name)) {
+ LOG(INFO) << net::IPAddressToString(iter->address);
+ return iter->address;
+ }
+ }
+
+ return net::IPAddressNumber();
+}
+
+} // namespace
+
+Printer::Printer() : initialized_(false) {
+}
+
+Printer::~Printer() {
+ Stop();
+}
+
+bool Printer::Start() {
+ if (initialized_)
+ return true;
+
+ // TODO(maksymb): Add switch for command line to control interface name.
+ net::IPAddressNumber ip = GetLocalIp("", false);
+ if (ip.empty()) {
+ LOG(ERROR) << "No local IP found. Cannot start printer.";
+ return false;
+ }
+ VLOG(1) << "Local address: " << net::IPAddressToString(ip);
+
+ // Starting DNS-SD server.
+ initialized_ = dns_server_.Start(
+ ServiceParameters(kServiceType,
+ kServiceNamePrefix,
+ kServiceDomainName,
+ ip,
+ ReadHttpPortFromCommandLine()),
+ ReadTtlFromCommandLine(),
+ CreateTxt());
+
+ return initialized_;
+}
+
+void Printer::Stop() {
+ if (!initialized_)
+ return;
+
+ dns_server_.Shutdown();
+}
+
+std::vector<std::string> Printer::CreateTxt() const {
+ std::vector<std::string> txt;
+
+ txt.push_back("txtvers=1");
+ txt.push_back("ty=Google Cloud Ready Printer GCP2.0 Prototype");
+ txt.push_back("note=Virtual printer");
+ txt.push_back("url=https://www.google.com/cloudprint");
+ txt.push_back("type=printer");
+ txt.push_back("id=");
+ txt.push_back("cs=offline");
+
+ return txt;
+}
+
diff --git a/cloud_print/gcp20/prototype/printer.h b/cloud_print/gcp20/prototype/printer.h
new file mode 100644
index 0000000..d2d72b6
--- /dev/null
+++ b/cloud_print/gcp20/prototype/printer.h
@@ -0,0 +1,42 @@
+// 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.
+
+#ifndef GCP20_PROTOTYPE_PRINTER_H_
+#define GCP20_PROTOTYPE_PRINTER_H_
+
+#include <string>
+#include <vector>
+
+#include "cloud_print/gcp20/prototype/dns_sd_server.h"
+
+// This class will maintain work of DNS-SD server, HTTP server and others.
+class Printer {
+ public:
+ // Constructs uninitialized object.
+ Printer();
+
+ // Destroys the object.
+ ~Printer();
+
+ // Starts all servers.
+ bool Start();
+
+ // Stops all servers.
+ void Stop();
+
+ private:
+ // Creates data for DNS TXT respond.
+ std::vector<std::string> CreateTxt() const;
+
+ // Contains |true| if initialized.
+ bool initialized_;
+
+ // Contains DNS-SD server.
+ DnsSdServer dns_server_;
+
+ DISALLOW_COPY_AND_ASSIGN(Printer);
+};
+
+#endif // GCP20_PROTOTYPE_PRINTER_H_
+
diff --git a/cloud_print/gcp20/prototype/service_parameters.cc b/cloud_print/gcp20/prototype/service_parameters.cc
new file mode 100644
index 0000000..e32581e
--- /dev/null
+++ b/cloud_print/gcp20/prototype/service_parameters.cc
@@ -0,0 +1,22 @@
+// 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.
+
+#include "cloud_print/gcp20/prototype/service_parameters.h"
+
+ServiceParameters::ServiceParameters() : http_port_(0) {
+}
+
+ServiceParameters::ServiceParameters(const std::string& service_type,
+ const std::string& service_name_prefix,
+ const std::string& service_domain_name,
+ const net::IPAddressNumber& http_ipv4,
+ uint16 http_port)
+ : service_type_(service_type),
+ service_name_(service_name_prefix + "." + service_type),
+ service_domain_name_(service_domain_name),
+ http_ipv4_(http_ipv4),
+ http_port_(http_port) {
+}
+
+
diff --git a/cloud_print/gcp20/prototype/service_parameters.h b/cloud_print/gcp20/prototype/service_parameters.h
new file mode 100644
index 0000000..79582ad
--- /dev/null
+++ b/cloud_print/gcp20/prototype/service_parameters.h
@@ -0,0 +1,32 @@
+// 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.
+
+#ifndef CLOUD_PRINT_GCP20_PROTOTYPE_SERVICE_PARAMETERS_H_
+#define CLOUD_PRINT_GCP20_PROTOTYPE_SERVICE_PARAMETERS_H_
+
+#include <string>
+
+#include "net/base/net_util.h"
+
+// Stores information about service.
+struct ServiceParameters {
+ ServiceParameters();
+
+ ~ServiceParameters() {}
+
+ ServiceParameters(const std::string& service_type,
+ const std::string& service_name_prefix,
+ const std::string& service_domain_name,
+ const net::IPAddressNumber& http_ipv4,
+ uint16 http_port);
+
+ std::string service_type_;
+ std::string service_name_;
+ std::string service_domain_name_;
+ net::IPAddressNumber http_ipv4_;
+ uint16 http_port_;
+};
+
+#endif // CLOUD_PRINT_GCP20_PROTOTYPE_SERVICE_PARAMETERS_H_
+