// Copyright 2017 The Chromium 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 "tools/traffic_annotation/auditor/instance.h"

#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "third_party/protobuf/src/google/protobuf/io/tokenizer.h"
#include "third_party/protobuf/src/google/protobuf/text_format.h"
#include "tools/traffic_annotation/auditor/traffic_annotation_auditor.h"

namespace {

// This class receives parsing errors from google::protobuf::TextFormat::Parser
// which is used during protobuf deserialization.
class SimpleErrorCollector : public google::protobuf::io::ErrorCollector {
 public:
  SimpleErrorCollector(int proto_starting_line)
      : google::protobuf::io::ErrorCollector(),
        line_offset_(proto_starting_line) {}

  ~SimpleErrorCollector() override = default;

  void AddError(int line,
                google::protobuf::io::ColumnNumber column,
                const std::string& message) override {
    AddMessage(line, column, message);
  }

  void AddWarning(int line,
                  google::protobuf::io::ColumnNumber column,
                  const std::string& message) override {
    AddMessage(line, column, message);
  }

  std::string GetMessage() { return message_; }

 private:
  void AddMessage(int line,
                  google::protobuf::io::ColumnNumber column,
                  const std::string& message) {
    message_ += base::StringPrintf(
        "%sLine %i, column %i, %s", message_.length() ? " " : "",
        line_offset_ + line, static_cast<int>(column), message.c_str());
  }

  std::string message_;
  int line_offset_;
};

// This macro merges the content of one string field from two annotations.
// DST->FLD is the destination field, and SRD->FLD is the source field.
#define MERGE_STRING_FIELDS(SRC, DST, FLD)                           \
  if (!SRC.FLD().empty()) {                                          \
    if (!DST->FLD().empty()) {                                       \
      DST->set_##FLD(base::StringPrintf("%s\n%s", SRC.FLD().c_str(), \
                                        DST->FLD().c_str()));        \
    } else {                                                         \
      DST->set_##FLD(SRC.FLD());                                     \
    }                                                                \
  }

std::map<int, std::string> kSemanticsFields = {
    {traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
         kSenderFieldNumber,
     "semantics::sender"},
    {traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
         kDescriptionFieldNumber,
     "semantics::description"},
    {traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
         kTriggerFieldNumber,
     "semantics::trigger"},
    {traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
         kDataFieldNumber,
     "semantics::data"},
    {traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
         kDestinationFieldNumber,
     "semantics::destination"},
};

std::map<int, std::string> kPolicyFields = {
    {traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
         kCookiesAllowedFieldNumber,
     "policy::cookies_allowed"},
    {traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
         kCookiesStoreFieldNumber,
     "policy::cookies_store"},
    {traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
         kSettingFieldNumber,
     "policy::setting"},
    {traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
         kChromePolicyFieldNumber,
     "policy::chrome_policy"},
    {traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
         kPolicyExceptionJustificationFieldNumber,
     "policy::policy_exception_justification"},
};

std::vector<int> kChromePolicyFields = {
    traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
        kChromePolicyFieldNumber,
    traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
        kPolicyExceptionJustificationFieldNumber};

}  // namespace

AnnotationInstance::AnnotationInstance()
    : type(Type::ANNOTATION_COMPLETE),
      unique_id_hash_code(0),
      second_id_hash_code(0),
      archive_content_hash_code(0),
      is_loaded_from_archive(false),
      is_merged(false) {}

AnnotationInstance::AnnotationInstance(const AnnotationInstance& other)
    : proto(other.proto),
      type(other.type),
      second_id(other.second_id),
      unique_id_hash_code(other.unique_id_hash_code),
      second_id_hash_code(other.second_id_hash_code),
      archive_content_hash_code(other.archive_content_hash_code),
      is_loaded_from_archive(other.is_loaded_from_archive),
      is_merged(other.is_merged){};

