// 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,
...
});
const req = resolve(loop, "tcp", addr,
service, &dial_tcp_resolvecb, state)?;
state.req = req;
return ev::mkreq(&dial_tcp_cancel, state);
};
fn dial_tcp_resolvecb(
user: nullable *opaque,
r: (([]ip::addr, u16) | error),
) void = {
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 = {
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 = {
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: *tcp_dialer;
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 = {
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,
...
});
const req = resolve(loop, "udp", addr,
service, &dial_udp_resolvecb, state)?;
state.req = req;
return ev::mkreq(&dial_udp_cancel, state);
};
fn dial_udp_resolvecb(
user: nullable *opaque,
r: (([]ip::addr, u16) | error),
) void = {
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 = {
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: *udp_dialer;
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 = {
const cb = state.cb;
const user = state.user;
free(state.ip);
free(state);
cb(user, r);
};