// Copyright 2022 The Chromium Authors.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_ANALYSIS_AGENT_INCLUDE_CONTENT_ANALYSIS_SDK_ANALYSIS_AGENT_H_
#define CONTENT_ANALYSIS_AGENT_INCLUDE_CONTENT_ANALYSIS_SDK_ANALYSIS_AGENT_H_

#include <memory>
#include <string>

#include "content_analysis/sdk/analysis.pb.h"
#include "content_analysis/sdk/result_codes.h"

// This is the main include file for code using Content Analysis Connector
// Agent SDK.  No other include is needed.
//
// An agent begins by creating an instance of Agent using the factory
// function Agent::Create().  This instance should live as long as the agent
// intends to receive content analysis requests from Google Chrome.
//
// Agent::Create() must be passed an object that implements the
// AgentEventHandler interface.  Methods on this interface will be called
// at the approriate time to handle specific events.  The events are:
//
// - A Google Chrome browser has started or stopped.  This events contains
//   information about the browser such as process id and executable path.
// - A request to analyze content.  The agent reads and analyses the
//   request in to determine a verdict: allow or block.  When the verdict is
//   known the response is sent back to Google Chrome.
// - An acknowledgement that Google Chrome has properly received the agent's
//   verdict.
//
// The agent is not required to serialize event handling.  That is, content
// analysis events can be analyze in the background and the response does
// not need to be sent before OnAnalysisRequested() returns.
//
// Google Chrome thottles the number of requests sent to the agent to 5
// current requests at a time but this is subject to change.

namespace content_analysis {
namespace sdk {

// Represents information about one instance of a Google Chrome browser
// process that is connected to the agent.
struct BrowserInfo {
  unsigned long pid = 0;  // Process ID of Google Chrome browser process.
  std::string binary_path;  // The full path to the process's main binary.
};

// Represents one content analysis request as generated by a given user action
// in Google Chrome.
//
// The agent should retrieve information about the content analysis request
// using the GetRequest() method.  The agent should analyze the request and
// update the response, returned by GetResponse(), with a verdict (allow or
// block).  Once the verdict is set the response can be sent back to Google
// Chrome by calling Send().
//
// The default verdict is to allow the requested user action.  If the final
// verdict is to allow then the agent does not need to update the response and
// can simply call Send().
//
// If the final verdict should be to block, the agent should first update the
// response by calling SetEventVerdictToBlock() before calling Send().
//
// This class is not thread safe.  However, it may be passed to another thread
// as long as the agent properly serializses access to the event.
//
// See the demo directory for an example of how to use this class.
class ContentAnalysisEvent {
 public:
  virtual ~ContentAnalysisEvent() = default;

  // Prepares the event for graceful shutdown.  Upon return calls to all
  // other methods of this class will fail.
  virtual ResultCode Close() = 0;

  // Retrives information about the browser that generated this content
  // analysis event.
  virtual const BrowserInfo& GetBrowserInfo() const = 0;

  // Retrieves a read-only reference to the content analysis request received
  // from Google Chrome.
  virtual const ContentAnalysisRequest& GetRequest() const = 0;

  // Retrieves a writable reference to the content analysis response that will
  // be sent to Google Chrome as the verdict for the request of this event.
  // The agent may modify this response in place before calling Send().
  virtual ContentAnalysisResponse& GetResponse() = 0;

  // Send the verdict to Google Chrome.  Once this method is called further
  // changes to the response are ignored.
  virtual ResultCode Send() = 0;

  // Returns a string containing internal state of the object that is useful
  // for debugging.
  virtual std::string DebugString() const = 0;

 protected:
  ContentAnalysisEvent() = default;
  ContentAnalysisEvent(const ContentAnalysisEvent& rhs) = delete;
  ContentAnalysisEvent(ContentAnalysisEvent&& rhs) = delete;
  ContentAnalysisEvent& operator=(const ContentAnalysisEvent& rhs) = delete;
  ContentAnalysisEvent& operator=(ContentAnalysisEvent&& rhs) = delete;

};

// Agents should implement this interface in order to handle events as needed.
//
// OnBrowserConnected() and OnBrowserDisonnected() notify the agent when
// instances of Google Chome start and stop.  The agent may perform any one-time
// actions as required for these events.  The default action is to do nothing
// for both events.  If the agent does not need perform any special actions
// these methods do not need to be overridden.
//
// OnAnalysisRequested() notifies the agent of a new content analysis request
// from Google Chrome.  The agent should perform the analysis and respond to
// the event.  It is not required for the agent complete the analysis and
// respond to before this callback returns.  The agent may pass the
// ContentAnalysisEvent to a background task and respond when ready.  This
// callback has no default action and agents must override it.
//
// OnResponseAcknowledged() notifies the agent that Google Chrome has received
// the content analysis response and how it has handled it.
class AgentEventHandler {
 public:
  AgentEventHandler() = default;
  virtual ~AgentEventHandler() = default;

  // Called when a new Google Chrome browser instance connects to the agent.
  // This is always called before the first OnAnalysisRequested() from that
  // browser.
  virtual void OnBrowserConnected(const BrowserInfo& info) {}

  // Called when a Google Chrome browser instance disconnects from the agent.
  // The agent will no longer receive new content analysis requests from this
  // browser.
  virtual void OnBrowserDisconnected(const BrowserInfo& info) {}

  // Called when a Google Chrome browser requests a content analysis.
  virtual void OnAnalysisRequested(
      std::unique_ptr<ContentAnalysisEvent> event) = 0;

