typecd: Add SOP' alt mode support code.

Add support in the Cable class for SOP' alt modes (SOP'' alt modes are
ignored for now since the Chrome OS EC doesn't perform discovery of
SOP'' yet).

Add a basic alt mode addition test to CableTest.

BUG=b:172097194
TEST=Run typecd unit tests and make sure everything still passes.

Change-Id: I02f47b7640d33e3d931603d4ef97ad547529ef97
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2511841
Commit-Queue: Prashant Malani <pmalani@chromium.org>
Tested-by: Prashant Malani <pmalani@chromium.org>
Reviewed-by: Benson Leung <bleung@google.com>
diff --git a/typecd/cable.cc b/typecd/cable.cc
index 47d2c2e..47466e5 100644
--- a/typecd/cable.cc
+++ b/typecd/cable.cc
@@ -4,6 +4,8 @@
 
 #include "typecd/cable.h"
 
+#include <re2/re2.h>
+
 namespace {
 
 // Ref:
@@ -21,10 +23,73 @@
 constexpr uint32_t kUSBSuperSpeed31Gen1 = 0x1;
 constexpr uint32_t kUSBSuperSpeed31Gen2 = 0x2;
 
+constexpr char kSOPPrimeAltModeRegex[] = R"(port(\d+)-plug0.(\d+))";
+
 }  // namespace
 
 namespace typecd {
 
+bool Cable::AddAltMode(const base::FilePath& mode_syspath) {
+  int port, index;
+  if (!RE2::FullMatch(mode_syspath.BaseName().value(), kSOPPrimeAltModeRegex,
+                      &port, &index)) {
+    LOG(ERROR) << "Couldn't parse alt mode index from syspath " << mode_syspath;
+    return false;
+  }
+
+  if (IsAltModePresent(index)) {
+    LOG(ERROR) << "Alt mode already registered for syspath " << mode_syspath;
+    return false;
+  }
+
+  auto alt_mode = AltMode::CreateAltMode(mode_syspath);
+  if (!alt_mode) {
+    LOG(ERROR) << "Error creating alt mode for syspath " << mode_syspath;
+    return false;
+  }
+
+  alt_modes_.emplace(index, std::move(alt_mode));
+  LOG(INFO) << "Added SOP' alt mode for port " << port << " index " << index;
+
+  return true;
+}
+
+void Cable::RemoveAltMode(const base::FilePath& mode_syspath) {
+  int port, index;
+  if (!RE2::FullMatch(mode_syspath.BaseName().value(), kSOPPrimeAltModeRegex,
+                      &port, &index)) {
+    LOG(ERROR) << "Couldn't parse alt mode index from syspath " << mode_syspath;
+    return;
+  }
+
+  auto it = alt_modes_.find(index);
+  if (it == alt_modes_.end()) {
+    LOG(INFO) << "Trying to delete non-existent SOP' alt mode " << index;
+    return;
+  }
+
+  alt_modes_.erase(it);
+
+  LOG(INFO) << "Removed SOP' alt mode for port " << port << " index " << index;
+}
+
+bool Cable::IsAltModePresent(int index) {
+  auto it = alt_modes_.find(index);
+  if (it != alt_modes_.end()) {
+    return true;
+  }
+
+  LOG(INFO) << "SOP' Alt mode not found at index " << index;
+  return false;
+}
+
+AltMode* Cable::GetAltMode(int index) {
+  if (!IsAltModePresent(index))
+    return nullptr;
+
+  return alt_modes_.find(index)->second.get();
+}
+
 // Ref:
 //   USB Type-C Connector Spec, release 2.0
 //   Figure F-1.
diff --git a/typecd/cable.h b/typecd/cable.h
index 43c6506..8072553 100644
--- a/typecd/cable.h
+++ b/typecd/cable.h
@@ -25,8 +25,47 @@
   Cable(const Cable&) = delete;
   Cable& operator=(const Cable&) = delete;
 
+  // Add an alternate mode for the plug associated with the cable.
+  // NOTE: We currently only process SOP' plugs.
+  // TODO(b/159859845): Add support for SOP'' plugs and alternate modes.
+  bool AddAltMode(const base::FilePath& mode_syspath);
+  void RemoveAltMode(const base::FilePath& mode_syspath);
+
+  // Return the total number of SOP' alternate modes supported by the cable. If
+  // this value hasn't been populated yet, the default value is -1, signifying
+  // that discovery is not yet complete.
+  int GetNumAltModes() { return num_alt_modes_; }
+
+  // Set the total number of SOP' alternate modes supported by the cable. This
+  // value should be populated either:
+  // - From the corresponding file in sysfs
+  //   <or>
+  // - When an appropriate signal is received from the kernel about completion
+  //   of SOP' Discovery.
+  //
+  // Since neither of the above have been implemented yet, we can call this
+  // function explicitly for the sake of unit tests.
+  void SetNumAltModes(int num_alt_modes) { num_alt_modes_ = num_alt_modes; }
+
+  // Check if a particular alt mode index (as specified by the Type C connector
+  // class framework) is registered.
+  bool IsAltModePresent(int index);
+
+  // Return the alternate modes with index |index|, and nullptr if such an
+  // alternate modes doesn't exist.
+  AltMode* GetAltMode(int index);
+
   // Check whether the cable supports Thunderbolt3 speed requirements.
   bool TBT3PDIdentityCheck();
+
+ private:
+  // Map representing all SOP' alternate modes.
+  // The key is the index of the alternate mode as determined
+  // by the connector class sysfs directory. For example,
+  // an alternate mode which has the directory: "sys/class/port0-plug0.2" will
+  // use a key of "2".
+  std::map<int, std::unique_ptr<AltMode>> alt_modes_;
+  int num_alt_modes_;
 };
 
 }  // namespace typecd
