//
// Copyright 2019 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include <grpc/support/port_platform.h>

#include "src/core/ext/filters/client_channel/xds/xds_bootstrap.h"

#include <errno.h>
#include <stdlib.h>

#include <grpc/support/string_util.h>

#include "src/core/lib/gpr/env.h"
#include "src/core/lib/iomgr/load_file.h"
#include "src/core/lib/slice/slice_internal.h"

namespace grpc_core {

std::unique_ptr<XdsBootstrap> XdsBootstrap::ReadFromFile(grpc_error** error) {
  grpc_core::UniquePtr<char> path(gpr_getenv("GRPC_XDS_BOOTSTRAP"));
  if (path == nullptr) {
    *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
        "GRPC_XDS_BOOTSTRAP env var not set");
    return nullptr;
  }
  grpc_slice contents;
  *error = grpc_load_file(path.get(), /*add_null_terminator=*/true, &contents);
  if (*error != GRPC_ERROR_NONE) return nullptr;
  return MakeUnique<XdsBootstrap>(contents, error);
}

XdsBootstrap::XdsBootstrap(grpc_slice contents, grpc_error** error)
    : contents_(contents) {
  tree_ = grpc_json_parse_string_with_len(
      reinterpret_cast<char*>(GPR_SLICE_START_PTR(contents_)),
      GPR_SLICE_LENGTH(contents_));
  if (tree_ == nullptr) {
    *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
        "failed to parse bootstrap file JSON");
    return;
  }
  if (tree_->type != GRPC_JSON_OBJECT || tree_->key != nullptr) {
    *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
        "malformed JSON in bootstrap file");
    return;
  }
  InlinedVector<grpc_error*, 1> error_list;
  bool seen_xds_server = false;
  bool seen_node = false;
  for (grpc_json* child = tree_->child; child != nullptr; child = child->next) {
    if (child->key == nullptr) {
      error_list.push_back(
          GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON key is null"));
    } else if (strcmp(child->key, "xds_server") == 0) {
      if (child->type != GRPC_JSON_OBJECT) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "\"xds_server\" field is not an object"));
      }
      if (seen_xds_server) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "duplicate \"xds_server\" field"));
      }
      seen_xds_server = true;
      grpc_error* parse_error = ParseXdsServer(child);
      if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
    } else if (strcmp(child->key, "node") == 0) {
      if (child->type != GRPC_JSON_OBJECT) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "\"node\" field is not an object"));
      }
      if (seen_node) {
        error_list.push_back(
            GRPC_ERROR_CREATE_FROM_STATIC_STRING("duplicate \"node\" field"));
      }
      seen_node = true;
      grpc_error* parse_error = ParseNode(child);
      if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
    }
  }
  if (!seen_xds_server) {
    error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
        "\"xds_server\" field not present"));
  }
  *error = GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing xds bootstrap file",
                                         &error_list);
}

XdsBootstrap::~XdsBootstrap() {
  grpc_json_destroy(tree_);
  grpc_slice_unref_internal(contents_);
}

grpc_error* XdsBootstrap::ParseXdsServer(grpc_json* json) {
  InlinedVector<grpc_error*, 1> error_list;
  server_uri_ = nullptr;
  bool seen_channel_creds = false;
  for (grpc_json* child = json->child; child != nullptr; child = child->next) {
    if (child->key == nullptr) {
      error_list.push_back(
          GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON key is null"));
    } else if (strcmp(child->key, "server_uri") == 0) {
      if (child->type != GRPC_JSON_STRING) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "\"server_uri\" field is not a string"));
      }
      if (server_uri_ != nullptr) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "duplicate \"server_uri\" field"));
      }
      server_uri_ = child->value;
    } else if (strcmp(child->key, "channel_creds") == 0) {
      if (child->type != GRPC_JSON_ARRAY) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "\"channel_creds\" field is not an array"));
      }
      if (seen_channel_creds) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "duplicate \"channel_creds\" field"));
      }
      seen_channel_creds = true;
      grpc_error* parse_error = ParseChannelCredsArray(child);
      if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
    }
  }
  if (server_uri_ == nullptr) {
    error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
        "\"server_uri\" field not present"));
  }
  return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"xds_server\" object",
                                       &error_list);
}

grpc_error* XdsBootstrap::ParseChannelCredsArray(grpc_json* json) {
  InlinedVector<grpc_error*, 1> error_list;
  size_t idx = 0;
  for (grpc_json *child = json->child; child != nullptr;
       child = child->next, ++idx) {
    if (child->key != nullptr) {
      char* msg;
      gpr_asprintf(&msg, "array element %" PRIuPTR " key is not null", idx);
      error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg));
    }
    if (child->type != GRPC_JSON_OBJECT) {
      char* msg;
      gpr_asprintf(&msg, "array element %" PRIuPTR " is not an object", idx);
      error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg));
    } else {
      grpc_error* parse_error = ParseChannelCreds(child, idx);
      if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
    }
  }
  return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"channel_creds\" array",
                                       &error_list);
}