AuditorResult AnnotationInstance::Deserialize(
    const std::vector<std::string>& serialized_lines,
    int start_line,
    int end_line) {
  if (end_line - start_line < 7) {
    return AuditorResult(AuditorResult::Type::ERROR_FATAL,
                         "Not enough lines to deserialize annotation.");
  }

  // Extract header lines.
  const std::string& file_path = serialized_lines[start_line++];
  const std::string& function_context = serialized_lines[start_line++];
  int line_number;
  base::StringToInt(serialized_lines[start_line++], &line_number);
  std::string function_type = serialized_lines[start_line++];
  const std::string& unique_id = serialized_lines[start_line++];
  second_id = serialized_lines[start_line++];

  // Decode function type.
  if (function_type == "Definition") {
    type = Type::ANNOTATION_COMPLETE;
  } else if (function_type == "Partial") {
    type = Type::ANNOTATION_PARTIAL;
  } else if (function_type == "Completing") {
    type = Type::ANNOTATION_COMPLETING;
  } else if (function_type == "BranchedCompleting") {
    type = Type::ANNOTATION_BRANCHED_COMPLETING;
  } else {
    return AuditorResult(AuditorResult::Type::ERROR_FATAL,
                         base::StringPrintf("Unexpected function type: %s",
                                            function_type.c_str()));
  }

  // Process test tags.
  unique_id_hash_code = TrafficAnnotationAuditor::ComputeHashValue(unique_id);
  if (unique_id_hash_code == TRAFFIC_ANNOTATION_FOR_TESTS.unique_id_hash_code ||
      unique_id_hash_code ==
          PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS.unique_id_hash_code) {
    return AuditorResult(AuditorResult::Type::ERROR_TEST_ANNOTATION, "",
                         file_path, line_number);
  }

  // Process undefined tags.
  if (unique_id_hash_code == NO_TRAFFIC_ANNOTATION_YET.unique_id_hash_code ||
      unique_id_hash_code ==
          NO_PARTIAL_TRAFFIC_ANNOTATION_YET.unique_id_hash_code) {
    return AuditorResult(AuditorResult::Type::ERROR_NO_ANNOTATION, "",
                         file_path, line_number);
  }

  // Process missing tag.
  if (unique_id_hash_code == MISSING_TRAFFIC_ANNOTATION.unique_id_hash_code)
    return AuditorResult(AuditorResult::Type::ERROR_MISSING_TAG_USED, "",
                         file_path, line_number);

  // Decode serialized proto.
  std::string annotation_text = "";
  while (start_line < end_line) {
    annotation_text += serialized_lines[start_line++] + "\n";
  }

  SimpleErrorCollector error_collector(line_number);
  google::protobuf::TextFormat::Parser parser;
  parser.RecordErrorsTo(&error_collector);
  if (!parser.ParseFromString(annotation_text,
                              (google::protobuf::Message*)&proto)) {
    return AuditorResult(AuditorResult::Type::ERROR_SYNTAX,
                         error_collector.GetMessage().c_str(), file_path,
                         line_number);
  }

  // Add other fields.
  traffic_annotation::NetworkTrafficAnnotation_TrafficSource* src =
      proto.mutable_source();
  src->set_file(file_path);
  src->set_function(function_context);
  src->set_line(line_number);
  proto.set_unique_id(unique_id);
  second_id_hash_code = TrafficAnnotationAuditor::ComputeHashValue(second_id);

  return AuditorResult(AuditorResult::Type::RESULT_OK);
}

// Returns the proto field numbers of TrafficSemantics.
void AnnotationInstance::GetSemanticsFieldNumbers(
    std::set<int>* field_numbers) const {
  field_numbers->clear();

  const traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics
      semantics = proto.semantics();

  if (!semantics.sender().empty())
    field_numbers->insert(semantics.kSenderFieldNumber);

  if (!semantics.description().empty())
    field_numbers->insert(semantics.kDescriptionFieldNumber);

  if (!semantics.trigger().empty())
    field_numbers->insert(semantics.kTriggerFieldNumber);

  if (!semantics.data().empty())
    field_numbers->insert(semantics.kDataFieldNumber);

  if (semantics.destination() !=
      traffic_annotation::
          NetworkTrafficAnnotation_TrafficSemantics_Destination_UNSPECIFIED) {
    field_numbers->insert(semantics.kDestinationFieldNumber);
  }
}