diff --git a/typecd/cable_test.cc b/typecd/cable_test.cc
index cc81ea2..ee46e7f 100644
--- a/typecd/cable_test.cc
+++ b/typecd/cable_test.cc
@@ -4,9 +4,13 @@
 
 #include "typecd/cable.h"
 
+#include <string>
+
+#include <base/strings/stringprintf.h>
 #include <gtest/gtest.h>
 
 #include "typecd/test_constants.h"
+#include "typecd/test_utils.h"
 
 namespace typecd {
 
@@ -69,4 +73,30 @@
   EXPECT_FALSE(cable->TBT3PDIdentityCheck());
 }
 
+// Check that calls of AddAltMode() done explicitly function correctly. Also
+// check that trying to add the same alt mode twice fails.
+TEST_F(CableTest, TestAltModeManualAddition) {
+  Cable cable((base::FilePath(kFakePort0CableSysPath)));
+
+  // Set up fake sysfs paths.
+  base::FilePath temp_dir;
+  ASSERT_TRUE(base::CreateNewTempDirectory("", &temp_dir));
+
+  std::string mode0_dirname =
+      base::StringPrintf("port%d-plug0.%d", 0, kDPAltModeIndex);
+  auto mode0_path = temp_dir.Append(mode0_dirname);
+  ASSERT_TRUE(CreateFakeAltMode(mode0_path, kDPSVID, kDPVDO, kDPVDOIndex));
+
+  EXPECT_TRUE(cable.AddAltMode(mode0_path));
+
+  std::string mode1_dirname =
+      base::StringPrintf("port%d-plug0.%d", 0, kTBTAltModeIndex);
+  auto mode1_path = temp_dir.Append(mode1_dirname);
+  ASSERT_TRUE(CreateFakeAltMode(mode1_path, kTBTSVID, kTBTVDO, kTBTVDOIndex));
+
+  EXPECT_TRUE(cable.AddAltMode(mode1_path));
+  // Trying to add an existing alt mode again should fail.
+  EXPECT_FALSE(cable.AddAltMode(mode1_path));
+}
+
 }  // namespace typecd