Free (GPLv2) TCP/IP stack developed by TASS Belgium
Fork of PicoTCP by
Diff: modules/pico_dhcp_client.c
- Revision:
- 51:18637a3d071f
- Parent:
- 29:1a47b7151851
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/pico_dhcp_client.c Sat Aug 03 08:50:27 2013 +0000 @@ -0,0 +1,731 @@ +/********************************************************************* +PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. +See LICENSE and COPYING for usage. + +Authors: Frederik Van Slycken, Kristof Roelants +*********************************************************************/ + +#include "pico_dhcp_client.h" +#include "pico_stack.h" +#include "pico_config.h" +#include "pico_device.h" +#include "pico_ipv4.h" +#include "pico_socket.h" + +#ifdef PICO_SUPPORT_DHCPC + +/*********** + * structs * + ***********/ + +static uint8_t dhcp_client_mutex = 1; /* to serialize client negotations if multiple devices */ + +struct dhcp_timer_param{ + uint16_t type; + struct pico_dhcp_client_cookie* cli; + int valid; +}; + +struct pico_dhcp_client_cookie +{ + uint32_t xid; + uint32_t *xid_user; + struct pico_ip4 address; + struct pico_ip4 netmask; + struct pico_ip4 gateway; + struct pico_ip4 nameserver; + struct pico_ip4 server_id; + uint32_t lease_time; + uint32_t T1; + uint32_t T2; + struct pico_socket* socket; + int connected; + struct pico_device* device; + unsigned long start_time; + int attempt; + enum dhcp_negotiation_state state; + void (*cb)(void* cli, int code); + struct dhcp_timer_param* timer_param_1; + struct dhcp_timer_param* timer_param_2; + struct dhcp_timer_param* timer_param_lease; + struct dhcp_timer_param* timer_param_retransmit; + int link_added; +}; + +static int dhcp_cookies_cmp(void *ka, void *kb) +{ + struct pico_dhcp_client_cookie *a = ka, *b = kb; + if (a->xid < b->xid) + return -1; + else if (a->xid > b->xid) + return 1; + else + return 0; +} +PICO_TREE_DECLARE(DHCPCookies, dhcp_cookies_cmp); + +/************************* + * function declarations * + *************************/ +static void pico_dhcp_state_machine(int type, struct pico_dhcp_client_cookie* cli, uint8_t* data, int len); +static void pico_dhcp_reinitiate_negotiation(unsigned long now, void *arg); + +//cb +static void pico_dhcp_wakeup(uint16_t ev, struct pico_socket *s); +static void dhcp_timer_cb(unsigned long tick, void* param); + +//util +static void pico_dhcp_retry(struct pico_dhcp_client_cookie *client); +static int dhclient_send(struct pico_dhcp_client_cookie *cli, uint8_t msg_type); +static int pico_dhcp_verify_and_identify_type(uint8_t* data, int len, struct pico_dhcp_client_cookie *cli); +static int init_cookie(struct pico_dhcp_client_cookie* cli); +static struct pico_dhcp_client_cookie* get_cookie_by_xid(uint32_t xid); +static uint32_t get_xid(uint8_t* data); + +//fsm functions +static int recv_offer(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); +static int recv_ack(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); +static int renew(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); +static int reset(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); +static int retransmit(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); + +//fsm implementation +static void pico_dhcp_state_machine(int type, struct pico_dhcp_client_cookie* cli, uint8_t* data, int len); + +/*************** + * entry point * + ***************/ + +static uint32_t pico_dhcp_execute_init(struct pico_dhcp_client_cookie *cli) +{ + if (!dhcp_client_mutex) { + pico_timer_add(3000, pico_dhcp_reinitiate_negotiation, cli); + return 0; + } + dhcp_client_mutex--; + + if (init_cookie(cli) < 0) + return -1; + + dbg("DHCPC: cookie with xid %u\n", cli->xid); + + if (pico_tree_insert(&DHCPCookies, cli)) { + pico_err = PICO_ERR_EAGAIN; + if(cli->cb != NULL) { + cli->cb(cli, PICO_DHCP_ERROR); + } + pico_free(cli); + return -1; /* Element key already exists */ + } + + if (dhclient_send(cli, PICO_DHCP_MSG_DISCOVER) < 0) + return -1; + + return 0; +} + +/* returns a pointer to the client cookie. The user should pass this pointer every time he calls a dhcp-function. This is so that we can (one day) support dhcp on multiple interfaces */ +int pico_dhcp_initiate_negotiation(struct pico_device *device, void (*callback)(void *cli, int code), uint32_t *xid) +{ + struct pico_dhcp_client_cookie *cli; + + if(!device || !callback || !xid){ + pico_err = PICO_ERR_EINVAL; + return -1; + } + cli = pico_zalloc(sizeof(struct pico_dhcp_client_cookie)); + if(!cli){ + pico_err = PICO_ERR_ENOMEM; + return -1; + } + + cli->device = device; + cli->cb = callback; + cli->xid_user = xid; + *(cli->xid_user) = 0; + + return pico_dhcp_execute_init(cli); +} + +static void pico_dhcp_reinitiate_negotiation(unsigned long now, void *arg) +{ + struct pico_dhcp_client_cookie *cli = (struct pico_dhcp_client_cookie *) arg; + + pico_dhcp_execute_init(cli); + + return; +} + +/******************** + * access functions * + ********************/ + +struct pico_ip4 pico_dhcp_get_address(void* cli) +{ + return ((struct pico_dhcp_client_cookie*)cli)->address; +} + +struct pico_ip4 pico_dhcp_get_gateway(void* cli) +{ + return ((struct pico_dhcp_client_cookie*)cli)->gateway; +} + +struct pico_ip4 pico_dhcp_get_nameserver(void* cli) +{ + return ((struct pico_dhcp_client_cookie*)cli)->nameserver; +} + +/************* + * callbacks * + *************/ + +static void pico_dhcp_wakeup(uint16_t ev, struct pico_socket *s) +{ + uint8_t buf[DHCPC_DATAGRAM_SIZE]; + int r=0; + uint32_t peer; + uint16_t port; + int type; + + struct pico_dhcp_client_cookie *cli; + dbg("DHCPC: called dhcp_wakeup\n"); + if (ev == PICO_SOCK_EV_RD) { + do { + r = pico_socket_recvfrom(s, buf, DHCPC_DATAGRAM_SIZE, &peer, &port); + cli = get_cookie_by_xid(get_xid(buf)); + if(cli == NULL) + return; + if (r > 0 && port == PICO_DHCPD_PORT) { + type = pico_dhcp_verify_and_identify_type(buf, r, cli); + pico_dhcp_state_machine(type, cli, buf, r); + } + } while(r>0); + } +} + +static void dhcp_timer_cb(unsigned long tick, void* param) +{ + struct dhcp_timer_param* param2 = (struct dhcp_timer_param*) param; + if(param2->valid == 1){ + //dbg("called timer cb on active timer type %d\n",param2->type); + pico_dhcp_state_machine(param2->type, param2->cli, NULL, 0); + } + if(param2->cli->timer_param_1 == param){ + param2->cli->timer_param_1 = NULL; + } + if(param2->cli->timer_param_2 == param){ + param2->cli->timer_param_2 = NULL; + } + if(param2->cli->timer_param_lease == param){ + param2->cli->timer_param_lease = NULL; + } + if(param2->cli->timer_param_retransmit == param){ + param2->cli->timer_param_retransmit = NULL; + } + + pico_free(param); + +} +/***************** + * fsm functions * + *****************/ + +static int recv_offer(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len) +{ + struct pico_dhcphdr *dhdr = (struct pico_dhcphdr *) data; + uint8_t *nextopt, opt_data[20], opt_type; + int opt_len = 20; + uint8_t msg_type = 0xFF; + int T1_set = 0; + int T2_set = 0; + + cli->address.addr = dhdr->yiaddr; + + opt_type = dhcp_get_next_option(dhdr->options, opt_data, &opt_len, &nextopt); + while (opt_type != PICO_DHCPOPT_END) { + if (opt_type == PICO_DHCPOPT_MSGTYPE) + msg_type = opt_data[0]; + if ((opt_type == PICO_DHCPOPT_LEASETIME) && (opt_len == 4)){ + memcpy(&cli->lease_time, opt_data, 4); + cli->lease_time = long_be(cli->lease_time); + } + if ((opt_type == PICO_DHCPOPT_RENEWALTIME) && (opt_len == 4)){ + memcpy(&cli->T1, opt_data, 4); + cli->T1 = long_be(cli->T1); + T1_set =1; + } + if ((opt_type == PICO_DHCPOPT_REBINDINGTIME) && (opt_len == 4)){ + memcpy(&cli->T2, opt_data, 4); + cli->T2 = long_be(cli->T2); + T2_set =1; + } + if ((opt_type == PICO_DHCPOPT_ROUTER) && (opt_len == 4)) //XXX assuming only one router will be advertised... + memcpy(&cli->gateway.addr, opt_data, 4); + if ((opt_type == PICO_DHCPOPT_DNS) && (opt_len == 4)) + memcpy(&cli->nameserver.addr, opt_data, 4); + if ((opt_type == PICO_DHCPOPT_NETMASK) && (opt_len == 4)) + memcpy(&cli->netmask.addr, opt_data, 4); + if ((opt_type == PICO_DHCPOPT_SERVERID) && (opt_len == 4)) + memcpy(&cli->server_id.addr, opt_data, 4); + if (opt_type == PICO_DHCPOPT_OPTIONOVERLOAD) + dbg("DHCPC: WARNING: option overload present (not processed)"); + + opt_len = 20; + opt_type = dhcp_get_next_option(NULL, opt_data, &opt_len, &nextopt); + } + + /* default values for T1 and T2 if necessary */ + if(T1_set != 1) + cli->T1 = cli->lease_time >> 1; + if(T2_set != 1) + cli->T2 = (cli->lease_time * 875) / 1000; + + + + if ((msg_type != PICO_DHCP_MSG_OFFER) || !cli->lease_time || !cli->netmask.addr || !cli->server_id.addr ) + return 0; + + + dhclient_send(cli, PICO_DHCP_MSG_REQUEST); + cli->state = DHCPSTATE_REQUEST; + return 1; +} + +static int recv_ack(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len) +{ + struct pico_ip4 address = {0}; + + if(cli->link_added == 0){ + /* close the socket bound on address 0.0.0.0 */ + pico_socket_close(cli->socket); + cli->socket = NULL; + pico_ipv4_link_del(cli->device, address); + pico_ipv4_link_add(cli->device, cli->address, cli->netmask); + cli->link_added = 1; + } + cli->state = DHCPSTATE_BOUND; + + dbg("DHCPC: T1: %d\n",cli->T1); + dbg("DHCPC: T2: %d\n",cli->T2); + dbg("DHCPC: lease time: %d\n",cli->lease_time); + + if(cli->timer_param_1) + cli->timer_param_1->valid = 0; + if(cli->timer_param_2) + cli->timer_param_2->valid = 0; + if(cli->timer_param_lease) + cli->timer_param_lease->valid = 0; + if(cli->timer_param_retransmit) + cli->timer_param_retransmit->valid = 0; + + + cli->timer_param_1 = pico_zalloc(sizeof(struct dhcp_timer_param)); + if(!cli->timer_param_1){ + if(cli->cb != NULL){ + pico_err = PICO_ERR_ENOMEM; + cli->cb(cli, PICO_DHCP_ERROR); + } + return 0; + } + cli->timer_param_2 = pico_zalloc(sizeof(struct dhcp_timer_param)); + if(!cli->timer_param_2){ + if(cli->cb != NULL){ + pico_err = PICO_ERR_ENOMEM; + cli->cb(cli, PICO_DHCP_ERROR); + } + return 0; + } + cli->timer_param_lease = pico_zalloc(sizeof(struct dhcp_timer_param)); + if(!cli->timer_param_lease){ + if(cli->cb != NULL){ + pico_err = PICO_ERR_ENOMEM; + cli->cb(cli, PICO_DHCP_ERROR); + } + return 0; + } + cli->timer_param_1->valid = 1; + cli->timer_param_2->valid = 1; + cli->timer_param_lease->valid = 1; + + cli->timer_param_1->cli = cli; + cli->timer_param_2->cli = cli; + cli->timer_param_lease->cli = cli; + + cli->timer_param_1->type = PICO_DHCP_EVENT_T1; + cli->timer_param_2->type = PICO_DHCP_EVENT_T2; + cli->timer_param_lease->type = PICO_DHCP_EVENT_LEASE; + //add timer + pico_timer_add(cli->T1*1000, dhcp_timer_cb, cli->timer_param_1); + pico_timer_add(cli->T2*1000, dhcp_timer_cb, cli->timer_param_2); + pico_timer_add(cli->lease_time*1000, dhcp_timer_cb, cli->timer_param_lease); + + *(cli->xid_user) = cli->xid; + if(cli->cb != NULL) + cli->cb(cli, PICO_DHCP_SUCCESS); + else + dbg("DHCPC: no callback\n"); + + dhcp_client_mutex++; + cli->state = DHCPSTATE_BOUND; + return 0; +} + +static int renew(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len) +{ + uint16_t port = PICO_DHCP_CLIENT_PORT; + + /* open and bind to currently acquired address */ + if (!cli->socket){ + cli->socket = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &pico_dhcp_wakeup); + if (!cli->socket) { + dbg("DHCPC: error opening socket on renew: %s\n", strerror(pico_err)); + if(cli->cb != NULL) + cli->cb(cli, PICO_DHCP_ERROR); + return -1; + } + if (pico_socket_bind(cli->socket, &cli->address, &port) != 0){ + dbg("DHCPC: error binding socket on renew: %s\n", strerror(pico_err)); + pico_socket_close(cli->socket); + if(cli->cb != NULL) + cli->cb(cli, PICO_DHCP_ERROR); + return -1; + } + } + cli->state = DHCPSTATE_RENEWING; + dhclient_send(cli, PICO_DHCP_MSG_REQUEST); + + return 0; +} + +static int reset(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len) +{ + if(cli->cb != NULL) + cli->cb(cli, PICO_DHCP_RESET); + //reset pretty much everything + + if(cli->timer_param_1) + cli->timer_param_1->valid = 0; + if(cli->timer_param_2) + cli->timer_param_2->valid = 0; + if(cli->timer_param_lease) + cli->timer_param_lease->valid = 0; + if(cli->timer_param_retransmit) + cli->timer_param_retransmit->valid = 0; + + pico_socket_close(cli->socket); + pico_ipv4_link_del(cli->device, cli->address); + + //initiate negotiations again + init_cookie(cli); + pico_dhcp_retry(cli); + dhclient_send(cli, PICO_DHCP_MSG_DISCOVER); + + return 0; + +} + +static int retransmit(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len) +{ + pico_dhcp_retry(cli); + + if(cli->state == DHCPSTATE_DISCOVER) + dhclient_send(cli, PICO_DHCP_MSG_DISCOVER); + else if(cli->state == DHCPSTATE_RENEWING) + dhclient_send(cli, PICO_DHCP_MSG_REQUEST); + else + dbg("DHCPC: WARNING: should not get here in state %d!\n", cli->state); + + return 0; + +} + +/********************** + * fsm implementation * + **********************/ + +struct dhcp_action_entry { + uint16_t tcpstate; + int (*offer)(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); + int (*ack)(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); + int (*nak)(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); + int (*timer1)(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); + int (*timer_lease)(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); + int (*timer_retransmit)(struct pico_dhcp_client_cookie *cli, uint8_t *data, int len); +}; + +static struct dhcp_action_entry dhcp_fsm[] = { + /* State offer ack nak timer1 timer_lease timer_retransmit*/ + { DHCPSTATE_DISCOVER, recv_offer, NULL, NULL, NULL, reset, retransmit}, + { DHCPSTATE_OFFER, NULL, NULL, NULL, NULL, reset, NULL}, + { DHCPSTATE_REQUEST, NULL, recv_ack, reset, NULL, reset, retransmit}, + { DHCPSTATE_BOUND, NULL, NULL, reset, renew, reset, NULL}, + { DHCPSTATE_RENEWING, NULL, recv_ack, reset, NULL, reset, retransmit}, +}; + + +static void pico_dhcp_state_machine(int type, struct pico_dhcp_client_cookie* cli, uint8_t* data, int len) +{ + dbg("DHCPC: received incoming event of type %d\n", type); + switch(type){ + case PICO_DHCP_MSG_OFFER: + if(dhcp_fsm[cli->state].offer != NULL) + dhcp_fsm[cli->state].offer(cli, data, len); + break; + case PICO_DHCP_MSG_ACK: + if(dhcp_fsm[cli->state].ack != NULL){ + dhcp_fsm[cli->state].ack(cli, data, len); + } + break; + case PICO_DHCP_MSG_NAK: + if(dhcp_fsm[cli->state].nak!= NULL){ + dhcp_fsm[cli->state].nak(cli, data, len); + } + break; + case PICO_DHCP_EVENT_T1: + if(dhcp_fsm[cli->state].timer1!= NULL){ + dhcp_fsm[cli->state].timer1(cli, NULL, 0); + } + break; + case PICO_DHCP_EVENT_LEASE: + if(dhcp_fsm[cli->state].timer_lease!= NULL){ + dhcp_fsm[cli->state].timer_lease(cli, NULL, 0); + } + break; + case PICO_DHCP_EVENT_RETRANSMIT: + if(dhcp_fsm[cli->state].timer_retransmit!= NULL){ + dhcp_fsm[cli->state].timer_retransmit(cli, NULL, 0); + } + break; + default: + dbg("DHCPC: event not supported yet!!\n"); + break; + } +} + + +/********************* + * utility functions * + *********************/ + +static void pico_dhcp_retry(struct pico_dhcp_client_cookie *cli) +{ + const int MAX_RETRY = 3; + uint32_t new_xid; + if (++cli->attempt > MAX_RETRY) { + cli->start_time = pico_tick; + cli->attempt = 0; + new_xid = pico_rand(); + while(get_cookie_by_xid(new_xid) != NULL){ + new_xid = pico_rand(); + } + cli->xid = new_xid; + cli->state = DHCPSTATE_DISCOVER; + } +} + +static int dhclient_send(struct pico_dhcp_client_cookie *cli, uint8_t msg_type) +{ + uint8_t buf_out[DHCPC_DATAGRAM_SIZE] = {0}; + struct pico_dhcphdr *dh_out = (struct pico_dhcphdr *) buf_out; + int sent = 0; + int i = 0; + struct pico_ip4 destination; + uint16_t port = PICO_DHCPD_PORT; + if(cli->state == DHCPSTATE_BOUND || cli->state == DHCPSTATE_RENEWING){ + destination.addr = cli->server_id.addr; + }else{ + destination.addr = long_be(0xFFFFFFFF); + } + + if(cli->device->eth == NULL){ + pico_err = PICO_ERR_EOPNOTSUPP; + if(cli->cb != NULL){ + cli->cb(cli, PICO_DHCP_ERROR); + } + return -1; + } + memcpy(dh_out->hwaddr, &cli->device->eth->mac, PICO_HLEN_ETHER); + dh_out->op = PICO_DHCP_OP_REQUEST; + dh_out->htype = PICO_HTYPE_ETHER; + dh_out->hlen = PICO_HLEN_ETHER; + dh_out->xid = cli->xid; + dh_out->secs = (msg_type == PICO_DHCP_MSG_REQUEST)?0:short_be((pico_tick - cli->start_time)/1000); + dh_out->dhcp_magic = PICO_DHCPD_MAGIC_COOKIE; + if (cli->state == DHCPSTATE_RENEWING) + dh_out->ciaddr = cli->address.addr; + + /* Option: msg type, len 1 */ + dh_out->options[i++] = PICO_DHCPOPT_MSGTYPE; + dh_out->options[i++] = 1; + dh_out->options[i++] = msg_type; + + if (( msg_type == PICO_DHCP_MSG_REQUEST) && ( cli->state != DHCPSTATE_RENEWING )) { + dh_out->options[i++] = PICO_DHCPOPT_REQIP; + dh_out->options[i++] = 4; + dh_out->options[i++] = (long_be(cli->address.addr) & 0xFF000000) >> 24; + dh_out->options[i++] = (long_be(cli->address.addr) & 0xFF0000) >> 16; + dh_out->options[i++] = (long_be(cli->address.addr) & 0xFF00) >> 8; + dh_out->options[i++] = (long_be(cli->address.addr) & 0xFF); + dh_out->options[i++] = PICO_DHCPOPT_SERVERID; + dh_out->options[i++] = 4; + dh_out->options[i++] = (long_be(cli->server_id.addr) & 0xFF000000) >> 24; + dh_out->options[i++] = (long_be(cli->server_id.addr) & 0xFF0000) >> 16; + dh_out->options[i++] = (long_be(cli->server_id.addr) & 0xFF00) >> 8; + dh_out->options[i++] = (long_be(cli->server_id.addr) & 0xFF); + } + + /* Option: req list, len 4 */ + dh_out->options[i++] = PICO_DHCPOPT_PARMLIST; + dh_out->options[i++] = 7; + dh_out->options[i++] = PICO_DHCPOPT_NETMASK; + dh_out->options[i++] = PICO_DHCPOPT_BCAST; + dh_out->options[i++] = PICO_DHCPOPT_TIME; + dh_out->options[i++] = PICO_DHCPOPT_ROUTER; + dh_out->options[i++] = PICO_DHCPOPT_HOSTNAME; + dh_out->options[i++] = PICO_DHCPOPT_RENEWALTIME; + dh_out->options[i++] = PICO_DHCPOPT_REBINDINGTIME; + + /* Option : max message size */ + if( msg_type == PICO_DHCP_MSG_REQUEST || msg_type == PICO_DHCP_MSG_DISCOVER){ + uint16_t dds = DHCPC_DATAGRAM_SIZE; + dh_out->options[i++] = PICO_DHCPOPT_MAXMSGSIZE; + dh_out->options[i++] = 2; + dh_out->options[i++] = (dds & 0xFF00) >> 8; + dh_out->options[i++] = (dds & 0xFF); + } + + + + dh_out->options[i] = PICO_DHCPOPT_END; + sent = pico_socket_sendto(cli->socket, buf_out, DHCPC_DATAGRAM_SIZE, &destination, port); + if (sent < 0) { + dbg("DHCPC: sendto failed: %s\n", strerror(pico_err)); + if(cli->cb != NULL) + cli->cb(cli, PICO_DHCP_ERROR); + } + + + //resend-timer : + if(cli->timer_param_retransmit != NULL) + cli->timer_param_retransmit->valid=0; + + cli->timer_param_retransmit = pico_zalloc(sizeof(struct dhcp_timer_param)); + if(!cli->timer_param_retransmit){ + if(cli->cb != NULL) + pico_err = PICO_ERR_ENOMEM; + cli->cb(cli, PICO_DHCP_ERROR); + return -1; + } + cli->timer_param_retransmit->valid = 1; + cli->timer_param_retransmit->cli = cli; + cli->timer_param_retransmit->type = PICO_DHCP_EVENT_RETRANSMIT; + pico_timer_add(4000, dhcp_timer_cb, cli->timer_param_retransmit); + + return 0; +} + +//identifies type & does some preprocessing : checking if everything is valid +static int pico_dhcp_verify_and_identify_type(uint8_t* data, int len, struct pico_dhcp_client_cookie *cli) +{ + struct pico_dhcphdr *dhdr = (struct pico_dhcphdr *) data; + uint8_t *nextopt, opt_data[20], opt_type; + int opt_len = 20; + + if (dhdr->xid != cli->xid) + return 0; + + if (!is_options_valid(dhdr->options, len - sizeof(struct pico_dhcphdr))) + return 0; + + if( dhdr->dhcp_magic != PICO_DHCPD_MAGIC_COOKIE) + return 0; + + opt_type = dhcp_get_next_option(dhdr->options, opt_data, &opt_len, &nextopt); + while (opt_type != PICO_DHCPOPT_END) { + /* parse interesting options here */ + if (opt_type == PICO_DHCPOPT_MSGTYPE) { + return *opt_data; + } + opt_len = 20; + opt_type = dhcp_get_next_option(NULL, opt_data, &opt_len, &nextopt); + } + return 0; + +} + +static int init_cookie(struct pico_dhcp_client_cookie* cli) +{ + uint8_t n = 3; + uint16_t port = PICO_DHCP_CLIENT_PORT; + struct pico_ip4 address, netmask; + + address.addr = long_be(0x00000000); + netmask.addr = long_be(0x00000000); + + cli->state = DHCPSTATE_DISCOVER; + cli->start_time = pico_tick; + cli->attempt = 0; + + cli->socket = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &pico_dhcp_wakeup); + if (!cli->socket) { + dbg("DHCPC: error opening socket: %s\n", strerror(pico_err)); + if(cli->cb != NULL) + cli->cb(cli, PICO_DHCP_ERROR); + return -1; + } + if (pico_socket_bind(cli->socket, &address, &port) != 0){ + dbg("DHCPC: error binding socket: %s\n", strerror(pico_err)); + pico_socket_close(cli->socket); + if(cli->cb != NULL) + cli->cb(cli, PICO_DHCP_ERROR); + return -1; + } + cli->socket->dev = cli->device; + + if(pico_ipv4_link_add(cli->device, address, netmask) != 0){ + dbg("DHCPC: error adding link: %s\n", strerror(pico_err)); + if(cli->cb != NULL) + cli->cb(cli, PICO_DHCP_ERROR); + return -1; + } + + /* attempt to generate a correct xid 3 times, then fail */ + do { + cli->xid = pico_rand(); + } while (!cli->xid && --n); + if (!cli->xid) { + if(cli->cb != NULL) + cli->cb(cli, PICO_DHCP_ERROR); + return -1; + } + + return 0; +} + +static struct pico_dhcp_client_cookie *get_cookie_by_xid(uint32_t xid) +{ + struct pico_dhcp_client_cookie test = { }, *cookie = NULL; + + if (!xid) + return NULL; + + test.xid = xid; + cookie = pico_tree_findKey(&DHCPCookies, &test); + if (!cookie) + return NULL; + else + return cookie; +} + +void *pico_dhcp_get_identifier(uint32_t xid) +{ + return (void *) get_cookie_by_xid(xid); +} + +static uint32_t get_xid(uint8_t* data) +{ + struct pico_dhcphdr *dhdr = (struct pico_dhcphdr *) data; + return dhdr->xid; +} + +#endif