blob: a0d92f3ada71098216b3bed3ca05eb4c1c51ba35 [file] [log] [blame]
// Copyright 2014 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 "extensions/browser/api/cast_channel/logger.h"
#include <stdint.h>
#include <string>
#include <utility>
#include "base/strings/string_util.h"
#include "base/time/clock.h"
#include "extensions/browser/api/cast_channel/cast_auth_util.h"
#include "extensions/browser/api/cast_channel/cast_socket.h"
#include "extensions/browser/api/cast_channel/logger_util.h"
#include "net/base/net_errors.h"
#include "third_party/zlib/zlib.h"
namespace extensions {
namespace api {
namespace cast_channel {
using net::IPEndPoint;
using proto::AggregatedSocketEvent;
using proto::EventType;
using proto::Log;
using proto::SocketEvent;
namespace {
const char* kInternalNamespacePrefix = "com.google.cast";
proto::ChallengeReplyErrorType ChallegeReplyErrorToProto(
AuthResult::ErrorType error_type) {
switch (error_type) {
case AuthResult::ERROR_NONE:
return proto::CHALLENGE_REPLY_ERROR_NONE;
case AuthResult::ERROR_PEER_CERT_EMPTY:
return proto::CHALLENGE_REPLY_ERROR_PEER_CERT_EMPTY;
case AuthResult::ERROR_WRONG_PAYLOAD_TYPE:
return proto::CHALLENGE_REPLY_ERROR_WRONG_PAYLOAD_TYPE;
case AuthResult::ERROR_NO_PAYLOAD:
return proto::CHALLENGE_REPLY_ERROR_NO_PAYLOAD;
case AuthResult::ERROR_PAYLOAD_PARSING_FAILED:
return proto::CHALLENGE_REPLY_ERROR_PAYLOAD_PARSING_FAILED;
case AuthResult::ERROR_MESSAGE_ERROR:
return proto::CHALLENGE_REPLY_ERROR_MESSAGE_ERROR;
case AuthResult::ERROR_NO_RESPONSE:
return proto::CHALLENGE_REPLY_ERROR_NO_RESPONSE;
case AuthResult::ERROR_FINGERPRINT_NOT_FOUND:
return proto::CHALLENGE_REPLY_ERROR_FINGERPRINT_NOT_FOUND;
case AuthResult::ERROR_CERT_PARSING_FAILED:
return proto::CHALLENGE_REPLY_ERROR_CERT_PARSING_FAILED;
case AuthResult::ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA:
return proto::CHALLENGE_REPLY_ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA;
case AuthResult::ERROR_CANNOT_EXTRACT_PUBLIC_KEY:
return proto::CHALLENGE_REPLY_ERROR_CANNOT_EXTRACT_PUBLIC_KEY;
case AuthResult::ERROR_SIGNED_BLOBS_MISMATCH:
return proto::CHALLENGE_REPLY_ERROR_SIGNED_BLOBS_MISMATCH;
default:
NOTREACHED();
return proto::CHALLENGE_REPLY_ERROR_NONE;
}
}
std::unique_ptr<char[]> Compress(const std::string& input, size_t* length) {
*length = 0;
z_stream stream = {0};
int result = deflateInit2(&stream,
Z_DEFAULT_COMPRESSION,
Z_DEFLATED,
// 16 is added to produce a gzip header + trailer.
MAX_WBITS + 16,
8, // memLevel = 8 is default.
Z_DEFAULT_STRATEGY);
DCHECK_EQ(Z_OK, result);
size_t out_size = deflateBound(&stream, input.size());
std::unique_ptr<char[]> out(new char[out_size]);
stream.next_in = reinterpret_cast<uint8_t*>(const_cast<char*>(input.data()));
stream.avail_in = input.size();
stream.next_out = reinterpret_cast<uint8_t*>(out.get());
stream.avail_out = out_size;
// Do a one-shot compression. This will return Z_STREAM_END only if |output|
// is large enough to hold all compressed data.
result = deflate(&stream, Z_FINISH);
bool success = (result == Z_STREAM_END);
if (!success)
VLOG(2) << "deflate() failed. Result: " << result;
result = deflateEnd(&stream);
DCHECK(result == Z_OK || result == Z_DATA_ERROR);
if (success)
*length = out_size - stream.avail_out;
return out;
}
// Propagate any error fields set in |event| to |last_errors|. If any error
// field in |event| is set, then also set |last_errors->event_type|.
void MaybeSetLastErrors(const SocketEvent& event, LastErrors* last_errors) {
if (event.has_net_return_value() &&
event.net_return_value() < net::ERR_IO_PENDING) {
last_errors->net_return_value = event.net_return_value();
last_errors->event_type = event.type();
}
if (event.has_challenge_reply_error_type()) {
last_errors->challenge_reply_error_type =
event.challenge_reply_error_type();
last_errors->event_type = event.type();
}
}
} // namespace
Logger::AggregatedSocketEventLog::AggregatedSocketEventLog() {
}
Logger::AggregatedSocketEventLog::~AggregatedSocketEventLog() {
}
Logger::Logger(std::unique_ptr<base::Clock> clock, base::Time unix_epoch_time)
: clock_(std::move(clock)), unix_epoch_time_(unix_epoch_time) {
DCHECK(clock_);
// Logger may not be necessarily be created on the IO thread, but logging
// happens exclusively there.
thread_checker_.DetachFromThread();
}
Logger::~Logger() {
}
void Logger::LogNewSocketEvent(const CastSocket& cast_socket) {
DCHECK(thread_checker_.CalledOnValidThread());
SocketEvent event = CreateEvent(proto::CAST_SOCKET_CREATED);
AggregatedSocketEvent& aggregated_socket_event =
LogSocketEvent(cast_socket.id(), event);
const net::IPAddress& ip = cast_socket.ip_endpoint().address();
DCHECK(ip.IsValid());
aggregated_socket_event.set_endpoint_id(ip.bytes().back());
aggregated_socket_event.set_channel_auth_type(cast_socket.channel_auth() ==
CHANNEL_AUTH_TYPE_SSL
? proto::SSL
: proto::SSL_VERIFIED);
}
void Logger::LogSocketEvent(int channel_id, EventType event_type) {
DCHECK(thread_checker_.CalledOnValidThread());
LogSocketEventWithDetails(channel_id, event_type, std::string());
}
void Logger::LogSocketEventWithDetails(int channel_id,
EventType event_type,
const std::string& details) {
DCHECK(thread_checker_.CalledOnValidThread());
SocketEvent event = CreateEvent(event_type);
if (!details.empty())
event.set_details(details);
LogSocketEvent(channel_id, event);
}
void Logger::LogSocketEventWithRv(int channel_id,
EventType event_type,
int rv) {
DCHECK(thread_checker_.CalledOnValidThread());
SocketEvent event = CreateEvent(event_type);
event.set_net_return_value(rv);
AggregatedSocketEvent& aggregated_socket_event =
LogSocketEvent(channel_id, event);
if ((event_type == proto::SOCKET_READ || event_type == proto::SOCKET_WRITE) &&
rv > 0) {
if (event_type == proto::SOCKET_READ) {
aggregated_socket_event.set_bytes_read(
aggregated_socket_event.bytes_read() + rv);
} else {
aggregated_socket_event.set_bytes_written(
aggregated_socket_event.bytes_written() + rv);
}
}
}
void Logger::LogSocketReadyState(int channel_id, proto::ReadyState new_state) {
DCHECK(thread_checker_.CalledOnValidThread());
SocketEvent event = CreateEvent(proto::READY_STATE_CHANGED);
event.set_ready_state(new_state);
LogSocketEvent(channel_id, event);
}
void Logger::LogSocketConnectState(int channel_id,
proto::ConnectionState new_state) {
DCHECK(thread_checker_.CalledOnValidThread());
SocketEvent event = CreateEvent(proto::CONNECTION_STATE_CHANGED);
event.set_connection_state(new_state);
LogSocketEvent(channel_id, event);
}
void Logger::LogSocketReadState(int channel_id, proto::ReadState new_state) {
DCHECK(thread_checker_.CalledOnValidThread());
SocketEvent event = CreateEvent(proto::READ_STATE_CHANGED);
event.set_read_state(new_state);
LogSocketEvent(channel_id, event);
}
void Logger::LogSocketWriteState(int channel_id, proto::WriteState new_state) {
DCHECK(thread_checker_.CalledOnValidThread());
SocketEvent event = CreateEvent(proto::WRITE_STATE_CHANGED);
event.set_write_state(new_state);
LogSocketEvent(channel_id, event);
}
void Logger::LogSocketErrorState(int channel_id, proto::ErrorState new_state) {
DCHECK(thread_checker_.CalledOnValidThread());
SocketEvent event = CreateEvent(proto::ERROR_STATE_CHANGED);
event.set_error_state(new_state);
LogSocketEvent(channel_id, event);
}
void Logger::LogSocketEventForMessage(int channel_id,
EventType event_type,
const std::string& message_namespace,
const std::string& details) {
DCHECK(thread_checker_.CalledOnValidThread());
SocketEvent event = CreateEvent(event_type);
if (base::StartsWith(message_namespace, kInternalNamespacePrefix,
base::CompareCase::INSENSITIVE_ASCII))
event.set_message_namespace(message_namespace);
event.set_details(details);
LogSocketEvent(channel_id, event);
}
void Logger::LogSocketChallengeReplyEvent(int channel_id,
const AuthResult& auth_result) {
DCHECK(thread_checker_.CalledOnValidThread());
SocketEvent event = CreateEvent(proto::AUTH_CHALLENGE_REPLY);
event.set_challenge_reply_error_type(
ChallegeReplyErrorToProto(auth_result.error_type));
LogSocketEvent(channel_id, event);
}
SocketEvent Logger::CreateEvent(EventType event_type) {
SocketEvent event;
event.set_type(event_type);
event.set_timestamp_micros(
(clock_->Now() - unix_epoch_time_).InMicroseconds());
return event;
}
AggregatedSocketEvent& Logger::LogSocketEvent(int channel_id,
const SocketEvent& socket_event) {
AggregatedSocketEventLogMap::iterator it =
aggregated_socket_events_.find(channel_id);
if (it == aggregated_socket_events_.end()) {
if (aggregated_socket_events_.size() >= kMaxSocketsToLog) {
AggregatedSocketEventLogMap::iterator erase_it =
aggregated_socket_events_.begin();
log_.set_num_evicted_aggregated_socket_events(
log_.num_evicted_aggregated_socket_events() + 1);
log_.set_num_evicted_socket_events(
log_.num_evicted_socket_events() +
erase_it->second->socket_events.size());
aggregated_socket_events_.erase(erase_it);
}
it = aggregated_socket_events_
.insert(std::make_pair(
channel_id, base::MakeUnique<AggregatedSocketEventLog>()))
.first;
it->second->aggregated_socket_event.set_id(channel_id);
}
std::deque<proto::SocketEvent>& socket_events = it->second->socket_events;
if (socket_events.size() >= kMaxEventsPerSocket) {
socket_events.pop_front();
log_.set_num_evicted_socket_events(log_.num_evicted_socket_events() + 1);
}
socket_events.push_back(socket_event);
MaybeSetLastErrors(socket_event, &(it->second->last_errors));
return it->second->aggregated_socket_event;
}
std::unique_ptr<char[]> Logger::GetLogs(size_t* length) const {
*length = 0;
Log log;
// Copy "global" values from |log_|. Don't use |log_| directly since this
// function is const.
log.CopyFrom(log_);
for (AggregatedSocketEventLogMap::const_iterator it =
aggregated_socket_events_.begin();
it != aggregated_socket_events_.end();
++it) {
AggregatedSocketEvent* new_aggregated_socket_event =
log.add_aggregated_socket_event();
new_aggregated_socket_event->CopyFrom(it->second->aggregated_socket_event);
const std::deque<SocketEvent>& socket_events = it->second->socket_events;
for (std::deque<SocketEvent>::const_iterator socket_event_it =
socket_events.begin();
socket_event_it != socket_events.end();
++socket_event_it) {
SocketEvent* socket_event =
new_aggregated_socket_event->add_socket_event();
socket_event->CopyFrom(*socket_event_it);
}
}
std::string serialized;
if (!log.SerializeToString(&serialized)) {
VLOG(2) << "Failed to serialized proto to string.";
return std::unique_ptr<char[]>();
}
return Compress(serialized, length);
}
void Logger::Reset() {
aggregated_socket_events_.clear();
log_.Clear();
}
LastErrors Logger::GetLastErrors(int channel_id) const {
AggregatedSocketEventLogMap::const_iterator it =
aggregated_socket_events_.find(channel_id);
if (it != aggregated_socket_events_.end()) {
return it->second->last_errors;
} else {
return LastErrors();
}
}
} // namespace cast_channel
} // namespace api
} // namespace extensions