OpenSolaris_b135/lib/librstp/common/portinfo.c

/************************************************************************ 
 * RSTP library - Rapid Spanning Tree (802.1t, 802.1w) 
 * Copyright (C) 2001-2003 Optical Access 
 * Author: Alex Rozin 
 * 
 * This file is part of RSTP library. 
 * 
 * RSTP library is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU Lesser General Public License as published by the 
 * Free Software Foundation; version 2.1 
 * 
 * RSTP library 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 Lesser 
 * General Public License for more details. 
 * 
 * You should have received a copy of the GNU Lesser General Public License 
 * along with RSTP library; see the file COPYING.  If not, write to the Free 
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 
 * 02111-1307, USA. 
 **********************************************************************/

#include "base.h"
#include "stpm.h"
#include "stp_vectors.h"

/* The Port Information State Machine : 17.21 */

#define STATES { \
  CHOOSE(DISABLED), \
  CHOOSE(ENABLED),  \
  CHOOSE(AGED),     \
  CHOOSE(UPDATE),   \
  CHOOSE(CURRENT),  \
  CHOOSE(RECEIVE),  \
  CHOOSE(SUPERIOR), \
  CHOOSE(REPEAT),   \
  CHOOSE(AGREEMENT)    \
}

#define GET_STATE_NAME STP_info_get_state_name
#include "choose.h"

#if 0 /* for debug */
void
_stp_dump (char* title, unsigned char* buff, int len)
{
  register int iii;

  stp_trace ("\n%s:", title);
  for (iii = 0; iii < len; iii++) {
    if (! (iii % 24)) stp_trace ("\n%6d:", iii);
    if (! (iii % 8)) stp_trace (" ");
    stp_trace ("%02lx", (unsigned long) buff[iii]);
  }
  stp_trace ("\n");
}
#endif

static RCVD_MSG_T
rcvBpdu (STATE_MACH_T* this)
{/* 17.19.8 */
  int   bridcmp;
  register PORT_T* port = this->owner.port;

  if (port->msgBpduType == BPDU_TOPO_CHANGE_TYPE) {
#ifdef STP_DBG
    if (this->debug) {
        stp_trace ("%s", "rcvBpdu: OtherMsg:BPDU_TOPO_CHANGE_TYPE");
    }
#endif
    return OtherMsg;
  }

  port->msgPortRole = RSTP_PORT_ROLE_UNKN;

  if (BPDU_RSTP == port->msgBpduType) {
    port->msgPortRole = (port->msgFlags & PORT_ROLE_MASK) >> PORT_ROLE_OFFS;
  }

  if (RSTP_PORT_ROLE_DESGN == port->msgPortRole ||
      BPDU_CONFIG_TYPE == port->msgBpduType) {
    bridcmp = STP_VECT_compare_vector (&port->msgPrio, &port->portPrio);

    if (bridcmp < 0 ||
        (! STP_VECT_compare_bridge_id (&port->msgPrio.design_bridge,
                                       &port->portPrio.design_bridge) &&
         port->msgPrio.design_port == port->portPrio.design_port      &&
         STP_compare_times (&port->msgTimes, &port->portTimes))) {
#ifdef STP_DBG
         if (this->debug) {
           stp_trace ("rcvBpdu: SuperiorDesignateMsg:bridcmp=%d", (int) bridcmp);
         }
#endif
      return SuperiorDesignateMsg;
    }
  }

  if (BPDU_CONFIG_TYPE == port->msgBpduType ||
      RSTP_PORT_ROLE_DESGN == port->msgPortRole) {
    if (! STP_VECT_compare_vector (&port->msgPrio,
                                   &port->portPrio) &&
        ! STP_compare_times (&port->msgTimes, &port->portTimes)) {
#ifdef STP_DBG
        if (this->debug) {
          stp_trace ("%s", "rcvBpdu: RepeatedDesignateMsg");
        }
#endif
        return RepeatedDesignateMsg;
    }
  }

  if (RSTP_PORT_ROLE_ROOT == port->msgBpduType                    &&
      port->operPointToPointMac                                   &&
      ! STP_VECT_compare_bridge_id (&port->msgPrio.design_bridge,
                                    &port->portPrio.design_bridge) &&
      AGREEMENT_BIT & port->msgFlags) {
#ifdef STP_DBG
    if (this->debug) {
      stp_trace ("%s", "rcvBpdu: ConfirmedRootMsg");
    }
#endif
    return ConfirmedRootMsg;
  }

#ifdef STP_DBG
    if (this->debug) {
      if (RSTP_PORT_ROLE_ROOT == port->msgBpduType) {
	if (!port->operPointToPointMac) {
	  stp_trace("rcvBpdu: OtherMsg: not point-to-point MAC");
	} else if (STP_VECT_compare_bridge_id (&port->msgPrio.design_bridge,
	  &port->portPrio.design_bridge)) {
	  STP_VECT_br_id_print("rcvBpdu: OtherMsg: msgPrio", &port->msgPrio.design_bridge, True);
	  STP_VECT_br_id_print("rcvBpdu:          portPrio", &port->portPrio.design_bridge, True);
	} else {
	  stp_trace("rcvBpdu: OtherMsg: agreement bit not set");
	}
      } else {
	stp_trace ("rcvBpdu: OtherMsg: type %d", port->msgBpduType);
      }
    }
#endif
  return OtherMsg;
}

