/*
 * Copyright 2006-2018, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Axel Dörfler, axeld@pinc-software.de
 *		Vegard Wærp, vegarwa@online.no
 *		Philippe Houdoin, <phoudoin at haiku-os dot org>
 */
 
 
#include "DHCPClient.h"
 
#include <Message.h>
#include <MessageRunner.h>
#include <NetworkDevice.h>
#include <NetworkInterface.h>
 
#include <algorithm>
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <syslog.h>
#include <sys/sockio.h>
#include <sys/time.h>
#include <unistd.h>
 
#include <Debug.h>
#include <Message.h>
#include <MessageRunner.h>
 
#include "NetServer.h"
 
 
// See RFC 2131 for DHCP, see RFC 1533 for BOOTP/DHCP options
 
#define DHCP_CLIENT_PORT	68
#define DHCP_SERVER_PORT	67
 
#define DEFAULT_TIMEOUT		0.25	// secs
#define MAX_TIMEOUT			64		// secs
 
#define AS_USECS(t) (1000000 * t)
 
#define MAX_RETRIES			5
 
enum message_opcode {
	BOOT_REQUEST = 1,
	BOOT_REPLY
};
 
enum message_option {
	OPTION_MAGIC = 0x63825363,
 
	// generic options
	OPTION_PAD = 0,
	OPTION_END = 255,
	OPTION_SUBNET_MASK = 1,
	OPTION_TIME_OFFSET = 2,
	OPTION_ROUTER_ADDRESS = 3,
	OPTION_DOMAIN_NAME_SERVER = 6,
	OPTION_HOST_NAME = 12,
	OPTION_DOMAIN_NAME = 15,
	OPTION_MAX_DATAGRAM_SIZE = 22,
	OPTION_INTERFACE_MTU = 26,
	OPTION_BROADCAST_ADDRESS = 28,
	OPTION_NETWORK_TIME_PROTOCOL_SERVERS = 42,
	OPTION_NETBIOS_NAME_SERVER = 44,
	OPTION_NETBIOS_SCOPE = 47,
 
	// DHCP specific options
	OPTION_REQUEST_IP_ADDRESS = 50,
	OPTION_ADDRESS_LEASE_TIME = 51,
	OPTION_OVERLOAD = 52,
	OPTION_MESSAGE_TYPE = 53,
	OPTION_SERVER_ADDRESS = 54,
	OPTION_REQUEST_PARAMETERS = 55,
	OPTION_ERROR_MESSAGE = 56,
	OPTION_MAX_MESSAGE_SIZE = 57,
	OPTION_RENEWAL_TIME = 58,
	OPTION_REBINDING_TIME = 59,
	OPTION_CLASS_IDENTIFIER = 60,
	OPTION_CLIENT_IDENTIFIER = 61,
};
 
enum message_type {
	DHCP_NONE = 0,
	DHCP_DISCOVER = 1,
	DHCP_OFFER = 2,
	DHCP_REQUEST = 3,
	DHCP_DECLINE = 4,
	DHCP_ACK = 5,
	DHCP_NACK = 6,
	DHCP_RELEASE = 7,
	DHCP_INFORM = 8
};
 
struct dhcp_option_cookie {
	dhcp_option_cookie()
		:
		state(0),
		file_has_options(false),
		server_name_has_options(false)
	{
	}
 
	const uint8* next;
	uint8	state;
	bool	file_has_options;
	bool	server_name_has_options;
};
 
struct dhcp_message {
	dhcp_message(message_type type);
 
	uint8		opcode;
	uint8		hardware_type;
	uint8		hardware_address_length;
	uint8		hop_count;
	uint32		transaction_id;
	uint16		seconds_since_start;
	uint16		flags;
	in_addr_t	client_address;
	in_addr_t	your_address;
	in_addr_t	server_address;
	in_addr_t	gateway_address;
	uint8		mac_address[16];
	uint8		server_name[64];
	uint8		file[128];
	uint32		options_magic;
	uint8		options[1260];
 
