#pragma once
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include <gromox/ndr.hpp>
#include "pdu_ndr_ids.hpp"

struct dcerpc_ctx_list {
	uint16_t context_id;
	SYNTAX_ID abstract_syntax;
	std::vector<SYNTAX_ID> transfer_syntaxes;
	bool contains(const SYNTAX_ID &) const;
};

union DCERPC_OBJECT {
	char empty;
	GUID object;
};

struct dcerpc_ack_ctx {
	uint16_t result;
	uint16_t reason;
	SYNTAX_ID syntax;
};

struct dcerpc_payload {
	virtual ~dcerpc_payload() = default;
};

struct dcerpc_request final : public dcerpc_payload {
	uint32_t alloc_hint = 0;
	uint16_t context_id = 0, opnum = 0;
	DCERPC_OBJECT object{};
	std::string pad, stub_and_verifier;
};

struct dcerpc_response final : public dcerpc_payload {
	uint32_t alloc_hint = 0;
	uint16_t context_id = 0;
	uint8_t cancel_count = 0;
	std::string pad, stub_and_verifier;
};

struct dcerpc_fault final : public dcerpc_payload {
	uint32_t alloc_hint = 0;
	uint16_t context_id = 0;
	uint8_t cancel_count = 0;
	int status = 0; /* dcerpc ncacn status */
	std::string pad;
};

struct dcerpc_fack final : public dcerpc_payload {
	uint32_t version = 0;
	uint8_t pad = 0;
	uint16_t window_size = 0;
	uint32_t max_tdsu = 0, max_frag_size = 0;
	uint16_t serial_no = 0;
	std::vector<uint32_t> selack;
};

struct dcerpc_cancel_ack final : public dcerpc_payload {
	uint32_t version = 0, id = 0, server_is_accepting = 0;
};

struct dcerpc_bind final : public dcerpc_payload {
	uint16_t max_xmit_frag = 0, max_recv_frag = 0;
	uint32_t assoc_group_id = 0;
	std::vector<dcerpc_ctx_list> ctx_list;
	std::string auth_info;
};

struct dcerpc_bind_ack final : public dcerpc_payload {
	uint16_t max_xmit_frag = 0, max_recv_frag = 0;
	uint32_t assoc_group_id = 0;
	uint16_t secondary_address_size = 0;
	char secondary_address[64]{};
	std::vector<dcerpc_ack_ctx> ctx_list;
	std::string pad, auth_info;
};

struct dcerpc_bind_nak final : public dcerpc_payload {
	uint16_t reject_reason = 0;
	std::vector<uint32_t> versions;
};

struct dcerpc_co_cancel final : public dcerpc_payload {
	std::string auth_info;
};

struct DCERPC_AUTH {
	void clear() { credentials.clear(); }

	uint8_t auth_type = 0, auth_level = 0, auth_pad_length = 0;
	uint8_t auth_reserved = 0;
	uint32_t auth_context_id = 0;
	std::string credentials;
};

struct dcerpc_auth3 final : public dcerpc_payload {
	uint32_t pad = 0;
	std::string auth_info;
};

struct dcerpc_orphaned final : public dcerpc_payload {
	std::string auth_info;
};

struct RTS_FLOWCONTROLACK {
	uint32_t bytes_received;
	uint32_t available_window;
	GUID channel_cookie;
};

struct RTS_CLIENTADDRESS {
	uint32_t address_type;
	char client_address[64];
};

union rts_cmds {
	uint32_t receivewindowsize;
	RTS_FLOWCONTROLACK flowcontrolack;
	uint32_t connectiontimeout;
	GUID cookie;
	uint32_t channellifetime;
	uint32_t clientkeepalive;
	uint32_t version;
	char empty;
	uint32_t padding;
	char negative_ance;
	char ance;
	RTS_CLIENTADDRESS clientaddress;
	GUID associationgroupid;
	uint32_t destination;
	uint32_t pingtrafficsentnotify;
};

struct rts_cmd {
	uint32_t command_type;
	rts_cmds command;
};

struct dcerpc_rts final : public dcerpc_payload {
	uint16_t num() const { return commands.size(); }

	uint16_t flags = 0;
	std::vector<rts_cmd> commands;
};

/*
 * RTS PDU Header
 *
 * NCA = Network Connection Architecture
 * CN = Connection (ncacn)
 * DG = Datagram / Connectionless (ncadg)
 *
 * C706 §12.6.1 / RPCH v19 §2.2.3.6.1
 */
struct dcerpc_ncacn_packet {
	constexpr dcerpc_ncacn_packet(bool be)
	{
		drep[0] = be ? 0 : DCERPC_DREP_LE;
	}
	uint8_t rpc_vers = 5;
	uint8_t rpc_vers_minor = 0;
	uint8_t pfc_flags = 0;
	uint8_t drep[4]{};

	/*
	 * Concerning NDR_PUSH: frag_length is 0 in the class, and so
	 * serialized with pdu_ndr_push_ncacnpkt. The produced blob is later
	 * updated with pdu_processor_set_frag_length.
	 */
	uint16_t frag_length = 0;
	uint16_t auth_length = 0;
	uint32_t call_id = 0;
	uint8_t pkt_type = DCERPC_PKT_INVALID;
	std::unique_ptr<dcerpc_payload> payload;
};

extern pack_result pdu_ndr_pull_dcerpc_auth(NDR_PULL *, DCERPC_AUTH *);
extern pack_result pdu_ndr_push_dcerpc_auth(NDR_PUSH *, const DCERPC_AUTH *);
extern pack_result pdu_ndr_pull_ncacnpkt(NDR_PULL *, dcerpc_ncacn_packet *);
extern pack_result pdu_ndr_push_ncacnpkt(NDR_PUSH *, dcerpc_ncacn_packet *);
