ircd/client.cpp
#include "client.hpp"
#include "client_state.hpp"
#include "server.hpp"
#include "irc_cmd.hpp"
#include "common.hpp"
#include <assert.h>
FdTable<Client*> Client::client_fds;
const char* const Client::dummy_host = "0.0.0.0";
Client::Client(int _fd, const ip_str_t _ip): fd(_fd), last_seen(NOW), msg_stream_in(_fd), msg_stream_out(_fd), host(strndup(_ip, strrchr(_ip,':')-_ip)), addr(strdup(_ip)), irc_state(new ClientState(this)) {
assert(client_fds[fd] == NULL);
client_fds[fd] = this;
Poll::getInst()->add(fd, &Client::handle, EVENT_IN);
}
void Client::createInst(int _fd, const ip_str_t _ip) {
assert(!log_ctx || !strcmp(log_ctx, _ip));
log_ctx = _ip;
new Client(_fd, _ip);
log_ctx = NULL;
}
Client::~Client() {
LOG("closing.");
Server::getInst()->disconnect(this, NULL); // if not yet done with msg
Poll::getInst()->del(fd);
io_handlers.close_handler(fd);
assert(client_fds[fd] == this);
client_fds[fd] = NULL;
free((void*)host);
free((void*)addr);
delete irc_state;
}
void Client::killall(const char* msg) {
for (unsigned i=0; i<client_fds.size; i++) {
if (client_fds[i]) {
Client* c = client_fds[i];
Server::notice(c, msg);
delete c;
}
}
}
void Client::handle(int fd, event_t events) {
Client* inst = client_fds[fd];
assert(inst);
assert(!log_ctx || !strcmp(log_ctx, inst->addr));
log_ctx = inst->addr;
inst->handle(events);
log_ctx = NULL;
}
void Client::handle(event_t event) {
// kick/ping clients that seem idle?
if (event == EVENT_TOUT) {
if (last_seen < NOW-SOCK_TIMEOUT) { // idle too long
LOG("kicking idle client");
Server::getInst()->disconnect(this, "*** Timeout.");
delete this;
return;
} else { // poll/tickle-timeout only -> send PING
if (!Server::getInst()->handle(this, NULL)) {
delete this;
}
return;
}
}
bool ok = true;
if (event & EVENT_IN) {
last_seen = NOW;
ok &= handle_in();
}
if ((event & EVENT_OUT) || (event & EVENT_IN)) { // try to flush
ok &= handle_out();
}
if ((event & EVENT_CLOSE) && ok) {
LOG("client closed connection");
ok = false;
}
if (!ok) {
delete this;
}
}
bool Client::handle_in() {
const ssize_t rlen = msg_stream_in.io();
if (rlen < 0) {
return false;
}
msg_t* msg = NULL;
while (true) {
ssize_t len = msg_stream_in.pop(&msg);
if (!len) {
break; // cannot parse yet
} else if (len == -1) {
Server::notice(this, ":Client-Bug: IRC Message parsing error");
return false;
}
Command* cmd = Command::parse(msg);
if (!cmd) {
Server::notice(this, ":IRC Command parsing error");
return false;
}
if (!Server::getInst()->handle(this, cmd)) {
delete cmd;
return false;
}
delete cmd;
}
if (rlen == 0) { // eof check after popping above
return false; // TODO: proper shutdown() support needed?
}
return true;
}
bool Client::handle_out() {
bool rv = msg_stream_out.io();
Poll::getInst()->mod(fd, &Client::handle, EVENT_IN|(msg_stream_out.empty()? EVENT_NONE: EVENT_OUT));
return rv;
}
bool Client::send(msg_t* buf, size_t len) {
bool rv = msg_stream_out.send(buf, len);
Poll::getInst()->mod(fd, &Client::handle, EVENT_IN|(msg_stream_out.empty()? EVENT_NONE: EVENT_OUT));
return rv;
}