// Returns the proto field numbers of TrafficPolicy.
void AnnotationInstance::GetPolicyFieldNumbers(
    std::set<int>* field_numbers) const {
  field_numbers->clear();

  const traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy policy =
      proto.policy();

  // If cookies are not allowed, the negated value of the
  // kCookiesAllowedFieldNumber is returned. As field numbers are positive, this
  // will not collide with any other value.
  if (policy.cookies_allowed() ==
      traffic_annotation::
          NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_YES) {
    field_numbers->insert(policy.kCookiesAllowedFieldNumber);
  } else if (policy.cookies_allowed() ==
             traffic_annotation::
                 NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_NO) {
    field_numbers->insert(-policy.kCookiesAllowedFieldNumber);
  }

  if (!policy.cookies_store().empty())
    field_numbers->insert(policy.kCookiesStoreFieldNumber);

  if (!policy.setting().empty())
    field_numbers->insert(policy.kSettingFieldNumber);

  if (policy.chrome_policy_size())
    field_numbers->insert(policy.kChromePolicyFieldNumber);

  if (!policy.policy_exception_justification().empty())
    field_numbers->insert(policy.kPolicyExceptionJustificationFieldNumber);
}

// Checks if an annotation has all required fields.
AuditorResult AnnotationInstance::IsComplete() const {
  std::vector<std::string> unspecifieds;
  std::string extra_texts;

  std::set<int> fields;
  GetSemanticsFieldNumbers(&fields);
  for (const auto& item : kSemanticsFields) {
    if (!base::ContainsKey(fields, item.first))
      unspecifieds.push_back(item.second);
  }

  GetPolicyFieldNumbers(&fields);
  for (const auto& item : kPolicyFields) {
    if (!base::ContainsKey(fields, item.first)) {
      // If 'cookies_allowed = NO' is provided, ignore not having
      // 'cookies_allowed = YES'.
      if (item.first ==
              traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
                  kCookiesAllowedFieldNumber &&
          base::ContainsKey(fields, -item.first))
        continue;

      // If |cookies_store| is not provided, ignore if 'cookies_allowed = NO' is
      // in the list.
      if (item.first ==
              traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
                  kCookiesStoreFieldNumber &&
          base::ContainsKey(
              fields,
              -traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
                  kCookiesAllowedFieldNumber))
        continue;

      // If either of |chrome_policy| or |policy_exception_justification| are
      // avaliable, ignore not having the other one.
      if (base::ContainsValue(kChromePolicyFields, item.first) &&
          (base::ContainsKey(fields, kChromePolicyFields[0]) ||
           base::ContainsKey(fields, kChromePolicyFields[1]))) {
        continue;
      }
      unspecifieds.push_back(item.second);
    }
  }

  if (!unspecifieds.size())
    return AuditorResult(AuditorResult::Type::RESULT_OK);

  std::string error_text;
  for (const std::string& item : unspecifieds)
    error_text += item + ", ";
  error_text = error_text.substr(0, error_text.length() - 2);
  return AuditorResult(AuditorResult::Type::ERROR_INCOMPLETE_ANNOTATION,
                       error_text, proto.source().file(),
                       proto.source().line());
}

// Checks if annotation fields are consistent.
AuditorResult AnnotationInstance::IsConsistent() const {
  const traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy policy =
      proto.policy();

  if (policy.cookies_allowed() ==
          traffic_annotation::
              NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_NO &&
      policy.cookies_store().size()) {
    return AuditorResult(
        AuditorResult::Type::ERROR_INCONSISTENT_ANNOTATION,
        "Cookies store is specified while cookies are not allowed.",
        proto.source().file(), proto.source().line());
  }

  if (policy.chrome_policy_size() &&
      policy.policy_exception_justification().size()) {
    return AuditorResult(
        AuditorResult::Type::ERROR_INCONSISTENT_ANNOTATION,
        "Both chrome policies and policy exception justification are present.",
        proto.source().file(), proto.source().line());
  }

  return AuditorResult(AuditorResult::Type::RESULT_OK);
}