/* ARGSUSED */
static Bool
recordProposed (STATE_MACH_T* this, char* reason)
{/* 17.19.9 */
  register PORT_T* port = this->owner.port;

  if (RSTP_PORT_ROLE_DESGN == port->msgPortRole &&
      (PROPOSAL_BIT & port->msgFlags)           &&
      port->operPointToPointMac) {
    return True;
  }
  return False;
}

static void
setTcFlags (STATE_MACH_T* this)
{/* 17.19.13 */
  register PORT_T* port = this->owner.port;

  if (BPDU_TOPO_CHANGE_TYPE == port->msgBpduType) {
#ifdef STP_DBG
      if (this->debug) {
        stp_trace ("port %s rx rcvdTcn", port->port_name);
      }
#endif
    port->rcvdTcn = True;
  } else {
    if (TOPOLOGY_CHANGE_BIT & port->msgFlags) {
#ifdef STP_DBG
      if (this->debug) {
        stp_trace ("(%s-%s) rx rcvdTc 0X%lx",
            port->owner->name, port->port_name,
            (unsigned long) port->msgFlags);
      }
#endif
      port->rcvdTc = True;
    }
    if (TOPOLOGY_CHANGE_ACK_BIT & port->msgFlags) {
#ifdef STP_DBG
      if (this->debug) {
        stp_trace ("port %s rx rcvdTcAck 0X%lx",
            port->port_name,
            (unsigned long) port->msgFlags);
      }
#endif
      port->rcvdTcAck = True;
    }
  }
}

static void
updtBPDUVersion (STATE_MACH_T* this)
{/* 17.19.18 */
  register PORT_T* port = this->owner.port;

  if (BPDU_TOPO_CHANGE_TYPE == port->msgBpduType) {
    port->rcvdSTP = True;
  }

  if (port->msgBpduVersion < 2) {
    port->rcvdSTP = True;
  }
  
  if (BPDU_RSTP == port->msgBpduType) {
    /* port->port->owner->ForceVersion >= NORMAL_RSTP
       we have checked in STP_info_rx_bpdu */
    port->rcvdRSTP = True;
  }
}

static void
updtRcvdInfoWhile (STATE_MACH_T* this)
{/* 17.19.19 */
  register int eff_age, dm, dt;
  register int hello3;
  register PORT_T* port = this->owner.port;
  
  eff_age = ( + port->portTimes.MaxAge) / 16;
  if (eff_age < 1) eff_age = 1;
  eff_age += port->portTimes.MessageAge;

  if (eff_age <= port->portTimes.MaxAge) {
    hello3 = 3 *  port->portTimes.HelloTime;
    dm = port->portTimes.MaxAge - eff_age;
    if (dm > hello3)
      dt = hello3;
    else
      dt = dm;
    port->rcvdInfoWhile = dt;
/****
    stp_trace ("ma=%d eff_age=%d dm=%d dt=%d p=%s",
               (int) port->portTimes.MessageAge,
               (int) eff_age, (int) dm, (int) dt, port->port_name);
****/
  } else {
    port->rcvdInfoWhile = 0;
/****/
#ifdef STP_DBG
    /*if (this->debug) */
    {
      stp_trace ("port %s: MaxAge=%d MessageAge=%d HelloTime=%d rcvdInfoWhile=null !",
            port->port_name,
                (int) port->portTimes.MaxAge,
                (int) port->portTimes.MessageAge,
                (int) port->portTimes.HelloTime);
    }
#endif
/****/
  }
}


