add encoding and more protocol logic
M MODULE/Protocols.pmod/DHCPv6.pmod/ClientIdOption.pike +8 -0
@@ 4,6 4,14 @@ constant option_type = 1;
 
 .DUID duid;
 
+mixed _encode() {
+  return ([ "duid": duid ]);
+}
+
+void _decode(mixed x) {
+  duid = x->duid;
+}
+
 protected variant void create(.DUID _duid) {
   duid = _duid;
 }

          
M MODULE/Protocols.pmod/DHCPv6.pmod/DHCPMessage.pike +16 -1
@@ 2,6 2,7 @@ constant message_type = 0;
 int transaction_id;
 
 array(.DHCPOption) options;
+multiset(int) option_types = (<>);
 
 inherit ADT.struct;
 

          
@@ 25,8 26,22 @@ protected void decode_body(Stdio.Buffer 
   object option; 
   while(sizeof(this)) {
     option = decode_option(buf);
+    if(option) {
+      options += ({option});
+      option_types[option->option_type] = 1;
+    }
   }
-  if(option) options += ({option});
+}
+
+int(0..1) has_option(int option_type) {
+  return option_types[option_type];
+}
+
+.DHCPOption get_option(int option_type) {
+  foreach(options;; .DHCPOption option)
+    if(option->option_type == option_type) return option;
+
+  return 0;
 }
 
 object decode_option(Stdio.Buffer buf) {

          
M MODULE/Protocols.pmod/DHCPv6.pmod/DUID.pike +18 -0
@@ 13,3 13,21 @@ protected variant void create(int enterp
 string encode() {
   return duid;
 }
+
+mixed _encode() {
+werror("_encode\n");
+  return (["duid": duid]);
+}
+
+void _decode(mixed x) {
+werror("decode: %O\n", x);
+  duid = x->duid;
+}
+
+protected int(0..1) _equal(mixed other) {
+  return objectp(other) && other->duid == duid;
+}
+
+protected int(0..1) `==(mixed other) {
+  return objectp(other) && other->duid == duid;
+}

          
M MODULE/Protocols.pmod/DHCPv6.pmod/ElapsedTimeOption.pike +8 -0
@@ 4,6 4,14 @@ constant option_type = 8;
 
 int elapsed_time;
 
+mixed _encode() {
+  return ([ "elapsed_time": elapsed_time ]);
+}
+
+void _decode(mixed x) {
+  elapsed_time = x->elapsed_time;
+}
+
 protected variant void create(int since) {
   if(!since) elapsed_time = 0;
   else {

          
M MODULE/Protocols.pmod/DHCPv6.pmod/IAID.pike +22 -0
@@ 1,9 1,31 @@ 
 string iaid;
+ 
+protected variant void create() {}
 
 protected variant void create(string _iaid) {
   iaid = _iaid;
 }
 
+mixed _encode() {
+  return (["iaid" : iaid]);
+}
+
+mixed _decode(mixed x) {
+  iaid = x->iaid;
+}
+
 string encode() {
   return sprintf("%04s", iaid);
 }
+
+protected int(0..1) _equal(mixed other) {
+  return objectp(other) && other->iaid == iaid;
+}
+
+protected int(0..1) `==(mixed other) {
+  return objectp(other) && other->iaid == iaid;
+}
+
+protected int _hash() {
+  return hash(iaid);
+}

          
M MODULE/Protocols.pmod/DHCPv6.pmod/IAPD.pike +28 -4
@@ 1,5 1,4 @@ 
 inherit .DHCPOption;
-constant IA_PD_OPTION = 26;
 
 constant option_type = 25;
 .IAID iaid;

          
@@ 7,6 6,17 @@ int t1;
 int t2;
 
 array options;
+multiset(int) option_types = (<>);
+
+mixed _encode() {
+  return (["t1": t1, "t2": t2, "iaid": (iaid)]);
+}
+
+mixed _decode(mixed x) {
+  t1 = x->t1;
+  t2 = x->t2;
+  iaid = x->iaid;
+}
 
 protected variant void create(.IAID _iaid, int t1_secs, int t2_secs, array _options) {
   iaid = _iaid;

          
@@ 36,9 46,12 @@ werror("parsing IAPD\n");
   object option;
 
   while(sizeof(buf)) {
-   option = decode_pd_option(buf);
+    option = decode_pd_option(buf);
  
-    if(option) options +=({option});
+    if(option) {
+      options +=({option});
+      option_types[option->option_type] = 1;
+    }
   }
 }
 

          
@@ 47,7 60,7 @@ object decode_pd_option(Stdio.Buffer buf
 
   int pd_type = buf->read_int(2);
 
-  if(pd_type != IA_PD_OPTION) {
+  if(pd_type != .OPTION_IA_PDOPTION) {
      throw(Error.Generic("Received invalid IA_PD option type " + pd_type +".\n"));
   }
 //  rk->rewind();

          
@@ 56,3 69,14 @@ object decode_pd_option(Stdio.Buffer buf
   object option = .IA_PDOption(buf->read_hstring(2));
   return option;
 }
+
+int(0..1) has_option(int option_type) {
+  return option_types[option_type];
+}
+
+.DHCPOption get_option(int option_type) {
+  foreach(options;; .DHCPOption option)
+    if(option->option_type == option_type) return option;
+
+  return 0;
+}

          
M MODULE/Protocols.pmod/DHCPv6.pmod/IA_PDOption.pike +13 -0
@@ 10,6 10,19 @@ string address;
 
 array options;
 
+mixed _encode() {
+  return (["preferred_lifetime": preferred_lifetime, "valid_lifetime": valid_lifetime, 
+	"prefix": prefix, "address": address, "options": options]);
+}
+
+void _decode(mixed x) {
+  preferred_lifetime = x->preferred_lifetime;
+  valid_lifetime = x->valid_lifetime;
+  prefix = x->prefix;
+  address = x->address;
+  options = x->options;
+}
+
 protected variant void create(int _preferred_lifetime, int _valid_lifetime, int _prefix, string|int(0..0) _address) {
   preferred_lifetime = _preferred_lifetime;
   valid_lifetime = _valid_lifetime;

          
M MODULE/Protocols.pmod/DHCPv6.pmod/OptionRequestOption.pike +8 -0
@@ 7,6 7,14 @@ protected void create(array(array(int)) 
   options = _options;
 }
 
+mixed _encode() {
+  return (["options": options]);
+}
+
+void _decode(mixed x) {
+  options = x->options;
+}
+
 protected variant void create() {
   options = ({});
 }

          
M MODULE/Protocols.pmod/DHCPv6.pmod/ServerIdOption.pike +8 -0
@@ 4,6 4,14 @@ constant option_type = 2;
 
 .DUID duid;
 
+mixed _encode() {
+  return ([ "duid": duid ]);
+}
+
+void _decode(mixed x) {
+  duid = x->duid;
+}
+
 protected variant void create(.DUID _duid) {
   duid = _duid;
 }

          
M MODULE/Protocols.pmod/DHCPv6.pmod/StatusCodeOption.pike +9 -0
@@ 5,6 5,15 @@ constant option_type = 13;
 int status_code;
 string status_message;
 
+mixed _encode() {
+  return ([ "status_code": status_code, "status_message": status_message ]);
+}
+
+void _decode(mixed x) {
+  status_code = x->status_code;
+  status_message = x->status_message;
+}
+
 protected variant void create(int code, string message) {
    status_code = code;
    status_message = message;

          
M MODULE/Protocols.pmod/DHCPv6.pmod/module.pmod +10 -0
@@ 15,6 15,16 @@ constant MESSAGE_INFORMATION_REQUEST = 1
 constant MESSAGE_RELAY_FORW = 12;
 constant MESSAGE_RELAY_REPL = 13;
 
+constant OPTION_IAPD = 25;
+constant OPTION_IA_PDOPTION = 26;
+constant OPTION_CLIENT_IDENTIFIER = 1;
+constant OPTION_SERVER_IDENTIFIER = 2;
+constant OPTION_OPTION_REQUEST = 6;
+constant OPTION_ELAPSED_TIME = 8;
+constant OPTION_STATUS_CODE = 13;
+
+constant STATUS_NO_PREFIX_AVAILABLE = 6;
+
 protected void create() {
   foreach(values(Protocols.DHCPv6);; mixed p) {
     if(!programp(p)) continue;

          
M dhcpv6_pd.pike +214 -14
@@ 1,54 1,254 @@ 
+import Protocols.DHCPv6;
 
 constant DHCP_CLIENT_PORT = 546; 
 constant DHCP_SERVER_PORT = 547;
 
+constant NOT_AWAITING = 0;
+constant AWAIT_ADVERTISE = 1;
+constant AWAIT_REPLY = 2;
+
 constant DHCP_BROADCAST_ADDRESS = "FF02::1:2";
 
+string leasefile = "/var/run/dhcpv6_pd_leases";
 string v6if = "net0";
 string identifier;
+string localv6addr;
+int have_leases;
+mapping lease_data = ([]);
+
+int current_state = NOT_AWAITING;
+int current_txn;
+mixed time_out_id;
+float last_timeout = 0.0;
 
 Stdio.UDP dhcp;
-Protocols.DHCPv6.DUID duid;
+DUID duid;
+IAID iaid;
+
+IAPD current_iapd;
+int current_iapd_confirmed;
 
 int main(int argc, array argv) {
   identifier = Standards.JSON.decode(Process.popen("sysinfo"))->UUID;
+  duid = DUID(0, identifier);
   werror("Identifier: %O\n", identifier);
+  localv6addr = get_if_address(v6if);
+  if(!localv6addr) {
+    fatal("Unable to determine IPv6 address for " + v6if + ".");
+  }
+  iaid = IAID(Crypto.MD5.hash(v6if)[0..3]);
+
+  werror("Address: %O\n", localv6addr);
+  string leases = Stdio.read_file(leasefile);
+  if(leases) {
+    mapping ld;
+    mixed e = catch(ld = decode_value(leases));
+    if(e) werror("Unable to decode lease data. Backtrace follows.\n%s\n", describe_backtrace(e));
+      else {
+      werror("Have leases for Prefix Delegation, will attempt to reconfirm them.\n");
+      lease_data = ld;
+      have_leases = 1;
+    }
+  }
+
   dhcp = Stdio.UDP();
-  dhcp->bind(DHCP_CLIENT_PORT, "2001:558:6003:2a:79b0:ea90:48d2:3ad7");
+  dhcp->bind(DHCP_CLIENT_PORT, localv6addr);
   dhcp->enable_broadcast();
   dhcp->set_nonblocking();
   dhcp->set_read_callback(got_packet);
-call_out(send_solicit, 5);
+
+  if(!have_leases) {
+    werror("Scheduling lease solicitation...\n");
+    call_out(begin_solicit, 5, 0);
+  } else {
+    werror("Scheduling lease confirmation...\n");
+  }
   return -1;
 }
 
+void fatal(string message, int|void retcode) {
+  werror("FATAL: %s\n", message);
+  exit(retcode||1);
+}
+
+string get_if_address(string v6if) {
+
+  mapping ifs = NetUtils.local_interfaces();
+
+  foreach(ifs; string iface; array addrs) {
+    if(iface == v6if || has_prefix(iface, v6if + ":")) {
+      foreach(addrs;; string addr) {
+       if(NetUtils.get_network_type(addr, 1) == "localhostv6") return (addr/"/")[0];
+      }
+    }
+  }
+ 
+  return 0;
+}
+
+void handle_advertise_message(AdvertiseMessage message, string addr) {
+  if(is_actionable_advertise(message)) {
+    current_state = NOT_AWAITING;
+    if(time_out_id) remove_call_out(time_out_id);
+    
+    current_iapd = message->get_option(OPTION_IAPD);
+    mapping lease_data = ([]);
+    lease_data->current_iapd = current_iapd;
+    lease_data->v6if = v6if;
+    lease_data->identifier = identifier;
+    lease_data->updated = time();
+    lease_data->server_identifier = message->get_option(OPTION_SERVER_IDENTIFIER);
+    lease_data->server_address = addr;
+    Stdio.write_file(leasefile, encode_value(lease_data));
+  }
+}
+
+void handle_reply_message(ReplyMessage message, string addr) {
+  if(is_actionable_reply(message)) {
+    current_state = NOT_AWAITING;
+    if(time_out_id) remove_call_out(time_out_id);
+
+    current_iapd = message->get_option(OPTION_IAPD);
+  }
+}
+
+int(0..1) is_actionable_advertise(AdvertiseMessage message) {
+werror("is_actionable_advertise?\n");
+werror("options: %O\n", message->options);
+  if(!message->has_option(OPTION_CLIENT_IDENTIFIER)) return 0;
+werror("have client identifier\n");
+  if(!message->has_option(OPTION_SERVER_IDENTIFIER)) return 0;
+werror("have server identifier\n");
+  werror("duid: %O -> %O\n", message->get_option(OPTION_CLIENT_IDENTIFIER)->duid->duid, duid->duid);
+  if(message->get_option(OPTION_CLIENT_IDENTIFIER)->duid != duid) return 0;
+werror("have matching duid\n");
+  if(message->has_option(OPTION_STATUS_CODE) && message->get_option(OPTION_STATUS_CODE)->status_code == STATUS_NO_PREFIX_AVAILABLE) {
+    string message = message->get_option(OPTION_STATUS_CODE)->status_message;
+    if(message) werror("Received status message in advertisement: %s\n", message);
+    return 0;
+  }
+  if(message->has_option(OPTION_IAPD)) {
+werror("have iapd option\n");
+    IAPD iapd = message->get_option(OPTION_IAPD);
+    if(!iapd->has_option(OPTION_IA_PDOPTION)) return 0;
+    if(iapd->iaid != iaid) return 0;
+    if(!iapd->t1 || !iapd->t2) return 0;
+werror("have ia pdoption\n");
+    IA_PDOption pdo = iapd->get_option(OPTION_IA_PDOPTION);
+    werror("pdo: %O\n", pdo);
+    if(!pdo->prefix) return 0;
+    return 1;
+  }
+
+  return 0;
+}
+
+int(0..1) is_actionable_reply(ReplyMessage message) {
+  if(!message->has_option(OPTION_CLIENT_IDENTIFIER)) return 0;
+  if(!message->has_option(OPTION_SERVER_IDENTIFIER)) return 0;
+  if(message->get_option(OPTION_CLIENT_IDENTIFIER)->duid != duid) return 0;
+}
+
 void got_packet(mapping data, mixed ... args) {
   werror("got_packet(%O, %O)\n", data, args);
-  object x = Protocols.DHCPv6.decode_message(data->data);
+  object x = decode_message(data->data);
   werror("x: %O options: %O\n", x, x->options[0]);
   werror("o: %O\n", mkmapping(indices(x->options[0]), values(x->options[0])));
+  if(current_txn == x->transaction_id) {
+    switch(x->message_type) {
+      case MESSAGE_ADVERTISE:
+        if(current_state == AWAIT_ADVERTISE) {
+        werror("Got an ADVERTISE message.\n"); 
+        handle_advertise_message(x, data->ip);
+        } else {
+          werror("Got an ADVERTISE message at an inappropriate time. Ignoring.\n");
+        }
+        break;
+      case MESSAGE_REPLY:
+        if(current_state == AWAIT_REPLY) {
+          werror("Got a REPLY message.\n");
+          handle_reply_message(x, data->ip);
+        } else {
+          werror("Got a REPLY at an inappropriate time. Ignoring.\n");
+        }
+        break;
+      default:
+        werror("Ignoring message of type " + x->message_type + ".\n");
+    }
+
+  } else {
+    werror("Ignoring message for someone else's transaction.\n");
+  }
 }
 
-void send(Protocols.DHCPv6.DHCPMessage message, string dest, int port) {
+void send(DHCPMessage message, string dest, int port) {
   string m = message->encode();
   werror("sending message: %O -> %O on port %d\n", message, m, port);
   dhcp->send(dest, port, m);
 }
 
-void send_broadcast(Protocols.DHCPv6.DHCPMessage message) {
+void send_broadcast(DHCPMessage message) {
   send(message, DHCP_BROADCAST_ADDRESS, DHCP_SERVER_PORT);
 }
 
-void send_solicit() {
-  object p = Protocols.DHCPv6.SolicitMessage(Protocols.DHCPv6.generate_transaction_id());
-  object id = Protocols.DHCPv6.ClientIdOption(Protocols.DHCPv6.DUID(0, identifier));
-  object pd_opt = Protocols.DHCPv6.IA_PDOption(3600*24, 3600*36, 64, 0);
-  object ia_ident = Protocols.DHCPv6.IAID(Crypto.MD5.hash(v6if)[0..3]);
+void begin_solicit(int|void since) {
+  call_out(send_solicit, 5, since);
+}
+
+void receive_timed_out(int since, int attempts) {
+  werror("Hit timeout awaiting actionable messages\n");
+  int old_state = current_state;
+  current_state = NOT_AWAITING;
+
+  if(attempts < 3) { 
+    float timeout = last_timeout + random(last_timeout * 2.0);
+    if(old_state == AWAIT_ADVERTISE)
+      call_out(send_solicit, timeout, since, attempts);
+    else if(old_state == AWAIT_REPLY)
+      call_out(send_request, timeout, since, attempts);
+  }
+  else {
+   werror("Hit retry limit; backing off.\n");
+   call_out(begin_solicit, 60, 0);
+  }
+}
+
+void send_request(int|void since, int|void attempts, float|void timeout) {
+  if(current_state != NOT_AWAITING) {
+    throw(Error.Generic("Can't send SOLICIT if we're already awaiting a message. Current state = " + current_state + "\n"));
+  }
+
+  // are we a re-transmission?
+  if(!since) {
+    since = time();
+    current_txn = generate_transaction_id();
+  } 
+}
+
+void send_solicit(int|void since, int|void attempts, float|void timeout) {
+  if(current_state != NOT_AWAITING) {
+    throw(Error.Generic("Can't send SOLICIT if we're already awaiting a message. Current state = " + current_state + "\n"));
+  }
+
+  // are we a re-transmission?
+  if(!since) {
+    since = time();
+    current_txn = generate_transaction_id();
+  } 
+  object p = SolicitMessage(current_txn);
+  object id = ClientIdOption(duid);
+  object pd_opt = IA_PDOption(3600*24, 3600*36, 64, 0);
+  object ia_ident = iaid; 
 
   p->options += ({id});
-  p->options += ({Protocols.DHCPv6.OptionRequestOption()});
-  p->options += ({Protocols.DHCPv6.ElapsedTimeOption(0)});
-  p->options += ({Protocols.DHCPv6.IAPD(ia_ident, 3600*24, 3600*36, ({pd_opt}))
+  p->options += ({OptionRequestOption()});
+  p->options += ({ElapsedTimeOption(since)});
+  p->options += ({IAPD(ia_ident, 3600*24, 3600*36, ({pd_opt}))
 });
+
+  if(!timeout) timeout = 5.0;
+  last_timeout = timeout;
+  time_out_id = call_out(receive_timed_out, timeout, since, attempts++);
+  current_state = AWAIT_ADVERTISE;
   send_broadcast(p);
 }