  // Called when a Google Chrome browser acknowledges the content analysis
  // response from the agent.  The default action is to do nothing.
  // If the agent does not need perform any special actions this methods does
  // not need to be overridden.
  virtual void OnResponseAcknowledged(
      const ContentAnalysisAcknowledgement& ack) {}

  // Called when a Google Chrome browser asks the agent to cancels one or
  // more content analysis requests.  This happens when the user presses the
  // Cancel button in the in-progress dialog.  This is expected to be a best
  // effort only; agents may choose to ignore this message or possibly only
  // cancel a subset of requests with the given user action id.
  //
  // The default action is to do nothing. If the agent does not need perform
  // any special actions this methods does not need to be overridden.
  virtual void OnCancelRequests(
      const ContentAnalysisCancelRequests& cancel) {}

  // Called whenever the Agent implementation detects an error.  `context`
  // is a string that provide a hint to the handler as to where the error
  // happened in the agent.  `error` represent the actual error detected.
  virtual void OnInternalError(const char* context, ResultCode error) {}
};

// Represents an agent that can perform content analysis for the Google Chrome
// browser.  This class holds the server endpoint that Google Chrome connects
// to when content analysis is required.
//
// Agent instances should outlive all ContentAnalysisEvent instances created
// with it. Agent instances are not thread safe except for Stop() which can be
// called from any thread to shutdown the agent.  Outstanding
// ContentAnalysisEvents created from this agent may or may not still complete.
//
// See the demo directory for an example of how to use this class.
class Agent {
 public:
  // Configuration options where creating an agent.  `name` is used to create
  // a channel between the agent and Google Chrome.
  struct Config {
    // Used to create a channel between the agent and Google Chrome.  Both must
    // use the same name to properly rendezvous with each other.  The channel
    // is platform specific.
    std::string name;

    // Set to true if there is a different agent instance per OS user.  Defaults
    // to false.
    bool user_specific = false;
  };

  // Creates a new agent instance.  If successful, an agent is returned.
  // Otherwise a nullptr is returned and `rc` contains the reason for the
  // failure.
  static std::unique_ptr<Agent> Create(
      Config config,
      std::unique_ptr<AgentEventHandler> handler,
      ResultCode* rc);

  virtual ~Agent() = default;

  // Returns the configuration parameters used to create the agent.
  virtual const Config& GetConfig() const = 0;

  // Handles events triggered on this agent and calls the coresponding
  // callbacks in the AgentEventHandler.  This method is blocking and returns
  // when Stop() is called or if an error occurs.
  virtual ResultCode HandleEvents() = 0;

  // Prepares the agent for graceful shutdown.  Any function blocked on
  // HandleEvents() will return.  It is safe to call this method from any
  // thread.
  virtual ResultCode Stop() = 0;

  // Returns a string containing internal state of the object that is useful
  // for debugging.
  virtual std::string DebugString() const = 0;

 protected:
  Agent() = default;
  Agent(const Agent& rhs) = delete;
  Agent(Agent&& rhs) = delete;
  Agent& operator=(const Agent& rhs) = delete;
  Agent& operator=(Agent&& rhs) = delete;
};

// Update the tag or status of `response`.  This function assumes that the
// response contains only one Result.  If one already exists it is updated
// otherwise a new Result is created.
//
// The response contained within ContentAnalysisEvent has already been updated.
// This function is useful only when create a new instance of
// ContentAnalysisResponse.
//
// If `tag` is not empty it will replace the result's tag.
// If `status` is not STATUS_UNKNOWN it will will replace the result's status.
ResultCode UpdateResponse(ContentAnalysisResponse& response,
                          const std::string& tag,
                          ContentAnalysisResponse::Result::Status status);

// Sets the response verdict of an event to `action`.  This is a convenience
// function that is equivalent to the following:
//
//   auto result = event->GetResponse().mutable_results(0);
//   auto rule = result->mutable_triggered_rules(0);
//   rule->set_action(action);
//
// This function assumes the event's response has already been initialized
// using UpdateResponse().
ResultCode SetEventVerdictTo(
    ContentAnalysisEvent* event,
    ContentAnalysisResponse::Result::TriggeredRule::Action action);

// Sets the reponse verdict of an event to "block".  This is a convenience
// function that is equivalent to the following:
//
//   SetEventVerdictTo(event,
//                     ContentAnalysisResponse::Result::TriggeredRule::BLOCK);
ResultCode SetEventVerdictToBlock(ContentAnalysisEvent* event);

// Helper class to handle the lifetime and access of print data.
class ScopedPrintHandle {
 public:
  virtual ~ScopedPrintHandle() = default;
  virtual const char* data() = 0;
  virtual size_t size() = 0;

 protected:
  ScopedPrintHandle() = default;

  ScopedPrintHandle(const ScopedPrintHandle&) = delete;
  ScopedPrintHandle& operator=(const ScopedPrintHandle&) = delete;

  ScopedPrintHandle(ScopedPrintHandle&&) = default;
  ScopedPrintHandle& operator=(ScopedPrintHandle&&) = default;
};

// Returns a `ScopedPrintHandle` initialized from the request's print data
// if it exists.
std::unique_ptr<ScopedPrintHandle>
CreateScopedPrintHandle(const ContentAnalysisRequest& request,
                        int64_t browser_pid);

}  // namespace sdk
}  // namespace content_analysis

#endif  // CONTENT_ANALYSIS_AGENT_INCLUDE_CONTENT_ANALYSIS_SDK_ANALYSIS_AGENT_H_
