+static void dhcpv6_add_server_cand(const struct dhcpv6_server_cand *cand)
+{
+ size_t cand_len, i;
+ struct dhcpv6_server_cand *c = odhcp6c_get_state(STATE_SERVER_CAND, &cand_len);
+
+ // Remove identical duid server candidate
+ for (i = 0; i < cand_len / sizeof(*c); ++i) {
+ if (cand->duid_len == c[i].duid_len &&
+ !memcmp(cand->duid, c[i].duid, cand->duid_len)) {
+ free(c[i].ia_na);
+ free(c[i].ia_pd);
+ odhcp6c_remove_state(STATE_SERVER_CAND, i * sizeof(*c), sizeof(*c));
+ break;
+ }
+ }
+
+ for (i = 0, c = odhcp6c_get_state(STATE_SERVER_CAND, &cand_len);
+ i < cand_len / sizeof(*c); ++i) {
+ if (c[i].preference < cand->preference)
+ break;
+ }
+
+ odhcp6c_insert_state(STATE_SERVER_CAND, i * sizeof(*c), cand, sizeof(*cand));
+}
+
+static void dhcpv6_clear_all_server_cand(void)
+{
+ size_t cand_len, i;
+ struct dhcpv6_server_cand *c = odhcp6c_get_state(STATE_SERVER_CAND, &cand_len);
+
+ // Server candidates need deep delete for IA_NA/IA_PD
+ for (i = 0; i < cand_len / sizeof(*c); ++i) {
+ free(c[i].ia_na);
+ free(c[i].ia_pd);
+ }
+ odhcp6c_clear_state(STATE_SERVER_CAND);
+}
+
+int dhcpv6_promote_server_cand(void)
+{
+ size_t cand_len;
+ struct dhcpv6_server_cand *cand = odhcp6c_get_state(STATE_SERVER_CAND, &cand_len);
+ uint16_t hdr[2];
+ int ret = (na_mode == IA_MODE_NONE && pd_mode == IA_MODE_NONE) ?
+ DHCPV6_STATELESS : DHCPV6_STATEFUL;
+
+ // Clear lingering candidate state info
+ odhcp6c_clear_state(STATE_SERVER_ID);
+ odhcp6c_clear_state(STATE_IA_NA);
+ odhcp6c_clear_state(STATE_IA_PD);
+
+ if (!cand_len)
+ return -1;
+
+ if (cand->has_noaddravail && na_mode == IA_MODE_TRY) {
+ na_mode = IA_MODE_NONE;
+
+ dhcpv6_retx[DHCPV6_MSG_SOLICIT].max_timeo = cand->sol_max_rt;
+ dhcpv6_retx[DHCPV6_MSG_INFO_REQ].max_timeo = cand->inf_max_rt;
+
+ return dhcpv6_request(DHCPV6_MSG_SOLICIT);
+ }
+
+ hdr[0] = htons(DHCPV6_OPT_SERVERID);
+ hdr[1] = htons(cand->duid_len);
+ odhcp6c_add_state(STATE_SERVER_ID, hdr, sizeof(hdr));
+ odhcp6c_add_state(STATE_SERVER_ID, cand->duid, cand->duid_len);
+ accept_reconfig = cand->wants_reconfigure;
+ if (cand->ia_na_len) {
+ odhcp6c_add_state(STATE_IA_NA, cand->ia_na, cand->ia_na_len);
+ free(cand->ia_na);
+ if (na_mode != IA_MODE_NONE)
+ ret = DHCPV6_STATEFUL;
+ }
+ if (cand->ia_pd_len) {
+ odhcp6c_add_state(STATE_IA_PD, cand->ia_pd, cand->ia_pd_len);
+ free(cand->ia_pd);
+ if (request_prefix)
+ ret = DHCPV6_STATEFUL;
+ }
+
+ dhcpv6_retx[DHCPV6_MSG_SOLICIT].max_timeo = cand->sol_max_rt;
+ dhcpv6_retx[DHCPV6_MSG_INFO_REQ].max_timeo = cand->inf_max_rt;
+
+ odhcp6c_remove_state(STATE_SERVER_CAND, 0, sizeof(*cand));
+
+ return ret;