Lindenii Project Forge
Login

hare-ev

Temporary fork of hare-ev for... reasons

Warning: Due to various recent migrations, viewing non-HEAD refs may be broken.

/ev/dial/ip.ha (raw)

// License: MPL-2.0
// (c) 2021-2023 Drew DeVault <sir@cmpwn.com>
// (c) 2021 Bor Grošelj Simić <bor.groseljsimic@telemach.net>
// (c) 2021 Ember Sawady <ecs@d2evs.net>
//
// Provides default dialers for tcp and udp
use errors;
use ev;
use math::random;
use net::ip;
use net::tcp;
use net::udp;
use net;
use time;

type tcp_dialer = struct {
	loop: *ev::loop,
	cb: *dialcb,
	user: nullable *opaque,
	req: ev::req,
	ip: []ip::addr,
	n: uint,
	port: u16,
};

fn dial_tcp(
	loop: *ev::loop,
	addr: str,
	service: str,
	cb: *dialcb,
	user: nullable *opaque,
) (ev::req | error) = {
	let state = alloc(tcp_dialer {
		loop = loop,
		cb = cb,
		user = user,
		...
	})?;

	match (resolve(loop, "tcp", addr,
		service, &dial_tcp_resolvecb, state)) {
	case let req: ev::req =>
		state.req = req;
		return ev::mkreq(&dial_tcp_cancel, state);
	case let e: error =>
		free(state);
		return e;
	};
};

fn dial_tcp_resolvecb(
	user: nullable *opaque,
	r: (([]ip::addr, u16) | error),
) (void | nomem) = {
	let state = user: *tcp_dialer;
	state.req = ev::req { ... };
	const (ip, port) = match (r) {
	case let r: ([]ip::addr, u16) =>
		yield r;
	case let err: error =>
		dial_tcp_complete(state, err)?;
		return;
	};

	state.ip = ip;
	state.port = port;
	dial_tcp_connect(state)?;
};

fn dial_tcp_connect(state: *tcp_dialer) (void | nomem) = {
	const req = match (ev::connect_tcp(state.loop,
			&dial_tcp_connectcb,
			state.ip[state.n], state.port,
			state)) {
	case let err: (net::error | errors::error) =>
		dial_tcp_complete(state, err)?;
		return;
	case let req: ev::req =>
		yield req;
	};
	state.req = req;
};

fn dial_tcp_connectcb(
	r: (*ev::file | net::error),
	user: nullable *opaque,
) (void | nomem) = {
	let state = user: *tcp_dialer;
	match (r) {
	case let sock: *ev::file =>
		ev::setuser(sock, null);
		dial_tcp_complete(state, sock)?;
		return;
	case let err: net::error =>
		if (err is errors::netunreachable) {
			state.n += 1;
			if (state.n < len(state.ip)) {
				dial_tcp_connect(state)?;
				return;
			};
		};
		dial_tcp_complete(state, err)?;
	};
};

fn dial_tcp_cancel(req: *ev::req) void = {
	let state = req.user: *tcp_dialer;
	ev::cancel(&state.req);
	free(state.ip);
	free(state);
};

fn dial_tcp_complete(state: *tcp_dialer, r: (*ev::file | error)) (void | nomem) = {
	const cb = state.cb;
	const user = state.user;
	free(state.ip);
	free(state);
	cb(user, r)?;
};

type udp_dialer = struct {
	loop: *ev::loop,
	cb: *dialcb,
	user: nullable *opaque,
	req: ev::req,
	ip: []ip::addr,
	n: uint,
	port: u16,
};

fn dial_udp(
	loop: *ev::loop,
	addr: str,
	service: str,
	cb: *dialcb,
	user: nullable *opaque,
) (ev::req | error) = {
	let state = alloc(udp_dialer {
		loop = loop,
		cb = cb,
		user = user,
		...
	})?;

	match (resolve(loop, "udp", addr,
		service, &dial_udp_resolvecb, state)) {
	case let req: ev::req =>
		state.req = req;
		return ev::mkreq(&dial_udp_cancel, state);
	case let e: error =>
		free(state);
		return e;
	};
};

fn dial_udp_resolvecb(
	user: nullable *opaque,
	r: (([]ip::addr, u16) | error),
) (void | nomem) = {
	let state = user: *udp_dialer;
	state.req = ev::req { ... };
	const (ip, port) = match (r) {
	case let r: ([]ip::addr, u16) =>
		yield r;
	case let err: error =>
		dial_udp_complete(state, err)?;
		return;
	};

	state.ip = ip;
	state.port = port;
	dial_udp_connect(state)?;
};

fn dial_udp_connect(state: *udp_dialer) (void | nomem) = {
	for (true) {
		match (ev::connect_udp(state.loop, state.ip[state.n], state.port)) {
		case let err: net::error =>
			if (err is errors::netunreachable) {
				state.n += 1;
				if (state.n < len(state.ip)) {
					continue;
				};
			};
			dial_udp_complete(state, err)?;
			break;
		case let err: errors::error =>
			dial_udp_complete(state, err)?;
			if (err is errors::netunreachable) {
				state.n += 1;
				if (state.n < len(state.ip)) {
					continue;
				};
			};
			break;
		case let sock: *ev::file =>
			dial_udp_complete(state, sock)?;
			break;
		};
	};
};

fn dial_udp_cancel(req: *ev::req) void = {
	let state = req.user: *udp_dialer;
	ev::cancel(&state.req);
	free(state.ip);
	free(state);
};

fn dial_udp_complete(state: *udp_dialer, r: (*ev::file | error)) (void | nomem) = {
	const cb = state.cb;
	const user = state.user;
	free(state.ip);
	free(state);
	cb(user, r)?;
};