/** @file
 *
 *  PROXY protocol definitions and parsers.
 *
 *  @section license License
 *
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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 "tscore/ink_assert.h"
#include "tscpp/util/TextView.h"
#include "ProxyProtocol.h"
#include "I_NetVConnection.h"

bool
ssl_has_proxy_v1(NetVConnection *sslvc, char *buffer, int64_t *bytes_r)
{
  ts::TextView tv;

  tv.assign(buffer, *bytes_r);

  // Client must send at least 15 bytes to get a reasonable match.
  if (tv.size() < PROXY_V1_CONNECTION_HEADER_LEN_MIN) {
    Debug("proxyprotocol_v1", "ssl_has_proxy_v1: not enough recv'd");
    return false;
  }

  // if we don't have the PROXY preface, we don't have a ProxyV1 header
  if (0 != memcmp(PROXY_V1_CONNECTION_PREFACE, buffer, PROXY_V1_CONNECTION_PREFACE_LEN)) {
    Debug("proxyprotocol_v1", "ssl_has_proxy_v1: failed the memcmp(%s, %s, %lu)", PROXY_V1_CONNECTION_PREFACE, buffer,
          PROXY_V1_CONNECTION_PREFACE_LEN);
    return false;
  }

  //  Find the terminating newline
  ts::TextView::size_type pos = tv.find('\n');
  if (pos == tv.npos) {
    Debug("proxyprotocol_v1", "ssl_has_proxy_v1: newline not found");
    return false;
  }

  // Parse the TextView before moving the bytes in the buffer
  if (!proxy_protov1_parse(sslvc, tv)) {
    *bytes_r = -EAGAIN;
    return false;
  }
  *bytes_r -= pos + 1;
  if (*bytes_r <= 0) {
    *bytes_r = -EAGAIN;
  } else {
    Debug("ssl", "Moving %" PRId64 " characters remaining in the buffer from %p to %p", *bytes_r, buffer + pos + 1, buffer);
    memmove(buffer, buffer + pos + 1, *bytes_r);
  }
  return true;
}

bool
http_has_proxy_v1(IOBufferReader *reader, NetVConnection *netvc)
{
  char buf[PROXY_V1_CONNECTION_HEADER_LEN_MAX + 1];
  ts::TextView tv;

  tv.assign(buf, reader->memcpy(buf, sizeof(buf), 0));

  // Client must send at least 15 bytes to get a reasonable match.
  if (tv.size() < PROXY_V1_CONNECTION_HEADER_LEN_MIN) {
    return false;
  }

  if (0 != memcmp(PROXY_V1_CONNECTION_PREFACE, buf, PROXY_V1_CONNECTION_PREFACE_LEN)) {
    return false;
  }

  // Find the terminating LF, which should already be in the buffer.
  ts::TextView::size_type pos = tv.find('\n');
  if (pos == tv.npos) { // not found, it's not a proxy protocol header.
    return false;
  }
  reader->consume(pos + 1); // clear out the header.

  // Now that we know we have a valid PROXY V1 preface, let's parse the
  // remainder of the header

  return proxy_protov1_parse(netvc, tv);
}

bool
proxy_protov1_parse(NetVConnection *netvc, ts::TextView hdr)
{
  static const std::string_view PREFACE{PROXY_V1_CONNECTION_PREFACE, PROXY_V1_CONNECTION_PREFACE_LEN};
  ts::TextView token;
  in_port_t port;

  // All the cases are special and sequence, might as well unroll them.

  // The header should begin with the PROXY preface
  token = hdr.split_prefix_at(' ');
  if (0 == token.size() || token != PREFACE) {
    Debug("proxyprotocol_v1", "proxy_protov1_parse: header [%.*s] does not start with preface [%.*s]", static_cast<int>(hdr.size()),
          hdr.data(), static_cast<int>(PREFACE.size()), PREFACE.data());
    return false;
  }
  Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = PREFACE", static_cast<int>(token.size()), token.data());

  // The INET protocol family - TCP4, TCP6 or UNKNOWN
  token = hdr.split_prefix_at(' ');
  if (0 == token.size()) {
    return false;
  }
  Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = INET Family", static_cast<int>(token.size()), token.data());

  // Next up is the layer 3 source address
  // - 255.255.255.255 or ffff:f...f:ffff ffff:f...f:fff
  token = hdr.split_prefix_at(' ');
  if (0 == token.size()) {
    return false;
  }
  Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = Source Address", static_cast<int>(token.size()), token.data());
  if (0 != netvc->set_proxy_protocol_src_addr(token)) {
    return false;
  }

  // Next is the layer3 destination address
  // - 255.255.255.255 or ffff:f...f:ffff ffff:f...f:fff
  token = hdr.split_prefix_at(' ');
  if (0 == token.size()) {
    return false;
  }
  Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = Destination Address", static_cast<int>(token.size()), token.data());
  if (0 != netvc->set_proxy_protocol_dst_addr(token)) {
    return false;
  }

  // Next is the TCP source port represented as a decimal number in the range of [0..65535] inclusive.
  token = hdr.split_prefix_at(' ');
  if (0 == token.size()) {
    return false;
  }
  Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = Source Port", static_cast<int>(token.size()), token.data());

  if (0 == (port = ts::svtoi(token))) {
    Debug("proxyprotocol_v1", "proxy_protov1_parse: src port [%d] token [%.*s] failed to parse", port,
          static_cast<int>(token.size()), token.data());
    return false;
  }
  netvc->set_proxy_protocol_src_port(port);

  // Next is the TCP destination port represented as a decimal number in the range of [0..65535] inclusive.
  // Final trailer is CR LF so split at CR.
  token = hdr.split_prefix_at('\r');
  if (0 == token.size()) {
    return false;
  }
  Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = Destination Port", static_cast<int>(token.size()), token.data());
  if (0 == (port = ts::svtoi(token))) {
    Debug("proxyprotocol_v1", "proxy_protov1_parse: dst port [%d] token [%.*s] failed to parse", port,
          static_cast<int>(token.size()), token.data());
    return false;
  }
  netvc->set_proxy_protocol_dst_port(port);

  netvc->set_proxy_protocol_version(NetVConnection::ProxyProtocolVersion::V1);

  return true;
}
