text: #include "common/ext_authz/ext_authz_impl.h"
  
  #include <chrono>
  #include <cstdint>
  #include <string>
  #include <vector>
  
  #include "envoy/access_log/access_log.h"
  #include "envoy/ssl/connection.h"
  
  #include "common/common/assert.h"
  #include "common/grpc/async_client_impl.h"
  #include "common/http/headers.h"
  #include "common/http/utility.h"
  #include "common/network/utility.h"
  #include "common/protobuf/protobuf.h"
  
  #include "fmt/format.h"
  
  namespace Envoy {
  namespace ExtAuthz {
  
  GrpcClientImpl::GrpcClientImpl(Grpc::AsyncClientPtr&& async_client,
                                 const Optional<std::chrono::milliseconds>& timeout)
      : service_method_(*Protobuf::DescriptorPool::generated_pool()->FindMethodByName(
            "envoy.service.auth.v2.Authorization.Check")),
        async_client_(std::move(async_client)), timeout_(timeout) {}
  
  GrpcClientImpl::~GrpcClientImpl() { ASSERT(!callbacks_); }
  
  void GrpcClientImpl::cancel() {
    ASSERT(callbacks_ != nullptr);
    request_->cancel();
    callbacks_ = nullptr;
  }
  
  void GrpcClientImpl::check(RequestCallbacks& callbacks,
                             const envoy::service::auth::v2::CheckRequest& request,
                             Tracing::Span& parent_span) {
    ASSERT(callbacks_ == nullptr);
    callbacks_ = &callbacks;
  
    request_ = async_client_->send(service_method_, request, *this, parent_span, timeout_);
  }
  
  void GrpcClientImpl::onSuccess(std::unique_ptr<envoy::service::auth::v2::CheckResponse>&& response,
                                 Tracing::Span& span) {
    CheckStatus status = CheckStatus::OK;
    ASSERT(response->status().code() != Grpc::Status::GrpcStatus::Unknown);
    if (response->status().code() != Grpc::Status::GrpcStatus::Ok) {
      status = CheckStatus::Denied;
      span.setTag(Constants::get().TraceStatus, Constants::get().TraceUnauthz);
    } else {
      span.setTag(Constants::get().TraceStatus, Constants::get().TraceOk);
    }
  
    callbacks_->onComplete(status);
    callbacks_ = nullptr;
  }
  
  void GrpcClientImpl::onFailure(Grpc::Status::GrpcStatus status, const std::string&,
                                 Tracing::Span&) {
    ASSERT(status != Grpc::Status::GrpcStatus::Ok);
    UNREFERENCED_PARAMETER(status);
    callbacks_->onComplete(CheckStatus::Error);
    callbacks_ = nullptr;
  }
  
  void CreateCheckRequest::setAttrContextPeer(envoy::service::auth::v2::AttributeContext_Peer& peer,
                                              const Network::Connection& connection,
                                              const std::string& service, const bool local) {
  
    // Set the address
    auto addr = peer.mutable_address();
    if (local) {
      Envoy::Network::Utility::addressToProtobufAddress(*connection.localAddress(), *addr);
    } else {
      Envoy::Network::Utility::addressToProtobufAddress(*connection.remoteAddress(), *addr);
    }
  
    // Set the principal
    // Preferably the SAN from the peer's cert or
    // Subject from the peer's cert.
    Ssl::Connection* ssl = const_cast<Ssl::Connection*>(connection.ssl());
    if (ssl != nullptr) {
      if (local) {
        peer.set_principal(ssl->uriSanLocalCertificate());
  
        if (peer.principal().empty()) {
          peer.set_principal(ssl->subjectLocalCertificate());
        }
      } else {
        peer.set_principal(ssl->uriSanPeerCertificate());
  
        if (peer.principal().empty()) {
          peer.set_principal(ssl->subjectPeerCertificate());
        }
      }
    }
  
    if (!service.empty()) {
      peer.set_service(service);
    }
  }
  
  std::string CreateCheckRequest::getHeaderStr(const Envoy::Http::HeaderEntry* entry) {
    if (entry) {
      return entry->value().getString();
    }
    return "";
  }
  
  void CreateCheckRequest::setHttpRequest(
      ::envoy::service::auth::v2::AttributeContext_HttpRequest& httpreq,
      const Envoy::Http::StreamDecoderFilterCallbacks* callbacks,
      const Envoy::Http::HeaderMap& headers) {
  
    // Set id
    // The streamId is not qualified as a const. Although it is as it does not modify the object.
    Envoy::Http::StreamDecoderFilterCallbacks* sdfc =
        const_cast<Envoy::Http::StreamDecoderFilterCallbacks*>(callbacks);
    httpreq.set_id(std::to_string(sdfc->streamId()));
  
    // Set method
    httpreq.set_method(getHeaderStr(headers.Method()));
    // Set path
    httpreq.set_path(getHeaderStr(headers.Path()));
    // Set host
    httpreq.set_host(getHeaderStr(headers.Host()));
    // Set scheme
    httpreq.set_scheme(getHeaderStr(headers.Scheme()));
  
    // Set size
    // need to convert to google buffer 64t;
    httpreq.set_size(sdfc->requestInfo().bytesReceived());
  
    // Set protocol
    if (sdfc->requestInfo().protocol().valid()) {
      httpreq.set_protocol(
          Envoy::Http::Utility::getProtocolString(sdfc->requestInfo().protocol().value()));
    }
  
    // Fill in the headers
    auto mutable_headers = httpreq.mutable_headers();
    headers.iterate(
        [](const Envoy::Http::HeaderEntry& e, void* ctx) {
          Envoy::Protobuf::Map<::std::string, ::std::string>* mutable_headers =
              static_cast<Envoy::Protobuf::Map<::std::string, ::std::string>*>(ctx);
          (*mutable_headers)[e.key().getString()] = e.value().getString();
          return Envoy::Http::HeaderMap::Iterate::Continue;
        },
        mutable_headers);
  }
  
  void CreateCheckRequest::setAttrContextRequest(
      ::envoy::service::auth::v2::AttributeContext_Request& req,
      const Envoy::Http::StreamDecoderFilterCallbacks* callbacks,
      const Envoy::Http::HeaderMap& headers) {
    setHttpRequest(*req.mutable_http(), callbacks, headers);
  }
  
  void CreateCheckRequest::createHttpCheck(const Envoy::Http::StreamDecoderFilterCallbacks* callbacks,
                                           const Envoy::Http::HeaderMap& headers,
                                           envoy::service::auth::v2::CheckRequest& request) {
  
    auto attrs = request.mutable_attributes();
  
    Envoy::Http::StreamDecoderFilterCallbacks* cb =
        const_cast<Envoy::Http::StreamDecoderFilterCallbacks*>(callbacks);
  
    const std::string service = getHeaderStr(headers.EnvoyDownstreamServiceCluster());
  
    setAttrContextPeer(*attrs->mutable_source(), *cb->connection(), service, false);
    setAttrContextPeer(*attrs->mutable_destination(), *cb->connection(), "", true);
    setAttrContextRequest(*attrs->mutable_request(), callbacks, headers);
  }
  
  void CreateCheckRequest::createTcpCheck(const Network::ReadFilterCallbacks* callbacks,
                                          envoy::service::auth::v2::CheckRequest& request) {
  
    auto attrs = request.mutable_attributes();
  
    Network::ReadFilterCallbacks* cb = const_cast<Network::ReadFilterCallbacks*>(callbacks);
    setAttrContextPeer(*attrs->mutable_source(), cb->connection(), "", false);
    setAttrContextPeer(*attrs->mutable_destination(), cb->connection(), "", true);
  }
  
  } // namespace ExtAuthz
  } // namespace Envoy
  