bool AnnotationInstance::IsCompletableWith(
    const AnnotationInstance& other) const {
  if (type != AnnotationInstance::Type::ANNOTATION_PARTIAL || second_id.empty())
    return false;
  if (other.type == AnnotationInstance::Type::ANNOTATION_COMPLETING) {
    return second_id_hash_code == other.unique_id_hash_code;
  } else if (other.type ==
             AnnotationInstance::Type::ANNOTATION_BRANCHED_COMPLETING) {
    return second_id_hash_code == other.second_id_hash_code;
  } else {
    return false;
  }
}

AuditorResult AnnotationInstance::CreateCompleteAnnotation(
    AnnotationInstance& completing_annotation,
    AnnotationInstance* combination) const {
  DCHECK(IsCompletableWith(completing_annotation));

  // To keep the source information meta data, if completing annotation is of
  // type COMPLETING, keep |this| as the main and the other as completing.
  // But if compliting annotation is of type BRANCHED_COMPLETING, reverse
  // the order.
  const AnnotationInstance* other;
  if (completing_annotation.type ==
      AnnotationInstance::Type::ANNOTATION_COMPLETING) {
    *combination = *this;
    other = &completing_annotation;
  } else {
    *combination = completing_annotation;
    other = this;
  }

  combination->is_merged = true;
  combination->type = AnnotationInstance::Type::ANNOTATION_COMPLETE;
  combination->second_id.clear();
  combination->second_id_hash_code = 0;

  // Update comment.
  std::string new_comments = combination->proto.comments();
  if (!other->proto.comments().empty()) {
    if (!new_comments.empty())
      new_comments += "\n";
    new_comments += other->proto.comments();
  }
  if (!new_comments.empty())
    new_comments += "\n";
  new_comments += base::StringPrintf(
      "This annotation is a merge of the following two annotations:\n"
      "'%s' in '%s:%i' and '%s' in '%s:%i'.",
      proto.unique_id().c_str(), proto.source().file().c_str(),
      proto.source().line(), completing_annotation.proto.unique_id().c_str(),
      completing_annotation.proto.source().file().c_str(),
      completing_annotation.proto.source().line());
  combination->proto.set_comments(new_comments);

  // Copy TrafficSemantics.
  const traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics
      src_semantics = other->proto.semantics();
  traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics* dst_semantics =
      combination->proto.mutable_semantics();

  MERGE_STRING_FIELDS(src_semantics, dst_semantics, sender);
  MERGE_STRING_FIELDS(src_semantics, dst_semantics, description);
  MERGE_STRING_FIELDS(src_semantics, dst_semantics, trigger);
  MERGE_STRING_FIELDS(src_semantics, dst_semantics, data);
  MERGE_STRING_FIELDS(src_semantics, dst_semantics, destination_other);

  // If destination is not specified in dst_semantics, get it from
  // src_semantics. If both are specified and they differ, issue error.
  if (dst_semantics->destination() ==
      traffic_annotation::
          NetworkTrafficAnnotation_TrafficSemantics_Destination_UNSPECIFIED) {
    dst_semantics->set_destination(src_semantics.destination());
  } else if (
      src_semantics.destination() !=
          traffic_annotation::
              NetworkTrafficAnnotation_TrafficSemantics_Destination_UNSPECIFIED &&
      src_semantics.destination() != dst_semantics->destination()) {
    AuditorResult error(
        AuditorResult::Type::ERROR_MERGE_FAILED,
        "Annotations contain different semantics::destination values.");
    error.AddDetail(proto.unique_id());
    error.AddDetail(completing_annotation.proto.unique_id());
    return error;
  }

  // Copy TrafficPolicy.
  const traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy src_policy =
      other->proto.policy();
  traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy* dst_policy =
      combination->proto.mutable_policy();

  MERGE_STRING_FIELDS(src_policy, dst_policy, cookies_store);
  MERGE_STRING_FIELDS(src_policy, dst_policy, setting);

  // Set cookies_allowed to the superseding value of both.
  dst_policy->set_cookies_allowed(
      std::max(dst_policy->cookies_allowed(), src_policy.cookies_allowed()));
  DCHECK_GT(traffic_annotation::
                NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_YES,
            traffic_annotation::
                NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_NO);
  DCHECK_GT(
      traffic_annotation::
          NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_NO,
      traffic_annotation::
          NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_UNSPECIFIED);

  for (int i = 0; i < src_policy.chrome_policy_size(); i++)
    *dst_policy->add_chrome_policy() = src_policy.chrome_policy(i);

  if (!src_policy.policy_exception_justification().empty()) {
    if (!dst_policy->policy_exception_justification().empty()) {
      dst_policy->set_policy_exception_justification(
          dst_policy->policy_exception_justification() + "\n");
    }
    dst_policy->set_policy_exception_justification(
        dst_policy->policy_exception_justification() +
        src_policy.policy_exception_justification());
  }

  return AuditorResult::Type::RESULT_OK;
}

