// Copyright 2016 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 "content/browser/broadcast_channel/broadcast_channel_provider.h"

#include "base/bind.h"
#include "base/stl_util.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "mojo/public/cpp/bindings/associated_binding.h"
#include "mojo/public/cpp/bindings/interface_ptr_set.h"
#include "mojo/public/cpp/bindings/strong_binding.h"

namespace content {

// There is a one-to-one mapping of BroadcastChannel instances in the renderer
// and Connection instances in the browser. The Connection is owned by a
// BroadcastChannelProvider.
class BroadcastChannelProvider::Connection
    : public blink::mojom::BroadcastChannelClient {
 public:
  Connection(const url::Origin& origin,
             const std::string& name,
             blink::mojom::BroadcastChannelClientAssociatedPtrInfo client,
             blink::mojom::BroadcastChannelClientAssociatedRequest connection,
             BroadcastChannelProvider* service);

  void OnMessage(blink::CloneableMessage message) override;
  void MessageToClient(const blink::CloneableMessage& message) const {
    client_->OnMessage(message.ShallowClone());
  }
  const url::Origin& origin() const { return origin_; }
  const std::string& name() const { return name_; }

  void set_connection_error_handler(const base::Closure& error_handler) {
    binding_.set_connection_error_handler(error_handler);
    client_.set_connection_error_handler(error_handler);
  }

 private:
  mojo::AssociatedBinding<blink::mojom::BroadcastChannelClient> binding_;
  blink::mojom::BroadcastChannelClientAssociatedPtr client_;

  BroadcastChannelProvider* service_;
  url::Origin origin_;
  std::string name_;
};

BroadcastChannelProvider::Connection::Connection(
    const url::Origin& origin,
    const std::string& name,
    blink::mojom::BroadcastChannelClientAssociatedPtrInfo client,
    blink::mojom::BroadcastChannelClientAssociatedRequest connection,
    BroadcastChannelProvider* service)
    : binding_(this, std::move(connection)),
      service_(service),
      origin_(origin),
      name_(name) {
  client_.Bind(std::move(client));
}

void BroadcastChannelProvider::Connection::OnMessage(
    blink::CloneableMessage message) {
  service_->ReceivedMessageOnConnection(this, message);
}

BroadcastChannelProvider::BroadcastChannelProvider() {}

mojo::BindingId BroadcastChannelProvider::Connect(
    RenderProcessHostId render_process_host_id,
    blink::mojom::BroadcastChannelProviderRequest request) {
  return bindings_.AddBinding(this, std::move(request), render_process_host_id);
}

void BroadcastChannelProvider::ConnectToChannel(
    const url::Origin& origin,
    const std::string& name,
    blink::mojom::BroadcastChannelClientAssociatedPtrInfo client,
    blink::mojom::BroadcastChannelClientAssociatedRequest connection) {
  RenderProcessHostId process_id = bindings_.dispatch_context();
  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
  if (!policy->CanAccessDataForOrigin(process_id, origin.GetURL())) {
    mojo::ReportBadMessage("BROADCAST_CHANNEL_INVALID_ORIGIN");
    return;
  }

  std::unique_ptr<Connection> c(new Connection(origin, name, std::move(client),
                                               std::move(connection), this));
  c->set_connection_error_handler(
      base::Bind(&BroadcastChannelProvider::UnregisterConnection,
                 base::Unretained(this), c.get()));
  connections_[origin].insert(std::make_pair(name, std::move(c)));
}

BroadcastChannelProvider::~BroadcastChannelProvider() {}

void BroadcastChannelProvider::UnregisterConnection(Connection* c) {
  url::Origin origin = c->origin();
  auto& connections = connections_[origin];
  for (auto it = connections.lower_bound(c->name()),
            end = connections.upper_bound(c->name());
       it != end; ++it) {
    if (it->second.get() == c) {
      connections.erase(it);
      break;
    }
  }
  if (connections.empty())
    connections_.erase(origin);
}

void BroadcastChannelProvider::ReceivedMessageOnConnection(
    Connection* c,
    const blink::CloneableMessage& message) {
  auto& connections = connections_[c->origin()];
  for (auto it = connections.lower_bound(c->name()),
            end = connections.upper_bound(c->name());
       it != end; ++it) {
    if (it->second.get() != c)
      it->second->MessageToClient(message);
  }
}

}  // namespace content
