/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2020 Facebook */

#ifndef _TEST_TCP_HDR_OPTIONS_H
#define _TEST_TCP_HDR_OPTIONS_H

struct bpf_test_option {
	__u8 flags;
	__u8 max_delack_ms;
	__u8 rand;
} __attribute__((packed));

enum {
	OPTION_RESEND,
	OPTION_MAX_DELACK_MS,
	OPTION_RAND,
	__NR_OPTION_FLAGS,
};

#define OPTION_F_RESEND		(1 << OPTION_RESEND)
#define OPTION_F_MAX_DELACK_MS	(1 << OPTION_MAX_DELACK_MS)
#define OPTION_F_RAND		(1 << OPTION_RAND)
#define OPTION_MASK		((1 << __NR_OPTION_FLAGS) - 1)

#define TEST_OPTION_FLAGS(flags, option) (1 & ((flags) >> (option)))
#define SET_OPTION_FLAGS(flags, option)	((flags) |= (1 << (option)))

/* Store in bpf_sk_storage */
struct hdr_stg {
	bool active;
	bool resend_syn; /* active side only */
	bool syncookie;  /* passive side only */
	bool fastopen;	/* passive side only */
};

struct linum_err {
	unsigned int linum;
	int err;
};

#define TCPHDR_FIN 0x01
#define TCPHDR_SYN 0x02
#define TCPHDR_RST 0x04
#define TCPHDR_PSH 0x08
#define TCPHDR_ACK 0x10
#define TCPHDR_URG 0x20
#define TCPHDR_ECE 0x40
#define TCPHDR_CWR 0x80
#define TCPHDR_SYNACK (TCPHDR_SYN | TCPHDR_ACK)

#define TCPOPT_EOL		0
#define TCPOPT_NOP		1
#define TCPOPT_MSS		2
#define TCPOPT_WINDOW		3
#define TCPOPT_EXP		254

#define TCP_BPF_EXPOPT_BASE_LEN 4
#define MAX_TCP_HDR_LEN		60
#define MAX_TCP_OPTION_SPACE	40

#ifdef BPF_PROG_TEST_TCP_HDR_OPTIONS

#define CG_OK	1
#define CG_ERR	0

#ifndef SOL_TCP
#define SOL_TCP 6
#endif

struct tcp_exprm_opt {
	__u8 kind;
	__u8 len;
	__u16 magic;
	union {
		__u8 data[4];
		__u32 data32;
	};
} __attribute__((packed));

struct tcp_opt {
	__u8 kind;
	__u8 len;
	union {
		__u8 data[4];
		__u32 data32;
	};
} __attribute__((packed));

struct {
	__uint(type, BPF_MAP_TYPE_HASH);
	__uint(max_entries, 2);
	__type(key, int);
	__type(value, struct linum_err);
} lport_linum_map SEC(".maps");

static inline unsigned int tcp_hdrlen(const struct tcphdr *th)
{
	return th->doff << 2;
}

static inline __u8 skops_tcp_flags(const struct bpf_sock_ops *skops)
{
	return skops->skb_tcp_flags;
}

static inline void clear_hdr_cb_flags(struct bpf_sock_ops *skops)
{
	bpf_sock_ops_cb_flags_set(skops,
				  skops->bpf_sock_ops_cb_flags &
				  ~(BPF_SOCK_OPS_PARSE_UNKNOWN_HDR_OPT_CB_FLAG |
				    BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG));
}

static inline void set_hdr_cb_flags(struct bpf_sock_ops *skops, __u32 extra)
{
	bpf_sock_ops_cb_flags_set(skops,
				  skops->bpf_sock_ops_cb_flags |
				  BPF_SOCK_OPS_PARSE_UNKNOWN_HDR_OPT_CB_FLAG |
				  BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG |
				  extra);
}
static inline void
clear_parse_all_hdr_cb_flags(struct bpf_sock_ops *skops)
{
	bpf_sock_ops_cb_flags_set(skops,
				  skops->bpf_sock_ops_cb_flags &
				  ~BPF_SOCK_OPS_PARSE_ALL_HDR_OPT_CB_FLAG);
}

static inline void
set_parse_all_hdr_cb_flags(struct bpf_sock_ops *skops)
{
	bpf_sock_ops_cb_flags_set(skops,
				  skops->bpf_sock_ops_cb_flags |
				  BPF_SOCK_OPS_PARSE_ALL_HDR_OPT_CB_FLAG);
}

#define RET_CG_ERR(__err) ({			\
	struct linum_err __linum_err;		\
	int __lport;				\
						\
	__linum_err.linum = __LINE__;		\
	__linum_err.err = __err;		\
	__lport = skops->local_port;		\
	bpf_map_update_elem(&lport_linum_map, &__lport, &__linum_err, BPF_NOEXIST); \
	clear_hdr_cb_flags(skops);					\
	clear_parse_all_hdr_cb_flags(skops);				\
	return CG_ERR;							\
})

#endif /* BPF_PROG_TEST_TCP_HDR_OPTIONS */

#endif /* _TEST_TCP_HDR_OPTIONS_H */
