// DHCP from a program: a discover and offer against a server, a full lease with what it gives,
// a refused lease, packets read with their options by name, and a small DHCP server of this
// program's own on udp.listen.
//
//   DHCP_SERVER=192.168.1.1 sudo quillo run dhcp.qwe      then open http://localhost:8080/offer
//
// A real client talks from port 68 and needs root; DHCP_SERVER and DHCP_CLIENT_PORT point it at
// a server on a high port instead, as a relay would.

use "dhcp.qwer"

let SERVER = env("DHCP_SERVER", "127.0.0.1:6767")
let CLIENT_PORT = int(env("DHCP_CLIENT_PORT", "6768"))
let MINE = int(env("DHCP_LISTEN_PORT", "6769"))
let ask = { server: SERVER, client_port: CLIENT_PORT, timeout_ms: 400, retries: 1, hostname: "station-7" }

route GET /offer {
    let o = dhcp_discover("02:00:00:aa:bb:cc", ask)?
    { type: o.type, address: o.address, server: o.server, mask: o.subnet_mask, routers: o.routers, dns: o.dns_servers, domain: o.domain_name, lease: o.lease_time }
}

route GET /lease {
    let l = dhcp_lease("02:00:00:aa:bb:cc", ask)?
    { type: l.type, address: l.address, renew: l.renewal_time, routes: l.options.classless_routes, search: l.options.domain_search, released: dhcp_release("02:00:00:aa:bb:cc", l.address, l.server, ask)? }
}

route GET /refused = {
    nak: dhcp_lease("02:00:00:00:00:66", ask).error,
    silent: dhcp_discover("02:00:00:00:00:01", { ...ask, server: "127.0.0.1:1", timeout_ms: 100, retries: 1 }).error,
    bad_mac: dhcp_discover("not-a-mac", ask).error,
}

// A packet from a relay, with its option 82 and a hostname long enough to be split in two.
route GET /packet {
    let long = repeat("a", 300)
    let p = dhcp_build({ mac: "02:00:00:11:22:33", xid: 3735928559, giaddr: "10.1.0.1", hops: 1, secs: 4, broadcast: true,
        options: { message_type: "discover", vendor_class: "MSFT 5.0", root_path: long, parameter_list: ["subnet_mask", "routers", 249], relay_info: { circuit_id: "eth0/1", remote_id: "switch-3" } } })?
    let r = dhcp_parse(p)?
    { op: r.op, type: r.type, xid: r.xid, mac: r.mac, relay: r.giaddr, flags: r.broadcast, secs: r.secs, vendor: r.options.vendor_class, asked: r.options.parameter_list, agent: r.options.relay_info, joined: len(r.options.root_path), size: len(p) }
}

route GET /options {
    let p = dhcp_build({ op: "reply", mac: "02:00:00:11:22:33", xid: 1, yiaddr: "192.168.1.50", file: "pxelinux.0", sname: "boot1",
        options: { message_type: "offer", server_id: "192.168.1.1", lease_time: 86400, mtu: 1500, ntp_servers: ["10.0.0.1", "10.0.0.2"], classless_routes: [{ network: "10.0.0.0/8", via: "192.168.1.254" }, { network: "0.0.0.0/0", via: "192.168.1.1" }], domain_search: ["example.com", "corp.example.com"], "224": "c0ffee" } })?
    dhcp_parse(p)?
}

route GET /damaged = {
    short: dhcp_parse("0101").error,
    no_cookie: dhcp_parse(repeat("00", 240)).error,
    cut_option: dhcp_parse("01010600" + repeat("00", 232) + "63825363" + "3510").error,
    unknown_option: dhcp_build({ mac: "02:00:00:00:00:01", options: { colour: "red" } }).error,
    bad_address: dhcp_build({ mac: "02:00:00:00:00:01", options: { routers: ["1.2.3"] } }).error,
    bad_type: dhcp_build({ mac: "02:00:00:00:00:01", options: { message_type: "hello" } }).error,
}

// A tiny server: one address, offered to anyone, and a refusal for one machine.
udp.listen(MINE, (p) => {
    let q = dhcp_parse(p.bytes)?
    let lease = { yiaddr: "10.9.0.20", server_id: "10.9.0.1", subnet_mask: "255.255.255.0", routers: ["10.9.0.1"], lease_time: 600 }
    if q.type == "discover" { udp.reply(p, dhcp_reply(p.bytes, "offer", lease)?) }
    if q.type == "request" {
        if q.mac == "02:00:00:00:00:99" { udp.reply(p, dhcp_reply(p.bytes, "nak", { server_id: "10.9.0.1", message: "not on this list" })?) }
        else { udp.reply(p, dhcp_reply(p.bytes, "ack", lease)?) }
    }
})

route GET /own {
    let mine = { ...ask, server: `127.0.0.1:${MINE}` }
    { lease: dhcp_lease("02:00:00:00:00:42", mine)?.address, refused: dhcp_lease("02:00:00:00:00:99", mine).error }
}
