| // Copyright 2019 The Fuchsia 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 "src/media/audio/audio_core/policy_loader.h" |
| |
| #include <fcntl.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/default.h> |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <fbl/unique_fd.h> |
| #include <rapidjson/error/en.h> |
| #include <rapidjson/ostreamwrapper.h> |
| #include <rapidjson/schema.h> |
| #include <rapidjson/stringbuffer.h> |
| #include <rapidjson/writer.h> |
| |
| #include "src/media/audio/audio_core/schema/audio_policy_schema.inl" |
| |
| namespace media::audio { |
| |
| namespace { |
| static constexpr size_t kMaxSettingFileSize = (64 << 10); |
| static const std::string kPolicyPath = "/config/data/audio_policy.json"; |
| |
| std::optional<fuchsia::media::AudioRenderUsage> JsonToRenderUsage(const rapidjson::Value& usage) { |
| static_assert(fuchsia::media::RENDER_USAGE_COUNT == 5, |
| "New Render Usage(s) added to fidl without updating config loader"); |
| |
| auto rule_str = usage.GetString(); |
| |
| if (!strcmp(rule_str, "BACKGROUND")) { |
| return fuchsia::media::AudioRenderUsage::BACKGROUND; |
| } else if (!strcmp(rule_str, "MEDIA")) { |
| return fuchsia::media::AudioRenderUsage::MEDIA; |
| } else if (!strcmp(rule_str, "INTERRUPTION")) { |
| return fuchsia::media::AudioRenderUsage::INTERRUPTION; |
| } else if (!strcmp(rule_str, "SYSTEM_AGENT")) { |
| return fuchsia::media::AudioRenderUsage::SYSTEM_AGENT; |
| } else if (!strcmp(rule_str, "COMMUNICATION")) { |
| return fuchsia::media::AudioRenderUsage::COMMUNICATION; |
| } else { |
| FX_LOGS(ERROR) << usage.GetString() << " not a valid AudioRenderUsage."; |
| } |
| |
| return std::nullopt; |
| } |
| |
| std::optional<fuchsia::media::AudioCaptureUsage> JsonToCaptureUsage(const rapidjson::Value& usage) { |
| static_assert(fuchsia::media::CAPTURE_USAGE_COUNT == 4, |
| "New Capture Usage(s) added to fidl without updating config loader"); |
| |
| auto rule_str = usage.GetString(); |
| |
| if (!strcmp(rule_str, "BACKGROUND")) { |
| return fuchsia::media::AudioCaptureUsage::BACKGROUND; |
| } else if (!strcmp(rule_str, "FOREGROUND")) { |
| return fuchsia::media::AudioCaptureUsage::FOREGROUND; |
| } else if (!strcmp(rule_str, "SYSTEM_AGENT")) { |
| return fuchsia::media::AudioCaptureUsage::SYSTEM_AGENT; |
| } else if (!strcmp(rule_str, "COMMUNICATION")) { |
| return fuchsia::media::AudioCaptureUsage::COMMUNICATION; |
| } else { |
| FX_LOGS(ERROR) << usage.GetString() << " not a valid AudioCaptureUsage."; |
| } |
| |
| return std::nullopt; |
| } |
| |
| std::optional<fuchsia::media::Behavior> JsonToBehavior(const rapidjson::Value& behavior) { |
| auto behavior_str = behavior.GetString(); |
| |
| if (!strcmp(behavior_str, "NONE")) { |
| return fuchsia::media::Behavior::NONE; |
| } else if (!strcmp(behavior_str, "DUCK")) { |
| return fuchsia::media::Behavior::DUCK; |
| } else if (!strcmp(behavior_str, "MUTE")) { |
| return fuchsia::media::Behavior::MUTE; |
| } else { |
| FX_LOGS(ERROR) << behavior_str << " not a valid Behavior."; |
| } |
| |
| return std::nullopt; |
| } |
| |
| static std::optional<fuchsia::media::Usage> JsonToUsage(const rapidjson::Value& usage) { |
| fuchsia::media::Usage ret; |
| if (usage.HasMember("render_usage")) { |
| auto u = JsonToRenderUsage(usage["render_usage"]); |
| if (!u) { |
| return std::nullopt; |
| } |
| ret.set_render_usage(*u); |
| return ret; |
| } |
| |
| if (usage.HasMember("capture_usage")) { |
| auto u = JsonToCaptureUsage(usage["capture_usage"]); |
| if (!u) { |
| return std::nullopt; |
| } |
| ret.set_capture_usage(*u); |
| return ret; |
| } |
| |
| return std::nullopt; |
| } |
| |
| } // namespace |
| |
| fit::result<AudioPolicy> PolicyLoader::ParseConfig(const char* file_body) { |
| rapidjson::Document doc; |
| |
| std::vector<AudioPolicy::Rule> rules; |
| rapidjson::ParseResult parse_res = doc.Parse(file_body); |
| if (parse_res.IsError()) { |
| FX_LOGS(ERROR) << "Failed to parse settings file JSON schema: " |
| << rapidjson::GetParseError_En(parse_res.Code()) << " " << parse_res.Offset() |
| << file_body + parse_res.Offset(); |
| return fit::error(); |
| } |
| |
| rapidjson::Document doc2; |
| parse_res = doc2.Parse(kAudioPolicySchema); |
| if (parse_res.IsError()) { |
| FX_LOGS(ERROR) << "Failed to parse settings file JSON schema: " |
| << rapidjson::GetParseError_En(parse_res.Code()) << " " << parse_res.Offset() |
| << kAudioPolicySchema + parse_res.Offset(); |
| return fit::error(); |
| } |
| |
| rapidjson::SchemaDocument schema_doc(doc2); |
| rapidjson::SchemaValidator validator(schema_doc); |
| |
| if (!doc.Accept(validator)) { |
| FX_LOGS(ERROR) << "Schema validation error when reading policy settings."; |
| |
| return fit::error(); |
| } |
| |
| const rapidjson::Value& rules_json = doc["audio_policy_rules"]; |
| if (!rules_json.IsArray()) { |
| return fit::error(); |
| } |
| for (auto& rule_json : rules_json.GetArray()) { |
| auto& rule = rules.emplace_back(); |
| if (!rule_json.IsObject()) { |
| return fit::error(); |
| } |
| if (rule_json.HasMember("active")) { |
| auto active = JsonToUsage(rule_json["active"]); |
| if (!active) { |
| FX_LOGS(ERROR) << "Rule `active` object invalid."; |
| return fit::error(); |
| } |
| rule.active = std::move(*active); |
| } else { |
| FX_LOGS(ERROR) << "Rule `active` object missing."; |
| return fit::error(); |
| } |
| |
| if (rule_json.HasMember("affected")) { |
| auto affected = JsonToUsage(rule_json["affected"]); |
| if (!affected) { |
| FX_LOGS(ERROR) << "Rule `affected` object invalid."; |
| return fit::error(); |
| } |
| rule.affected = std::move(*affected); |
| } else { |
| FX_LOGS(ERROR) << "Rule `affected` object missing."; |
| return fit::error(); |
| } |
| |
| if (rule_json.HasMember("behavior")) { |
| auto behavior = JsonToBehavior(rule_json["behavior"]); |
| if (!behavior) { |
| FX_LOGS(ERROR) << "Rule `behavior` object invalid."; |
| return fit::error(); |
| } |
| rule.behavior = std::move(*behavior); |
| } else { |
| FX_LOGS(ERROR) << "Rule `behavior` object missing."; |
| return fit::error(); |
| } |
| } |
| |
| FX_LOGS(INFO) << "Successfully loaded " << rules.size() << " rules."; |
| |
| return fit::ok(AudioPolicy{std::move(rules)}); |
| } |
| |
| fit::result<std::optional<AudioPolicy>> LoadConfigFromFile(const std::string config) { |
| fbl::unique_fd json_file; |
| json_file.reset(open(config.c_str(), O_RDONLY)); |
| |
| if (!json_file.is_valid()) { |
| return fit::ok(std::nullopt); |
| } |
| |
| // Figure out the size of the file, then allocate storage for reading the |
| // whole thing. |
| off_t file_size = lseek(json_file.get(), 0, SEEK_END); |
| if ((file_size <= 0)) { |
| FX_LOGS(ERROR) << "Could not find filesize"; |
| return fit::error(); |
| } |
| |
| if (static_cast<size_t>(file_size) > kMaxSettingFileSize) { |
| FX_LOGS(ERROR) << "Config file too large. Max file size: " << kMaxSettingFileSize |
| << " Config file size: " << file_size; |
| return fit::error(); |
| } |
| |
| if (lseek(json_file.get(), 0, SEEK_SET) != 0) { |
| FX_LOGS(ERROR) << "Failed to seek to 0."; |
| return fit::error(); |
| } |
| |
| // Allocate the buffer and read in the contents. |
| auto buffer = std::make_unique<char[]>(file_size + 1); |
| if (read(json_file.get(), buffer.get(), file_size) != file_size) { |
| FX_LOGS(ERROR) << "Failed to read buffer."; |
| return fit::error(); |
| } |
| buffer[file_size] = 0; |
| |
| auto result = PolicyLoader::ParseConfig(buffer.get()); |
| if (result.is_error()) { |
| return fit::error(); |
| } |
| return fit::ok(std::make_optional(result.take_value())); |
| } |
| |
| AudioPolicy PolicyLoader::LoadPolicy() { |
| auto result = LoadConfigFromFile(kPolicyPath); |
| if (result.is_ok()) { |
| auto maybe_policy = result.take_value(); |
| if (maybe_policy.has_value()) { |
| FX_LOGS(INFO) << "Loaded policy with " << maybe_policy->rules().size() << " rules."; |
| return std::move(*maybe_policy); |
| } else { |
| FX_LOGS(INFO) << "No policy found; using default."; |
| return AudioPolicy(); |
| } |
| } |
| |
| FX_LOGS(WARNING) << "Failed to load audio policy from " << kPolicyPath << ", using default."; |
| return AudioPolicy(); |
| } |
| |
| } // namespace media::audio |