/* ARGSUSED */
void
STP_info_rx_bpdu (PORT_T* port, struct stp_bpdu_t* bpdu, size_t len)
{  
#if 0
  _stp_dump ("\nall BPDU", ((unsigned char*) bpdu) - 12, len + 12);
  _stp_dump ("ETH_HEADER", (unsigned char*) &bpdu->eth, 5);
  _stp_dump ("BPDU_HEADER", (unsigned char*) &bpdu->hdr, 4);
  stp_trace ("protocol=%02x%02x version=%02x bpdu_type=%02x\n",
     bpdu->hdr.protocol[0], bpdu->hdr.protocol[1],
     bpdu->hdr.version, bpdu->hdr.bpdu_type);

  _stp_dump ("\nBPDU_BODY", (unsigned char*) &bpdu->body, sizeof (BPDU_BODY_T) + 2);
  stp_trace ("flags=%02x\n", bpdu->body.flags);
  _stp_dump ("root_id", bpdu->body.root_id, 8);
  _stp_dump ("root_path_cost", bpdu->body.root_path_cost, 4);
  _stp_dump ("bridge_id", bpdu->body.bridge_id, 8);
  _stp_dump ("port_id", bpdu->body.port_id, 2);
  _stp_dump ("message_age", bpdu->body.message_age, 2);
  _stp_dump ("max_age", bpdu->body.max_age, 2);
  _stp_dump ("hello_time", bpdu->body.hello_time, 2);
  _stp_dump ("forward_delay", bpdu->body.forward_delay, 2);
  _stp_dump ("ver_1_len", bpdu->ver_1_len, 2);
#endif
  
  /* check bpdu type */
  switch (bpdu->hdr.bpdu_type) {
    case BPDU_CONFIG_TYPE:
      port->rx_cfg_bpdu_cnt++;
#ifdef STP_DBG
      if (port->info->debug) 
        stp_trace ("CfgBpdu on port %s", port->port_name);
#endif
      if (port->admin_non_stp) return;
      port->rcvdBpdu = True;
      break;
    case BPDU_TOPO_CHANGE_TYPE:
      port->rx_tcn_bpdu_cnt++;
#ifdef STP_DBG
      if (port->info->debug)
        stp_trace ("TcnBpdu on port %s", port->port_name);
#endif
      if (port->admin_non_stp) return;
      port->rcvdBpdu = True;
      port->msgBpduVersion = bpdu->hdr.version;
      port->msgBpduType = bpdu->hdr.bpdu_type;
      return;
    default:
      stp_trace ("RX undef bpdu type=%d", (int) bpdu->hdr.bpdu_type);
      return;
    case BPDU_RSTP:
      port->rx_rstp_bpdu_cnt++;
      if (port->admin_non_stp) return;
      if (port->owner->ForceVersion >= NORMAL_RSTP) {
        port->rcvdBpdu = True;
      } else {          
        return;
      }
#ifdef STP_DBG
      if (port->info->debug)
        stp_trace ("BPDU_RSTP on port %s", port->port_name);
#endif
      break;
  }

  port->msgBpduVersion = bpdu->hdr.version;
  port->msgBpduType =    bpdu->hdr.bpdu_type;
  port->msgFlags =       bpdu->body.flags;

  /* 17.18.11 */
  STP_VECT_get_vector (&bpdu->body, &port->msgPrio);
  port->msgPrio.bridge_port = port->port_id;

  /* 17.18.12 */
  STP_get_times (&bpdu->body, &port->msgTimes);

  /* 17.18.25, 17.18.26 : see setTcFlags() */
}