	size_t MinSize() const { return 576; }
	size_t Size() const;
 
	bool HasOptions() const;
	bool NextOption(dhcp_option_cookie& cookie, message_option& option,
		const uint8*& data, size_t& size) const;
	const uint8* FindOption(message_option which) const;
	const uint8* LastOption() const;
	message_type Type() const;
 
	uint8* PrepareMessage(uint8 type);
	uint8* PutOption(uint8* options, message_option option);
	uint8* PutOption(uint8* options, message_option option, uint8 data);
	uint8* PutOption(uint8* options, message_option option, uint16 data);
	uint8* PutOption(uint8* options, message_option option, uint32 data);
	uint8* PutOption(uint8* options, message_option option, const uint8* data,
		uint32 size);
	uint8* FinishOptions(uint8* options);
 
	static const char* TypeToString(message_type type);
} _PACKED;
 
struct socket_timeout {
	socket_timeout(int socket)
		:
		timeout(time_t(AS_USECS(DEFAULT_TIMEOUT))),
		tries(0)
	{
		UpdateSocket(socket);
	}
 
	time_t timeout; // in micro secs
	uint8 tries;
 
	bool Shift(int socket, bigtime_t stateMaxTime, const char* device);
	void UpdateSocket(int socket) const;
};
 
#define DHCP_FLAG_BROADCAST		0x8000
 
#define ARP_HARDWARE_TYPE_ETHER	1
 
const uint32 kMsgLeaseTime = 'lstm';
 
