| // Copyright 2019 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "modemfwd/firmware_manifest.h" |
| |
| #include <utility> |
| |
| #include <base/strings/string_number_conversions.h> |
| #include <brillo/proto_file_io.h> |
| |
| #include "modemfwd/firmware_directory.h" |
| |
| #include "modemfwd/proto_bindings/firmware_manifest.pb.h" |
| |
| namespace modemfwd { |
| |
| namespace { |
| |
| struct DeviceEntries { |
| const MainFirmware* main_firmware; |
| std::vector<const CarrierFirmware*> carrier_firmware; |
| }; |
| |
| bool SortByDevice(const FirmwareManifest& manifest, |
| std::map<DeviceType, DeviceEntries>* out_sorted) { |
| for (const MainFirmware& info : manifest.main_firmware()) { |
| if (info.device_id().empty() || info.filename().empty() || |
| info.version().empty() || !Compression_IsValid(info.compression())) { |
| LOG(ERROR) << "Found malformed main firmware manifest entry"; |
| return false; |
| } |
| |
| DeviceType type{info.device_id(), info.variant()}; |
| if ((*out_sorted)[type].main_firmware) { |
| LOG(ERROR) << "Device " << type.device_id() |
| << (type.variant().empty() ? "" |
| : " for variant " + type.variant()) |
| << " has multiple main firmwares"; |
| return false; |
| } |
| |
| (*out_sorted)[type].main_firmware = &info; |
| } |
| |
| for (const CarrierFirmware& info : manifest.carrier_firmware()) { |
| if (info.device_id().empty() || info.filename().empty() || |
| info.version().empty() || info.carrier_id().empty() || |
| !Compression_IsValid(info.compression())) { |
| LOG(ERROR) << "Found malformed carrier firmware manifest entry"; |
| return false; |
| } |
| |
| DeviceType type{info.device_id(), info.variant()}; |
| (*out_sorted)[type].carrier_firmware.push_back(&info); |
| } |
| |
| return true; |
| } |
| |
| bool ConstructCache(const DeviceEntries& entries, |
| const base::FilePath& directory_path, |
| DeviceFirmwareCache* out_cache) { |
| std::unique_ptr<FirmwareFileInfo> main_info; |
| if (entries.main_firmware) { |
| // Create the firmware file info for the main firmware. |
| auto compression = |
| ToFirmwareFileInfoCompression(entries.main_firmware->compression()); |
| if (!compression.has_value()) |
| return false; |
| |
| main_info = std::make_unique<FirmwareFileInfo>( |
| directory_path.Append(entries.main_firmware->filename()), |
| entries.main_firmware->version(), compression.value()); |
| } |
| |
| DeviceFirmwareCache::CarrierIndex* index = &out_cache->carrier_firmware; |
| for (const CarrierFirmware* carrier_firmware : entries.carrier_firmware) { |
| // Convert the manifest entry into a FirmwareFileInfo. |
| auto compression = |
| ToFirmwareFileInfoCompression(carrier_firmware->compression()); |
| if (!compression.has_value()) |
| return false; |
| |
| auto carrier_info = std::make_unique<FirmwareFileInfo>( |
| directory_path.Append(carrier_firmware->filename()), |
| carrier_firmware->version(), compression.value()); |
| |
| // Add the carrier (and if applicable, main) firmware to the cache under |
| // the carrier ID for this entry. |
| for (const std::string& supported_carrier : |
| carrier_firmware->carrier_id()) { |
| if (index->count(supported_carrier) > 0) { |
| LOG(ERROR) << "Duplicate carrier firmware entry for carrier " |
| << supported_carrier; |
| // It's possible that we've left a dangling pointer to carrier_info |
| // in another carrier's mapping, so we should clear the index here in |
| // case a user does the wrong thing and uses the invalid result. |
| index->clear(); |
| return false; |
| } |
| |
| index->emplace(supported_carrier, carrier_info.get()); |
| if (main_info) |
| out_cache->main_firmware.emplace(supported_carrier, main_info.get()); |
| } |
| out_cache->all_files.push_back(std::move(carrier_info)); |
| } |
| |
| // Add the main FW for generic carriers if we didn't already do that, and |
| // ensure that we put the main firmware in the cache's list. |
| if (main_info) { |
| if (index->count(FirmwareDirectory::kGenericCarrierId) == 0) |
| index->emplace(FirmwareDirectory::kGenericCarrierId, main_info.get()); |
| out_cache->all_files.push_back(std::move(main_info)); |
| } |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| base::Optional<FirmwareFileInfo::Compression> ToFirmwareFileInfoCompression( |
| Compression compression) { |
| switch (compression) { |
| case Compression::NONE: |
| return FirmwareFileInfo::Compression::NONE; |
| case Compression::XZ: |
| return FirmwareFileInfo::Compression::XZ; |
| default: |
| std::string name = Compression_Name(compression); |
| if (name.empty()) |
| name = base::NumberToString(compression); |
| LOG(ERROR) << "Unsupported compression: " << name; |
| return base::nullopt; |
| } |
| } |
| |
| bool ParseFirmwareManifest(const base::FilePath& manifest, |
| FirmwareIndex* index) { |
| FirmwareManifest manifest_proto; |
| if (!brillo::ReadTextProtobuf(manifest, &manifest_proto)) |
| return false; |
| |
| base::FilePath directory = manifest.DirName(); |
| std::map<DeviceType, DeviceEntries> sorted; |
| |
| if (!SortByDevice(manifest_proto, &sorted)) |
| return false; |
| |
| for (const auto& device : sorted) { |
| DeviceFirmwareCache cache; |
| if (!ConstructCache(device.second, directory, &cache)) |
| return false; |
| |
| (*index)[device.first] = std::move(cache); |
| } |
| |
| return true; |
| } |
| |
| } // namespace modemfwd |