int AnnotationInstance::GetContentHashCode() const {
  if (is_loaded_from_archive)
    return archive_content_hash_code;

  traffic_annotation::NetworkTrafficAnnotation source_free_proto = proto;
  source_free_proto.clear_source();
  std::string content;
  google::protobuf::TextFormat::PrintToString(source_free_proto, &content);
  return TrafficAnnotationAuditor::ComputeHashValue(content);
}

// static
AnnotationInstance AnnotationInstance::LoadFromArchive(
    AnnotationInstance::Type type,
    const std::string& unique_id,
    int unique_id_hash_code,
    int second_id_hash_code,
    int content_hash_code,
    const std::set<int>& semantics_fields,
    const std::set<int>& policy_fields,
    const std::string& file_path) {
  AnnotationInstance annotation;

  annotation.is_loaded_from_archive = true;
  annotation.type = type;
  annotation.proto.set_unique_id(unique_id);
  annotation.proto.mutable_source()->set_file(file_path);
  annotation.unique_id_hash_code = unique_id_hash_code;

  if (annotation.NeedsTwoIDs()) {
    annotation.second_id_hash_code = second_id_hash_code;
    // As we don't have the actual second id, a generated value is written to
    // ensure that the field is not empty. Current set of auditor tests and
    // unittests just check if this field is not empty when a second id is
    // required. Tests that are based on matching the ids (like
    // partial/completing annotations) are based on the hash codes.
    annotation.second_id =
        base::StringPrintf("ARCHIVED_ID_%i", annotation.second_id_hash_code);
  }

  annotation.archive_content_hash_code = content_hash_code;

  // The values of the semantics and policy are set so that the tests would know
  // which fields were available before archive.
  if (base::ContainsKey(
          semantics_fields,
          traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
              kSenderFieldNumber)) {
    annotation.proto.mutable_semantics()->set_sender("[Archived]");
  }

  if (base::ContainsKey(
          semantics_fields,
          traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
              kDescriptionFieldNumber)) {
    annotation.proto.mutable_semantics()->set_description("[Archived]");
  }

  if (base::ContainsKey(
          semantics_fields,
          traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
              kTriggerFieldNumber)) {
    annotation.proto.mutable_semantics()->set_trigger("[Archived]");
  }

  if (base::ContainsKey(
          semantics_fields,
          traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
              kDataFieldNumber)) {
    annotation.proto.mutable_semantics()->set_data("[Archived]");
  }

  if (base::ContainsKey(
          semantics_fields,
          traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
              kDestinationFieldNumber)) {
    annotation.proto.mutable_semantics()->set_destination(
        traffic_annotation::
            NetworkTrafficAnnotation_TrafficSemantics_Destination_WEBSITE);
  }

  if (base::ContainsKey(
          policy_fields,
          traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
              kCookiesAllowedFieldNumber)) {
    annotation.proto.mutable_policy()->set_cookies_allowed(
        traffic_annotation::
            NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_YES);
  }

  if (base::ContainsKey(
          policy_fields,
          -traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
              kCookiesAllowedFieldNumber)) {
    annotation.proto.mutable_policy()->set_cookies_allowed(
        traffic_annotation::
            NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_NO);
  }

  if (base::ContainsKey(
          policy_fields,
          traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
              kCookiesStoreFieldNumber)) {
    annotation.proto.mutable_policy()->set_cookies_store("[Archived]");
  }

  if (base::ContainsKey(
          policy_fields,
          traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
              kSettingFieldNumber)) {
    annotation.proto.mutable_policy()->set_setting("[Archived]");
  }

  if (base::ContainsKey(
          policy_fields,
          traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
              kChromePolicyFieldNumber)) {
    annotation.proto.mutable_policy()->add_chrome_policy();
  }

  if (base::ContainsKey(
          policy_fields,
          traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
              kPolicyExceptionJustificationFieldNumber)) {
    annotation.proto.mutable_policy()->set_policy_exception_justification(
        "[Archived]");
  }

  return annotation;
}

