#ifndef PUTTY_SSH2CONNECTION_H
#define PUTTY_SSH2CONNECTION_H

struct outstanding_channel_request;
struct outstanding_global_request;

struct ssh2_connection_state {
    int crState;

    ssh_sharing_state *connshare;
    char *peer_verstring;

    mainchan *mainchan;
    SshChannel *mainchan_sc;
    bool ldisc_opts[LD_N_OPTIONS];
    int session_attempt, session_status;
    int term_width, term_height;
    bool want_user_input;
    bufchain *user_input;

    bool ssh_is_simple;
    bool persistent;
    bool started;

    Conf *conf;

    tree234 *channels;                 /* indexed by local id */
    bool all_channels_throttled;

    bool X11_fwd_enabled;
    tree234 *x11authtree;

    bool got_pty;

    tree234 *rportfwds;
    PortFwdManager *portfwdmgr;
    bool portfwdmgr_configured;

    prompts_t *antispoof_prompt;
    int antispoof_ret;

    const SftpServerVtable *sftpserver_vt;
    const SshServerConfig *ssc;

    /*
     * These store the list of global requests that we're waiting for
     * replies to. (REQUEST_FAILURE doesn't come with any indication
     * of what message caused it, so we have to keep track of the
     * queue ourselves.)
     */
    struct outstanding_global_request *globreq_head, *globreq_tail;

    ConnectionLayer cl;
    PacketProtocolLayer ppl;
};

typedef void (*gr_handler_fn_t)(struct ssh2_connection_state *s,
                                PktIn *pktin, void *ctx);
void ssh2_queue_global_request_handler(
    struct ssh2_connection_state *s, gr_handler_fn_t handler, void *ctx);

struct ssh2_channel {
    struct ssh2_connection_state *connlayer;

    unsigned remoteid, localid;
    int type;
    /* True if we opened this channel but server hasn't confirmed. */
    bool halfopen;

    /* Bitmap of whether we've sent/received CHANNEL_EOF and
     * CHANNEL_CLOSE. */
#define CLOSES_SENT_EOF    1
#define CLOSES_SENT_CLOSE  2
#define CLOSES_RCVD_EOF    4
#define CLOSES_RCVD_CLOSE  8
    int closes;

    /*
     * This flag indicates that an EOF is pending on the outgoing side
     * of the channel: that is, wherever we're getting the data for
     * this channel has sent us some data followed by EOF. We can't
     * actually send the EOF until we've finished sending the data, so
     * we set this flag instead to remind us to do so once our buffer
     * is clear.
     */
    bool pending_eof;

    /*
     * True if this channel is causing the underlying connection to be
     * throttled.
     */
    bool throttling_conn;

    /*
     * True if we currently have backed-up data on the direction of
     * this channel pointing out of the SSH connection, and therefore
     * would prefer the 'Channel' implementation not to read further
     * local input if possible.
     */
    bool throttled_by_backlog;

    bufchain outbuffer, errbuffer;
    unsigned remwindow, remmaxpkt;
    /* locwindow is signed so we can cope with excess data. */
    int locwindow, locmaxwin;
    /*
     * remlocwin is the amount of local window that we think
     * the remote end had available to it after it sent the
     * last data packet or window adjust ack.
     */
    int remlocwin;

    /*
     * These store the list of channel requests that we're waiting for
     * replies to. (CHANNEL_FAILURE doesn't come with any indication
     * of what message caused it, so we have to keep track of the
     * queue ourselves.)
     */
    struct outstanding_channel_request *chanreq_head, *chanreq_tail;

    enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state;

    ssh_sharing_connstate *sharectx; /* sharing context, if this is a
                                      * downstream channel */
    Channel *chan;      /* handle the client side of this channel, if not */
    SshChannel sc;      /* entry point for chan to talk back to */
};

typedef void (*cr_handler_fn_t)(struct ssh2_channel *, PktIn *, void *);

void ssh2_channel_init(struct ssh2_channel *c);
PktOut *ssh2_chanreq_init(struct ssh2_channel *c, const char *type,
                          cr_handler_fn_t handler, void *ctx);