static const uint8 kRequestParameters[] = {
	OPTION_SUBNET_MASK, OPTION_ROUTER_ADDRESS,
	OPTION_DOMAIN_NAME_SERVER, OPTION_BROADCAST_ADDRESS,
	OPTION_DOMAIN_NAME
};
 
 
dhcp_message::dhcp_message(message_type type)
{
	// ASSERT(this == offsetof(this, opcode));
	memset(this, 0, sizeof(*this));
	options_magic = htonl(OPTION_MAGIC);
 
	uint8* next = PrepareMessage(type);
	FinishOptions(next);
}
 
 
bool
dhcp_message::HasOptions() const
{
	return options_magic == htonl(OPTION_MAGIC);
}
 
 
bool
dhcp_message::NextOption(dhcp_option_cookie& cookie,
	message_option& option, const uint8*& data, size_t& size) const
{
	if (!HasOptions())
		return false;
 
	if (cookie.state == 0) {
		cookie.state++;
		cookie.next = options;
	}
 
	uint32 bytesLeft = 0;
 
	switch (cookie.state) {
		case 1:
			// options from "options"
			bytesLeft = sizeof(options) - (cookie.next - options);
			break;
 
		case 2:
			// options from "file"
			bytesLeft = sizeof(file) - (cookie.next - file);
			break;
 
		case 3:
			// options from "server_name"
			bytesLeft = sizeof(server_name) - (cookie.next - server_name);
			break;
	}
 
	while (true) {
		if (bytesLeft == 0) {
			cookie.state++;
 
			// handle option overload in file and/or server_name fields.
			switch (cookie.state) {
				case 2:
					// options from "file" field
					if (cookie.file_has_options) {
						bytesLeft = sizeof(file);
						cookie.next = file;
					}
					break;
 
				case 3:
					// options from "server_name" field
					if (cookie.server_name_has_options) {
						bytesLeft = sizeof(server_name);
						cookie.next = server_name;
					}
					break;
 
				default:
					// no more place to look for options
					// if last option is not OPTION_END,
					// there is no space left for other option!
					if (option != OPTION_END)
						cookie.next = NULL;
					return false;
			}
 
			if (bytesLeft == 0) {
				// no options in this state, try next one
				continue;
			}
		}
 
		option = (message_option)cookie.next[0];
		if (option == OPTION_END) {
			bytesLeft = 0;
			continue;
		} else if (option == OPTION_PAD) {
			bytesLeft--;
			cookie.next++;
			continue;
		}
 
		size = cookie.next[1];
		data = &cookie.next[2];
		cookie.next += 2 + size;
		bytesLeft -= 2 + size;
 
		if (option == OPTION_OVERLOAD) {
			cookie.file_has_options = data[0] & 1;
			cookie.server_name_has_options = data[0] & 2;
			continue;
		}
 
		return true;
	}
}
 
 
const uint8*
dhcp_message::FindOption(message_option which) const
{
	dhcp_option_cookie cookie;
	message_option option;
	const uint8* data;
	size_t size;
	while (NextOption(cookie, option, data, size)) {
		// iterate through all options
		if (option == which)
			return data;
	}
 
	return NULL;
}
 
 
const uint8*
dhcp_message::LastOption() const
{
	dhcp_option_cookie cookie;
	message_option option;
	const uint8* data;
	size_t size;
	while (NextOption(cookie, option, data, size)) {
		// iterate through all options
	}
 
	return cookie.next;
}
 
 
message_type
dhcp_message::Type() const
{
	const uint8* data = FindOption(OPTION_MESSAGE_TYPE);
	if (data)
		return (message_type)data[0];
 
	return DHCP_NONE;
}
 
 
size_t
dhcp_message::Size() const
{
	const uint8* last = LastOption();
 
	if (last < options) {
		// if last option is stored above "options" field, it means
		// the whole options field and message is already filled...
		return sizeof(dhcp_message);
	}
 
	return sizeof(dhcp_message) - sizeof(options) + last + 1 - options;
}
 
 
uint8*
dhcp_message::PrepareMessage(uint8 type)
{
	uint8* next = options;
	next = PutOption(next, OPTION_MESSAGE_TYPE, type);
	next = PutOption(next, OPTION_MAX_MESSAGE_SIZE,
		(uint16)htons(sizeof(dhcp_message)));
	return next;
}
 
 
uint8*
dhcp_message::PutOption(uint8* options, message_option option)
{
	options[0] = option;
	return options + 1;
}
 
 
uint8*
dhcp_message::PutOption(uint8* options, message_option option, uint8 data)
{
	return PutOption(options, option, &data, 1);
}
 
 
uint8*
dhcp_message::PutOption(uint8* options, message_option option, uint16 data)
{
	return PutOption(options, option, (uint8*)&data, sizeof(data));
}
 
 
uint8*
dhcp_message::PutOption(uint8* options, message_option option, uint32 data)
{
	return PutOption(options, option, (uint8*)&data, sizeof(data));
}
 
 
uint8*
dhcp_message::PutOption(uint8* options, message_option option,
	const uint8* data, uint32 size)
{
	// TODO: check enough space is available
 
	options[0] = option;
	options[1] = size;
	memcpy(&options[2], data, size);
 
	return options + 2 + size;
}
 
 
uint8*
dhcp_message::FinishOptions(uint8* options)
{
	return PutOption(options, OPTION_END);
}
 
 
/*static*/ const char*
dhcp_message::TypeToString(message_type type)
{
	switch (type) {
#define CASE(x) case x: return #x;
		CASE(DHCP_NONE)
		CASE(DHCP_DISCOVER)
		CASE(DHCP_OFFER)
		CASE(DHCP_REQUEST)
		CASE(DHCP_DECLINE)
		CASE(DHCP_ACK)
		CASE(DHCP_NACK)
		CASE(DHCP_RELEASE)
		CASE(DHCP_INFORM)
#undef CASE
	}
 
	return "<unknown>";
}
 
 
void
socket_timeout::UpdateSocket(int socket) const
{
	struct timeval value;
	value.tv_sec = timeout / 1000000;
	value.tv_usec = timeout % 1000000;
	setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &value, sizeof(value));
}
 
 
bool
socket_timeout::Shift(int socket, bigtime_t stateMaxTime, const char* device)
{
	tries++;
	timeout += timeout;
	if (timeout > AS_USECS(MAX_TIMEOUT))
		timeout = AS_USECS(MAX_TIMEOUT);
 
	if (tries > MAX_RETRIES) {
		if (stateMaxTime == -1)
			return false;
		bigtime_t remaining = (stateMaxTime - system_time()) / 2 + 1;
		timeout = std::max(remaining, bigtime_t(60));
	}
 
	syslog(LOG_DEBUG, "%s: Timeout shift: %lu msecs (try %lu)\n",
		device, timeout / 1000, tries);
 
	UpdateSocket(socket);
	return true;
}
 
 
//	#pragma mark -
 
 
DHCPClient::DHCPClient(BMessenger target, const char* device)
	:
	AutoconfigClient("dhcp", target, device),
	fConfiguration(kMsgConfigureInterface),
	fResolverConfiguration(kMsgConfigureResolver),
	fRunner(NULL),
	fAssignedAddress(0),
	fServer(AF_INET, NULL, DHCP_SERVER_PORT, B_UNCONFIGURED_ADDRESS_FAMILIES),
	fLeaseTime(0)
{
	fTransactionID = (uint32)system_time() ^ rand();
 
	BNetworkAddress link;
	BNetworkInterface interface(device);
	fStatus = interface.GetHardwareAddress(link);
	if (fStatus != B_OK)
		return;
 
	memcpy(fMAC, link.LinkLevelAddress(), sizeof(fMAC));
 
	if ((interface.Flags() & IFF_AUTO_CONFIGURED) != 0) {
		// Check for interface previous auto-configured address, if any.
		BNetworkInterfaceAddress interfaceAddress;
		int index = interface.FindFirstAddress(AF_INET);
		if (index >= 0
			&& interface.GetAddressAt(index, interfaceAddress) == B_OK) {
			BNetworkAddress address = interfaceAddress.Address();
			const sockaddr_in& addr = (sockaddr_in&)address.SockAddr();
			fAssignedAddress = addr.sin_addr.s_addr;
 
			if ((ntohl(fAssignedAddress) & IN_CLASSB_NET) == 0xa9fe0000) {
				// previous auto-configured address is a link-local one:
				// there is no point asking a DHCP server to renew such
				// server-less assigned address...
				fAssignedAddress = 0;
			}
		}
	}
 
	openlog_thread("DHCP", 0, LOG_DAEMON);
}
 
 
DHCPClient::~DHCPClient()
{
	if (fStatus != B_OK)
		return;
 
	delete fRunner;
 
	int socket = ::socket(AF_INET, SOCK_DGRAM, 0);
	if (socket < 0)
		return;
 
	// release lease
 
	dhcp_message release(DHCP_RELEASE);
	_PrepareMessage(release, BOUND);
 
	_SendMessage(socket, release, fServer);
	close(socket);
 
	closelog_thread();
}
 
 
status_t
DHCPClient::Initialize()
{
	fStatus = _Negotiate(fAssignedAddress == 0 ? INIT : INIT_REBOOT);
	syslog(LOG_DEBUG, "%s: DHCP status = %s\n", Device(), strerror(fStatus));
	return fStatus;
}
 
 
status_t
DHCPClient::_Negotiate(dhcp_state state)
{
	if (state == BOUND)
		return B_OK;
 
	fStartTime = system_time();
	fTransactionID++;
 
	char hostName[MAXHOSTNAMELEN];
	if (gethostname(hostName, MAXHOSTNAMELEN) == 0)
		fHostName.SetTo(hostName, MAXHOSTNAMELEN);
	else
		fHostName.Truncate(0);
 
	int socket = ::socket(AF_INET, SOCK_DGRAM, 0);
	if (socket < 0)
		return errno;
 
	// Enable reusing the port. This is needed in case there is more
	// than 1 interface that needs to be configured. Note that the only reason
	// this works is because there is code below to bind to a specific
	// interface.
	int option = 1;
	setsockopt(socket, SOL_SOCKET, SO_REUSEPORT, &option, sizeof(option));
 
	BNetworkAddress local;
	local.SetToWildcard(AF_INET, DHCP_CLIENT_PORT);
 
	option = 1;
	setsockopt(socket, SOL_SOCKET, SO_BROADCAST, &option, sizeof(option));
 
	if (bind(socket, local, local.Length()) < 0) {
		close(socket);
		return errno;
	}
 
	bigtime_t previousLeaseTime = fLeaseTime;
 
	status_t status = B_OK;
	while (state != BOUND) {
		status = _StateTransition(socket, state);
		if (status != B_OK && (state == SELECTING || state == REBOOTING))
			break;
	}
 
	close(socket);
 
	if (fLeaseTime == 0)
		fLeaseTime = previousLeaseTime;
	if (fLeaseTime == 0)
		fLeaseTime = 60;
 
	if (fRenewalTime == 0)
		fRenewalTime = fLeaseTime / 2;
	if (fRebindingTime == 0)
		fRebindingTime = fLeaseTime * 7 / 8;
	fLeaseTime += fRequestTime;
	fRenewalTime += fRequestTime;
	fRebindingTime += fRequestTime;
	_RestartLease(fRenewalTime);
 
	fStatus = status;
	if (status)
		return status;
 
	// configure interface
	BMessage reply;
	status = Target().SendMessage(&fConfiguration, &reply);
	if (status == B_OK)
		status = reply.FindInt32("status", &fStatus);
 
	// configure resolver
	reply.MakeEmpty();
	fResolverConfiguration.AddString("device", Device());
	status = Target().SendMessage(&fResolverConfiguration, &reply);
	if (status == B_OK)
		status = reply.FindInt32("status", &fStatus);
	return status;
}
 
 
status_t
DHCPClient::_GotMessage(dhcp_state& state, dhcp_message* message)
{
	switch (state) {
		case SELECTING:
			if (message->Type() == DHCP_OFFER) {
				state = REQUESTING;
 
				fAssignedAddress = message->your_address;
				syslog(LOG_INFO, "  your_address: %s\n",
						_AddressToString(fAssignedAddress).String());
 
				fConfiguration.MakeEmpty();
				fConfiguration.AddString("device", Device());
				fConfiguration.AddBool("auto_configured", true);
 
				BMessage address;
				address.AddString("family", "inet");
				address.AddString("address", _AddressToString(fAssignedAddress));
				fResolverConfiguration.MakeEmpty();
				_ParseOptions(*message, address, fResolverConfiguration);
 
				fConfiguration.AddMessage("address", &address);
				return B_OK;
			}
 
			return B_BAD_VALUE;
 
		case REBOOTING:
		case REBINDING:
		case RENEWING:
		case REQUESTING:
			if (message->Type() == DHCP_ACK) {
				// TODO: we might want to configure the stuff, don't we?
				BMessage address;
				fResolverConfiguration.MakeEmpty();
				_ParseOptions(*message, address, fResolverConfiguration);
					// TODO: currently, only lease time and DNS is updated this
					// way
 
				// our address request has been acknowledged
				state = BOUND;
 
				return B_OK;
			}
 
			if (message->Type() == DHCP_NACK) {
				// server reject our request on previous assigned address
				// back to square one...
				fAssignedAddress = 0;
				state = INIT;
				return B_OK;
			}
 
		default:
			return B_BAD_VALUE;
	}
}
 
 
status_t
DHCPClient::_StateTransition(int socket, dhcp_state& state)
{
	if (state == INIT) {
		// The local interface does not have an address yet, bind the socket
		// to the device directly.
		BNetworkDevice device(Device());
		int index = device.Index();
 
		setsockopt(socket, SOL_SOCKET, SO_BINDTODEVICE, &index, sizeof(int));
	}
 
	BNetworkAddress broadcast;
	broadcast.SetToBroadcast(AF_INET, DHCP_SERVER_PORT);
 
	socket_timeout timeout(socket);
 
	dhcp_message discover(DHCP_DISCOVER);
	_PrepareMessage(discover, state);
 
	dhcp_message request(DHCP_REQUEST);
	_PrepareMessage(request, state);
 
	bool skipRequest = false;
	dhcp_state originalState = state;
	fRequestTime = system_time();
	while (true) {
		if (!skipRequest) {
			_SendMessage(socket, originalState == INIT ? discover : request,
				originalState == RENEWING ? fServer : broadcast);
 
			if (originalState == INIT)
				state = SELECTING;
			else if (originalState == INIT_REBOOT)
				state = REBOOTING;
		}
 
		char buffer[2048];
		struct sockaddr_in from;
		socklen_t fromLength = sizeof(from);
		ssize_t bytesReceived = recvfrom(socket, buffer, sizeof(buffer),
			0, (struct sockaddr*)&from, &fromLength);
		if (bytesReceived < 0 && errno == B_TIMED_OUT) {
			// depending on the state, we'll just try again
			if (!_TimeoutShift(socket, state, timeout))
				return B_TIMED_OUT;
			skipRequest = false;
			continue;
		} else if (bytesReceived < 0)
			return errno;
 
		skipRequest = true;
		dhcp_message* message = (dhcp_message*)buffer;
		if (message->transaction_id != htonl(fTransactionID)
			|| !message->HasOptions()
			|| memcmp(message->mac_address, discover.mac_address,
				discover.hardware_address_length)) {
			// this message is not for us
			syslog(LOG_DEBUG, "%s: Ignoring %s not for us from %s\n",
				Device(), dhcp_message::TypeToString(message->Type()),
				_AddressToString(from.sin_addr.s_addr).String());
			continue;
		}
 
		syslog(LOG_DEBUG, "%s: Received %s from %s\n",
			Device(), dhcp_message::TypeToString(message->Type()),
			_AddressToString(from.sin_addr.s_addr).String());
 
		if (_GotMessage(state, message) == B_OK)
			break;
	}
 
	return B_OK;
}
 
 
void
DHCPClient::_RestartLease(bigtime_t leaseTime)
{
	if (leaseTime == 0)
		return;
 
	BMessage lease(kMsgLeaseTime);
	fRunner = new BMessageRunner(this, &lease, leaseTime - system_time(), 1);
}
 
 
void
DHCPClient::_ParseOptions(dhcp_message& message, BMessage& address,
	BMessage& resolverConfiguration)
{
	dhcp_option_cookie cookie;
	message_option option;
	const uint8* data;
	size_t size;
	while (message.NextOption(cookie, option, data, size)) {
		// iterate through all options
		switch (option) {
			case OPTION_ROUTER_ADDRESS:
				syslog(LOG_DEBUG, "  gateway: %s\n",
					_AddressToString(data).String());
				address.AddString("gateway", _AddressToString(data));
				break;
			case OPTION_SUBNET_MASK:
				syslog(LOG_DEBUG, "  subnet: %s\n",
					_AddressToString(data).String());
				address.AddString("mask", _AddressToString(data));
				break;
			case OPTION_BROADCAST_ADDRESS:
				syslog(LOG_DEBUG, "  broadcast: %s\n",
					_AddressToString(data).String());
				address.AddString("broadcast", _AddressToString(data));
				break;
			case OPTION_DOMAIN_NAME_SERVER:
			{
				for (uint32 i = 0; i < size / 4; i++) {
					syslog(LOG_DEBUG, "  nameserver[%d]: %s\n", i,
						_AddressToString(&data[i * 4]).String());
					resolverConfiguration.AddString("nameserver",
						_AddressToString(&data[i * 4]).String());
				}
				resolverConfiguration.AddInt32("nameserver_count",
					size / 4);
				break;
			}
			case OPTION_SERVER_ADDRESS:
			{
				syslog(LOG_DEBUG, "  server: %s\n",
					_AddressToString(data).String());
				status_t status = fServer.SetAddress(*(in_addr_t*)data);
				if (status != B_OK) {
					syslog(LOG_ERR, "   BNetworkAddress::SetAddress failed with %s!\n",
						strerror(status));
					fServer.Unset();
				}
				break;
			}
 
			case OPTION_ADDRESS_LEASE_TIME:
				syslog(LOG_DEBUG, "  lease time: %lu seconds\n",
					ntohl(*(uint32*)data));
				fLeaseTime = ntohl(*(uint32*)data) * 1000000LL;
				break;
			case OPTION_RENEWAL_TIME:
				syslog(LOG_DEBUG, "  renewal time: %lu seconds\n",
					ntohl(*(uint32*)data));
				fRenewalTime = ntohl(*(uint32*)data) * 1000000LL;
				break;
			case OPTION_REBINDING_TIME:
				syslog(LOG_DEBUG, "  rebinding time: %lu seconds\n",
					ntohl(*(uint32*)data));
				fRebindingTime = ntohl(*(uint32*)data) * 1000000LL;
				break;
 
			case OPTION_HOST_NAME:
				syslog(LOG_DEBUG, "  host name: \"%.*s\"\n", (int)size,
					(const char*)data);
				break;
 
			case OPTION_DOMAIN_NAME:
			{
				char domain[256];
				strlcpy(domain, (const char*)data,
					min_c(size + 1, sizeof(domain)));
 
				syslog(LOG_DEBUG, "  domain name: \"%s\"\n", domain);
 
				resolverConfiguration.AddString("domain", domain);
				break;
			}
 
			case OPTION_MESSAGE_TYPE:
				break;
 
			case OPTION_ERROR_MESSAGE:
				syslog(LOG_INFO, "  error message: \"%.*s\"\n", (int)size,
					(const char*)data);
				break;
 
			default:
				syslog(LOG_DEBUG, "  UNKNOWN OPTION %lu (0x%x)\n",
					(uint32)option, (uint32)option);
				break;
		}
	}
}
 
 
void
DHCPClient::_PrepareMessage(dhcp_message& message, dhcp_state state)
{
	message.opcode = BOOT_REQUEST;
	message.hardware_type = ARP_HARDWARE_TYPE_ETHER;
	message.hardware_address_length = 6;
	message.transaction_id = htonl(fTransactionID);
	message.seconds_since_start = htons(min_c((system_time() - fStartTime)
		/ 1000000LL, 65535));
	memcpy(message.mac_address, fMAC, 6);
 
	message_type type = message.Type();
 
	uint8 *next = message.PrepareMessage(type);
 
	switch (type) {
		case DHCP_DISCOVER:
			next = message.PutOption(next, OPTION_REQUEST_PARAMETERS,
				kRequestParameters, sizeof(kRequestParameters));
 
			if (fHostName.Length() > 0) {
				next = message.PutOption(next, OPTION_HOST_NAME,
					reinterpret_cast<const uint8*>(fHostName.String()),
					fHostName.Length());
			}
			break;
 
		case DHCP_REQUEST:
			next = message.PutOption(next, OPTION_REQUEST_PARAMETERS,
				kRequestParameters, sizeof(kRequestParameters));
 
			if (fHostName.Length() > 0) {
				next = message.PutOption(next, OPTION_HOST_NAME,
					reinterpret_cast<const uint8*>(fHostName.String()),
					fHostName.Length());
			}
 
			if (state == REQUESTING) {
				const sockaddr_in& server = (sockaddr_in&)fServer.SockAddr();
				next = message.PutOption(next, OPTION_SERVER_ADDRESS,
					(uint32)server.sin_addr.s_addr);
			}
 
			if (state == INIT || state == INIT_REBOOT
				|| state == REQUESTING) {
				next = message.PutOption(next, OPTION_REQUEST_IP_ADDRESS,
					(uint32)fAssignedAddress);
			} else
				message.client_address = fAssignedAddress;
			break;
 
		case DHCP_RELEASE: {
			const sockaddr_in& server = (sockaddr_in&)fServer.SockAddr();
			next = message.PutOption(next, OPTION_SERVER_ADDRESS,
				(uint32)server.sin_addr.s_addr);
 
			message.client_address = fAssignedAddress;
			break;
		}
 
		default:
			break;
	}
 
	message.FinishOptions(next);
}
 
 
bool
DHCPClient::_TimeoutShift(int socket, dhcp_state& state,
	socket_timeout& timeout)
{
	bigtime_t stateMaxTime = -1;
 
	// Compute the date at which we must consider the DHCP negociation failed.
	// This varies depending on the current state. In renewing and rebinding
	// states, it is based on the lease expiration.
	// We can stay for up to 1 minute in the selecting and requesting states
	// (these correspond to sending DHCP_DISCOVER and DHCP_REQUEST,
	// respectively).
	// All other states time out immediately after a single try.
	// If this timeout expires, the DHCP negociation is aborted and starts
	// over. As long as the timeout is not expired, we repeat the message with
	// increasing delays (the delay is computed in timeout.shift below, and is
	// at most equal to the timeout, but usually much shorter).
	if (state == RENEWING)
		stateMaxTime = fRebindingTime;
	else if (state == REBINDING)
		stateMaxTime = fLeaseTime;
	else if (state == SELECTING || state == REQUESTING)
		stateMaxTime = fRequestTime + AS_USECS(MAX_TIMEOUT);
 
	if (system_time() > stateMaxTime) {
		state = state == REBINDING ? INIT : REBINDING;
		return false;
	}
 
	return timeout.Shift(socket, stateMaxTime, Device());
}
 
 
/*static*/ BString
DHCPClient::_AddressToString(const uint8* data)
{
	BString target = inet_ntoa(*(in_addr*)data);
	return target;
}
 
 
/*static*/ BString
DHCPClient::_AddressToString(in_addr_t address)
{
	BString target = inet_ntoa(*(in_addr*)&address);
	return target;
}
 
 
status_t
DHCPClient::_SendMessage(int socket, dhcp_message& message,
	const BNetworkAddress& address) const
{
	message_type type = message.Type();
	BString text;
	text << dhcp_message::TypeToString(type);
 
	const uint8* requestAddress = message.FindOption(OPTION_REQUEST_IP_ADDRESS);
	if (type == DHCP_REQUEST && requestAddress != NULL)
		text << " for " << _AddressToString(requestAddress).String();
 
	syslog(LOG_DEBUG, "%s: Send %s to %s\n", Device(), text.String(),
		address.ToString().String());
 
	ssize_t bytesSent = sendto(socket, &message, message.Size(),
		address.IsBroadcast() ? MSG_BCAST : 0, address, address.Length());
	if (bytesSent < 0)
		return errno;
 
	return B_OK;
}
 
 
dhcp_state
DHCPClient::_CurrentState() const
{
	bigtime_t now = system_time();
 
	if (now > fLeaseTime || fStatus != B_OK)
		return INIT;
	if (now >= fRebindingTime)
		return REBINDING;
	if (now >= fRenewalTime)
		return RENEWING;
	return BOUND;
}
 
 
void
DHCPClient::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case kMsgLeaseTime:
			_Negotiate(_CurrentState());
			break;
 
		default:
			BHandler::MessageReceived(message);
			break;
	}
}

V576 Incorrect format. Consider checking the third actual argument of the 'syslog' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the third actual argument of the 'syslog' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the fifth actual argument of the 'syslog' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the third actual argument of the 'syslog' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the third actual argument of the 'syslog' function. The memsize type argument is expected.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fStartTime, fRequestTime, fRenewalTime, fRebindingTime.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: next.