// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "sandbox/mac/sandbox_serializer.h"

#include <optional>
#include <string>
#include <vector>

#include "sandbox/mac/sandbox_logging.h"
#include "sandbox/mac/seatbelt.h"


namespace sandbox {

namespace {

// TODO(crbug.com/393161124): replace with different encoding.
void EncodeVarInt(uint64_t from, std::string* into) {
  do {
    unsigned char c = from & 0x7f;
    from >>= 7;
    if (from) {
      c |= 0x80;
    }
    into->push_back(c);
  } while (from);
}

bool DecodeVarInt(std::string_view* from, uint64_t* into) {
  std::string_view::const_iterator it = from->begin();
  int shift = 0;
  uint64_t ret = 0;
  do {
    // Shifting 32 or more bits is undefined behavior.
    if (it == from->end() || shift >= 32) {
      return false;
    }

    unsigned char c = *it;
    ret |= static_cast<uint64_t>(c & 0x7f) << shift;
    shift += 7;
  } while (*it++ & 0x80);
  *into = ret;
  from->remove_prefix(it - from->begin());
  return true;
}

void EncodeString(const std::string& value, std::string* into) {
  EncodeVarInt(value.length(), into);
  into->append(value);
}

bool DecodeString(std::string_view* slice, std::string* value) {
  uint64_t length;
  if (!DecodeVarInt(slice, &length) || length < 0) {
    return false;
  }
  size_t size = length;
  if (slice->size() < size) {
    return false;
  }

  value->assign(slice->data(), size);
  slice->remove_prefix(size);
  return true;
}

}  // namespace

SandboxSerializer::SandboxSerializer(Target mode) : mode_(mode) {
  if (mode_ == Target::kCompiled) {
    params_ = Seatbelt::Parameters::Create();
  }
}

SandboxSerializer::~SandboxSerializer() = default;

SandboxSerializer::DeserializedPolicy::DeserializedPolicy() = default;
SandboxSerializer::DeserializedPolicy::DeserializedPolicy(
    DeserializedPolicy&& other) = default;
SandboxSerializer::DeserializedPolicy::~DeserializedPolicy() = default;

void SandboxSerializer::SetProfile(const std::string& profile) {
  profile_ = profile;
}

bool SandboxSerializer::SetBooleanParameter(const std::string& key,
                                            bool value) {
  return SetParameter(key, value ? "TRUE" : "FALSE");
}

bool SandboxSerializer::SetParameter(const std::string& key,
                                     const std::string& value) {
  // Regardless of the mode, add the strings to the map because
  // Seatbelt::Parameters::Set does not copy the strings, which means temporary
  // std::string references need to be owned somewhere.
  auto it = source_params_.insert({key, value});

  if (mode_ == Target::kCompiled && it.second) {
    if (!params_.Set(it.first->first.c_str(), it.first->second.c_str())) {
      source_params_.erase(it.first);
      return false;
    }
  }

  return it.second;
}

bool SandboxSerializer::SerializePolicy(std::string& serialized_policy,
                                        std::string& error) {
  constexpr size_t kBytesForModeVarInt = 1;
  constexpr size_t kMaxBytesForVarInt = (sizeof(uint64_t) * 8 + 6) / 7;
  serialized_policy.clear();

  switch (mode_) {
    case Target::kSource:
      serialized_policy.reserve(kBytesForModeVarInt + kMaxBytesForVarInt +
                                profile_.size() + 512 * source_params_.size());
      EncodeVarInt(static_cast<int>(Target::kSource), &serialized_policy);
      EncodeString(profile_, &serialized_policy);
      for (const auto& [key, value] : source_params_) {
        EncodeString(key, &serialized_policy);
        EncodeString(value, &serialized_policy);
      }
      serialized_policy.shrink_to_fit();
      return true;
    case Target::kCompiled:
      std::string compiled;
      if (!Seatbelt::Compile(profile_.c_str(), params_, compiled, &error)) {
        return false;
      }
      serialized_policy.reserve(kBytesForModeVarInt + kMaxBytesForVarInt +
                                compiled.size());
      EncodeVarInt(static_cast<int>(Target::kCompiled), &serialized_policy);
      EncodeString(compiled, &serialized_policy);
      return true;
  }
}

// static
std::optional<SandboxSerializer::DeserializedPolicy>
SandboxSerializer::DeserializePolicy(const std::string& serialized_policy,
                                     std::string& error) {
  std::string_view remaining_serialized_policy = serialized_policy;
  uint64_t mode;
  if (!DecodeVarInt(&remaining_serialized_policy, &mode)) {
    error = "unexpected serialized policy mode";
    return std::nullopt;
  }
  if (mode > static_cast<uint64_t>(Target::kMaxValue)) {
    error = "unexpected policy mode";
    return std::nullopt;
  }
  DeserializedPolicy deserialized_policy;
  deserialized_policy.mode = static_cast<Target>(mode);
  switch (deserialized_policy.mode) {
    case Target::kCompiled:
      if (!DecodeString(&remaining_serialized_policy,
                        &(deserialized_policy.profile))) {
        error = "could not decode compiled policy string";
        return std::nullopt;
      }
      break;
    case Target::kSource:
      std::string profile;
      if (!DecodeString(&remaining_serialized_policy,
                        &(deserialized_policy.profile))) {
        error = "could not decode source mode profile string";
        return std::nullopt;
      }
      while (!remaining_serialized_policy.empty()) {
        std::string key;
        if (!DecodeString(&remaining_serialized_policy, &key)) {
          error = "could not decode source mode parameter key";
          return std::nullopt;
        }
        deserialized_policy.params.push_back(key);

        std::string value;
        if (!DecodeString(&remaining_serialized_policy, &value)) {
          error = "could not decode source mode parameter value";
          return std::nullopt;
        }
        deserialized_policy.params.push_back(value);
      }
      break;
  }
  return deserialized_policy;
}

// static
bool SandboxSerializer::ApplySerializedPolicy(
    const std::string& serialized_policy) {
  std::string error;
  std::optional<DeserializedPolicy> deserialized_policy =
      DeserializePolicy(serialized_policy, error);
  if (!deserialized_policy) {
    logging::Error("SandboxSerializer: Failed to deserialize policy: %s",
                   error.c_str());
    return false;
  }

  switch (deserialized_policy->mode) {
    case Target::kCompiled:
      if (!Seatbelt::ApplyCompiledProfile(deserialized_policy->profile,
                                          &error)) {
        logging::Error("SandboxSerializer: Failed to apply compiled policy: %s",
                       error.c_str());
        return false;
      }
      break;
    case Target::kSource:
      if (!Seatbelt::InitWithParams(deserialized_policy->profile, 0,
                                    deserialized_policy->params, &error)) {
        logging::Error(
            "SandboxSerializer: Failed to initialize sandbox with source mode "
            "policy: %s",
            error.c_str());
        return false;
      }
      break;
  }
  return true;
}

}  // namespace sandbox