grpc_error* XdsBootstrap::ParseChannelCreds(grpc_json* json, size_t idx) {
  InlinedVector<grpc_error*, 1> error_list;
  ChannelCreds channel_creds;
  for (grpc_json* child = json->child; child != nullptr; child = child->next) {
    if (child->key == nullptr) {
      error_list.push_back(
          GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON key is null"));
    } else if (strcmp(child->key, "type") == 0) {
      if (child->type != GRPC_JSON_STRING) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "\"type\" field is not a string"));
      }
      if (channel_creds.type != nullptr) {
        error_list.push_back(
            GRPC_ERROR_CREATE_FROM_STATIC_STRING("duplicate \"type\" field"));
      }
      channel_creds.type = child->value;
    } else if (strcmp(child->key, "config") == 0) {
      if (child->type != GRPC_JSON_OBJECT) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "\"config\" field is not an object"));
      }
      if (channel_creds.config != nullptr) {
        error_list.push_back(
            GRPC_ERROR_CREATE_FROM_STATIC_STRING("duplicate \"config\" field"));
      }
      channel_creds.config = child;
    }
  }
  if (channel_creds.type != nullptr) channel_creds_.push_back(channel_creds);
  // Can't use GRPC_ERROR_CREATE_FROM_VECTOR() here, because the error
  // string is not static in this case.
  if (error_list.empty()) return GRPC_ERROR_NONE;
  char* msg;
  gpr_asprintf(&msg, "errors parsing index %" PRIuPTR, idx);
  grpc_error* error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
  gpr_free(msg);
  for (size_t i = 0; i < error_list.size(); ++i) {
    error = grpc_error_add_child(error, error_list[i]);
  }
  return error;
}

grpc_error* XdsBootstrap::ParseNode(grpc_json* json) {
  InlinedVector<grpc_error*, 1> error_list;
  node_ = MakeUnique<Node>();
  bool seen_metadata = false;
  bool seen_locality = false;
  for (grpc_json* child = json->child; child != nullptr; child = child->next) {
    if (child->key == nullptr) {
      error_list.push_back(
          GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON key is null"));
    } else if (strcmp(child->key, "id") == 0) {
      if (child->type != GRPC_JSON_STRING) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "\"id\" field is not a string"));
      }
      if (node_->id != nullptr) {
        error_list.push_back(
            GRPC_ERROR_CREATE_FROM_STATIC_STRING("duplicate \"id\" field"));
      }
      node_->id = child->value;
    } else if (strcmp(child->key, "cluster") == 0) {
      if (child->type != GRPC_JSON_STRING) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "\"cluster\" field is not a string"));
      }
      if (node_->cluster != nullptr) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "duplicate \"cluster\" field"));
      }
      node_->cluster = child->value;
    } else if (strcmp(child->key, "locality") == 0) {
      if (child->type != GRPC_JSON_OBJECT) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "\"locality\" field is not an object"));
      }
      if (seen_locality) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "duplicate \"locality\" field"));
      }
      seen_locality = true;
      grpc_error* parse_error = ParseLocality(child);
      if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
    } else if (strcmp(child->key, "metadata") == 0) {
      if (child->type != GRPC_JSON_OBJECT) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "\"metadata\" field is not an object"));
      }
      if (seen_metadata) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "duplicate \"metadata\" field"));
      }
      seen_metadata = true;
      InlinedVector<grpc_error*, 1> parse_errors =
          ParseMetadataStruct(child, &node_->metadata);
      if (!parse_errors.empty()) {
        grpc_error* parse_error = GRPC_ERROR_CREATE_FROM_VECTOR(
            "errors parsing \"metadata\" object", &parse_errors);
        error_list.push_back(parse_error);
      }
    }
  }
  return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"node\" object",
                                       &error_list);
}

grpc_error* XdsBootstrap::ParseLocality(grpc_json* json) {
  InlinedVector<grpc_error*, 1> error_list;
  node_->locality_region = nullptr;
  node_->locality_zone = nullptr;
  node_->locality_subzone = nullptr;
  for (grpc_json* child = json->child; child != nullptr; child = child->next) {
    if (child->key == nullptr) {
      error_list.push_back(
          GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON key is null"));
    } else if (strcmp(child->key, "region") == 0) {
      if (child->type != GRPC_JSON_STRING) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "\"region\" field is not a string"));
      }
      if (node_->locality_region != nullptr) {
        error_list.push_back(
            GRPC_ERROR_CREATE_FROM_STATIC_STRING("duplicate \"region\" field"));
      }
      node_->locality_region = child->value;
    } else if (strcmp(child->key, "zone") == 0) {
      if (child->type != GRPC_JSON_STRING) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "\"zone\" field is not a string"));
      }
      if (node_->locality_zone != nullptr) {
        error_list.push_back(
            GRPC_ERROR_CREATE_FROM_STATIC_STRING("duplicate \"zone\" field"));
      }
      node_->locality_zone = child->value;
    } else if (strcmp(child->key, "subzone") == 0) {
      if (child->type != GRPC_JSON_STRING) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "\"subzone\" field is not a string"));
      }
      if (node_->locality_subzone != nullptr) {
        error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
            "duplicate \"subzone\" field"));
      }
      node_->locality_subzone = child->value;
    }
  }
  return GRPC_ERROR_CREATE_FROM_VECTOR("errors parsing \"locality\" object",
                                       &error_list);
}

