/* vim: set filetype=c:
 *
 **************************************************************************
 *
 * Boot-ROM-Code to load an operating system across a TCP/IP network.
 *
 * Module:  getvend.c86
 * Purpose: Get information out of BOOTP/DHCP answer
 * Entries: get_vend
 *
 **************************************************************************
 *
 * Copyright (C) 1998-2007 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: getvend.c86,v 1.8 2007/01/06 18:30:46 gkminix Exp $
 */


#include <common.h86>
#include <romconfig.h86>
#include <i386/string.h86>
#include "headers/net.h86"
#include "headers/arpa.h86"
#include "bootp.h86"



/*
 **************************************************************************
 * 
 * Variables local to this module
 */
static short lastbuf = -1;		/* buffer with last requested ID */
static short lastid;			/* last request ID */
static __u8 overload;			/* overload flags */
static __u8 *lastptr;			/* pointer to last requested ID */
static __u8 *vextptr;			/* pointer to vendor extension record */



/*
 **************************************************************************
 * 
 * Find a tag in the BOOTP/DHCP record. It returns a pointer to the length
 * byte of the tag. It is not possible to find a NOP tag with this routine.
 *
 */
static __u8 *find_tag(short id, __u8 *startp, __u8 *endp)
{
  register __u8 *cp = startp;

  while (TRUE) {
	/*
	 * The construction with if()break takes a lot less space when
	 * compiled with GCC than putting the expression into the while()
	 * statement.
	 */
	if (*cp == VEND_RFC_END || cp >= endp)
		break;
	if (*cp != VEND_RFC_NOP) {
		if (*cp == id)
			break;
		cp += *(cp + 1) + 1;
	}
	cp++;
  }
  return(*cp == id ? cp + 1 : NULL);
}



/*
 **************************************************************************
 * 
 * Find a tag either in the vendor information area or the boot filename
 * or server name fields.
 */
static __u8 *search_tag(short id, __u8 *lastp)
{
  register __u8 *cp;
  __u8 *startp, *endp;
  __u8 *retp = NULL;

  /* Setup start and end pointers */
  startp = bootp_bufs[cur_bootp_buf]->bp_vend + BOOTP_VM_SIZE;
  endp = (__u8 *)(bootp_bufs[cur_bootp_buf]) + bootp_sizes[cur_bootp_buf];

  /* Search for the tag in the vendor extension area */
  if (lastp == NULL)
	retp = find_tag(id, startp, endp);
  else if (lastp >= startp && lastp < endp)
	retp = find_tag(id, lastp, endp);

  /* Only search overload areas for valid tags, not NOP, END etc. */
  if (id != VEND_RFC_NOP && id != VEND_RFC_END && id != VEND_RFC_OVERLOAD) {
	/* Search for the tag in the filename area if buffer is overloaded */
	if (retp == NULL && (overload & VEND_OVR_FILE)) {
		cp = bootp_bufs[cur_bootp_buf]->bp_file;
		if (lastp == NULL)
			retp = find_tag(id, cp, cp + BOOTP_FILE_SIZE);
		else if (lastp >= cp && lastp < (cp + BOOTP_FILE_SIZE))
			retp = find_tag(id, lastp, cp + BOOTP_FILE_SIZE);
	}

	/* Search for the tag in the server name area if buffer is overloaded */
	if (retp == NULL && (overload & VEND_OVR_SNAME)) {
		cp = bootp_bufs[cur_bootp_buf]->bp_sname;
		if (lastp == NULL)
			retp = find_tag(id, cp, cp + BOOTP_SNAME_SIZE);
		else if (lastp >= cp && lastp < (cp + BOOTP_SNAME_SIZE))
			retp = find_tag(id, lastp, cp + BOOTP_SNAME_SIZE);
	}
  }
  return(retp);
}



/*
 **************************************************************************
 * 
 * Return the overload flag of a BOOTP/DHCP buffer
 */
static inline void check_overload(void)
{
  register __u8 *cp;

  cp = search_tag(VEND_RFC_OVERLOAD, NULL);
  overload = (cp == NULL ? 0 : *(cp + 1));
}



/*
 **************************************************************************
 * 
 * Check if we have a vendor extension field
 */
static inline void check_vend_ext(void)
{
  vextptr = search_tag(VEND_RFC_VENDOR, NULL);
}



/*
 **************************************************************************
 * 
 * Return information from the vendor area of a BOOTP/DHCP record. Note
 * that this will work only with RFC1048-compliant messages.
 * It will return a pointer to the length byte of the tag.
 *
 */
__u8 *get_vend(short id)
{
  register __u8 *cp;
  __u8 *endp;
  __u8 *retp = NULL;

  /* Check if we are accessing the same buffer as before */
  if (lastbuf != cur_bootp_buf || id == VEND_RFC_NOP) {
	lastbuf = cur_bootp_buf;
	lastptr = NULL;
	check_overload();
	check_vend_ext();
  } else {
	/* Determine start address for new search */
	if (lastid != id)
		lastptr = NULL;
	else if (lastptr != NULL)
		lastptr += *lastptr + 1;
  }

  /* Now actually search for the tag, NOP just recycles */
  if (id == VEND_RFC_NOP)
	retp = NULL;
  else if (id < 0x0100)
	retp = search_tag(id, lastptr);
  else if (vextptr != NULL && (id & 0x0F00) == VEND_NB_BASE) {
	cp = vextptr;
	endp = cp + (*cp + 1);
	cp++;
	if (lastptr == NULL)
		retp = find_tag(id & 0x00FF, cp, endp);
	else if (lastptr >= cp && lastptr < endp)
		retp = find_tag(id & 0x00FF, lastptr, endp);
  }
  lastid = id;
  lastptr = retp;
  return(retp);
}

