modemfwd: make the carrier firmware check more robust

In the firmware manifest, a given carrier firmware might be used for
multiple carrier UUIDs (e.g. a carrier and its MVNO).
In this case, when asked for the flashed carrier firmware version and UUID,
the l850gl-helper might return an arbitrary UUID (which is hardcoded on
their side) for this firmware rather than current real one.
Detect this corner case to avoid endlessly re-flashing the carrier
firmware by comparing the carrier firmware file for the flashed UUID
with the file for the detected UUID (rather than comparing just the
UUIDs).

BUG=b:171367064
TEST=unit-tests: added SkipCarrierWithTwoUuidSameFirmware test case,
fails before the change and pass with it.
TEST=manual testing: put 2 different UUID in the
firmware_manifest and make the l850gl-helper returns the wrong one (not
matching the network one) and verify that we are not doing useless
updates.

Change-Id: I6aab9535f0c7dee43844c4477138e850853e3aec
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2507711
Tested-by: Vincent Palatin <vpalatin@chromium.org>
Reviewed-by: Eric Caruso <ejcaruso@chromium.org>
Commit-Queue: Vincent Palatin <vpalatin@chromium.org>
(cherry picked from commit a6d2f6a8e1590ca1fd5ac0b914160dec10c70a49)
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2581749
Tested-by: Henry Sun <henrysun@google.com>
Reviewed-by: Henry Sun <henrysun@google.com>
Commit-Queue: Henry Sun <henrysun@google.com>
diff --git a/modemfwd/firmware_directory.cc b/modemfwd/firmware_directory.cc
index 4c9066a..113802e 100644
--- a/modemfwd/firmware_directory.cc
+++ b/modemfwd/firmware_directory.cc
@@ -87,6 +87,35 @@
     return result;
   }
 
+  // modemfwd::IsUsingSameFirmware overrides.
+  bool IsUsingSameFirmware(const std::string& device_id,
+                           const std::string& carrier_a,
+                           const std::string& carrier_b) override {
+    // easy case: identical carrier UUID
+    if (carrier_a == carrier_b)
+      return true;
+
+    DeviceType type{device_id, variant_};
+    auto device_it = index_.find(type);
+    // no firmware for this device
+    if (device_it == index_.end())
+      return true;
+
+    const DeviceFirmwareCache& cache = device_it->second;
+    auto main_a = cache.main_firmware.find(carrier_a);
+    auto main_b = cache.main_firmware.find(carrier_b);
+    auto cust_a = cache.carrier_firmware.find(carrier_a);
+    auto cust_b = cache.carrier_firmware.find(carrier_b);
+    // one or several firmwares are missing
+    if (main_a == cache.main_firmware.end() ||
+        main_b == cache.main_firmware.end() ||
+        cust_a == cache.carrier_firmware.end() ||
+        cust_b == cache.carrier_firmware.end())
+      return main_a == main_b && cust_a == cust_b;
+    // same firmware if they are pointing to the 2 same files.
+    return main_a->second == main_b->second && cust_a->second == cust_b->second;
+  }
+
  private:
   bool FindFirmwareForCarrier(
       const DeviceFirmwareCache::CarrierIndex& carrier_index,
diff --git a/modemfwd/firmware_directory.h b/modemfwd/firmware_directory.h
index 650af1e..3a9334a 100644
--- a/modemfwd/firmware_directory.h
+++ b/modemfwd/firmware_directory.h
@@ -34,6 +34,13 @@
   // that supports the carrier |carrier_id|, such as a generic one.
   virtual Files FindFirmware(const std::string& device_id,
                              std::string* carrier_id) = 0;
+
+  // Determine whether two potentially different carrier ID |carrier_a| and
+  // |carrier_b| are using the same base and carrier firmwares.
+  // e.g. a carrier and MVNO networks.
+  virtual bool IsUsingSameFirmware(const std::string& device_id,
+                                   const std::string& carrier_a,
+                                   const std::string& carrier_b) = 0;
 };
 
 std::unique_ptr<FirmwareDirectory> CreateFirmwareDirectory(
diff --git a/modemfwd/firmware_directory_stub.cc b/modemfwd/firmware_directory_stub.cc
index a883405..ee18032 100644
--- a/modemfwd/firmware_directory_stub.cc
+++ b/modemfwd/firmware_directory_stub.cc
@@ -85,4 +85,25 @@
   return false;
 }
 