InlinedVector<grpc_error*, 1> XdsBootstrap::ParseMetadataStruct(
    grpc_json* json,
    std::map<const char*, XdsBootstrap::MetadataValue, StringLess>* result) {
  InlinedVector<grpc_error*, 1> error_list;
  for (grpc_json* child = json->child; child != nullptr; child = child->next) {
    if (child->key == nullptr) {
      error_list.push_back(
          GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON key is null"));
      continue;
    }
    if (result->find(child->key) != result->end()) {
      char* msg;
      gpr_asprintf(&msg, "duplicate metadata key \"%s\"", child->key);
      error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg));
      gpr_free(msg);
    }
    MetadataValue& value = (*result)[child->key];
    grpc_error* parse_error = ParseMetadataValue(child, 0, &value);
    if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
  }
  return error_list;
}

InlinedVector<grpc_error*, 1> XdsBootstrap::ParseMetadataList(
    grpc_json* json, std::vector<MetadataValue>* result) {
  InlinedVector<grpc_error*, 1> error_list;
  size_t idx = 0;
  for (grpc_json *child = json->child; child != nullptr;
       child = child->next, ++idx) {
    if (child->key != nullptr) {
      char* msg;
      gpr_asprintf(&msg, "JSON key is non-null for index %" PRIuPTR, idx);
      error_list.push_back(GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg));
      gpr_free(msg);
    }
    result->emplace_back();
    grpc_error* parse_error = ParseMetadataValue(child, idx, &result->back());
    if (parse_error != GRPC_ERROR_NONE) error_list.push_back(parse_error);
  }
  return error_list;
}

grpc_error* XdsBootstrap::ParseMetadataValue(grpc_json* json, size_t idx,
                                             MetadataValue* result) {
  grpc_error* error = GRPC_ERROR_NONE;
  auto context_func = [json, idx]() {
    char* context;
    if (json->key != nullptr) {
      gpr_asprintf(&context, "key \"%s\"", json->key);
    } else {
      gpr_asprintf(&context, "index %" PRIuPTR, idx);
    }
    return context;
  };
  switch (json->type) {
    case GRPC_JSON_STRING:
      result->type = MetadataValue::Type::STRING;
      result->string_value = json->value;
      break;
    case GRPC_JSON_NUMBER:
      result->type = MetadataValue::Type::DOUBLE;
      errno = 0;  // To distinguish error.
      result->double_value = strtod(json->value, nullptr);
      if (errno != 0) {
        char* context = context_func();
        char* msg;
        gpr_asprintf(&msg, "error parsing numeric value for %s: \"%s\"",
                     context, json->value);
        error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
        gpr_free(context);
        gpr_free(msg);
      }
      break;
    case GRPC_JSON_TRUE:
      result->type = MetadataValue::Type::BOOL;
      result->bool_value = true;
      break;
    case GRPC_JSON_FALSE:
      result->type = MetadataValue::Type::BOOL;
      result->bool_value = false;
      break;
    case GRPC_JSON_NULL:
      result->type = MetadataValue::Type::MD_NULL;
      break;
    case GRPC_JSON_ARRAY: {
      result->type = MetadataValue::Type::LIST;
      InlinedVector<grpc_error*, 1> error_list =
          ParseMetadataList(json, &result->list_value);
      if (!error_list.empty()) {
        // Can't use GRPC_ERROR_CREATE_FROM_VECTOR() here, because the error
        // string is not static in this case.
        char* context = context_func();
        char* msg;
        gpr_asprintf(&msg, "errors parsing struct for %s", context);
        error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
        gpr_free(context);
        gpr_free(msg);
        for (size_t i = 0; i < error_list.size(); ++i) {
          error = grpc_error_add_child(error, error_list[i]);
        }
      }
      break;
    }
    case GRPC_JSON_OBJECT: {
      result->type = MetadataValue::Type::STRUCT;
      InlinedVector<grpc_error*, 1> error_list =
          ParseMetadataStruct(json, &result->struct_value);
      if (!error_list.empty()) {
        // Can't use GRPC_ERROR_CREATE_FROM_VECTOR() here, because the error
        // string is not static in this case.
        char* context = context_func();
        char* msg;
        gpr_asprintf(&msg, "errors parsing struct for %s", context);
        error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(msg);
        gpr_free(context);
        gpr_free(msg);
        for (size_t i = 0; i < error_list.size(); ++i) {
          error = grpc_error_add_child(error, error_list[i]);
          GRPC_ERROR_UNREF(error_list[i]);
        }
      }
      break;
    }
    default:
      break;
  }
  return error;
}

}  // namespace grpc_core
