#pragma once
#include "chain.hpp"
#include "sock.hpp"
#include "stats.hpp"
#include "ssl.hpp"
#include "aio.hpp"
#include <map>
/** simple fd or ssl i/o operation wrapper that handles fd, ssl, and poll cleanup **/
class IoWrapper {
private:
int fd;
ssl_t* ssl;
bool polled;
bool got_close;
public:
INLINE IoWrapper(): fd(-1), ssl(NULL), polled(false), got_close(false), reset_ptr(NULL) {}
INLINE IoWrapper(int _fd): fd(_fd), ssl(NULL), polled(false), got_close(false), reset_ptr(NULL) {}
INLINE IoWrapper(ssl_t* _ssl): fd(_ssl->fd), ssl(_ssl), polled(false), got_close(false), reset_ptr(NULL) {}
INLINE ~IoWrapper() { reset(); }
void reset(); ///< closes fd, frees ssl if any, removes from poll, resets #reset_ptr
void** reset_ptr; ///< upon (real) reset, set this to NULL, if any
void set(int); ///< resets and switches to this fd
void set(ssl_t*); ///< resets and switches to this ssl, but allows upgrading from fd to its own ssl
//INLINE const ssl_t* get() const { return ssl; }
INLINE operator int() const { return fd; }
INLINE operator const int*() const { return &fd; }
INLINE bool is_valid() const { return (fd != -1); }
INLINE bool is_ssl() const { return (ssl != NULL); }
INLINE int info() const { return ssl? -fd: fd; } ///< for logging purposes only
bool poll(poll_handler_t, event_t, bool short_timeout=false, void* =NULL); ///< poll add/mod/del wrapper, removes upon EVENT_NONE
INLINE bool closed() const { return got_close; }
INLINE void closed(bool c) { assert(got_close == c || c); got_close = c; }
INLINE ssize_t do_read(char* b, size_t l) { return ssl? ssl_read(ssl, b, l): ((fd != -1)? read(fd, b, l): (errno = EBADF, -1)); }
INLINE ssize_t do_write(const char* b, size_t l) { return ssl? ssl_write(ssl, b, l): ((fd != -1)? write(fd, b, l): (errno = EBADF, -1)); }
bool send_alert(); ///< tries to send a tls alert if it's no ssl connection yet or if it's ssl and no data has been sent yet
};
class IoSource {
private:
IoWrapper* handler;
chain_t* chain;
size_t total; // TODO: need uint64_t?
bool read_wants_write;
public:
stat_t* rx_stats; ///< whether incoming traffic should be accounted for
int err; ///< last encoutered read errno
msec_t stime; ///< first read timestamp
static void (*data_in_handler)(const chain_t*);
INLINE IoSource(IoWrapper* _handler, chain_t* _out_chain): handler(_handler), chain(_out_chain), total(0), read_wants_write(false), rx_stats(NULL), err(0), stime(0) {
assert(handler && chain);
}
event_t events(const IoWrapper*) const;
INLINE event_t events() const {
assert(handler->is_valid());
return read_wants_write? EVENT_OUT: (chain->is_full()? EVENT_NONE: EVENT_IN);
}
INLINE size_t get_total() const { return total; }
bool do_io(); ///< reads and appends to chain if possible, resets #handler upon err, returns true if new data has actually been read.
};
class IoSink {
private:
IoWrapper* handler;
chain_t* in_chain;
chain_t* out_chain; ///< can be NULL
size_t total; // TODO: need uint64_t?
bool write_wants_read;
std::map<size_t, buf_t*> inject_bufs; ///< inject/write some data at the given total offset. TODO: this wastes lots of space, might use the slab pool
public:
int err; ///< last encoutered write errno
msec_t stime; ///< first write timestamp
INLINE IoSink(IoWrapper* _handler, chain_t* _in_chain): handler(_handler), in_chain(_in_chain), out_chain(NULL), total(0), write_wants_read(false), err(0), stime(0) {
assert(handler && in_chain);
}
~IoSink();
event_t events(const IoWrapper*) const;
INLINE event_t events() const {
assert(handler->is_valid() && !handler->closed());
return write_wants_read? EVENT_IN: (in_chain->is_empty()? EVENT_NONE: EVENT_OUT); // XXX: don't care about #out_chain
}
INLINE size_t get_total() const { return total; }
bool splice_to(chain_t*); ///< tries to set or remove the chain to splice to
bool do_io(); ///< pops from #in_chain and writes, eventually tries to push to #out_chain, returns true if something has actually been written.
bool set_http_req(const sockaddr_t& cip, const sockaddr_t& sip, const char* sni, size_t sni_len); ///< change http request to a proxy request with x-forwarded-* headers. ATTENTION: This is only done for the first of possibly more keepalive-requests, so the upstream proxy must stick to the initial headers.
};
class AioSink {
private:
AioFile* aio;
chain_t* chain;
static void cb(intptr_t); ///< free's the given buf_t, if any. @see AioFile::aio_handler_t
public:
INLINE AioSink(): aio(NULL), chain(NULL) {}
INLINE ~AioSink() {
if (aio) aio->finish(); // TODO: try do_io() one last time?
}
bool set(const char*, chain_t*); ///< sets or unsets file to write to from chain
void do_io();
};