summaryrefslogtreecommitdiffstats
path: root/third_party/content_analysis_sdk/agent/src
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/content_analysis_sdk/agent/src')
-rw-r--r--third_party/content_analysis_sdk/agent/src/agent_base.cc42
-rw-r--r--third_party/content_analysis_sdk/agent/src/agent_base.h40
-rw-r--r--third_party/content_analysis_sdk/agent/src/agent_mac.cc34
-rw-r--r--third_party/content_analysis_sdk/agent/src/agent_mac.h27
-rw-r--r--third_party/content_analysis_sdk/agent/src/agent_posix.cc36
-rw-r--r--third_party/content_analysis_sdk/agent/src/agent_posix.h27
-rw-r--r--third_party/content_analysis_sdk/agent/src/agent_utils_win.cc28
-rw-r--r--third_party/content_analysis_sdk/agent/src/agent_utils_win.h19
-rw-r--r--third_party/content_analysis_sdk/agent/src/agent_win.cc546
-rw-r--r--third_party/content_analysis_sdk/agent/src/agent_win.h196
-rw-r--r--third_party/content_analysis_sdk/agent/src/agent_win_unittest.cc522
-rw-r--r--third_party/content_analysis_sdk/agent/src/event_base.cc65
-rw-r--r--third_party/content_analysis_sdk/agent/src/event_base.h38
-rw-r--r--third_party/content_analysis_sdk/agent/src/event_mac.cc29
-rw-r--r--third_party/content_analysis_sdk/agent/src/event_mac.h29
-rw-r--r--third_party/content_analysis_sdk/agent/src/event_mac_unittest.cc53
-rw-r--r--third_party/content_analysis_sdk/agent/src/event_posix.cc28
-rw-r--r--third_party/content_analysis_sdk/agent/src/event_posix.h29
-rw-r--r--third_party/content_analysis_sdk/agent/src/event_posix_unittest.cc53
-rw-r--r--third_party/content_analysis_sdk/agent/src/event_win.cc133
-rw-r--r--third_party/content_analysis_sdk/agent/src/event_win.h51
-rw-r--r--third_party/content_analysis_sdk/agent/src/event_win_unittest.cc116
-rw-r--r--third_party/content_analysis_sdk/agent/src/scoped_print_handle_base.cc17
-rw-r--r--third_party/content_analysis_sdk/agent/src/scoped_print_handle_base.h25
-rw-r--r--third_party/content_analysis_sdk/agent/src/scoped_print_handle_mac.cc36
-rw-r--r--third_party/content_analysis_sdk/agent/src/scoped_print_handle_mac.h24
-rw-r--r--third_party/content_analysis_sdk/agent/src/scoped_print_handle_posix.cc36
-rw-r--r--third_party/content_analysis_sdk/agent/src/scoped_print_handle_posix.h24
-rw-r--r--third_party/content_analysis_sdk/agent/src/scoped_print_handle_win.cc67
-rw-r--r--third_party/content_analysis_sdk/agent/src/scoped_print_handle_win.h29
30 files changed, 2399 insertions, 0 deletions
diff --git a/third_party/content_analysis_sdk/agent/src/agent_base.cc b/third_party/content_analysis_sdk/agent/src/agent_base.cc
new file mode 100644
index 0000000000..c584dac5b6
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/agent_base.cc
@@ -0,0 +1,42 @@
+// 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.
+
+#include <utility>
+
+#include "agent_base.h"
+
+namespace content_analysis {
+namespace sdk {
+
+AgentBase::AgentBase(Config config, std::unique_ptr<AgentEventHandler> handler)
+ : config_(std::move(config)), handler_(std::move(handler)) {}
+
+const Agent::Config& AgentBase::GetConfig() const {
+ return config_;
+}
+
+ResultCode AgentBase::Stop() {
+ return ResultCode::OK;
+}
+
+ResultCode AgentBase::NotifyError(const char* context, ResultCode error) {
+ if (handler_) {
+ handler_->OnInternalError(context, error);
+ }
+ return error;
+}
+
+#define RC_RECOVERABLE(RC, MSG) case ResultCode::RC: return MSG;
+#define RC_UNRECOVERABLE(RC, MSG) case ResultCode::RC: return MSG;
+const char* ResultCodeToString(ResultCode rc) {
+ switch (rc) {
+#include "content_analysis/sdk/result_codes.inc"
+ }
+ return "Unknown error code.";
+}
+#undef RC_RECOVERABLE
+#undef RC_UNRECOVERABLE
+
+} // namespace sdk
+} // namespace content_analysis
diff --git a/third_party/content_analysis_sdk/agent/src/agent_base.h b/third_party/content_analysis_sdk/agent/src/agent_base.h
new file mode 100644
index 0000000000..8ce7df29ef
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/agent_base.h
@@ -0,0 +1,40 @@
+// 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_SRC_AGENT_BASE_H_
+#define CONTENT_ANALYSIS_AGENT_SRC_AGENT_BASE_H_
+
+#include <memory>
+
+#include "content_analysis/sdk/analysis_agent.h"
+
+namespace content_analysis {
+namespace sdk {
+
+// Base Agent class with code common to all platforms.
+class AgentBase : public Agent {
+ public:
+ // Agent:
+ const Config& GetConfig() const override;
+ ResultCode Stop() override;
+
+ protected:
+ AgentBase(Config config, std::unique_ptr<AgentEventHandler> handler);
+
+ AgentEventHandler* handler() const { return handler_.get(); }
+ const Config& configuration() const { return config_; }
+
+ // Notifies the handler of the given error. Returns the error
+ // passed into the method.
+ ResultCode NotifyError(const char* context, ResultCode error);
+
+ private:
+ Config config_;
+ std::unique_ptr<AgentEventHandler> handler_;
+};
+
+} // namespace sdk
+} // namespace content_analysis
+
+#endif // CONTENT_ANALYSIS_AGENT_SRC_AGENT_BASE_H_ \ No newline at end of file
diff --git a/third_party/content_analysis_sdk/agent/src/agent_mac.cc b/third_party/content_analysis_sdk/agent/src/agent_mac.cc
new file mode 100644
index 0000000000..5bbac5fe49
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/agent_mac.cc
@@ -0,0 +1,34 @@
+// 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.
+
+#include "agent_mac.h"
+#include "event_mac.h"
+
+namespace content_analysis {
+namespace sdk {
+
+// static
+std::unique_ptr<Agent> Agent::Create(
+ Config config,
+ std::unique_ptr<AgentEventHandler> handler,
+ ResultCode* rc) {
+ *rc = ResultCode::ERR_UNEXPECTED;
+ return std::make_unique<AgentMac>(std::move(config), std::move(handler));
+}
+
+AgentMac::AgentMac(
+ Config config,
+ std::unique_ptr<AgentEventHandler> handler)
+ : AgentBase(std::move(config), std::move(handler)) {}
+
+ResultCode AgentMac::HandleEvents() {
+ return ResultCode::ERR_UNEXPECTED;
+}
+
+std::string AgentMac::DebugString() const {
+ return std::string();
+}
+
+} // namespace sdk
+} // namespace content_analysis
diff --git a/third_party/content_analysis_sdk/agent/src/agent_mac.h b/third_party/content_analysis_sdk/agent/src/agent_mac.h
new file mode 100644
index 0000000000..6fd3d49f7b
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/agent_mac.h
@@ -0,0 +1,27 @@
+// 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_SRC_AGENT_MAC_H_
+#define CONTENT_ANALYSIS_SRC_AGENT_MAC_H_
+
+#include "agent_base.h"
+
+namespace content_analysis {
+namespace sdk {
+
+// Agent implementaton for macOS.
+class AgentMac : public AgentBase {
+ public:
+ AgentMac(Config config, std::unique_ptr<AgentEventHandler> handler);
+
+ ResultCode HandleEvents() override;
+ std::string DebugString() const override;
+
+ // TODO(rogerta): Fill in implementation.
+};
+
+} // namespace sdk
+} // namespace content_analysis
+
+#endif // CONTENT_ANALYSIS_SRC_AGENT_MAC_H_
diff --git a/third_party/content_analysis_sdk/agent/src/agent_posix.cc b/third_party/content_analysis_sdk/agent/src/agent_posix.cc
new file mode 100644
index 0000000000..c206261213
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/agent_posix.cc
@@ -0,0 +1,36 @@
+// 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.
+
+#include <utility>
+
+#include "agent_posix.h"
+#include "event_posix.h"
+
+namespace content_analysis {
+namespace sdk {
+
+// static
+std::unique_ptr<Agent> Agent::Create(
+ Config config,
+ std::unique_ptr<AgentEventHandler> handler,
+ ResultCode* rc) {
+ *rc = ResultCode::ERR_UNEXPECTED;
+ return std::make_unique<AgentPosix>(std::move(config), std::move(handler));
+}
+
+AgentPosix::AgentPosix(
+ Config config,
+ std::unique_ptr<AgentEventHandler> handler)
+ : AgentBase(std::move(config), std::move(handler)) {}
+
+ResultCode AgentPosix::HandleEvents() {
+ return ResultCode::ERR_UNEXPECTED;
+}
+
+std::string AgentPosix::DebugString() const {
+ return std::string();
+}
+
+} // namespace sdk
+} // namespace content_analysis
diff --git a/third_party/content_analysis_sdk/agent/src/agent_posix.h b/third_party/content_analysis_sdk/agent/src/agent_posix.h
new file mode 100644
index 0000000000..cd9e67ad25
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/agent_posix.h
@@ -0,0 +1,27 @@
+// 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_SRC_AGENT_POSIX_H_
+#define CONTENT_ANALYSIS_SRC_AGENT_POSIX_H_
+
+#include "agent_base.h"
+
+namespace content_analysis {
+namespace sdk {
+
+// Agent implementaton for linux.
+class AgentPosix : public AgentBase {
+ public:
+ AgentPosix(Config config, std::unique_ptr<AgentEventHandler> handler);
+
+ ResultCode HandleEvents() override;
+ std::string DebugString() const override;
+
+ // TODO(rogerta): Fill in implementation.
+};
+
+} // namespace sdk
+} // namespace content_analysis
+
+#endif // CONTENT_ANALYSIS_SRC_AGENT_POSIX_H_ \ No newline at end of file
diff --git a/third_party/content_analysis_sdk/agent/src/agent_utils_win.cc b/third_party/content_analysis_sdk/agent/src/agent_utils_win.cc
new file mode 100644
index 0000000000..5a5f411d84
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/agent_utils_win.cc
@@ -0,0 +1,28 @@
+// 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.
+
+#include <windows.h>
+
+#include "content_analysis/sdk/result_codes.h"
+
+namespace content_analysis {
+namespace sdk {
+
+#define ERR_TO_RC(ERR, RC) case ERR: return ResultCode::RC;
+
+ResultCode ErrorToResultCode(DWORD err) {
+ switch (err) {
+ ERR_TO_RC(ERROR_SUCCESS, OK);
+ ERR_TO_RC(ERROR_ACCESS_DENIED, ERR_AGENT_ALREADY_EXISTS);
+ ERR_TO_RC(ERROR_BROKEN_PIPE, ERR_BROKEN_PIPE);
+ ERR_TO_RC(ERROR_INVALID_NAME, ERR_INVALID_CHANNEL_NAME);
+ ERR_TO_RC(ERROR_MORE_DATA, ERR_MORE_DATA);
+ ERR_TO_RC(ERROR_IO_PENDING, ERR_IO_PENDING);
+ default:
+ return ResultCode::ERR_UNEXPECTED;
+ }
+}
+
+} // namespace sdk
+} // namespace content_analysis
diff --git a/third_party/content_analysis_sdk/agent/src/agent_utils_win.h b/third_party/content_analysis_sdk/agent/src/agent_utils_win.h
new file mode 100644
index 0000000000..7368ac794e
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/agent_utils_win.h
@@ -0,0 +1,19 @@
+// 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_SRC_EVENT_MAC_H_
+#define CONTENT_ANALYSIS_SRC_EVENT_MAC_H_
+
+#include "event_base.h"
+
+namespace content_analysis {
+namespace sdk {
+
+// Maps a Windows error status code (ERROR_xxx codes) to an SDK result code.
+ResultCode ErrorToResultCode(DWORD err);
+
+} // namespace sdk
+} // namespace content_analysis
+
+#endif // CONTENT_ANALYSIS_SRC_EVENT_MAC_H_
diff --git a/third_party/content_analysis_sdk/agent/src/agent_win.cc b/third_party/content_analysis_sdk/agent/src/agent_win.cc
new file mode 100644
index 0000000000..5d0b2c62b5
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/agent_win.cc
@@ -0,0 +1,546 @@
+// 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.
+
+#include <sstream>
+#include <utility>
+#include <vector>
+
+#include <windows.h>
+#include <sddl.h>
+
+#include "common/utils_win.h"
+
+#include "agent_utils_win.h"
+#include "agent_win.h"
+#include "event_win.h"
+
+namespace content_analysis {
+namespace sdk {
+
+// The minimum number of pipe in listening mode. This is greater than one to
+// handle the case of multiple instance of Google Chrome browser starting
+// at the same time.
+const DWORD kMinNumListeningPipeInstances = 2;
+
+// The minimum number of handles to wait on. This is the minimum number
+// of pipes in listening mode plus the stop event.
+const DWORD kMinNumWaitHandles = kMinNumListeningPipeInstances + 1;
+
+// static
+std::unique_ptr<Agent> Agent::Create(
+ Config config,
+ std::unique_ptr<AgentEventHandler> handler,
+ ResultCode* rc) {
+ auto agent = std::make_unique<AgentWin>(std::move(config), std::move(handler), rc);
+ return *rc == ResultCode::OK ? std::move(agent) : nullptr;
+}
+
+AgentWin::Connection::Connection(const std::string& pipename,
+ bool user_specific,
+ AgentEventHandler* handler,
+ bool is_first_pipe,
+ ResultCode* rc)
+ : handler_(handler) {
+ *rc = ResultCode::OK;
+ memset(&overlapped_, 0, sizeof(overlapped_));
+ // Create a manual reset event as specified for overlapped IO.
+ // Use default security attriutes and no name since this event is not
+ // shared with other processes.
+ overlapped_.hEvent = CreateEvent(/*securityAttr=*/nullptr,
+ /*manualReset=*/TRUE,
+ /*initialState=*/FALSE,
+ /*name=*/nullptr);
+ if (!overlapped_.hEvent) {
+ *rc = ResultCode::ERR_CANNOT_CREATE_CHANNEL_IO_EVENT;
+ return;
+ }
+
+ *rc = ResetInternal(pipename, user_specific, is_first_pipe);
+}
+
+AgentWin::Connection::~Connection() {
+ Cleanup();
+
+ if (handle_ != INVALID_HANDLE_VALUE) {
+ CloseHandle(handle_);
+ }
+
+ // Invalid event handles are represented as null.
+ if (overlapped_.hEvent) {
+ CloseHandle(overlapped_.hEvent);
+ }
+}
+
+ResultCode AgentWin::Connection::Reset(
+ const std::string& pipename,
+ bool user_specific) {
+ return NotifyIfError("ConnectionReset",
+ ResetInternal(pipename, user_specific, false));
+}
+
+ResultCode AgentWin::Connection::HandleEvent(HANDLE handle) {
+ auto rc = ResultCode::OK;
+ DWORD count;
+ BOOL success = GetOverlappedResult(handle, &overlapped_, &count,
+ /*wait=*/FALSE);
+ if (!is_connected_) {
+ // This connection is currently listing for a new connection from a Google
+ // Chrome browser. If the result is a success, this means the browser has
+ // connected as expected. Otherwise an error occured so report it to the
+ // caller.
+ if (success) {
+ // A Google Chrome browser connected to the agent. Reset this
+ // connection object to handle communication with the browser and then
+ // tell the handler about it.
+
+ is_connected_ = true;
+ buffer_.resize(internal::kBufferSize);
+
+ rc = BuildBrowserInfo();
+ if (rc == ResultCode::OK) {
+ handler_->OnBrowserConnected(browser_info_);
+ }
+ } else {
+ rc = ErrorToResultCode(GetLastError());
+ NotifyIfError("GetOverlappedResult", rc);
+ }
+ } else {
+ // Some data has arrived from Google Chrome. This data is (part of) an
+ // instance of the proto message `ChromeToAgent`.
+ //
+ // If the message is small it is received in by one call to ReadFile().
+ // If the message is larger it is received in by multiple calls to
+ // ReadFile().
+ //
+ // `success` is true if the data just read is the last bytes for a message.
+ // Otherwise it is false.
+ rc = OnReadFile(success, count);
+ }
+
+ // If all data has been read, queue another read.
+ if (rc == ResultCode::OK || rc == ResultCode::ERR_MORE_DATA) {
+ rc = QueueReadFile(rc == ResultCode::OK);
+ }
+
+ if (rc != ResultCode::OK && rc != ResultCode::ERR_IO_PENDING &&
+ rc != ResultCode::ERR_MORE_DATA) {
+ Cleanup();
+ } else {
+ // Don't propagate all the "success" error codes to the called to keep
+ // this simpler.
+ rc = ResultCode::OK;
+ }
+
+ return rc;
+}
+
+void AgentWin::Connection::AppendDebugString(std::stringstream& state) const {
+ state << "{handle=" << handle_;
+ state << " connected=" << is_connected_;
+ state << " pid=" << browser_info_.pid;
+ state << " rsize=" << read_size_;
+ state << " fsize=" << final_size_;
+ state << "}";
+}
+
+ResultCode AgentWin::Connection::ConnectPipe() {
+ // In overlapped mode, connecting to a named pipe always returns false.
+ if (ConnectNamedPipe(handle_, &overlapped_)) {
+ return ErrorToResultCode(GetLastError());
+ }
+
+ DWORD err = GetLastError();
+ if (err == ERROR_IO_PENDING) {
+ // Waiting for a Google Chrome Browser to connect.
+ return ResultCode::OK;
+ } else if (err == ERROR_PIPE_CONNECTED) {
+ // A Google Chrome browser is already connected. Make sure event is in
+ // signaled state in order to process the connection.
+ if (SetEvent(overlapped_.hEvent)) {
+ err = ERROR_SUCCESS;
+ } else {
+ err = GetLastError();
+ }
+ }
+
+ return ErrorToResultCode(err);
+}
+
+ResultCode AgentWin::Connection::ResetInternal(const std::string& pipename,
+ bool user_specific,
+ bool is_first_pipe) {
+ auto rc = ResultCode::OK;
+
+ // If this is the not the first time, disconnect from any existing Google
+ // Chrome browser. Otherwise creater a new pipe.
+ if (handle_ != INVALID_HANDLE_VALUE) {
+ if (!DisconnectNamedPipe(handle_)) {
+ rc = ErrorToResultCode(GetLastError());
+ }
+ } else {
+ rc = ErrorToResultCode(
+ internal::CreatePipe(pipename, user_specific, is_first_pipe, &handle_));
+ }
+
+ // Make sure event starts in reset state.
+ if (rc == ResultCode::OK && !ResetEvent(overlapped_.hEvent)) {
+ rc = ErrorToResultCode(GetLastError());
+ }
+
+ if (rc == ResultCode::OK) {
+ rc = ConnectPipe();
+ }
+
+ if (rc != ResultCode::OK) {
+ Cleanup();
+ handle_ = INVALID_HANDLE_VALUE;
+ }
+
+ return rc;
+}
+
+void AgentWin::Connection::Cleanup() {
+ if (is_connected_ && handler_) {
+ handler_->OnBrowserDisconnected(browser_info_);
+ }
+
+ is_connected_ = false;
+ browser_info_ = BrowserInfo();
+ buffer_.clear();
+ cursor_ = nullptr;
+ read_size_ = 0;
+ final_size_ = 0;
+
+ if (handle_ != INVALID_HANDLE_VALUE) {
+ // Cancel all outstanding IO requests on this pipe by using a null for
+ // overlapped.
+ CancelIoEx(handle_, /*overlapped=*/nullptr);
+ }
+
+ // This function does not close `handle_` or the event in `overlapped` so
+ // that the server can resuse the pipe with an new Google Chrome browser
+ // instance.
+}
+
+ResultCode AgentWin::Connection::QueueReadFile(bool reset_cursor) {
+ if (reset_cursor) {
+ cursor_ = buffer_.data();
+ read_size_ = buffer_.size();
+ final_size_ = 0;
+ }
+
+ // When this function is called there are the following possiblities:
+ //
+ // 1/ Data is already available and the buffer is filled in. ReadFile()
+ // return TRUE and the event is set.
+ // 2/ Data is not avaiable yet. ReadFile() returns FALSE and the last error
+ // is ERROR_IO_PENDING(997) and the event is reset.
+ // 3/ Some error occurred, like for example Google Chrome stops. ReadFile()
+ // returns FALSE and the last error is something other than
+ // ERROR_IO_PENDING, for example ERROR_BROKEN_PIPE(109). The event
+ // state is unchanged.
+ auto rc = ResultCode::OK;
+ DWORD count;
+ if (!ReadFile(handle_, cursor_, read_size_, &count, &overlapped_)) {
+ DWORD err = GetLastError();
+ rc = ErrorToResultCode(err);
+
+ // IO pending is not an error so don't notify.
+ //
+ // Ignore broken pipes for notifications since that happens when the Google
+ // Chrome browser shuts down. The agent will be notified of a browser
+ // disconnect in that case.
+ //
+ // More data means that `buffer_` was too small to read the entire message
+ // from the browser. The buffer has already been resized. Another call to
+ // ReadFile() is needed to get the remainder.
+ if (rc != ResultCode::ERR_IO_PENDING &&
+ rc != ResultCode::ERR_BROKEN_PIPE &&
+ rc != ResultCode::ERR_MORE_DATA) {
+ NotifyIfError("QueueReadFile", rc, err);
+ }
+ }
+
+ return rc;
+}
+
+ResultCode AgentWin::Connection::OnReadFile(BOOL done_reading, DWORD count) {
+ final_size_ += count;
+
+ // If `done_reading` is TRUE, this means the full message has been read.
+ // Call the appropriate handler method.
+ if (done_reading) {
+ return CallHandler();
+ }
+
+ // Otherwise there are two possibilities:
+ //
+ // 1/ The last error is ERROR_MORE_DATA(234). This means there are more
+ // bytes to read before the request message is complete. Resize the
+ // buffer and adjust the cursor. The caller will queue up another read
+ // and wait. don't notify the handler since this is not an error.
+ // 2/ Some error occured. In this case notify the handler and return the
+ // error.
+
+ DWORD err = GetLastError();
+ if (err == ERROR_MORE_DATA) {
+ read_size_ = internal::kBufferSize;
+ buffer_.resize(buffer_.size() + read_size_);
+ cursor_ = buffer_.data() + buffer_.size() - read_size_;
+ return ErrorToResultCode(err);
+ }
+
+ return NotifyIfError("OnReadFile", ErrorToResultCode(err));
+}
+
+ResultCode AgentWin::Connection::CallHandler() {
+ ChromeToAgent message;
+ if (!message.ParseFromArray(buffer_.data(), final_size_)) {
+ // Malformed message.
+ return NotifyIfError("ParseChromeToAgent",
+ ResultCode::ERR_INVALID_REQUEST_FROM_BROWSER);
+ }
+
+ auto rc = ResultCode::OK;
+
+ if (message.has_request()) {
+ // This is a request from Google Chrome to perform a content analysis
+ // request.
+ //
+ // Move the request from `message` to the event to reduce the amount
+ // of memory allocation/copying and also because the the handler takes
+ // ownership of the event.
+ auto event = std::make_unique<ContentAnalysisEventWin>(
+ handle_, browser_info_, std::move(*message.mutable_request()));
+ rc = event->Init();
+ if (rc == ResultCode::OK) {
+ handler_->OnAnalysisRequested(std::move(event));
+ } else {
+ NotifyIfError("RequestValidation", rc);
+ }
+ } else if (message.has_ack()) {
+ // This is an ack from Google Chrome that it has received a content
+ // analysis response from the agent.
+ handler_->OnResponseAcknowledged(message.ack());
+ } else if (message.has_cancel()) {
+ // Google Chrome is informing the agent that the content analysis
+ // request(s) associated with the given user action id have been
+ // canceled by the user.
+ handler_->OnCancelRequests(message.cancel());
+ } else {
+ // Malformed message.
+ rc = NotifyIfError("NoRequestOrAck",
+ ResultCode::ERR_INVALID_REQUEST_FROM_BROWSER);
+ }
+
+ return rc;
+}
+
+ResultCode AgentWin::Connection::BuildBrowserInfo() {
+ if (!GetNamedPipeClientProcessId(handle_, &browser_info_.pid)) {
+ return NotifyIfError("BuildBrowserInfo",
+ ResultCode::ERR_CANNOT_GET_BROWSER_PID);
+ }
+
+ if (!internal::GetProcessPath(browser_info_.pid,
+ &browser_info_.binary_path)) {
+ return NotifyIfError("BuildBrowserInfo",
+ ResultCode::ERR_CANNOT_GET_BROWSER_BINARY_PATH);
+ }
+
+ return ResultCode::OK;
+}
+
+ResultCode AgentWin::Connection::NotifyIfError(
+ const char* context,
+ ResultCode rc,
+ DWORD err) {
+ if (handler_ && rc != ResultCode::OK) {
+ std::stringstream stm;
+ stm << context << " pid=" << browser_info_.pid;
+ if (err != ERROR_SUCCESS) {
+ stm << context << " err=" << err;
+ }
+
+ handler_->OnInternalError(stm.str().c_str(), rc);
+ }
+ return rc;
+}
+
+AgentWin::AgentWin(
+ Config config,
+ std::unique_ptr<AgentEventHandler> event_handler,
+ ResultCode* rc)
+ : AgentBase(std::move(config), std::move(event_handler)) {
+ *rc = ResultCode::OK;
+ if (handler() == nullptr) {
+ *rc = ResultCode::ERR_AGENT_EVENT_HANDLER_NOT_SPECIFIED;
+ return;
+ }
+
+ stop_event_ = CreateEvent(/*securityAttr=*/nullptr,
+ /*manualReset=*/TRUE,
+ /*initialState=*/FALSE,
+ /*name=*/nullptr);
+ if (stop_event_ == nullptr) {
+ *rc = ResultCode::ERR_CANNOT_CREATE_AGENT_STOP_EVENT;
+ return;
+ }
+
+ std::string pipename =
+ internal::GetPipeNameForAgent(configuration().name,
+ configuration().user_specific);
+ if (pipename.empty()) {
+ *rc = ResultCode::ERR_INVALID_CHANNEL_NAME;
+ return;
+ }
+
+ pipename_ = pipename;
+
+ connections_.reserve(kMinNumListeningPipeInstances);
+ for (DWORD i = 0; i < kMinNumListeningPipeInstances; ++i) {
+ connections_.emplace_back(
+ std::make_unique<Connection>(pipename_, configuration().user_specific,
+ handler(), i == 0, rc));
+ if (*rc != ResultCode::OK || !connections_.back()->IsValid()) {
+ Shutdown();
+ break;
+ }
+ }
+}
+
+AgentWin::~AgentWin() {
+ Shutdown();
+}
+
+ResultCode AgentWin::HandleEvents() {
+ std::vector<HANDLE> wait_handles;
+ auto rc = ResultCode::OK;
+ bool stopped = false;
+ while (!stopped && rc == ResultCode::OK) {
+ rc = HandleOneEvent(wait_handles, &stopped);
+ }
+
+ return rc;
+}
+
+ResultCode AgentWin::Stop() {
+ SetEvent(stop_event_);
+ return AgentBase::Stop();
+}
+
+std::string AgentWin::DebugString() const {
+ std::stringstream state;
+ state.setf(std::ios::boolalpha);
+ state << "AgentWin{pipe=\"" << pipename_;
+ state << "\" stop=" << stop_event_;
+
+ for (size_t i = 0; i < connections_.size(); ++i) {
+ state << " conn@" << i;
+ connections_[i]->AppendDebugString(state);
+ }
+
+ state << "}" << std::ends;
+ return state.str();
+}
+
+void AgentWin::GetHandles(std::vector<HANDLE>& wait_handles) const {
+ // Reserve enough space in the handles vector to include the stop event plus
+ // all connections.
+ wait_handles.clear();
+ wait_handles.reserve(1 + connections_.size());
+
+ for (auto& state : connections_) {
+ HANDLE wait_handle = state->GetWaitHandle();
+ if (!wait_handle) {
+ wait_handles.clear();
+ break;
+ }
+ wait_handles.push_back(wait_handle);
+ }
+
+ // Push the stop event last so that connections_ index calculations in
+ // HandleOneEvent() don't have to account for this handle.
+ wait_handles.push_back(stop_event_);
+}
+
+ResultCode AgentWin::HandleOneEventForTesting() {
+ std::vector<HANDLE> wait_handles;
+ bool stopped;
+ return HandleOneEvent(wait_handles, &stopped);
+}
+
+bool AgentWin::IsAClientConnectedForTesting() {
+ for (const auto& state : connections_) {
+ if (state->IsConnected()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+ResultCode AgentWin::HandleOneEvent(
+ std::vector<HANDLE>& wait_handles,
+ bool* stopped) {
+ *stopped = false;
+
+ // Wait on the specified handles for an event to occur.
+ GetHandles(wait_handles);
+ if (wait_handles.size() < kMinNumWaitHandles) {
+ return NotifyError("GetHandles", ResultCode::ERR_AGENT_NOT_INITIALIZED);
+ }
+
+ DWORD index = WaitForMultipleObjects(
+ wait_handles.size(), wait_handles.data(),
+ /*waitAll=*/FALSE, /*timeoutMs=*/INFINITE);
+ if (index == WAIT_FAILED) {
+ return NotifyError("WaitForMultipleObjects",
+ ErrorToResultCode(GetLastError()));
+ }
+
+ // If the index of signaled handle is the last one in wait_handles, then the
+ // stop event was signaled.
+ index -= WAIT_OBJECT_0;
+ if (index == wait_handles.size() - 1) {
+ *stopped = true;
+ return ResultCode::OK;
+ }
+
+ auto& connection = connections_[index];
+ bool was_listening = !connection->IsConnected();
+ auto rc = connection->HandleEvent(wait_handles[index]);
+ if (rc != ResultCode::OK) {
+ // If `connection` was not listening and there are more than
+ // kMinNumListeningPipeInstances pipes, delete this connection. Otherwise
+ // reset it so that it becomes a listener.
+ if (!was_listening &&
+ connections_.size() > kMinNumListeningPipeInstances) {
+ connections_.erase(connections_.begin() + index);
+ } else {
+ rc = connection->Reset(pipename_, configuration().user_specific);
+ }
+ }
+
+ // If `connection` was listening and is now connected, create a new
+ // one so that there are always kMinNumListeningPipeInstances listening.
+ if (rc == ResultCode::OK && was_listening && connection->IsConnected()) {
+ connections_.emplace_back(
+ std::make_unique<Connection>(pipename_, configuration().user_specific,
+ handler(), false, &rc));
+ }
+
+ return ResultCode::OK;
+}
+
+void AgentWin::Shutdown() {
+ connections_.clear();
+ pipename_.clear();
+ if (stop_event_ != nullptr) {
+ CloseHandle(stop_event_);
+ stop_event_ = nullptr;
+ }
+}
+
+} // namespace sdk
+} // namespace content_analysis
diff --git a/third_party/content_analysis_sdk/agent/src/agent_win.h b/third_party/content_analysis_sdk/agent/src/agent_win.h
new file mode 100644
index 0000000000..a6a0c73311
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/agent_win.h
@@ -0,0 +1,196 @@
+// 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_SRC_AGENT_WIN_H_
+#define CONTENT_ANALYSIS_AGENT_SRC_AGENT_WIN_H_
+
+#include <windows.h>
+
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "agent_base.h"
+
+namespace content_analysis {
+namespace sdk {
+
+// Agent implementaton for Windows.
+class AgentWin : public AgentBase {
+ public:
+ // Creates a new agent given the specific configuration and handler.
+ // If an error occurs during creation, `rc` is set to the specific
+ // error. Otherwise `rc` is ResultCode::OK.
+ AgentWin(Config config, std::unique_ptr<AgentEventHandler> handler,
+ ResultCode* rc);
+ ~AgentWin() override;
+
+ // Agent:
+ ResultCode HandleEvents() override;
+ ResultCode Stop() override;
+ std::string DebugString() const override;
+
+ // Handles one pipe event and returns. Used only in tests.
+ ResultCode HandleOneEventForTesting();
+
+ // Returns true if there is at least one client connected to this agent.
+ bool IsAClientConnectedForTesting();
+
+private:
+ // Represents one connection to a Google Chrome browser, or one pipe
+ // listening for a Google Chrome browser to connect.
+ class Connection {
+ public:
+ // Starts listening on a pipe with the given name. `is_first_pipe` should
+ // be true only for the first pipe created by the agent. If an error
+ // occurs while creating the connction object it is returned in `rc`.
+ // If no errors occur then rc is set to ResultCode::OK.
+ //
+ // When `user_specific` is true there is a different agent instance per OS
+ // user.
+ //
+ // `Connection` objects cannot be copied or moved because the OVERLAPPED
+ // structure cannot be changed or moved in memory while an I/O operation
+ // is in progress.
+ Connection(const std::string& pipename,
+ bool user_specific,
+ AgentEventHandler* handler,
+ bool is_first_pipe,
+ ResultCode* rc);
+ Connection(const Connection& other) = delete;
+ Connection(Connection&& other) = delete;
+ Connection& operator=(const Connection& other) = delete;
+ Connection& operator=(Connection&& other) = delete;
+ ~Connection();
+
+ bool IsValid() const { return handle_ != INVALID_HANDLE_VALUE; }
+ bool IsConnected() const { return is_connected_; }
+ HANDLE GetWaitHandle() const { return overlapped_.hEvent; }
+
+ // Resets this connection object to listen for a new Google Chrome browser.
+ // When `user_specific` is true there is a different agent instance per OS
+ // user.
+ ResultCode Reset(const std::string& pipename, bool user_specific);
+
+ // Hnadles an event for this connection. `wait_handle` corresponds to
+ // this connections wait handle.
+ ResultCode HandleEvent(HANDLE wait_handle);
+
+ // Append debuf information to the string stream.
+ void AppendDebugString(std::stringstream& state) const;
+
+ private:
+ // Listens for a new connection from Google Chrome.
+ ResultCode ConnectPipe();
+
+ // Resets this connection object to listen for a new Google Chrome browser.
+ // When `user_specific` is true there is a different agent instance per OS
+ // user.
+ ResultCode ResetInternal(const std::string& pipename,
+ bool user_specific,
+ bool is_first_pipe);
+
+ // Cleans up this connection object so that it can be reused with a new
+ // Google Chroem browser instance. The handles assocated with this object
+ // are not closed. On return, this object is neither connected nor
+ // listening and any buffer used to hold browser messages are cleared.
+ void Cleanup();
+
+ // Queues a read on the pipe to receive a message from Google Chrome.
+ // ERROR_SUCCESS, ERROR_IO_PENDING, and ERROR_MORE_DATA are successful
+ // return values. Other values represent an error with the connection.
+ // If `reset_cursor` is true the internal read buffer cursor is reset to
+ // the start of the buffer, otherwise it is unchanged.
+ ResultCode QueueReadFile(bool reset_cursor);
+
+ // Called when data from Google Chrome is available for reading from the
+ // pipe. ERROR_SUCCESS and ERROR_MORE_DATA are both successful return
+ // values. Other values represent an error with the connection.
+ //
+ // `done_reading` is true if the code has finished reading an entire message
+ // from chrome. Regardless of whether reading is done, `count` contains
+ // the number of bytes read.
+ //
+ // If `done_reading` is true, the data received from the browser is parsed
+ // as if it were a `ChromeToAgent` proto message and the handler is called
+ // as needed.
+ //
+ // If `done_reading` is false, the data received from the browser is
+ // appended to the data already received from the browser. `buffer_` is
+ // resized to allow reading more data from the browser.
+ //
+ // In all cases the caller is expected to use QueueReadFile() to continue
+ // reading data from the browser.
+ ResultCode OnReadFile(BOOL done_reading, DWORD count);
+
+ // Calls the appropriate method the handler depending on the message
+ // received from Google Chrome.
+ ResultCode CallHandler();
+
+ // Fills in the browser_info_ member of this Connection. Assumes
+ // IsConnected() is true.
+ ResultCode BuildBrowserInfo();
+
+ // Notifies the handler of the given error iff `rc` is not equal to
+ // ResultCode::OK. Appends the Google Chrome browser process id to the
+ // context before calling the handler. Also append `err` to the context
+ // if it is not ERROR_SUCCESS.
+ //
+ // Returns the error passed into the method.
+ ResultCode NotifyIfError(const char* context,
+ ResultCode rc,
+ DWORD err=ERROR_SUCCESS);
+
+ // The handler to call for various agent events.
+ AgentEventHandler* handler_ = nullptr;
+
+ // Members used to communicate with Google Chrome.
+ HANDLE handle_ = INVALID_HANDLE_VALUE;
+ OVERLAPPED overlapped_;
+
+ // True if this connection is assigned to a specific Google Chrome browser,
+ // otherwise this connection is listening for a new browser.
+ bool is_connected_ = false;
+
+ // Information about the Google Chrome browser process.
+ BrowserInfo browser_info_;
+
+ // Members used to read messages from Google Chrome.
+ std::vector<char> buffer_;
+ char* cursor_ = nullptr;
+ DWORD read_size_ = 0;
+ DWORD final_size_ = 0;
+ };
+
+ // Returns handles that can be used to wait for events from all handles
+ // managed by this agent. This includes all connection objects and the
+ // stop event. The stop event is always last in the list.
+ void GetHandles(std::vector<HANDLE>& wait_handles) const;
+
+ // Handles one pipe event and returns. If the return value is
+ // ResultCode::OK, the `stopped` argument is set to true if the agent
+ // should stop handling more events. If the return value is not
+ // ResultCode::OK, `stopped` is undefined.
+ ResultCode HandleOneEvent(std::vector<HANDLE>& wait_handles, bool* stopped);
+
+ // Performs a clean shutdown of the agent.
+ void Shutdown();
+
+ // Name used to create the pipes between the agent and Google Chrome browsers.
+ std::string pipename_;
+
+ // A list of pipes to already connected Google Chrome browsers.
+ // The first kMinNumListeningPipeInstances pipes in the list correspond to
+ // listening pipes.
+ std::vector<std::unique_ptr<Connection>> connections_;
+
+ // An event that is set when the agent should stop. Set in Stop().
+ HANDLE stop_event_ = nullptr;
+};
+
+} // namespace sdk
+} // namespace content_analysis
+
+#endif // CONTENT_ANALYSIS_AGENT_SRC_AGENT_WIN_H_
diff --git a/third_party/content_analysis_sdk/agent/src/agent_win_unittest.cc b/third_party/content_analysis_sdk/agent/src/agent_win_unittest.cc
new file mode 100644
index 0000000000..c0bddf82f4
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/agent_win_unittest.cc
@@ -0,0 +1,522 @@
+// 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.
+
+#include <latch>
+#include <memory>
+#include <thread>
+
+#include "agent/src/agent_win.h"
+#include "agent/src/event_win.h"
+#include "browser/src/client_win.h"
+#include "gtest/gtest.h"
+
+namespace content_analysis {
+namespace sdk {
+namespace testing {
+
+// A handler that counts the number of times the callback methods are invoked.
+// Also remembers the last BrowserInfo structure passed to it from any of the
+// callbacks.
+struct TestHandler : public AgentEventHandler {
+ void OnBrowserConnected(const BrowserInfo& info) override {
+ last_info_ = info;
+ ++connect_count_;
+ }
+ void OnBrowserDisconnected(const BrowserInfo& info) override {
+ last_info_ = info;
+ ++disconnect_count_;
+ }
+ void OnAnalysisRequested(
+ std::unique_ptr<ContentAnalysisEvent> event) override {
+ ++request_count_;
+ ResultCode ret = event->Send();
+ ASSERT_EQ(ResultCode::OK, ret);
+ }
+ void OnResponseAcknowledged(
+ const ContentAnalysisAcknowledgement& ack) override {
+ ++ack_count_;
+ }
+ void OnCancelRequests(
+ const ContentAnalysisCancelRequests& cancel) override {
+ ++cancel_count_;
+ }
+
+ int connect_count_ = 0;
+ int disconnect_count_ = 0;
+ int request_count_ = 0;
+ int ack_count_ = 0;
+ int cancel_count_ = 0;
+ BrowserInfo last_info_;
+};
+
+// A test handler that closes its event before sending the response.
+struct CloseEventTestHandler : public TestHandler {
+ void OnAnalysisRequested(
+ std::unique_ptr<ContentAnalysisEvent> event) override {
+ ++request_count_;
+
+ // Closing the event before sending should generate an error.
+ ResultCode ret = event->Close();
+ ASSERT_EQ(ResultCode::OK, ret);
+
+ ret = event->Send();
+ ASSERT_NE(ResultCode::OK, ret);
+ }
+};
+
+// A test handler that attempts to send two responses for a given request.
+struct DoubleSendTestHandler : public TestHandler {
+ void OnAnalysisRequested(
+ std::unique_ptr<ContentAnalysisEvent> event) override {
+ ++request_count_;
+
+ ResultCode ret = event->Send();
+ ASSERT_EQ(ResultCode::OK, ret);
+
+ // Trying to send again fails.
+ ret = event->Send();
+ ASSERT_NE(ResultCode::OK, ret);
+ }
+};
+
+// A test handler that signals a latch after a client connects.
+// Can only be used with one client.
+struct SignalClientConnectedTestHandler : public TestHandler {
+ void OnBrowserConnected(const BrowserInfo& info) override {
+ TestHandler::OnBrowserConnected(info);
+ wait_for_client.count_down();
+ }
+
+ std::latch wait_for_client{ 1 };
+};
+
+// A test handler that signals a latch after a request is processed.
+// Can only be used with one request.
+struct SignalClientRequestedTestHandler : public TestHandler {
+ void OnAnalysisRequested(
+ std::unique_ptr<ContentAnalysisEvent> event) override {
+ TestHandler::OnAnalysisRequested(std::move(event));
+ wait_for_request.count_down();
+ }
+
+ std::latch wait_for_request{ 1 };
+};
+
+std::unique_ptr<AgentWin> CreateAgent(
+ Agent::Config config,
+ TestHandler** handler_ptr,
+ ResultCode expected_rc=ResultCode::OK) {
+ ResultCode rc;
+ auto handler = std::make_unique<TestHandler>();
+ *handler_ptr = handler.get();
+ auto agent = std::make_unique<AgentWin>(
+ std::move(config), std::move(handler), &rc);
+ EXPECT_EQ(expected_rc, rc);
+ return agent;
+}
+
+std::unique_ptr<ClientWin> CreateClient(
+ Client::Config config) {
+ int rc;
+ auto client = std::make_unique<ClientWin>(std::move(config), &rc);
+ return rc == 0 ? std::move(client) : nullptr;
+}
+
+ContentAnalysisRequest BuildRequest(std::string content=std::string()) {
+ ContentAnalysisRequest request;
+ request.set_request_token("req-token");
+ *request.add_tags() = "dlp";
+ request.set_text_content(content); // Moved.
+ return request;
+}
+
+TEST(AgentTest, Create) {
+ const Agent::Config config{"test", false};
+ TestHandler* handler_ptr;
+ auto agent = CreateAgent(config, &handler_ptr);
+ ASSERT_TRUE(agent);
+ ASSERT_TRUE(handler_ptr);
+
+ ASSERT_EQ(config.name, agent->GetConfig().name);
+ ASSERT_EQ(config.user_specific, agent->GetConfig().user_specific);
+}
+
+TEST(AgentTest, Create_InvalidPipename) {
+ // TODO(rogerta): The win32 docs say that a backslash is an invalid
+ // character for a pipename, but it seemed to work correctly in testing.
+ // Using an empty name was the easiest way to generate an invalid pipe
+ // name.
+ const Agent::Config config{"", false};
+ TestHandler* handler_ptr;
+ auto agent = CreateAgent(config, &handler_ptr,
+ ResultCode::ERR_INVALID_CHANNEL_NAME);
+ ASSERT_TRUE(agent);
+
+ ASSERT_EQ(ResultCode::ERR_AGENT_NOT_INITIALIZED,
+ agent->HandleOneEventForTesting());
+}
+
+// Can't create two agents with the same name.
+TEST(AgentTest, Create_SecondFails) {
+ const Agent::Config config{ "test", false };
+ TestHandler* handler_ptr1;
+ auto agent1 = CreateAgent(config, &handler_ptr1);
+ ASSERT_TRUE(agent1);
+
+ TestHandler* handler_ptr2;
+ auto agent2 = CreateAgent(config, &handler_ptr2,
+ ResultCode::ERR_AGENT_ALREADY_EXISTS);
+ ASSERT_TRUE(agent2);
+
+ ASSERT_EQ(ResultCode::ERR_AGENT_NOT_INITIALIZED,
+ agent2->HandleOneEventForTesting());
+}
+
+TEST(AgentTest, Stop) {
+ TestHandler* handler_ptr;
+ auto agent = CreateAgent({ "test", false }, &handler_ptr);
+ ASSERT_TRUE(agent);
+
+ // Create a separate thread to stop the agent.
+ std::thread thread([&agent]() {
+ agent->Stop();
+ });
+
+ agent->HandleEvents();
+ thread.join();
+}
+
+TEST(AgentTest, ConnectAndStop) {
+ ResultCode rc;
+ auto handler = std::make_unique<SignalClientConnectedTestHandler>();
+ auto* handler_ptr = handler.get();
+ auto agent = std::make_unique<AgentWin>(
+ Agent::Config{"test", false}, std::move(handler), &rc);
+ ASSERT_TRUE(agent);
+ ASSERT_EQ(ResultCode::OK, rc);
+
+ // Client thread waits until latch reaches zero.
+ std::latch stop_client{ 1 };
+
+ // Create a thread to handle the client. Since the client is sync, it can't
+ // run in the same thread as the agent.
+ std::thread client_thread([&stop_client]() {
+ auto client = CreateClient({ "test", false });
+ ASSERT_TRUE(client);
+ stop_client.wait();
+ });
+
+ // A thread that stops the agent after one client connects.
+ std::thread stop_agent([&handler_ptr, &agent]() {
+ handler_ptr->wait_for_client.wait();
+ agent->Stop();
+ });
+
+ agent->HandleEvents();
+
+ stop_client.count_down();
+ client_thread.join();
+ stop_agent.join();
+}
+
+TEST(AgentTest, Connect_UserSpecific) {
+ ResultCode rc;
+ auto handler = std::make_unique<SignalClientConnectedTestHandler>();
+ auto* handler_ptr = handler.get();
+ auto agent = std::make_unique<AgentWin>(
+ Agent::Config{ "test", true }, std::move(handler), &rc);
+ ASSERT_TRUE(agent);
+ ASSERT_EQ(ResultCode::OK, rc);
+
+ // Create a thread to handle the client. Since the client is sync, it can't
+ // run in the same thread as the agent.
+ std::thread client_thread([]() {
+ // If the user_specific does not match the agent, the client should not
+ // connect.
+ auto client = CreateClient({ "test", false });
+ ASSERT_FALSE(client);
+
+ auto client2 = CreateClient({ "test", true });
+ ASSERT_TRUE(client2);
+ });
+
+ // A thread that stops the agent after one client connects.
+ std::thread stop_agent([&handler_ptr, &agent]() {
+ handler_ptr->wait_for_client.wait();
+ agent->Stop();
+ });
+
+ agent->HandleEvents();
+
+ client_thread.join();
+ stop_agent.join();
+}
+
+TEST(AgentTest, ConnectRequestAndStop) {
+ ResultCode rc;
+ auto handler = std::make_unique<SignalClientRequestedTestHandler>();
+ auto* handler_ptr = handler.get();
+ auto agent = std::make_unique<AgentWin>(
+ Agent::Config{"test", false}, std::move(handler), &rc);
+ ASSERT_TRUE(agent);
+ ASSERT_EQ(ResultCode::OK, rc);
+
+ // Create a thread to handle the client. Since the client is sync, it can't
+ // run in the same thread as the agent.
+ std::thread client_thread([]() {
+ auto client = CreateClient({ "test", false });
+ ASSERT_TRUE(client);
+
+ ContentAnalysisRequest request = BuildRequest("test");
+ ContentAnalysisResponse response;
+ client->Send(request, &response);
+ });
+
+ // A thread that stops the agent after one client connects.
+ std::thread stop_agent([&handler_ptr, &agent]() {
+ handler_ptr->wait_for_request.wait();
+ agent->Stop();
+ });
+
+ agent->HandleEvents();
+
+ client_thread.join();
+ stop_agent.join();
+}
+
+TEST(AgentTest, ConnectAndClose) {
+ const Agent::Config aconfig{ "test", false };
+ const Client::Config cconfig{ "test", false };
+
+ // Create an agent and client that connects to it.
+ TestHandler* handler_ptr;
+ auto agent = CreateAgent(aconfig, &handler_ptr);
+ ASSERT_TRUE(agent);
+ auto client = CreateClient(cconfig);
+ ASSERT_TRUE(client);
+ ASSERT_EQ(cconfig.name, client->GetConfig().name);
+ ASSERT_EQ(cconfig.user_specific, client->GetConfig().user_specific);
+
+ agent->HandleOneEventForTesting();
+ ASSERT_EQ(1, handler_ptr->connect_count_);
+ ASSERT_EQ(0, handler_ptr->disconnect_count_);
+ ASSERT_EQ(0, handler_ptr->cancel_count_);
+ ASSERT_EQ(GetCurrentProcessId(), handler_ptr->last_info_.pid);
+
+ // Close the client and make sure a disconnect is received.
+ client.reset();
+ agent->HandleOneEventForTesting();
+ ASSERT_EQ(1, handler_ptr->connect_count_);
+ ASSERT_EQ(1, handler_ptr->disconnect_count_);
+ ASSERT_EQ(0, handler_ptr->cancel_count_);
+ ASSERT_EQ(GetCurrentProcessId(), handler_ptr->last_info_.pid);
+}
+
+TEST(AgentTest, Request) {
+ TestHandler* handler_ptr;
+ auto agent = CreateAgent({"test", false}, &handler_ptr);
+ ASSERT_TRUE(agent);
+
+ bool done = false;
+
+ // Create a thread to handle the client. Since the client is sync, it can't
+ // run in the same thread as the agent.
+ std::thread client_thread([&done]() {
+ auto client = CreateClient({"test", false});
+ ASSERT_TRUE(client);
+
+ // Send a request and wait for a response.
+ ContentAnalysisRequest request = BuildRequest();
+ ContentAnalysisResponse response;
+ int ret = client->Send(request, &response);
+ ASSERT_EQ(0, ret);
+ ASSERT_EQ(request.request_token(), response.request_token());
+
+ done = true;
+ });
+
+ while (!done) {
+ agent->HandleOneEventForTesting();
+ }
+ ASSERT_EQ(1, handler_ptr->request_count_);
+ ASSERT_EQ(0, handler_ptr->ack_count_);
+ ASSERT_EQ(0, handler_ptr->cancel_count_);
+
+ client_thread.join();
+}
+
+TEST(AgentTest, Request_Large) {
+ TestHandler* handler_ptr;
+ auto agent = CreateAgent({"test", false}, &handler_ptr);
+ ASSERT_TRUE(agent);
+
+ bool done = false;
+
+ // Create a thread to handle the client. Since the client is sync, it can't
+ // run in the same thread as the agent.
+ std::thread client_thread([&done]() {
+ auto client = CreateClient({"test", false});
+ ASSERT_TRUE(client);
+
+ // Send a request and wait for a response. Create a large string, which
+ // means larger than the initial mesasge buffer size specified when
+ // creating the pipes (4096 bytes).
+ ContentAnalysisRequest request = BuildRequest(std::string(5000, 'a'));
+ ContentAnalysisResponse response;
+ int ret = client->Send(request, &response);
+ ASSERT_EQ(0, ret);
+ ASSERT_EQ(request.request_token(), response.request_token());
+
+ done = true;
+ });
+
+ while (!done) {
+ agent->HandleOneEventForTesting();
+ }
+ ASSERT_EQ(1, handler_ptr->request_count_);
+ ASSERT_EQ(0, handler_ptr->ack_count_);
+ ASSERT_EQ(0, handler_ptr->cancel_count_);
+
+ client_thread.join();
+}
+
+TEST(AgentTest, Request_DoubleSend) {
+ ResultCode rc;
+ auto handler = std::make_unique<DoubleSendTestHandler>();
+ DoubleSendTestHandler* handler_ptr = handler.get();
+ auto agent = std::make_unique<AgentWin>(
+ Agent::Config{"test", false}, std::move(handler), &rc);
+ ASSERT_TRUE(agent);
+ ASSERT_EQ(ResultCode::OK, rc);
+
+ bool done = false;
+
+ // Create a thread to handle the client. Since the client is sync, it can't
+ // run in the same thread as the agent.
+ std::thread client_thread([&done]() {
+ auto client = CreateClient({ "test", false });
+ ASSERT_TRUE(client);
+
+ // Send a request and wait for a response.
+ ContentAnalysisRequest request = BuildRequest();
+ ContentAnalysisResponse response;
+ int ret = client->Send(request, &response);
+ ASSERT_EQ(0, ret);
+ ASSERT_EQ(request.request_token(), response.request_token());
+
+ done = true;
+ });
+
+ while (!done) {
+ agent->HandleOneEventForTesting();
+ }
+ ASSERT_EQ(1, handler_ptr->request_count_);
+ ASSERT_EQ(0, handler_ptr->ack_count_);
+ ASSERT_EQ(0, handler_ptr->cancel_count_);
+
+ client_thread.join();
+}
+
+TEST(AgentTest, Request_CloseEvent) {
+ ResultCode rc;
+ auto handler = std::make_unique<CloseEventTestHandler>();
+ CloseEventTestHandler* handler_ptr = handler.get();
+ auto agent = std::make_unique<AgentWin>(
+ Agent::Config{"test", false}, std::move(handler), &rc);
+ ASSERT_TRUE(agent);
+ ASSERT_EQ(ResultCode::OK, rc);
+
+ bool done = false;
+
+ // Create a thread to handle the client. Since the client is sync, it can't
+ // run in the same thread as the agent.
+ std::thread client_thread([&done]() {
+ auto client = CreateClient({"test", false});
+ ASSERT_TRUE(client);
+
+ // Send a request and wait for a response.
+ ContentAnalysisRequest request = BuildRequest();
+ ContentAnalysisResponse response;
+ int ret = client->Send(request, &response);
+ ASSERT_EQ(0, ret);
+ ASSERT_EQ(request.request_token(), response.request_token());
+
+ done = true;
+ });
+
+ while (!done) {
+ agent->HandleOneEventForTesting();
+ }
+ ASSERT_EQ(1, handler_ptr->request_count_);
+
+ client_thread.join();
+}
+
+TEST(AgentTest, Ack) {
+ TestHandler* handler_ptr;
+ auto agent = CreateAgent({ "test", false }, &handler_ptr);
+ ASSERT_TRUE(agent);
+
+ bool done = false;
+
+ // Create a thread to handle the client. Since the client is sync, it can't
+ // run in the same thread as the agent.
+ std::thread client_thread([&done]() {
+ auto client = CreateClient({"test", false});
+ ASSERT_TRUE(client);
+
+ // Send a request and wait for a response.
+ ContentAnalysisRequest request = BuildRequest();
+ ContentAnalysisResponse response;
+ int ret = client->Send(request, &response);
+ ASSERT_EQ(0, ret);
+
+ ContentAnalysisAcknowledgement ack;
+ ack.set_request_token(request.request_token());
+ ret = client->Acknowledge(ack);
+ ASSERT_EQ(0, ret);
+
+ done = true;
+ });
+
+ while (!done) {
+ agent->HandleOneEventForTesting();
+ }
+ ASSERT_EQ(1, handler_ptr->request_count_);
+ ASSERT_EQ(1, handler_ptr->ack_count_);
+ ASSERT_EQ(0, handler_ptr->cancel_count_);
+
+ client_thread.join();
+}
+
+TEST(AgentTest, Cancel) {
+ TestHandler* handler_ptr;
+ auto agent = CreateAgent({ "test", false }, &handler_ptr);
+ ASSERT_TRUE(agent);
+
+ // Create a thread to handle the client. Since the client is sync, it can't
+ // run in the same thread as the agent.
+ std::thread client_thread([]() {
+ auto client = CreateClient({"test", false});
+ ASSERT_TRUE(client);
+
+ ContentAnalysisCancelRequests cancel;
+ cancel.set_user_action_id("1234567890");
+ int ret = client->CancelRequests(cancel);
+ ASSERT_EQ(0, ret);
+ });
+
+ while (handler_ptr->cancel_count_ == 0) {
+ agent->HandleOneEventForTesting();
+ }
+ ASSERT_EQ(0, handler_ptr->request_count_);
+ ASSERT_EQ(0, handler_ptr->ack_count_);
+
+ client_thread.join();
+}
+
+} // namespace testing
+} // namespace sdk
+} // namespace content_analysis
+
diff --git a/third_party/content_analysis_sdk/agent/src/event_base.cc b/third_party/content_analysis_sdk/agent/src/event_base.cc
new file mode 100644
index 0000000000..c8186a4112
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/event_base.cc
@@ -0,0 +1,65 @@
+// 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.
+
+#include "event_base.h"
+
+namespace content_analysis {
+namespace sdk {
+
+ContentAnalysisEventBase::ContentAnalysisEventBase(
+ const BrowserInfo& browser_info)
+ : browser_info_(browser_info) {}
+
+ResultCode ContentAnalysisEventBase::Close() {
+ return ResultCode::OK;
+}
+
+ResultCode UpdateResponse(ContentAnalysisResponse& response,
+ const std::string& tag,
+ ContentAnalysisResponse::Result::Status status) {
+ auto result = response.results_size() > 0
+ ? response.mutable_results(0)
+ : response.add_results();
+
+ if (!tag.empty()) {
+ result->set_tag(tag);
+ }
+
+ if (status != ContentAnalysisResponse::Result::STATUS_UNKNOWN) {
+ result->set_status(status);
+ }
+
+ return ResultCode::OK;
+}
+
+ResultCode SetEventVerdictTo(
+ ContentAnalysisEvent* event,
+ ContentAnalysisResponse::Result::TriggeredRule::Action action) {
+ // This function expects that the event's result has already been
+ // initialized by a call to UpdateResponse().
+
+ if (event->GetResponse().results_size() == 0) {
+ return ResultCode::ERR_MISSING_RESULT;
+ }
+
+ auto result = event->GetResponse().mutable_results(0);
+
+ // Content analysis responses generated with this SDK contain at most one
+ // triggered rule.
+ auto rule = result->triggered_rules_size() > 0
+ ? result->mutable_triggered_rules(0)
+ : result->add_triggered_rules();
+
+ rule->set_action(action);
+
+ return ResultCode::OK;
+}
+
+ResultCode SetEventVerdictToBlock(ContentAnalysisEvent* event) {
+ return SetEventVerdictTo(event,
+ ContentAnalysisResponse::Result::TriggeredRule::BLOCK);
+}
+
+} // namespace sdk
+} // namespace content_analysis
diff --git a/third_party/content_analysis_sdk/agent/src/event_base.h b/third_party/content_analysis_sdk/agent/src/event_base.h
new file mode 100644
index 0000000000..3e41c0ea44
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/event_base.h
@@ -0,0 +1,38 @@
+// 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_SRC_EVENT_BASE_H_
+#define CONTENT_ANALYSIS_AGENT_SRC_EVENT_BASE_H_
+
+#include "content_analysis/sdk/analysis_agent.h"
+
+namespace content_analysis {
+namespace sdk {
+
+// Base ContentAnalysisEvent class with code common to all platforms.
+class ContentAnalysisEventBase : public ContentAnalysisEvent {
+ public:
+ // ContentAnalysisEvent:
+ ResultCode Close() override;
+ const BrowserInfo& GetBrowserInfo() const override { return browser_info_; }
+ const ContentAnalysisRequest& GetRequest() const override { return request_; }
+ ContentAnalysisResponse& GetResponse() override { return *response(); }
+
+ protected:
+ explicit ContentAnalysisEventBase(const BrowserInfo& browser_info);
+
+ ContentAnalysisRequest* request() { return &request_; }
+ AgentToChrome* agent_to_chrome() { return &agent_to_chrome_; }
+ ContentAnalysisResponse* response() { return agent_to_chrome()->mutable_response(); }
+
+private:
+ BrowserInfo browser_info_;
+ ContentAnalysisRequest request_;
+ AgentToChrome agent_to_chrome_;
+};
+
+} // namespace sdk
+} // namespace content_analysis
+
+#endif // CONTENT_ANALYSIS_AGENT_SRC_EVENT_BASE_H_
diff --git a/third_party/content_analysis_sdk/agent/src/event_mac.cc b/third_party/content_analysis_sdk/agent/src/event_mac.cc
new file mode 100644
index 0000000000..46822e473d
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/event_mac.cc
@@ -0,0 +1,29 @@
+// 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.
+
+#include "event_mac.h"
+
+#include "scoped_print_handle_mac.h"
+
+namespace content_analysis {
+namespace sdk {
+
+ContentAnalysisEventMac::ContentAnalysisEventMac(
+ const BrowserInfo& browser_info,
+ ContentAnalysisRequest req)
+ : ContentAnalysisEventBase(browser_info) {
+ *request() = std::move(req);
+}
+
+ResultCode ContentAnalysisEventMac::Send() {
+ return ResultCode::ERR_UNEXPECTED;
+}
+
+std::string ContentAnalysisEventMac::DebugString() const {
+ return std::string();
+}
+
+
+} // namespace sdk
+} // namespace content_analysis
diff --git a/third_party/content_analysis_sdk/agent/src/event_mac.h b/third_party/content_analysis_sdk/agent/src/event_mac.h
new file mode 100644
index 0000000000..0d89c47035
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/event_mac.h
@@ -0,0 +1,29 @@
+// 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_SRC_EVENT_MAC_H_
+#define CONTENT_ANALYSIS_SRC_EVENT_MAC_H_
+
+#include "event_base.h"
+
+namespace content_analysis {
+namespace sdk {
+
+// ContentAnalysisEvent implementaton for macOS.
+class ContentAnalysisEventMac : public ContentAnalysisEventBase {
+ public:
+ ContentAnalysisEventMac(const BrowserInfo& browser_info,
+ ContentAnalysisRequest request);
+
+ // ContentAnalysisEvent:
+ ResultCode Send() override;
+ std::string DebugString() const override;
+
+ // TODO(rogerta): Fill in implementation.
+};
+
+} // namespace sdk
+} // namespace content_analysis
+
+#endif // CONTENT_ANALYSIS_SRC_EVENT_MAC_H_
diff --git a/third_party/content_analysis_sdk/agent/src/event_mac_unittest.cc b/third_party/content_analysis_sdk/agent/src/event_mac_unittest.cc
new file mode 100644
index 0000000000..cf41d6bcdb
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/event_mac_unittest.cc
@@ -0,0 +1,53 @@
+// 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.
+
+#include <memory>
+
+#include "agent/src/event_mac.h"
+#include "content_analysis/sdk/analysis_agent.h"
+#include "gtest/gtest.h"
+
+namespace content_analysis {
+namespace sdk {
+namespace testing {
+
+std::unique_ptr<ContentAnalysisEventMac> CreateEvent(
+ const BrowserInfo& browser_info,
+ ContentAnalysisRequest request) {
+ return std::make_unique<ContentAnalysisEventMac>(
+ browser_info, std::move(request));
+}
+
+TEST(EventTest, Create_BrowserInfo) {
+ const BrowserInfo bi{12345, "/path/to/binary"};
+ ContentAnalysisRequest request;
+ *request.add_tags() = "foo";
+ request.set_request_token("req-token");
+
+ auto event = CreateEvent(bi, request);
+ ASSERT_TRUE(event);
+
+ ASSERT_EQ(bi.pid, event->GetBrowserInfo().pid);
+ ASSERT_EQ(bi.binary_path, event->GetBrowserInfo().binary_path);
+}
+
+TEST(EventTest, Create_Request) {
+ const BrowserInfo bi{ 12345, "/path/to/binary" };
+ ContentAnalysisRequest request;
+ *request.add_tags() = "foo";
+ request.set_request_token("req-token");
+
+ auto event = CreateEvent(bi, request);
+ ASSERT_TRUE(event);
+
+ ASSERT_EQ(1u, event->GetRequest().tags_size());
+ ASSERT_EQ(request.tags(0), event->GetRequest().tags(0));
+ ASSERT_TRUE(event->GetRequest().has_request_token());
+ ASSERT_EQ(request.request_token(), event->GetRequest().request_token());
+}
+
+} // namespace testing
+} // namespace sdk
+} // namespace content_analysis
+
diff --git a/third_party/content_analysis_sdk/agent/src/event_posix.cc b/third_party/content_analysis_sdk/agent/src/event_posix.cc
new file mode 100644
index 0000000000..1b0854af90
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/event_posix.cc
@@ -0,0 +1,28 @@
+// 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.
+
+#include "event_posix.h"
+
+#include "scoped_print_handle_posix.h"
+
+namespace content_analysis {
+namespace sdk {
+
+ ContentAnalysisEventPosix::ContentAnalysisEventPosix(
+ const BrowserInfo& browser_info,
+ ContentAnalysisRequest req)
+ : ContentAnalysisEventBase(browser_info) {
+ *request() = std::move(req);
+}
+
+ResultCode ContentAnalysisEventPosix::Send() {
+ return ResultCode::ERR_UNEXPECTED;
+}
+
+std::string ContentAnalysisEventPosix::DebugString() const {
+ return std::string();
+}
+
+} // namespace sdk
+} // namespace content_analysis
diff --git a/third_party/content_analysis_sdk/agent/src/event_posix.h b/third_party/content_analysis_sdk/agent/src/event_posix.h
new file mode 100644
index 0000000000..f3d0e24330
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/event_posix.h
@@ -0,0 +1,29 @@
+// 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_SRC_EVENT_POSIX_H_
+#define CONTENT_ANALYSIS_SRC_EVENT_POSIX_H_
+
+#include "event_base.h"
+
+namespace content_analysis {
+namespace sdk {
+
+// ContentAnalysisEvent implementaton for linux.
+class ContentAnalysisEventPosix : public ContentAnalysisEventBase {
+ public:
+ ContentAnalysisEventPosix(const BrowserInfo& browser_info,
+ ContentAnalysisRequest request);
+
+ // ContentAnalysisEvent:
+ ResultCode Send() override;
+ std::string DebugString() const override;
+
+ // TODO(rogerta): Fill in implementation.
+};
+
+} // namespace sdk
+} // namespace content_analysis
+
+#endif // CONTENT_ANALYSIS_SRC_EVENT_POSIX_H_ \ No newline at end of file
diff --git a/third_party/content_analysis_sdk/agent/src/event_posix_unittest.cc b/third_party/content_analysis_sdk/agent/src/event_posix_unittest.cc
new file mode 100644
index 0000000000..841934c2fd
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/event_posix_unittest.cc
@@ -0,0 +1,53 @@
+// 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.
+
+#include <memory>
+
+#include "agent/src/event_posix.h"
+#include "content_analysis/sdk/analysis_agent.h"
+#include "gtest/gtest.h"
+
+namespace content_analysis {
+namespace sdk {
+namespace testing {
+
+std::unique_ptr<ContentAnalysisEventPosix> CreateEvent(
+ const BrowserInfo& browser_info,
+ ContentAnalysisRequest request) {
+ return std::make_unique<ContentAnalysisEventPosix>(
+ browser_info, std::move(request));
+}
+
+TEST(EventTest, Create_BrowserInfo) {
+ const BrowserInfo bi{12345, "/path/to/binary"};
+ ContentAnalysisRequest request;
+ *request.add_tags() = "foo";
+ request.set_request_token("req-token");
+
+ auto event = CreateEvent(bi, request);
+ ASSERT_TRUE(event);
+
+ ASSERT_EQ(bi.pid, event->GetBrowserInfo().pid);
+ ASSERT_EQ(bi.binary_path, event->GetBrowserInfo().binary_path);
+}
+
+TEST(EventTest, Create_Request) {
+ const BrowserInfo bi{ 12345, "/path/to/binary" };
+ ContentAnalysisRequest request;
+ *request.add_tags() = "foo";
+ request.set_request_token("req-token");
+
+ auto event = CreateEvent(bi, request);
+ ASSERT_TRUE(event);
+
+ ASSERT_EQ(1u, event->GetRequest().tags_size());
+ ASSERT_EQ(request.tags(0), event->GetRequest().tags(0));
+ ASSERT_TRUE(event->GetRequest().has_request_token());
+ ASSERT_EQ(request.request_token(), event->GetRequest().request_token());
+}
+
+} // namespace testing
+} // namespace sdk
+} // namespace content_analysis
+
diff --git a/third_party/content_analysis_sdk/agent/src/event_win.cc b/third_party/content_analysis_sdk/agent/src/event_win.cc
new file mode 100644
index 0000000000..907bdfb858
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/event_win.cc
@@ -0,0 +1,133 @@
+// 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.
+
+#include <ios>
+#include <iostream>
+#include <sstream>
+#include <utility>
+
+#include "event_win.h"
+
+#include "common/utils_win.h"
+
+#include "agent_utils_win.h"
+#include "scoped_print_handle_win.h"
+
+namespace content_analysis {
+namespace sdk {
+
+// Writes a string to the pipe. Returns ERROR_SUCCESS if successful, else
+// returns GetLastError() of the write. This function does not return until
+// the entire message has been sent (or an error occurs).
+static DWORD WriteMessageToPipe(HANDLE pipe, const std::string& message) {
+ if (message.empty()) {
+ return ERROR_SUCCESS;
+ }
+
+ internal::ScopedOverlapped overlapped;
+ if (!overlapped.is_valid()) {
+ return GetLastError();
+ }
+
+ DWORD err = ERROR_SUCCESS;
+ const char* cursor = message.data();
+ for (DWORD size = message.length(); size > 0;) {
+ if (WriteFile(pipe, cursor, size, /*written=*/nullptr, overlapped)) {
+ err = ERROR_SUCCESS;
+ break;
+ }
+
+ // If an I/O is not pending, return the error.
+ err = GetLastError();
+ if (err != ERROR_IO_PENDING) {
+ break;
+ }
+
+ DWORD written;
+ if (!GetOverlappedResult(pipe, overlapped, &written, /*wait=*/TRUE)) {
+ err = GetLastError();
+ break;
+ }
+
+ cursor += written;
+ size -= written;
+ }
+
+ return err;
+}
+
+
+ContentAnalysisEventWin::ContentAnalysisEventWin(
+ HANDLE handle,
+ const BrowserInfo& browser_info,
+ ContentAnalysisRequest req)
+ : ContentAnalysisEventBase(browser_info),
+ hPipe_(handle) {
+ *request() = std::move(req);
+}
+
+ContentAnalysisEventWin::~ContentAnalysisEventWin() {
+ Shutdown();
+}
+
+ResultCode ContentAnalysisEventWin::Init() {
+ // TODO(rogerta): do some extra validation of the request?
+ if (request()->request_token().empty()) {
+ return ResultCode::ERR_MISSING_REQUEST_TOKEN;
+ }
+
+ response()->set_request_token(request()->request_token());
+
+ // Prepare the response so that ALLOW verdicts are the default().
+ return UpdateResponse(*response(),
+ request()->tags_size() > 0 ? request()->tags(0) : std::string(),
+ ContentAnalysisResponse::Result::SUCCESS);
+}
+
+ResultCode ContentAnalysisEventWin::Close() {
+ Shutdown();
+ return ContentAnalysisEventBase::Close();
+}
+
+ResultCode ContentAnalysisEventWin::Send() {
+ if (response_sent_) {
+ return ResultCode::ERR_RESPONSE_ALREADY_SENT;
+ }
+
+ response_sent_ = true;
+
+ DWORD err = WriteMessageToPipe(hPipe_,
+ agent_to_chrome()->SerializeAsString());
+ return ErrorToResultCode(err);
+}
+
+std::string ContentAnalysisEventWin::DebugString() const {
+ std::stringstream state;
+ state.setf(std::ios::boolalpha);
+ state << "ContentAnalysisEventWin{handle=" << hPipe_;
+ state << " pid=" << GetBrowserInfo().pid;
+ state << " rtoken=" << GetRequest().request_token();
+ state << " sent=" << response_sent_;
+ state << "}" << std::ends;
+
+ return state.str();
+}
+
+void ContentAnalysisEventWin::Shutdown() {
+ if (hPipe_ != INVALID_HANDLE_VALUE) {
+ // If no response has been sent yet, attempt to send it now. Otherwise
+ // the client may be stuck waiting. After shutdown the agent will not
+ // have any other chance to respond.
+ if (!response_sent_) {
+ Send();
+ }
+
+ // This event does not own the pipe, so don't close it.
+ FlushFileBuffers(hPipe_);
+ hPipe_ = INVALID_HANDLE_VALUE;
+ }
+}
+
+} // namespace sdk
+} // namespace content_analysis
diff --git a/third_party/content_analysis_sdk/agent/src/event_win.h b/third_party/content_analysis_sdk/agent/src/event_win.h
new file mode 100644
index 0000000000..f631f693dc
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/event_win.h
@@ -0,0 +1,51 @@
+// 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_SRC_EVENT_WIN_H_
+#define CONTENT_ANALYSIS_AGENT_SRC_EVENT_WIN_H_
+
+#include <windows.h>
+
+#include "event_base.h"
+
+namespace content_analysis {
+namespace sdk {
+
+// ContentAnalysisEvent implementaton for Windows.
+class ContentAnalysisEventWin : public ContentAnalysisEventBase {
+ public:
+ ContentAnalysisEventWin(HANDLE handle,
+ const BrowserInfo& browser_info,
+ ContentAnalysisRequest request);
+ ~ContentAnalysisEventWin() override;
+
+ // Initialize the event. This involves reading the request from Google
+ // Chrome and making sure it is well formed.
+ ResultCode Init();
+
+ // ContentAnalysisEvent:
+ ResultCode Close() override;
+ ResultCode Send() override;
+ std::string DebugString() const override;
+ std::string SerializeStringToSendToBrowser() {
+ return agent_to_chrome()->SerializeAsString();
+ }
+ void SetResponseSent() { response_sent_ = true; }
+
+ HANDLE Pipe() const { return hPipe_; }
+
+ private:
+ void Shutdown();
+
+ // This handle is not owned by the event.
+ HANDLE hPipe_ = INVALID_HANDLE_VALUE;
+
+ // Set to true when Send() is called the first time.
+ bool response_sent_ = false;
+};
+
+} // namespace sdk
+} // namespace content_analysis
+
+#endif // CONTENT_ANALYSIS_AGENT_SRC_EVENT_WIN_H_ \ No newline at end of file
diff --git a/third_party/content_analysis_sdk/agent/src/event_win_unittest.cc b/third_party/content_analysis_sdk/agent/src/event_win_unittest.cc
new file mode 100644
index 0000000000..ffc3103ac1
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/event_win_unittest.cc
@@ -0,0 +1,116 @@
+// 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.
+
+#include <memory>
+
+#include "agent/src/event_win.h"
+#include "common/utils_win.h"
+#include "gtest/gtest.h"
+
+namespace content_analysis {
+namespace sdk {
+namespace testing {
+
+std::unique_ptr<ContentAnalysisEventWin> CreateEvent(
+ HANDLE handle,
+ const BrowserInfo& browser_info,
+ ContentAnalysisRequest request) {
+ return std::make_unique<ContentAnalysisEventWin>(
+ handle, browser_info, std::move(request));
+}
+
+TEST(EventTest, Create_BrowserInfo) {
+ const BrowserInfo bi{12345, "/path/to/binary"};
+ ContentAnalysisRequest request;
+ *request.add_tags() = "foo";
+ request.set_request_token("req-token");
+
+ auto event = CreateEvent(INVALID_HANDLE_VALUE, bi, request);
+ ASSERT_TRUE(event);
+
+ ASSERT_EQ(bi.pid, event->GetBrowserInfo().pid);
+ ASSERT_EQ(bi.binary_path, event->GetBrowserInfo().binary_path);
+}
+
+TEST(EventTest, Create_Request) {
+ const BrowserInfo bi{ 12345, "/path/to/binary" };
+ ContentAnalysisRequest request;
+ *request.add_tags() = "foo";
+ request.set_request_token("req-token");
+
+ auto event = CreateEvent(INVALID_HANDLE_VALUE, bi, request);
+ ASSERT_TRUE(event);
+
+ ASSERT_EQ(1u, event->GetRequest().tags_size());
+ ASSERT_EQ(request.tags(0), event->GetRequest().tags(0));
+ ASSERT_TRUE(event->GetRequest().has_request_token());
+ ASSERT_EQ(request.request_token(), event->GetRequest().request_token());
+}
+
+TEST(EventTest, Create_Init) {
+ const BrowserInfo bi{ 12345, "/path/to/binary" };
+ ContentAnalysisRequest request;
+ *request.add_tags() = "foo";
+ request.set_request_token("req-token");
+
+ auto event = CreateEvent(INVALID_HANDLE_VALUE, bi, request);
+ ASSERT_TRUE(event);
+
+ ASSERT_EQ(ResultCode::OK, event->Init());
+
+ // Initializing an event should initialize the contained response for a
+ // success verdict that matches the request.
+ ASSERT_EQ(request.request_token(), event->GetResponse().request_token());
+ ASSERT_EQ(1u, event->GetResponse().results_size());
+ ASSERT_EQ(ContentAnalysisResponse::Result::SUCCESS,
+ event->GetResponse().results(0).status());
+ ASSERT_TRUE(event->GetResponse().results(0).has_tag());
+ ASSERT_EQ(request.tags(0), event->GetResponse().results(0).tag());
+ ASSERT_EQ(0u, event->GetResponse().results(0).triggered_rules_size());
+}
+
+// Initializing an event whose request has no request token is an error.
+TEST(EventTest, Create_Init_RequestNoRequestToken) {
+ const BrowserInfo bi{ 12345, "/path/to/binary" };
+ ContentAnalysisRequest request;
+ *request.add_tags() = "foo";
+
+ auto event = CreateEvent(INVALID_HANDLE_VALUE, bi, request);
+ ASSERT_TRUE(event);
+
+ ASSERT_EQ(ResultCode::ERR_MISSING_REQUEST_TOKEN, event->Init());
+}
+
+TEST(EventTest, Write_BadPipe) {
+ HANDLE pipe;
+ DWORD err = internal::CreatePipe(
+ internal::GetPipeNameForAgent("testpipe", false), false, true, &pipe);
+ ASSERT_EQ(ERROR_SUCCESS, err);
+ ASSERT_NE(INVALID_HANDLE_VALUE, pipe);
+
+ // Create an event with the dummy pipe and initilalize it.
+ const BrowserInfo bi{ 12345, "/path/to/binary" };
+ ContentAnalysisRequest request;
+ request.set_request_token("req-token");
+ *request.add_tags() = "dlp";
+ request.set_text_content("test");
+ auto event = std::make_unique<ContentAnalysisEventWin>(
+ pipe, bi, std::move(request));
+ ASSERT_TRUE(event);
+ ResultCode rc = event->Init();
+ ASSERT_EQ(ResultCode::OK, rc);
+
+ // Close the handle before trying to send the response.
+ // This simulates an error with the pipe.
+ CloseHandle(pipe);
+ ASSERT_EQ(ERROR_SUCCESS, GetLastError());
+
+ // The following call should not hang.
+ event->Send();
+}
+
+} // namespace testing
+} // namespace sdk
+} // namespace content_analysis
+
diff --git a/third_party/content_analysis_sdk/agent/src/scoped_print_handle_base.cc b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_base.cc
new file mode 100644
index 0000000000..802b553b20
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_base.cc
@@ -0,0 +1,17 @@
+// Copyright 2023 The Chromium Authors.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "scoped_print_handle_base.h"
+
+namespace content_analysis {
+namespace sdk {
+
+ScopedPrintHandleBase::ScopedPrintHandleBase(
+ const ContentAnalysisRequest::PrintData& print_data)
+ : size_(print_data.size()) {}
+
+size_t ScopedPrintHandleBase::size() { return size_; }
+
+} // namespace sdk
+} // namespace content_analysis
diff --git a/third_party/content_analysis_sdk/agent/src/scoped_print_handle_base.h b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_base.h
new file mode 100644
index 0000000000..1ae20381d7
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_base.h
@@ -0,0 +1,25 @@
+// Copyright 2023 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_SRC_SCOPED_PRINT_HANDLE_BASE_H_
+#define CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_BASE_H_
+
+#include "content_analysis/sdk/analysis_agent.h"
+
+namespace content_analysis {
+namespace sdk {
+
+class ScopedPrintHandleBase : public ScopedPrintHandle {
+ public:
+ ScopedPrintHandleBase(const ContentAnalysisRequest::PrintData& print_data);
+
+ size_t size() override;
+ protected:
+ size_t size_ = 0;
+};
+
+} // namespace sdk
+} // namespace content_analysis
+
+#endif // CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_BASE_H_
diff --git a/third_party/content_analysis_sdk/agent/src/scoped_print_handle_mac.cc b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_mac.cc
new file mode 100644
index 0000000000..316576bf90
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_mac.cc
@@ -0,0 +1,36 @@
+// Copyright 2023 The Chromium Authors.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "scoped_print_handle_mac.h"
+
+namespace content_analysis {
+namespace sdk {
+
+std::unique_ptr<ScopedPrintHandle>
+CreateScopedPrintHandle(const ContentAnalysisRequest& request,
+ int64_t browser_pid) {
+ if (!request.has_print_data() || !request.print_data().has_handle()) {
+ return nullptr;
+ }
+
+ return std::make_unique<ScopedPrintHandleMac>(request.print_data());
+}
+
+ScopedPrintHandleMac::ScopedPrintHandleMac(
+ const ContentAnalysisRequest::PrintData& print_data)
+ : ScopedPrintHandleBase(print_data) {
+ // TODO
+}
+
+ScopedPrintHandleMac::~ScopedPrintHandleMac() {
+ // TODO
+}
+
+const char* ScopedPrintHandleMac::data() {
+ // TODO
+ return nullptr;
+}
+
+} // namespace sdk
+} // namespace content_analysis
diff --git a/third_party/content_analysis_sdk/agent/src/scoped_print_handle_mac.h b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_mac.h
new file mode 100644
index 0000000000..d407ef5375
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_mac.h
@@ -0,0 +1,24 @@
+// Copyright 2023 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_SRC_SCOPED_PRINT_HANDLE_MAC_H_
+#define CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_MAC_H_
+
+#include "scoped_print_handle_base.h"
+
+namespace content_analysis {
+namespace sdk {
+
+class ScopedPrintHandleMac : public ScopedPrintHandleBase {
+ public:
+ ScopedPrintHandleMac(const ContentAnalysisRequest::PrintData& print_data);
+ ~ScopedPrintHandleMac() override;
+
+ const char* data() override;
+};
+
+} // namespace sdk
+} // namespace content_analysis
+
+#endif // CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_MAC_H_
diff --git a/third_party/content_analysis_sdk/agent/src/scoped_print_handle_posix.cc b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_posix.cc
new file mode 100644
index 0000000000..cd2eb2cc8e
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_posix.cc
@@ -0,0 +1,36 @@
+// Copyright 2023 The Chromium Authors.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "scoped_print_handle_posix.h"
+
+namespace content_analysis {
+namespace sdk {
+
+std::unique_ptr<ScopedPrintHandle>
+CreateScopedPrintHandle(const ContentAnalysisRequest& request,
+ int64_t browser_pid) {
+ if (!request.has_print_data() || !request.print_data().has_handle()) {
+ return nullptr;
+ }
+
+ return std::make_unique<ScopedPrintHandlePosix>(request.print_data());
+}
+
+ScopedPrintHandlePosix::ScopedPrintHandlePosix(
+ const ContentAnalysisRequest::PrintData& print_data)
+ : ScopedPrintHandleBase(print_data) {
+ // TODO
+}
+
+ScopedPrintHandlePosix::~ScopedPrintHandlePosix() {
+ // TODO
+}
+
+const char* ScopedPrintHandlePosix::data() {
+ // TODO
+ return nullptr;
+}
+
+} // namespace sdk
+} // namespace content_analysis
diff --git a/third_party/content_analysis_sdk/agent/src/scoped_print_handle_posix.h b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_posix.h
new file mode 100644
index 0000000000..e9125f58eb
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_posix.h
@@ -0,0 +1,24 @@
+// Copyright 2023 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_SRC_SCOPED_PRINT_HANDLE_POSIX_H_
+#define CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_POSIX_H_
+
+#include "scoped_print_handle_base.h"
+
+namespace content_analysis {
+namespace sdk {
+
+class ScopedPrintHandlePosix : public ScopedPrintHandleBase {
+ public:
+ ScopedPrintHandlePosix(const ContentAnalysisRequest::PrintData& print_data);
+ ~ScopedPrintHandlePosix() override;
+
+ const char* data() override;
+};
+
+} // namespace sdk
+} // namespace content_analysis
+
+#endif // CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_POSIX_H_
diff --git a/third_party/content_analysis_sdk/agent/src/scoped_print_handle_win.cc b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_win.cc
new file mode 100644
index 0000000000..2573d676ea
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_win.cc
@@ -0,0 +1,67 @@
+// Copyright 2023 The Chromium Authors.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "scoped_print_handle_win.h"
+
+namespace content_analysis {
+namespace sdk {
+
+std::unique_ptr<ScopedPrintHandle>
+CreateScopedPrintHandle(const ContentAnalysisRequest& request,
+ int64_t browser_pid) {
+ if (!request.has_print_data() || !request.print_data().has_handle()) {
+ return nullptr;
+ }
+
+ // The handle in the request must be duped to be read by the agent
+ // process. If that doesn't work for any reason, return null.
+ // See https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle
+ // for details.
+ HANDLE browser_process = OpenProcess(
+ /*dwDesiredAccess=*/PROCESS_DUP_HANDLE,
+ /*bInheritHandle=*/false,
+ /*dwProcessId=*/browser_pid);
+ if (!browser_process)
+ return nullptr;
+
+ HANDLE dupe = nullptr;
+ DuplicateHandle(
+ /*hSourceProcessHandle=*/browser_process,
+ /*hSourceHandle=*/reinterpret_cast<HANDLE>(request.print_data().handle()),
+ /*hTargetProcessHandle=*/GetCurrentProcess(),
+ /*lpTargetHandle=*/&dupe,
+ /*dwDesiredAccess=*/PROCESS_DUP_HANDLE | FILE_MAP_READ,
+ /*bInheritHandle=*/false,
+ /*dwOptions=*/0);
+
+ CloseHandle(browser_process);
+
+ if (!dupe)
+ return nullptr;
+
+ ContentAnalysisRequest::PrintData dupe_print_data;
+ dupe_print_data.set_handle(reinterpret_cast<int64_t>(dupe));
+ dupe_print_data.set_size(request.print_data().size());
+
+
+ return std::make_unique<ScopedPrintHandleWin>(dupe_print_data);
+}
+
+ScopedPrintHandleWin::ScopedPrintHandleWin(
+ const ContentAnalysisRequest::PrintData& print_data)
+ : ScopedPrintHandleBase(print_data),
+ handle_(reinterpret_cast<HANDLE>(print_data.handle())) {
+ mapped_ = MapViewOfFile(handle_, FILE_MAP_READ, 0, 0, 0);
+}
+
+ScopedPrintHandleWin::~ScopedPrintHandleWin() {
+ CloseHandle(handle_);
+}
+
+const char* ScopedPrintHandleWin::data() {
+ return reinterpret_cast<const char*>(mapped_);
+}
+
+} // namespace sdk
+} // namespace content_analysis
diff --git a/third_party/content_analysis_sdk/agent/src/scoped_print_handle_win.h b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_win.h
new file mode 100644
index 0000000000..31923285be
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent/src/scoped_print_handle_win.h
@@ -0,0 +1,29 @@
+// Copyright 2023 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_SRC_SCOPED_PRINT_HANDLE_WIN_H_
+#define CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_WIN_H_
+
+#include <windows.h>
+
+#include "scoped_print_handle_base.h"
+
+namespace content_analysis {
+namespace sdk {
+
+class ScopedPrintHandleWin : public ScopedPrintHandleBase {
+ public:
+ ScopedPrintHandleWin(const ContentAnalysisRequest::PrintData& print_data);
+ ~ScopedPrintHandleWin() override;
+
+ const char* data() override;
+ private:
+ void* mapped_ = nullptr;
+ HANDLE handle_ = nullptr;
+};
+
+} // namespace sdk
+} // namespace content_analysis
+
+#endif // CONTENT_ANALYSIS_AGENT_SRC_SCOPED_PRINT_HANDLE_WIN_H_