typedef enum ChanopenOutcome {
    CHANOPEN_RESULT_FAILURE,
    CHANOPEN_RESULT_SUCCESS,
    CHANOPEN_RESULT_DOWNSTREAM,
} ChanopenOutcome;

typedef struct ChanopenResult {
    ChanopenOutcome outcome;
    union {
        struct {
            char *wire_message;        /* must be freed by recipient */
            unsigned reason_code;
        } failure;
        struct {
            Channel *channel;
        } success;
        struct {
            ssh_sharing_connstate *share_ctx;
        } downstream;
    } u;
} ChanopenResult;

PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type);

PktOut *ssh2_portfwd_chanopen(
    struct ssh2_connection_state *s, struct ssh2_channel *c,
    const char *hostname, int port,
    const char *description, const SocketPeerInfo *peerinfo);

struct ssh_rportfwd *ssh2_rportfwd_alloc(
    ConnectionLayer *cl,
    const char *shost, int sport, const char *dhost, int dport,
    int addressfamily, const char *log_description, PortFwdRecord *pfr,
    ssh_sharing_connstate *share_ctx);
void ssh2_rportfwd_remove(
    ConnectionLayer *cl, struct ssh_rportfwd *rpf);
SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan);
SshChannel *ssh2_serverside_x11_open(
    ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi);
SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan);

void ssh2channel_send_exit_status(SshChannel *c, int status);
void ssh2channel_send_exit_signal(
    SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg);
void ssh2channel_send_exit_signal_numeric(
    SshChannel *c, int signum, bool core_dumped, ptrlen msg);
void ssh2channel_request_x11_forwarding(
    SshChannel *c, bool want_reply, const char *authproto,
    const char *authdata, int screen_number, bool oneshot);
void ssh2channel_request_agent_forwarding(SshChannel *c, bool want_reply);
void ssh2channel_request_pty(
    SshChannel *c, bool want_reply, Conf *conf, int w, int h);
bool ssh2channel_send_env_var(
    SshChannel *c, bool want_reply, const char *var, const char *value);
void ssh2channel_start_shell(SshChannel *c, bool want_reply);
void ssh2channel_start_command(
    SshChannel *c, bool want_reply, const char *command);
bool ssh2channel_start_subsystem(
    SshChannel *c, bool want_reply, const char *subsystem);
bool ssh2channel_send_env_var(
    SshChannel *c, bool want_reply, const char *var, const char *value);
bool ssh2channel_send_serial_break(
    SshChannel *c, bool want_reply, int length);
bool ssh2channel_send_signal(
    SshChannel *c, bool want_reply, const char *signame);
void ssh2channel_send_terminal_size_change(SshChannel *c, int w, int h);

#define CHANOPEN_RETURN_FAILURE(code, msgparams) do             \
    {                                                           \
        ChanopenResult toret;                                   \
        toret.outcome = CHANOPEN_RESULT_FAILURE;                \
        toret.u.failure.reason_code = code;                     \
        toret.u.failure.wire_message = dupprintf msgparams;     \
        return toret;                                           \
    } while (0)

#define CHANOPEN_RETURN_SUCCESS(chan) do                \
    {                                                   \
        ChanopenResult toret;                           \
        toret.outcome = CHANOPEN_RESULT_SUCCESS;        \
        toret.u.success.channel = chan;                 \
        return toret;                                   \
    } while (0)

#define CHANOPEN_RETURN_DOWNSTREAM(shctx) do            \
    {                                                   \
        ChanopenResult toret;                           \
        toret.outcome = CHANOPEN_RESULT_DOWNSTREAM;     \
        toret.u.downstream.share_ctx = shctx;           \
        return toret;                                   \
    } while (0)

ChanopenResult ssh2_connection_parse_channel_open(
    struct ssh2_connection_state *s, ptrlen type,
    PktIn *pktin, SshChannel *sc);

bool ssh2_connection_parse_global_request(
    struct ssh2_connection_state *s, ptrlen type, PktIn *pktin);

bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s);

#endif /* PUTTY_SSH2CONNECTION_H */