void STP_info_enter_state (STATE_MACH_T* this)
{
  register PORT_T* port = this->owner.port;

  switch (this->State) {
    case BEGIN:
      port->rcvdMsg = OtherMsg;
      port->msgBpduType = (unsigned char)-1;
      port->msgPortRole = RSTP_PORT_ROLE_UNKN;
      port->msgFlags = 0;

      /* clear port statistics */
      port->rx_cfg_bpdu_cnt =
      port->rx_rstp_bpdu_cnt =
      port->rx_tcn_bpdu_cnt = 0;
      /* FALLTHRU */
    case DISABLED:
      port->rcvdBpdu = port->rcvdRSTP = port->rcvdSTP = False;
      port->updtInfo = port->proposing = False; /* In DISABLED */
      port->agreed = port->proposed = False;
      port->rcvdInfoWhile = 0;
      port->infoIs = Disabled;
      port->reselect = True;
      port->selected = False;
      break;
    case ENABLED: /* IEEE 802.1y, 17.21, Z.14 */
      STP_VECT_copy (&port->portPrio, &port->designPrio);
      STP_copy_times (&port->portTimes, &port->designTimes);
      break;
    case AGED:
      port->infoIs = Aged;
      port->reselect = True;
      port->selected = False;
      break;
    case UPDATE:
      STP_VECT_copy (&port->portPrio, &port->designPrio);
      STP_copy_times (&port->portTimes, &port->designTimes);
      port->updtInfo = False;
      port->agreed = port->synced = False; /* In UPDATE */
      port->proposed = port->proposing = False; /* in UPDATE */
      port->infoIs = Mine;
      port->newInfo = True;
#ifdef STP_DBG
      if (this->debug) {
        STP_VECT_br_id_print ("updated: portPrio.design_bridge",
                            &port->portPrio.design_bridge, True);
        STP_VECT_br_id_print ("updated:   portPrio.root_bridge",
                            &port->portPrio.root_bridge, True);
      }
#endif
      break;
    case CURRENT:
      break;
    case RECEIVE:
      port->rcvdMsg = rcvBpdu (this);
      updtBPDUVersion (this);
      setTcFlags (this);
      port->rcvdBpdu = False;
      break;
    case SUPERIOR:
      STP_VECT_copy (&port->portPrio, &port->msgPrio);
      STP_copy_times (&port->portTimes, &port->msgTimes);
      updtRcvdInfoWhile (this);
#if 1 /* due 802.1y, Z.7 */
      port->agreed = False; /* deleted due 802.y in SUPERIOR */
      port->synced = False; /* due 802.y deleted in SUPERIOR */
#endif
      port->proposing = False; /* in SUPERIOR */
      port->proposed = recordProposed (this, "SUPERIOR");
      port->infoIs = Received;
      port->reselect = True;
      port->selected = False;
#ifdef STP_DBG
      if (this->debug) {
        STP_VECT_br_id_print ("stored: portPrio.design_bridge",
                            &port->portPrio.design_bridge, True);
        STP_VECT_br_id_print ("stored:   portPrio.root_bridge",
                            &port->portPrio.root_bridge, True);
        stp_trace ("proposed=%d on port %s",
                   (int) port->proposed, port->port_name);
      }
#endif
      break;
    case REPEAT:
      port->proposed = recordProposed (this, "REPEAT");
      updtRcvdInfoWhile (this);
      break;
  case AGREEMENT:
#ifdef STP_DBG
      if (port->roletrns->debug) {
        stp_trace ("(%s-%s) rx AGREEMENT flag !",
            port->owner->name, port->port_name);
      }
#endif
      
      port->agreed = True;
      port->proposing = False; /* In AGREEMENT */
      break;
  }

}

Bool STP_info_check_conditions (STATE_MACH_T* this)
{
  register PORT_T* port = this->owner.port;

  if ((! port->portEnabled && port->infoIs != Disabled) || BEGIN == this->State) {
    return STP_hop_2_state (this, DISABLED);
  }

  switch (this->State) {
    case DISABLED:
      if (port->updtInfo) {
        return STP_hop_2_state (this, DISABLED);
      }
      if (port->portEnabled && port->selected) {
        return STP_hop_2_state (this, ENABLED);
      }
      if (port->rcvdBpdu) {
        return STP_hop_2_state (this, DISABLED);
      }
      break; 
    case ENABLED: /* IEEE 802.1y, 17.21, Z.14 */
      return STP_hop_2_state (this, AGED);
      break; 
    case AGED:
      if (port->selected && port->updtInfo) {
        return STP_hop_2_state (this, UPDATE);
      }
      break;
    case UPDATE:
      return STP_hop_2_state (this, CURRENT);
      break;
    case CURRENT:
      if (port->selected && port->updtInfo) {
        return STP_hop_2_state (this, UPDATE);
      }

      if (Received == port->infoIs       &&
          ! port->rcvdInfoWhile &&
          ! port->updtInfo               &&
          ! port->rcvdBpdu) {
        return STP_hop_2_state (this, AGED);
      }
      if (port->rcvdBpdu && !port->updtInfo) {
        return STP_hop_2_state (this, RECEIVE);
      }
      break;
    case RECEIVE:
      switch (port->rcvdMsg) {
        case SuperiorDesignateMsg:
          return STP_hop_2_state (this, SUPERIOR);
        case RepeatedDesignateMsg:
          return STP_hop_2_state (this, REPEAT);
        case ConfirmedRootMsg:
          return STP_hop_2_state (this, AGREEMENT);
        default:
          return STP_hop_2_state (this, CURRENT);
      }
      break;
    case SUPERIOR:
      return STP_hop_2_state (this, CURRENT);
      break;
    case REPEAT:
      return STP_hop_2_state (this, CURRENT);
      break;
    case AGREEMENT:
      return STP_hop_2_state (this, CURRENT);
      break;
  }

  return False;
}