/*
 * Copyright (c) 2020 Cisco and/or its affiliates.
 * 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.
 */

#ifndef __CNAT_CLIENT_H__
#define __CNAT_CLIENT_H__

#include <cnat/cnat_types.h>

/**
 * A client is a representation of an IP address behind the NAT.
 * A client thus sends packet to a VIP.
 * Clients are learned in the Data-plane when they send packets,
 * but, since they make additions to the FIB they must be programmed
 * in the main thread. They are aged out when they become idle.
 *
 * A client interposes in the FIB graph for the prefix corresponding
 * to the client (e.g. client's-IP/32). As a result this client object
 * is cloned as the interpose DPO. The clones are removed when the lock
 * count drops to zero. The originals are removed when the client ages.
 * At forwarding time the client preforms the reverse translation and
 * then ships the packet to where the FIB would send it.
 */
typedef struct cnat_client_t_
{
  CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);

  /**
   * the client's IP address
   */
  ip_address_t cc_ip;

  /**
   * How to send packets to this client post translation
   */
  dpo_id_t cc_parent;

  /**
   * the FIB entry this client sources
   */
  fib_node_index_t cc_fei;

  /**
   * number of DPO locks
   */
  u32 cc_locks;

  /**
   * Translations refcount for cleanup
   */
  u32 tr_refcnt;

  /**
   * Session refcount for cleanup
   */
  u32 session_refcnt;

  /**
   * Parent cnat_client index if cloned via interpose
   * or own index if vanilla client.
   * Used to get translations & update session_refcnt
   */
  index_t parent_cci;

  /**
   * Client flags
   */
  u8 flags;
} cnat_client_t;

extern u8 *format_cnat_client (u8 * s, va_list * args);
extern void cnat_client_free_by_ip (ip46_address_t * addr, u8 af);

extern cnat_client_t *cnat_client_pool;
extern dpo_type_t cnat_client_dpo;

#define CC_INDEX_INVALID ((u32)(~0))

static_always_inline cnat_client_t *
cnat_client_get (index_t i)
{
  return (pool_elt_at_index (cnat_client_pool, i));
}

typedef struct cnat_learn_arg_t_
{
  ip_address_t addr;
} cnat_learn_arg_t;

/**
 * A translation that references this VIP was deleted
 */
extern void cnat_client_translation_deleted (index_t cci);

/**
 * A translation that references this VIP was added
 */
extern void cnat_client_translation_added (index_t cci);
/**
 * Called in the main thread by RPC from the workers to learn a
 * new client
 */
extern void cnat_client_learn (const cnat_learn_arg_t * l);

extern index_t cnat_client_add (const ip_address_t * ip, u8 flags);

/**
 * Check all the clients were purged by translation & session purge
 */
extern int cnat_client_purge (void);

/**
 * CNat Client (dpo) flags
 */
typedef enum
{
  /* IP already present in the FIB, need to interpose dpo */
  CNAT_FLAG_EXCLUSIVE = (1 << 1),
  /* Prune this entry */
  CNAT_FLAG_EXPIRES = (1 << 2),
} cnat_entry_flag_t;


extern void cnat_client_throttle_pool_process ();

/**
 * DB of clients
 */
typedef struct cnat_client_db_t_
{
  uword *crd_cip4;
  uword *crd_cip6;
  /* Pool of addresses that have been throttled
     and need to be refcounted before calling
     cnat_client_free_by_ip */
  ip_address_t **throttle_pool;
  clib_spinlock_t *throttle_pool_lock;
} cnat_client_db_t;

extern cnat_client_db_t cnat_client_db;

/**
 * Find a client from an IP4 address
 */
static_always_inline cnat_client_t *
cnat_client_ip4_find (const ip4_address_t * ip)
{
  uword *p;

  p = hash_get (cnat_client_db.crd_cip4, ip->as_u32);

  if (p)
    return (pool_elt_at_index (cnat_client_pool, p[0]));

  return (NULL);
}

static_always_inline u32
cnat_client_ip4_find_index (const ip4_address_t * ip)
{
  uword *p;

  p = hash_get (cnat_client_db.crd_cip4, ip->as_u32);

  if (p)
    return p[0];

  return -1;
}

/**
 * Find a client from an IP6 address
 */
static_always_inline cnat_client_t *
cnat_client_ip6_find (const ip6_address_t * ip)
{
  uword *p;

  p = hash_get_mem (cnat_client_db.crd_cip6, ip);

  if (p)
    return (pool_elt_at_index (cnat_client_pool, p[0]));

  return (NULL);
}

/**
 * Add a session refcnt to this client
 */
static_always_inline u32
cnat_client_cnt_session (cnat_client_t * cc)
{
  cnat_client_t *ccp = cnat_client_get (cc->parent_cci);
  return clib_atomic_add_fetch (&ccp->session_refcnt, 1);
}

/**
 * Del a session refcnt to this client
 */
static_always_inline u32
cnat_client_uncnt_session (cnat_client_t * cc)
{
  cnat_client_t *ccp = cnat_client_get (cc->parent_cci);
  return clib_atomic_sub_fetch (&ccp->session_refcnt, 1);
}

/*
 * fd.io coding-style-patch-verification: ON
 *
 * Local Variables:
 * eval: (c-set-style "gnu")
 * End:
 */

#endif