std::string AnnotationInstance::Serialize() const {
  std::string text;
  std::set<int> fields;

  text = base::StringPrintf(
      "{\tType: %i\n"
      "\tID: %s\n"
      "\tHashcode 1: %i\n"
      "\tHashcode 2: %i\n"
      "\tFrom Archive: %i\n"
      "\tSource File: %s\n",
      static_cast<int>(type), proto.unique_id().c_str(), unique_id_hash_code,
      second_id_hash_code, is_loaded_from_archive,
      proto.source().file().c_str());

  GetSemanticsFieldNumbers(&fields);
  text += "\tSemantics: ";
  for (int i : fields)
    text += base::StringPrintf("%s,", kSemanticsFields[i].c_str());

  GetPolicyFieldNumbers(&fields);
  text += "\n\tPolicies: ";
  for (int i : fields) {
    text += base::StringPrintf("%s%s,", i < 0 ? "-" : "",
                               kPolicyFields[abs(i)].c_str());
  }
  text += "\n}";

  return text;
}

std::ostream& operator<<(std::ostream& out,
                         const AnnotationInstance& instance) {
  return out << instance.Serialize();
}

CallInstance::CallInstance() : line_number(0), is_annotated(false) {}

CallInstance::CallInstance(const CallInstance& other)
    : file_path(other.file_path),
      line_number(other.line_number),
      function_context(other.function_context),
      function_name(other.function_name),
      is_annotated(other.is_annotated){};

AuditorResult CallInstance::Deserialize(
    const std::vector<std::string>& serialized_lines,
    int start_line,
    int end_line) {
  if (end_line - start_line != 5) {
    return AuditorResult(AuditorResult::Type::ERROR_FATAL,
                         "Not enough lines to deserialize call.");
  }

  file_path = serialized_lines[start_line++];
  function_context = serialized_lines[start_line++];
  int line_number_int;
  base::StringToInt(serialized_lines[start_line++], &line_number_int);
  line_number = static_cast<uint32_t>(line_number_int);
  function_name = serialized_lines[start_line++];
  int is_annotated_int;
  base::StringToInt(serialized_lines[start_line++], &is_annotated_int);
  is_annotated = is_annotated_int != 0;
  return AuditorResult(AuditorResult::Type::RESULT_OK);
}

AssignmentInstance::AssignmentInstance() : line_number(0) {}

AssignmentInstance::AssignmentInstance(const AssignmentInstance& other)
    : file_path(other.file_path),
      line_number(other.line_number),
      function_context(other.function_context){};

AuditorResult AssignmentInstance::Deserialize(
    const std::vector<std::string>& serialized_lines,
    int start_line,
    int end_line) {
  if (end_line - start_line != 3) {
    return AuditorResult(AuditorResult::Type::ERROR_FATAL,
                         "Not enough lines to deserialize assignment.");
  }
  file_path = serialized_lines[start_line++];
  function_context = serialized_lines[start_line++];
  int line_number_int;
  base::StringToInt(serialized_lines[start_line++], &line_number_int);
  line_number = static_cast<uint32_t>(line_number_int);
  return AuditorResult(AuditorResult::Type::RESULT_OK);
}