/*
 * Copyright (c) 2016-2024 Apple Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */
#ifndef _SKYWALK_TEST_COMMON_H_
#define _SKYWALK_TEST_COMMON_H_

#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <uuid/uuid.h>
#include <arpa/inet.h>

#include <skywalk/os_skywalk_private.h>
#include <darwintest.h>

#define SKT_LOG(_fmt, ...) do {       \
	int skt_errno = errno;        \
	T_LOG(_fmt, ##__VA_ARGS__);   \
	errno = skt_errno;            \
} while (0)

/* expects a variable "error" */
#define SKTC_ASSERT_ERR(t) do {                                         \
	if (!(t))                                                       \
	        SKT_LOG("%s:%d Unexpected: error %d errno %d: %s",      \
	            __func__, __LINE__, error, errno, strerror(errno)); \
	assert(t);                                                      \
} while (0)

#define SKD(_lvl, _fmt, ...)                          \
	do {                                          \
	        if (sktc_verbose >= (_lvl)) {         \
	                SKT_LOG(_fmt, ##__VA_ARGS__); \
	        }                                     \
	} while (0)

#define SKD0(_fmt, ...) SKD(0, _fmt, ##__VA_ARGS__)
#define SKD1(_fmt, ...) SKD(1, _fmt, ##__VA_ARGS__)
#define SKD2(_fmt, ...) SKD(2, _fmt, ##__VA_ARGS__)
#define SKD3(_fmt, ...) SKD(3, _fmt, ##__VA_ARGS__)

extern int sktc_verbose;
extern nexus_controller_t sktc_nexus_controller;
extern uuid_t sktc_provider_uuid;
extern uuid_t sktc_instance_uuid;
extern uuid_string_t sktc_instance_uuid_string;

void sktc_setup_channel_worker(uuid_t instance_uuid, nexus_port_t channel_port,
    ring_id_t ringid, char *key, size_t keylen, bool echo, bool defunct_ok);
void sktc_cleanup_channel_worker(void);

extern void sktc_generic_upipe_nexus_init(void);
extern void sktc_generic_upipe_echo_init(void);
extern void sktc_generic_upipe_null_init(void);
extern void sktc_generic_upipe_fini(void);
#define SKTC_GENERIC_UPIPE_ARGV { NULL, NULL, NULL, sktc_instance_uuid_string, NULL}

extern void sktc_generic_kpipe_init(void);
extern void sktc_generic_kpipe_fini(void);
#define SKTC_GENERIC_KPIPE_ARGV { NULL, NULL, NULL, sktc_instance_uuid_string, NULL}

extern void sktc_generic_memory_init(void);
extern void sktc_generic_memory_fini(void);

extern channel_slot_t send_bytes(channel_ring_t, uint32_t);
extern int chew_slots(channel_ring_t, uint32_t);
extern void set_watermark(channel_t, bool, channel_threshold_unit_t, uint32_t);

enum timeout_behavior {
	TIMEOUT_FAIL,
	TIMEOUT_EXPECT,
	TIMEOUT_DONT_CARE,
	TIMEOUT_DISABLE
};
extern int wait_on_fd(int, int16_t, channel_t, uint32_t,
    enum timeout_behavior);

struct stage_ctx {
	uint32_t test_stage;
	pthread_cond_t change_cond;
	pthread_mutex_t change_mtx;
};

extern void test_stage_init(struct stage_ctx *, uint32_t);
extern void test_stage_wait(struct stage_ctx *, uint32_t);
extern void test_stage_change(struct stage_ctx *, uint32_t);
extern void test_stage_destroy(struct stage_ctx *);

struct sktc_nexus_attr {
	nexus_name_t    name;
	nexus_type_t    type;
	uint64_t        ntxrings;
	uint64_t        nrxrings;
	uint64_t        ntxslots;
	uint64_t        nrxslots;
	uint64_t        slotsize;
	uint64_t        metasize;
	uint64_t        anonymous;
	uint64_t        userchannel;
	uint64_t        maxfrags;
	uint64_t        rejectonclose;
};

#define SKTC_NEXUS_ATTR_INIT()   \
    {                            \
	    .name = {'\0'},      \
	    .type = -1,          \
	    .ntxrings = -1,      \
	    .nrxrings = -1,      \
	    .ntxslots = -1,      \
	    .nrxslots = -1,      \
	    .slotsize = -1,      \
	    .metasize = -1,      \
	    .anonymous = -1,     \
	    .userchannel = -1,   \
	    .maxfrags = -1,      \
	    .rejectonclose = -1, \
    }

extern void sktc_build_nexus(nexus_controller_t ncd,
    struct sktc_nexus_attr *sktc_attr, uuid_t *providerp, uuid_t *instancep);

extern void sktc_setup_nexus(struct sktc_nexus_attr *);

extern void sktc_cleanup_nexus(void);

extern int sktc_bind_nexus_key(nexus_port_t, const void *, size_t);
extern int sktc_unbind_nexus_key(nexus_port_t);

#define FETH_NAME       "feth"
#define FETH_FORMAT     FETH_NAME "%d"
#define FETH0_NAME      FETH_NAME "0"
#define FETH1_NAME      FETH_NAME "1"

static inline void
sktc_feth_name_for_unit(char * namebuf, size_t namebuf_size, int unit)
{
	snprintf(namebuf, namebuf_size, FETH_FORMAT, unit);
}

extern int sktc_ifnet_feth_create(int unit);
extern int sktc_ifnet_feth_destroy(int unit);

extern void sktc_ifnet_feth0_create(void);
extern void sktc_ifnet_feth0_destroy(void);
extern void sktc_ifnet_feth1_create(void);
extern void sktc_ifnet_feth1_destroy(void);

extern void sktc_ifnet_feth0_1_create(void);
extern void sktc_ifnet_feth0_1_destroy(void);

#define FETH_FLAGS_NATIVE       0x1
#define FETH_FLAGS_TXSTART      0x2
#define FETH_FLAGS_WMM          0x4
#define FETH_FLAGS_MULTI_BUFLET 0x8
#define FETH_FLAGS_NONSHAREDPOOL 0x10
#define FETH_FLAGS_NONSHAREDSPLITPOOLS 0x20
#define FETH_FLAGS_TX_HEADROOM  0x40
#define FETH_FLAGS_USER_ACCESS  0x80
#define FETH_FLAGS_LOW_LATENCY  0x100
#define FETH_FLAGS_NXATTACH     0x200
#define FETH_FLAGS_FCS          0x400
#define FETH_FLAGS_TRAILER      0x800
#define FETH_FLAGS_LLINK        0x1000
#define FETH_FLAGS_MULTI_LLINK  0x2000

extern void sktc_ifnet_feth_pair_create(uint32_t flags);
extern void sktc_ifnet_feth_pair_destroy(void);

#define RD_NAME         "rd"
#define RD_FORMAT       RD_NAME "%d"
#define RD0_NAME        RD_NAME "0"

extern void sktc_ifnet_rd_create(void);
extern void sktc_ifnet_rd_destroy(void);

static inline void
sktc_rd_name_for_unit(char * namebuf, size_t namebuf_size, int unit)
{
	snprintf(namebuf, namebuf_size, RD_FORMAT, unit);
}

#define RD_IF_TYPE_ETHERNET  0x1
#define RD_IF_TYPE_CELLULAR  0x2

extern int sktc_ifnet_add_addr(char *ifname, struct in_addr *addr, struct in_addr *mask, struct in_addr *broadaddr);
extern int sktc_ifnet_add_addr6(char *ifname, struct in6_addr *addr, struct in6_addr *dstaddr, int prefix_len, int flags);
extern int sktc_ifnet_del_addr(char *ifname, struct in_addr *addr);
extern int sktc_ifnet_del_addr6(char *ifname, struct in6_addr *addr);
extern int sktc_ifnet_add_scoped_default_route(char * ifname, struct in_addr ifa);

static inline struct in_addr
sktc_make_in_addr(uint32_t s)
{
	struct in_addr  ip;

	ip.s_addr = htonl(s);
	return ip;
}

#define FETH0_INADDR    0x0a00fa01
#define FETH1_INADDR    0x0a00fb01
#define NOWHERE_INADDR  0x12345678

static inline struct in_addr
sktc_feth0_in_addr(void)
{
	return sktc_make_in_addr(FETH0_INADDR);
}

static inline struct in_addr
sktc_feth1_in_addr(void)
{
	return sktc_make_in_addr(FETH1_INADDR);
}

#define RD0_INADDR    0x0a00fa01

static inline struct in_addr
sktc_rd0_in_addr(void)
{
	static_assert(RD0_INADDR == FETH0_INADDR);
	return sktc_make_in_addr(RD0_INADDR);
}

static inline struct in_addr
sktc_nowhere_in_addr(void)
{
	return sktc_make_in_addr(NOWHERE_INADDR);
}

#define FETH0_INET6_ADDR    "ff02::1:ff00:101"
#define FETH1_INET6_ADDR    "ff02::1:ff00:202"

static inline void
sktc_feth0_inet6_addr(struct in6_addr *ip6_addr)
{
	assert(inet_pton(AF_INET6, FETH0_INET6_ADDR, ip6_addr) == 1);
}

static inline void
sktc_feth1_inet6_addr(struct in6_addr *ip6_addr)
{
	assert(inet_pton(AF_INET6, FETH1_INET6_ADDR, ip6_addr) == 1);
}

extern int sktc_get_mac_addr(const char *ifname, uint8_t *addr);
extern bool sktc_get_netif_nexus(const char *ifname, uuid_t netif);
extern bool sktc_get_flowswitch_nexus(const char *ifname, uuid_t fsw);
extern int sktc_ifnet_feth0_set_dequeue_stall(boolean_t enable);
extern int sktc_ifnet_feth1_set_dequeue_stall(boolean_t enable);

extern int sktc_set_classq_update_interval(uint64_t ns);
extern int sktc_reset_classq_update_interval();
extern int sktc_set_classq_target_qdelay(uint64_t ns);
extern int sktc_reset_classq_target_qdelay();
extern int sktc_set_classq_update_intervals(uint64_t ns);
extern int sktc_reset_classq_update_intervals(void);
extern int sktc_set_classq_target_qdelays(uint64_t ns);
extern int sktc_reset_classq_target_qdelays(void);

extern void sktc_set_tcp_msl(int);
extern void sktc_restore_tcp_msl(void);
extern void sktc_enable_ip_reass();
extern void sktc_restore_ip_reass();
extern bool sktc_is_ip_reass_enabled();
extern bool sktc_is_netagent_enabled(void);
extern uint64_t sktc_get_channel_attr(const channel_t chd,
    channel_attr_type_t type);
extern void sktc_config_fsw_rx_agg_tcp(uint32_t);
extern void sktc_restore_fsw_rx_agg_tcp(void);
extern void sktc_enable_channel_buflet_alloc(void);
extern void sktc_restore_channel_buflet_alloc(void);
extern void skt_process_if_adv(nexus_port_t port, channel_t chan);

/*
 * Process a channel event:
 * - Validate that the event data len matches the event type.
 * - Validate that a matching handler is passed, and invoke it.
 * - If no matching handler is defined, the validation fails.
 */
typedef void (^transmit_status_event_handler_t)(
	const os_channel_event_packet_transmit_status_t *event_data);
typedef void (^transmit_expired_event_handler_t)(
	const os_channel_event_packet_transmit_expired_t *event_data);
typedef void (^wildcard_event_handler_t)(
	os_channel_event_type_t event_type,
	const uint8_t *event_data,
	size_t event_dlen);

extern void skt_process_channel_event(
	channel_t chan, uint8_t payload_type, uint32_t stream_id,
	transmit_status_event_handler_t transmit_status_handler,
	transmit_expired_event_handler_t transmit_expired_handler,
	wildcard_event_handler_t wildcard_handler);
extern int skt_add_arp_entry(struct in_addr host, struct ether_addr *eaddr);
void skt_aqstatpr(const char *interface);

struct protox {
	void    (*pr_cblocks)(uint32_t, char *, int);
	/* control blocks printing routine */
	void    (*pr_stats)(uint32_t, char *, int);
	/* statistics printing routine */
	void    (*pr_istats)(char *);   /* per/if statistics printing routine */
	char    *pr_name;               /* well-known name */
	int     pr_protocol;
};
extern struct protox protox[];
void skt_printproto(register struct protox *tp, char *name);

const char *BOLD;
const char *BOLD_RED;
const char *BOLD_GREEN;
const char *BOLD_YELLOW;
const char *BOLD_BLUE;
const char *BOLD_MAGENTA;
const char *BOLD_CYAN;
const char *BOLD_WHITE;
const char *NORMAL;

#endif /* _SKYWALK_TEST_COMMON_H_ */