+bool FirmwareDirectoryStub::IsUsingSameFirmware(const std::string& device_id,
+                                                const std::string& carrier_a,
+                                                const std::string& carrier_b) {
+  // easy case: identical carrier UUID
+  if (carrier_a == carrier_b)
+    return true;
+
+  FirmwareFileInfo info_a;
+  FirmwareFileInfo info_b;
+  bool has_a =
+      GetValue(carrier_fw_info_, std::make_pair(device_id, carrier_a), &info_a);
+  bool has_b =
+      GetValue(carrier_fw_info_, std::make_pair(device_id, carrier_b), &info_b);
+  // one or several firmwares are missing
+  if (!has_a || !has_b)
+    return false;
+
+  // same firmware if they are pointing to the 2 same files.
+  return info_a.firmware_path == info_b.firmware_path;
+}
+
 }  // namespace modemfwd
diff --git a/modemfwd/firmware_directory_stub.h b/modemfwd/firmware_directory_stub.h
index 2f245d2..2a6c249 100644
--- a/modemfwd/firmware_directory_stub.h
+++ b/modemfwd/firmware_directory_stub.h
@@ -30,6 +30,10 @@
   // modemfwd::FirmwareDirectory overrides.
   FirmwareDirectory::Files FindFirmware(const std::string& device_id,
                                         std::string* carrier_id) override;
+  // modemfwd::IsUsingSameFirmware overrides.
+  bool IsUsingSameFirmware(const std::string& device_id,
+                           const std::string& carrier_a,
+                           const std::string& carrier_b) override;
 
  private:
   using CarrierFirmwareMap =
diff --git a/modemfwd/modem_flasher.cc b/modemfwd/modem_flasher.cc
index 6bb252a..5c3b257 100644
--- a/modemfwd/modem_flasher.cc
+++ b/modemfwd/modem_flasher.cc
@@ -164,7 +164,9 @@
     ELOG(INFO) << "No carrier firmware is currently installed";
   }
 
-  if (!has_carrier_fw || carrier_fw_id != current_carrier ||
+  if (!has_carrier_fw ||
+      !firmware_directory_->IsUsingSameFirmware(device_id, carrier_fw_id,
+                                                current_carrier) ||
       carrier_fw_version != file_info.version) {
     FirmwareFile firmware_file;
     if (!firmware_file.PrepareFrom(file_info))
diff --git a/modemfwd/modem_flasher_test.cc b/modemfwd/modem_flasher_test.cc
index 36b1357..baa7cfa 100644
--- a/modemfwd/modem_flasher_test.cc
+++ b/modemfwd/modem_flasher_test.cc
@@ -34,6 +34,7 @@
 constexpr char kMainFirmware2Version[] = "versionB";
 
 constexpr char kCarrier1[] = "uuid_1";
+constexpr char kCarrier1Mvno[] = "uuid_1_1";
 constexpr char kCarrier1Firmware1Path[] = "carrier_1_fw_1.fls";
 constexpr char kCarrier1Firmware1Version[] = "v1.00";
 constexpr char kCarrier1Firmware2Path[] = "carrier_1_fw_2.fls";
@@ -698,4 +699,23 @@
   modem_flasher_->TryFlash(modem.get());
 }
 
+TEST_F(ModemFlasherTest, SkipCarrierWithTwoUuidSameFirmware) {
+  base::FilePath current_firmware(kCarrier1Firmware1Path);
+  AddCarrierFirmwareFile(kDeviceId1, kCarrier1, current_firmware,
+                         kCarrier1Firmware2Version);
+  AddCarrierFirmwareFile(kDeviceId1, kCarrier1Mvno, current_firmware,
+                         kCarrier1Firmware2Version);
+
+  auto modem = GetDefaultModem();
+  EXPECT_CALL(*modem, GetDeviceId()).Times(AtLeast(1));
+  EXPECT_CALL(*modem, GetCarrierFirmwareVersion()).Times(AtLeast(1));
+  // The modem will say that the currently flashed firmware has the carrier UUID
+  // KCarrier1Mvno while the current carrier UUID is always returned as
+  // kCarrier1.
+  SetCarrierFirmwareInfo(modem.get(), kCarrier1Mvno, kCarrier1Firmware2Version);
+  EXPECT_CALL(*modem, FlashMainFirmware(_, _)).Times(0);
+  EXPECT_CALL(*modem, FlashCarrierFirmware(_, _)).Times(0);
+  modem_flasher_->TryFlash(modem.get());
+}
+
 }  // namespace modemfwd