]> git.ipfire.org Git - people/ms/rstp.git/commitdiff
Add in new RSTP implementation to replace RSTPLIB.
authorSrinivas Aji <Aji_Srinivas@emc.com>
Thu, 29 Nov 2007 19:58:09 +0000 (01:28 +0530)
committerSrinivas Aji <Aji_Srinivas@emc.com>
Wed, 27 Feb 2008 18:03:50 +0000 (23:33 +0530)
   We have a new implementation of RSTP, which is meant to implement the
   Spanning Tree Protocol in IEEE 802.1D-2004. This implementation is
   written as a library consisting of one .c file and one .h file.
   It is written from scratch directly from the 802.1D-2004 document.
   This commit adds the files rstp.h and rstp.c to the directory, in
   preparation for modifying other code to use this instead of rstplib.

Signed-off-by: Srinivas Aji <Aji_Srinivas@emc.com>
rstp.c [new file with mode: 0644]
rstp.h [new file with mode: 0644]

diff --git a/rstp.c b/rstp.c
new file mode 100644 (file)
index 0000000..7191778
--- /dev/null
+++ b/rstp.c
@@ -0,0 +1,3071 @@
+/*****************************************************************************
+  Copyright (c) 2007 EMC Corporation.
+
+  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 (at your option)
+  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., 59
+  Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+  The full GNU General Public License is included in this distribution in the
+  file called LICENSE.
+
+  Authors: Srinivas Aji <Aji_Srinivas@emc.com>
+
+******************************************************************************/
+
+/*********************************************************************
+  This is an implementation of the Rapid Spanning Tree Protocol
+  based on the 802.1D-2004 standard.
+  Section references in the code refer to those parts of the standard.
+*********************************************************************/
+
+#include "rstp.h"
+#include <string.h>
+
+/* Macros to be used in the later parts */
+
+#ifndef NULL
+#define NULL ((void *)0)
+#endif
+
+#define FALSE 0
+#define TRUE 1
+
+
+/* memcmp wrapper, validating size equality of _a and _b */
+#define CMP(_a, _op, _b)                                           \
+(memcmp(&(_a), &(_b),                                              \
+  __builtin_choose_expr(sizeof(_a) == sizeof(_b),                  \
+                        sizeof(_a),                                \
+                        (void)0)                                   \
+) _op 0)
+
+#define ZERO(_a) memset(&(_a), 0, sizeof(_a))
+
+/* memcmp wrapper, validating size equality of _a and _b */
+#define CPY(_a, _b)                                           \
+(memcpy(&(_a), &(_b),                                              \
+  __builtin_choose_expr(sizeof(_a) == sizeof(_b),                  \
+                        sizeof(_a),                                \
+                        (void)0)                                   \
+))
+
+#define BRIDGE_ARGS_PROTO        Bridge *BRIDGE
+#define PORT_ARGS_PROTO          Bridge *BRIDGE, Port *PORT
+
+#define BRIDGE_ARGS              BRIDGE
+#define PORT_ARGS                BRIDGE, PORT
+#define FPORT_ARGS               BRIDGE, FPORT
+
+/* Define BRIDGE and PORT as global pointers to a struct type which holds
+   a user_ref of value NULL, and no other fields.
+   That way log messages can use BRIDGE and PORT always.
+*/
+
+struct _dummy_user_ref_struct
+{
+  void *user_ref;
+} _user_ref_var = { .user_ref = NULL},
+  *BRIDGE = &_user_ref_var, *PORT = &_user_ref_var;
+
+/* Logging macros */
+#define DEBUG(fmt...) \
+  STP_OUT_logmsg(BRIDGE->user_ref, PORT->user_ref, STP_LOG_LEVEL_DEBUG, fmt)
+#define ERROR(fmt...) \
+  STP_OUT_logmsg(BRIDGE->user_ref, PORT->user_ref, STP_LOG_LEVEL_ERROR, fmt)
+#define ABORT(fmt...) \
+  ({ ERROR(fmt); *(int *)0 = 1; })
+
+/***************** Macros for state machine descriptions ********************/
+
+#define _CAT2(x, y) x ## _ ## y
+
+#define _CAT(x, y) _CAT2(x, y)
+
+#define __STR(x) #x
+
+#define _STR(x) __STR(x)
+
+#define STN(_state) _CAT(STM_NAME, _state)
+
+#define STM_LABEL(_state) _CAT(STM_NAME, label_ ## _state)
+
+#define STM_FUNC(_name) _CAT(_name, func)
+
+/* BEGIN is state 0 in all state machines */
+#define STATES(_args...) enum _CAT(STM_NAME, states) { STN(BEGIN), _args }
+
+/* Transition definition */
+#define TR(_cond, _new_state) \
+({                                                                           \
+  if (_cond) {                                                               \
+    new_state = STN(_new_state);                                             \
+    DEBUG("%s: Transition to state %s\n", _STR(STM_NAME), #_new_state);      \
+    goto STM_LABEL(_new_state);                                              \
+  }                                                                          \
+})
+
+/* Global conditions: Transition conditions that don't depend on current state.
+   In our state machine, they get checked after those that are specific to the
+   current state
+   new_state is the state we last transitioned to, or 0 if no transition
+   was made.
+*/
+#define GC(_transitions...)      \
+         global_conditions:      \
+             _transitions        \
+             return new_state
+
+/* Transitions from BEGIN state */
+#define BG(_transitions...)                                              \
+         case STN(BEGIN):                                                \
+             _transitions                                                \
+             ABORT("%s: No where to go from BEGIN\n", _STR(STM_NAME));   \
+             return -1
+
+/* State definition */
+#define ST(_state, _actions, _transitions) \
+         STM_LABEL(_state):                \
+             _actions                      \
+             if (single_step)              \
+                 return new_state;         \
+         case STN(_state):                 \
+             _transitions                  \
+             goto global_conditions
+
+#define STM_FUNC_DECL(_type, _args...) \
+  int _type(int state, int single_step, _args)
+
+/* This just barely works. _args expansion includes commas */
+//int STM_FUNC(STM_NAME)(int state, int single_step, _args)
+
+#define STM(_args, _contents...)                                 \
+static STM_FUNC_DECL(STM_FUNC(STM_NAME), _args)                  \
+{                                                                \
+  int new_state = 0;                                             \
+  switch (state) {                                               \
+    _contents                                                    \
+      default:                                                   \
+    ABORT("%s: Got unknown state %d\n", __func__, state);        \
+    return -1;                                                   \
+  }                                                              \
+}
+
+#define UCT 1
+
+#define STM_RUN(_state, _transitioned, _func, _args...) \
+({                                                               \
+  int new_state = _func(*_state, 0/*no single step*/, _args);    \
+  if (new_state > 0) {                                           \
+    *_state = new_state;                                         \
+    *_transitioned = TRUE;                                       \
+  }                                                              \
+})
+
+#define STM_BEGIN(_state, _func, _args...) \
+({                                                               \
+  *_state = _func(0/*BEGIN*/, 1/*single step*/, _args);          \
+})
+
+/************ End of Macros for state machine descriptions ***********/
+
+//Types:
+
+typedef unsigned char uchar;
+
+typedef unsigned int uint;
+
+typedef unsigned short uint16;
+
+typedef uint bool;
+
+typedef struct _BridgeId {
+  uchar bridge_identifier_priority[2];
+  uchar bridge_address[6];
+} __attribute__((packed)) BridgeId;
+
+typedef struct {
+  uchar cost[4];
+} PathCost;
+
+static inline uint path_cost_to_uint(PathCost x)
+{
+  return
+    (x.cost[0] << 24) + (x.cost[1] << 16) + (x.cost[2] << 8) + (x.cost[3]);
+
+}
+
+
+static inline PathCost path_cost_add(PathCost x, uint y)
+{
+  uint z = y +
+    (x.cost[0] << 24) + (x.cost[1] << 16) + (x.cost[2] << 8) + (x.cost[3]);
+
+  PathCost r = {
+    .cost = {
+      (z >> 24), (z >> 16) & 0xff, (z >> 8) & 0xff, z & 0xff
+    }
+  };
+
+  return r;
+}
+
+typedef struct _PortId {
+  uchar port_id[2]; /* 4 high bits of priority and 12 for Id */
+} __attribute__((packed)) PortId;
+
+static inline uint port_id_to_uint(PortId p)
+{
+  return (p.port_id[0] << 8) + p.port_id[1];
+}
+
+typedef struct _PriorityVector
+{
+  BridgeId root_bridge_id;
+  PathCost root_path_cost;
+  BridgeId designated_bridge_id;
+  PortId   designated_port_id;
+  PortId   bridge_port_id;
+} __attribute__((packed)) PriorityVector;
+
+/* First 4 components of priority vector */
+typedef struct _PriorityVector4
+{
+  BridgeId root_bridge_id;
+  PathCost root_path_cost;
+  BridgeId designated_bridge_id;
+  PortId   designated_port_id;
+} __attribute__((packed)) PriorityVector4;
+
+
+typedef struct _Times
+{
+  uint forward_delay;
+  uint hello_time;
+  uint max_age;
+  uint message_age;
+} Times;
+
+typedef enum _RcvdInfo {
+  SuperiorDesignatedInfo,
+  RepeatedDesignatedInfo,
+  InferiorDesignatedInfo,
+  InferiorRootAlternateInfo,
+  OtherInfo
+} RcvdInfo;
+
+
+typedef enum _InfoType {
+  Mine,
+  Aged,
+  Received,
+  Disabled
+} InfoType;
+
+// We accomodate the port role encoding in BPDUs as per 9.2.9
+// into this type, defining an extra value of AltBackupPort valid
+// only in a BPDU.
+typedef enum _Role {
+  UnknownPort = 0,
+  AltBackupPort = 1,
+  RootPort = 2,
+  DesignatedPort = 3,
+  AlternatePort,
+  BackupPort,
+  DisabledPort
+} Role;
+
+typedef enum _BPDUType {
+  BPDUTypeConfig = 0,
+  BPDUTypeRST = 2,
+  BPDUTypeTCN = 128
+} BPDUType;
+
+/* Defining fields directly in struct _Port */
+#if 0
+typedef struct _ParsedBPDU
+{
+  uint version;
+  BPDUType bpdu_type;
+
+  /* flags */
+  bool bpdu_tcFlag;           // bit 1 of flags
+  bool bpdu_proposalFlag;     // bit 2 of flags
+  Role bpdu_role;             // bits 3 & 4 of flags
+  bool bpdu_learningFlag;     // bit 5 of flags
+  bool bpdu_forwardingFlag;   // bit 6 of flags
+  bool bpdu_agreementFlag;    // bit 7 of flags
+  bool bpdu_tcAckFlag;        // bit 8 of flags
+
+  PriorityVector4 bpdu_priority;
+  Times bpdu_times;
+
+} ParsedBPDU;
+#endif
+
+#define STP_PROTOCOL_ID 0
+
+typedef struct _RawBPDU
+{
+  uint16 protocol_id;
+  uchar version;
+  uchar type;
+  /* TCN has only upto this */
+  uchar TCN_END[0];
+  uchar flags;
+#define BPDU_FLAG_TC                     0x1
+#define BPDU_FLAG_PROPOSAL               0x2
+#define BPDU_FLAG_ROLE_MASK              0xc
+#define BPDU_FLAG_ROLE(role)             ((role << 2) & BPDU_FLAG_ROLE_MASK)
+#define BPDU_FLAG_ROLE_GET(flags)        ((flags & BPDU_FLAG_ROLE_MASK)>>2)
+#define BPDU_FLAG_LEARNING               0x10
+#define BPDU_FLAG_FORWARDING             0x20
+#define BPDU_FLAG_AGREEMENT              0x40
+#define BPDU_FLAG_TC_ACK                 0x80
+
+  PriorityVector4 priority;
+
+  uchar message_age[2];
+  uchar max_age[2];
+  uchar hello_time[2];
+  uchar forward_delay[2];
+  /* End of Config BPDU */
+  uchar CONFIG_END[0];
+
+  uchar version1_len;
+  /* End of RST BPDU */
+  uchar RST_END[0];
+
+} __attribute__((packed)) RawBPDU;
+
+
+
+/* Values for adminPointToPointMAC */
+typedef enum _AdminP2P {
+  P2PForceFalse = STP_ADMIN_P2P_FORCE_FALSE,
+  P2PForceTrue = STP_ADMIN_P2P_FORCE_TRUE,
+  P2PAuto = STP_ADMIN_P2P_AUTO
+} AdminP2P;
+
+
+/* Number of per bridge and per port state machines. */
+#define NUM_BRIDGE_STATE_MACHINES 1
+#define NUM_PORT_STATE_MACHINES  9
+
+/*
+  Framework:
+
+  Wherever this is a bridge context, we assume the variable BRIDGE points
+  to it. And wherever there is a port context, we assume PORT points to it.
+
+  We define all the fields in the Bridge and Port structures as starting
+  with and underscore and have
+  #define bridgevar BRIDGE->_bridgevar
+  and
+  #define portvar PORT->_portvar
+
+  This way we keep the expressions in the state machines fairly close
+  to the way they appear in the standard's figures.
+
+*/
+
+typedef STP_Port     Port;
+typedef STP_Bridge   Bridge;
+
+/* Per Bridge: */
+
+struct STP_Bridge_
+{
+
+  /* Administratively set variables */
+
+  // 17.13.2
+  // - Not defining - Never used.
+  // This is meant to control FDB, we don't deal with that here.
+  /*int _AgeingTime;*/
+  /*#define AgeingTime BRIDGE->_AgeingTime*/
+
+  // 17.13.4
+  // This is item a) in the list in the introduction in 17.13
+  // As per that, we need to reinitialize state machines by asserting BEGIN
+  // when changing this
+  uint _ForceProtocolVersion;
+#define ForceProtocolVersion BRIDGE->_ForceProtocolVersion
+
+  // 17.13.5
+  // - Not defining - this is part of BridgeTimes below
+  /*int BridgeForwardDelay;*/
+#define BridgeForwardDelay BridgeTimes.forward_delay
+
+  // 17.13.6
+  // - Not defining - this is part of BridgeTimes below
+  /*int BridgeHelloTime;*/
+#define BridgeHelloTime BridgeTimes.hello_time
+
+  // 17.13.7 BridgeIdentifierPriority
+  // This is item b) in the list in the introduction in 17.13
+  // As per that, we need to clear selected and set reselect when changing this
+  // Let us do it for all ports since this is a bridge variable.
+  // - Not defining this variable, since it is part of BridgeIdentifier below
+  /*int BridgeIdentifierPriority;*/
+#define BridgeIdentifierPriority BridgeIdentifier.bridge_identifier_priority
+
+  // 17.13.8
+  // - Not defining - this is part of BridgeTimes below
+  /*int BridgeMaxAge;*/
+#define BridgeMaxAge BridgeTimes.max_age
+
+  // 17.13.9
+  uint _MigrateTime;
+#define MigrateTime BRIDGE->_MigrateTime
+
+  // 17.13.12
+  uint _TransmitHoldCount;
+
+#define TransmitHoldCount BRIDGE->_TransmitHoldCount
+
+  /* Other (not administrative) */
+
+  // 17.18.1
+  // Condition that initializes all state machines.
+  // But machines will spin if it continues to be asserted,
+  // So we make it state that we put all machines in instead of asserting this
+  /*bool BEGIN;*/
+
+  // 17.18.1
+  // The high 16 bits are BridgeIdentifierPriority, noted above (17.13.7)
+  // We need to change the rest of BridgeIdentifier if the bridge MAC address
+  // changes. Standard doesn't expect this. Let us reinitialize state machines
+  // when this happens
+  BridgeId _BridgeIdentifier; /* Includes priority */
+#define BridgeIdentifier BRIDGE->_BridgeIdentifier
+
+  // 17.18.3, = B:0:B:0:0 - where B is Bridge Identifier
+  // - Not defining it .
+  // - We will compute it from BridgeIdentifier in updtRolesTree()
+  /*PriorityVector _BridgePriority;*/
+  /*#define BridgePriority BRIDGE->_BridgePriority*/
+
+  // 17.18.4
+  // This holds BridgeForwardDelay, BridgeHelloTime, BridgeMaxAge
+  // in .forward_delay, .hello_time, .max_age
+  // .message_age is 0
+  Times _BridgeTimes;
+#define BridgeTimes BRIDGE->_BridgeTimes
+
+  // 17.18.5
+  // 5th component of root priority vector
+  PortId _rootPortId;
+#define rootPortId BRIDGE->_rootPortId
+
+  // 17.18.6
+  // first 4 components of root priority vector
+  PriorityVector4 _rootPriority;
+#define rootPriority BRIDGE->_rootPriority
+
+  // 17.18.7
+  /* operational times got from portTimes for root port, or from BridgeTimes */
+  Times _rootTimes;
+#define rootTimes BRIDGE->_rootTimes
+
+
+
+  uint time_since_tc;
+  uint tc_count;
+  uint tcWhile_count; /* How many port tcWhile's are nonzero */
+
+  /* Our stuff - not from standard */
+
+  Port *port_list;
+  void *user_ref;
+  bool stp_on;
+  int state[NUM_BRIDGE_STATE_MACHINES];
+};
+
+
+/* Per port */
+
+struct STP_Port_
+{
+
+  /* Administrative variables: */
+
+  // 17.13.2
+  bool _AdminEdgePort;
+#define AdminEdgePort PORT->_AdminEdgePort
+
+  // 17.13.3
+  bool _AutoEdgePort;
+#define AutoEdgePort PORT->_AutoEdgePort
+
+  // 17.13.10
+  // This is item c) in the introduction to 17.13
+  // As per that, we need to clear selected and set reselect for this port
+  // when changing this
+  // - Not defining this, it is part of portId below
+  /*int PortIdentifierPriority;*/
+  // Not defining macro either, it is just 4 high bits in portId.port_id[0]
+  /*#define PortIdentifierPriority portId.portIdPriority*/
+
+  // 17.13.11
+  // This is item d) in the introduction to 17.13
+  // As per that, we need to clear selected and set reselect for this port
+  // when changing this
+  // PortPathCost defined below ref 17.19.20, is set to this if this is
+  // non-zero, else it is auto-set based on link speed.
+  uint _AdminPortPathCost;
+#define AdminPortPathCost PORT->_AdminPortPathCost
+
+  // 6.4.2
+  // MAC Enabled: Controlled by ifconfig <portif> up/down
+  // We will sense and act on it
+  // We set port enabled in that case
+
+  // 6.4.3
+  AdminP2P _adminPointToPointMAC;
+#define adminPointToPointMAC PORT->_adminPointToPointMAC
+
+  bool _operPointToPointMAC;
+#define operPointToPointMAC PORT->_operPointToPointMAC
+
+  /* Timers: */
+
+  // 17.17.1
+  uint _edgeDelayWhile;
+#define edgeDelayWhile PORT->_edgeDelayWhile
+
+  // 17.17.2
+  uint _fdWhile;
+#define fdWhile PORT->_fdWhile
+
+  // 17.17.3
+  uint _helloWhen;
+#define helloWhen PORT->_helloWhen
+
+  // 17.17.4
+  uint _mdelayWhile;
+#define mdelayWhile PORT->_mdelayWhile
+
+  // 17.17.5
+  uint _rbWhile;
+#define rbWhile PORT->_rbWhile
+
+  // 17.17.6
+  uint _rcvdInfoWhile;
+#define rcvdInfoWhile PORT->_rcvdInfoWhile
+
+  // 17.17.7
+  uint _rrWhile;
+#define rrWhile PORT->_rrWhile
+
+  // 17.17.8
+  uint _tcWhile;
+#define tcWhile PORT->_tcWhile
+
+  // 17.19.1
+  // - Not defining - Never used
+  // This is supposed to control FDB ageing on a per port basis.
+  // We don't control FDB ageing here.
+  /*int _ageingTime;*/
+  /*#define ageingTime PORT->_ageingTime*/
+
+  // 17.19.2
+  bool _agree;
+#define agree PORT->_agree
+
+  // 17.19.3
+  bool _agreed;
+#define agreed PORT->_agreed
+
+  // 17.19.4
+  // First 4 components of designated priority vector
+  // 5th component is portId
+  PriorityVector4 _designatedPriority;
+#define designatedPriority PORT->_designatedPriority
+
+  // 17.19.5
+  Times _designatedTimes;
+#define designatedTimes PORT->_designatedTimes
+
+  // 17.19.6
+  bool _disputed;
+#define disputed PORT->_disputed
+
+  // 17.19.8
+ /* Set by topology change state machine to instruct filtering database to
+     remove all entries for this Port, immediately if rstpVersion is TRUE
+     or by rapid ageing if stpVersion is TRUE. Reset by filtering database once
+     entries are removed if rstpVersion is TRUE and immediately if
+     stpVersion is TRUE
+  */
+  bool _fdbFlush;
+#define fdbFlush PORT->_fdbFlush
+
+  // 17.19.8
+  bool _forward;
+#define forward PORT->_forward
+
+  // 17.19.9
+  // Setting this will involve and OUT action
+  bool _forwarding;
+#define forwarding PORT->_forwarding
+
+  // 17.19.10
+  // Indicates origin/state of portInfo held for the port
+  InfoType _infoIs;
+#define infoIs PORT->_infoIs
+
+  // 17.19.11
+  bool _learn;
+#define learn PORT->_learn
+
+  // 17.19.12
+  bool _learning;
+#define learning PORT->_learning
+
+  // 17.19.13
+  bool _mcheck;
+#define mcheck PORT->_mcheck
+
+  // 17.19.14
+  // First 4 components of priority vector from received BPDU
+  PriorityVector4 _msgPriority;
+#define msgPriority PORT->_msgPriority
+
+  // 17.19.15
+  // Times from received BPDU
+  Times _msgTimes;
+#define msgTimes PORT->_msgTimes
+
+  // 17.19.16
+  /* Set if a BPDU is to be transmitted,
+     reset by Port Transmit state machine */
+  bool _newInfo;
+#define newInfo PORT->_newInfo
+
+  // 17.19.17
+  bool _operEdge;
+#define operEdge PORT->_operEdge
+
+  // 17.19.18
+  bool _portEnabled;
+#define portEnabled PORT->_portEnabled
+
+  // 17.19.19
+  PortId _portId;
+#define portId PORT->_portId
+
+  // 17.19.20
+  uint _PortPathCost;
+#define PortPathCost PORT->_PortPathCost
+
+  // 17.19.21
+  // First 4 components of port's priority vector
+  PriorityVector4 _portPriority;
+#define portPriority PORT->_portPriority
+
+  // 17.19.22
+  /* Ports timer parameter values */
+  Times _portTimes;
+#define portTimes PORT->_portTimes
+
+  // 17.19.23
+  bool _proposed;
+#define proposed PORT->_proposed
+
+  // 17.19.24
+  bool _proposing;
+#define proposing PORT->_proposing
+
+  // 17.19.25
+  /* Set by system when Config, TCN, or RST BPDU is received
+     - IN action sets this */
+  /* CORR: rcvdBPDU
+     But all other references in the doc use rcvdBpdu, so using that. */
+  bool _rcvdBpdu;
+#define rcvdBpdu PORT->_rcvdBpdu
+
+  // 17.19.26
+  /* Result of rcvInfo() procedure */
+  RcvdInfo _rcvdInfo;
+#define rcvdInfo PORT->_rcvdInfo
+
+  // 17.19.27
+  bool _rcvdMsg;
+#define rcvdMsg PORT->_rcvdMsg
+
+  // 17.19.28
+  bool _rcvdRSTP;
+#define rcvdRSTP PORT->_rcvdRSTP
+
+  // 17.19.29
+  bool _rcvdSTP;
+#define rcvdSTP PORT->_rcvdSTP
+
+  // 17.19.30
+  bool _rcvdTc;
+#define rcvdTc PORT->_rcvdTc
+
+  // 17.19.31
+  bool _rcvdTcAck;
+#define rcvdTcAck PORT->_rcvdTcAck
+
+  // 17.19.32
+  bool _rcvdTcn;
+#define rcvdTcn PORT->_rcvdTcn
+
+  // 17.19.33
+  bool _reRoot;
+#define reRoot PORT->_reRoot
+
+  // 17.19.34
+  bool _reselect;
+#define reselect PORT->_reselect
+
+  // 17.19.35
+  Role _role;
+#define role PORT->_role
+
+  // 17.19.36
+  bool _selected;
+#define selected PORT->_selected
+
+  // 17.19.37
+  Role _selectedRole;
+#define selectedRole PORT->_selectedRole
+
+  // 17.19.38
+  bool _sendRSTP;
+#define sendRSTP PORT->_sendRSTP
+
+  // 17.19.39
+  bool _sync;
+#define sync PORT->_sync
+
+  // 17.19.40
+  bool _synced;
+#define synced PORT->_synced
+
+  // 17.19.41
+  bool _tcAck;
+#define tcAck PORT->_tcAck
+
+  // 17.19.42
+  bool _tcProp;
+#define tcProp PORT->_tcProp
+
+  // 17.19.43
+  bool _tick;
+#define tick PORT->_tick
+
+  // 17.19.44
+  uint _txCount;
+#define txCount PORT->_txCount
+
+  // 17.19.45
+  bool _updtInfo;
+#define updtInfo PORT->_updtInfo
+
+  // Not in 17.19, but is used. When we receive a BPDU,
+  // fill in the received BPDU into this.
+  // We just define the fields directly
+
+  uint _bpduVersion;
+#define bpduVersion PORT->_bpduVersion
+  BPDUType _bpduType;
+#define bpduType PORT->_bpduType
+  /* flags */
+  bool _bpduTcFlag;           // bit 1 of flags
+#define bpduTcFlag PORT->_bpduTcFlag
+  bool _bpduProposalFlag;     // bit 2 of flags
+#define bpduProposalFlag PORT->_bpduProposalFlag
+  Role _bpduRole;             // bits 3 & 4 of flags
+#define bpduRole PORT->_bpduRole
+  bool _bpduLearningFlag;     // bit 5 of flags
+#define bpduLearningFlag PORT->_bpduLearningFlag
+  bool _bpduForwardingFlag;   // bit 6 of flags
+#define bpduForwardingFlag PORT->_bpduForwardingFlag
+  bool _bpduAgreementFlag;    // bit 7 of flags
+#define bpduAgreementFlag PORT->_bpduAgreementFlag
+  bool _bpduTcAckFlag;        // bit 8 of flags
+#define bpduTcAckFlag PORT->_bpduTcAckFlag
+  PriorityVector4 _bpduPriority;
+#define bpduPriority PORT->_bpduPriority
+  Times _bpduTimes;
+#define bpduTimes PORT->_bpduTimes
+
+
+
+
+  /* Our stuff - not from standard */
+
+  Port *port_next;
+  Bridge *bridge;
+  void *user_ref;
+
+  uint port_state_flags;
+  uint speed;
+  bool duplex;
+  int state[NUM_PORT_STATE_MACHINES];
+
+};
+
+/* Macros for iterating over all ports */
+
+/* Check whether expr is true for all ports */
+/* PORT is not defined by this but is possible defined where this is used.
+   FPORT iterates over all ports, so we need to use the FPORT->_field
+   (with underscore) for for port variables in the expression here */
+#define ForAllPort(expr)                                                     \
+({                                                                           \
+  Port *FPORT;                                                               \
+  bool res = TRUE;                                                           \
+  for (FPORT = BRIDGE->port_list; FPORT != NULL; FPORT = FPORT->port_next)   \
+    if (!(expr)) {                                                           \
+      res = FALSE;                                                           \
+      break;                                                                 \
+    }                                                                        \
+  res;                                                                       \
+})
+
+/* Execute statements for all ports */
+/* PORT is not defined by this but is possible defined where this is used.
+   FPORT iterates over all ports, so we need to use the FPORT->_field
+   (with underscore) for for port variables in statements here */
+#define ForAllPortDo(statements...)                                          \
+({                                                                           \
+  Port *FPORT;                                                               \
+  for (FPORT = BRIDGE->port_list; FPORT != NULL; FPORT = FPORT->port_next)   \
+    { statements }                                                           \
+})
+
+// 17.20 ----- conditions and paramaters
+
+// 17.20.1
+#define AdminEdge AdminEdgePort
+
+// 17.20.2
+#define AutoEdge AutoEdgePort
+
+// 17.20.3
+#define allSynced ForAllPort(FPORT->_synced || FPORT->_role == RootPort)
+
+// 17.20.4
+#define EdgeDelay (operPointToPointMAC?MigrateTime:MaxAge)
+
+// 17.20.5
+#define forwardDelay (sendRSTP?HelloTime:FwdDelay)
+
+// 17.20.6
+#define FwdDelay designatedTimes.forward_delay
+
+// 17.20.7
+#define HelloTime designatedTimes.hello_time
+
+// 17.20.8
+#define MaxAge designatedTimes.max_age
+
+// 17.20.9
+// #define MigrateTime MigrateTime
+
+// 17.20.10
+#define reRooted ForAllPort(FPORT == PORT || FPORT->_rrWhile == 0)
+
+// 17.20.11
+#define rstpVersion (ForceProtocolVersion >= 2)
+
+// 17.20.12
+#define stpVersion (ForceProtocolVersion < 2)
+
+// 17.20.13
+#define TxHoldCount TransmitHoldCount
+
+// 17.21 ---------- Procedures ------------------------
+
+// 17.21.1
+// Definition has an argument newInfoIs, but the uses don't seem to have it.
+// So we define it without that, assuming newInfoIs == infoIs
+static bool betterorsameInfo(PORT_ARGS_PROTO/*, InfoType newInfoIs*/)
+{
+  return
+    (/*newInfoIs == Received &&*/ infoIs == Received &&
+     CMP(msgPriority, <=, portPriority)) ||
+    (/*newInfoIs == Mine &&*/ infoIs == Mine &&
+     CMP(designatedPriority, <=, portPriority));
+}
+
+// 17.21.2
+static void clearReselectTree(BRIDGE_ARGS_PROTO)
+{
+  ForAllPortDo(
+               FPORT->_reselect = FALSE;
+               );
+}
+
+// 17.21.3
+static void disableForwarding(PORT_ARGS_PROTO)
+{
+  /* Cause forwarding process to stop forwarding frames through this port. */
+  /* Complete before returning */
+  /* OUT */
+  PORT->port_state_flags &= ~STP_PORT_STATE_FLAG_FORWARDING;
+  STP_OUT_port_set_state(PORT->user_ref, PORT->port_state_flags);
+}
+
+// 17.21.4
+static void disableLearning(PORT_ARGS_PROTO)
+{
+  /* Cause port to stop learning process */
+  /* Complete before returning */
+  /* OUT */
+  PORT->port_state_flags &= ~STP_PORT_STATE_FLAG_LEARNING;
+  STP_OUT_port_set_state(PORT->user_ref, PORT->port_state_flags);
+}
+
+// 17.21.5
+static void enableForwarding(PORT_ARGS_PROTO)
+{
+  /* Cause port to start forwarding. */
+  /* Complete before returning */
+  /* OUT */
+  PORT->port_state_flags |= STP_PORT_STATE_FLAG_FORWARDING;
+  STP_OUT_port_set_state(PORT->user_ref, PORT->port_state_flags);
+}
+
+// 17.21.6
+static void enableLearning(PORT_ARGS_PROTO)
+{
+  /* Cause port to start learning. */
+  /* Complete before returning */
+  /* OUT */
+  PORT->port_state_flags |= STP_PORT_STATE_FLAG_LEARNING;
+  STP_OUT_port_set_state(PORT->user_ref, PORT->port_state_flags);
+}
+
+/**** Topology change tracking and reporting ****/
+
+// change is +1 if a tcWhile became non-zero, -1 if a tcWhile became 0.
+static inline void update_tcWhile_count(BRIDGE_ARGS_PROTO, int change)
+{
+  if (change == 0)
+    return;
+
+  if (BRIDGE->tcWhile_count == 0) {
+    /* Becoming nonzero from zero */
+    BRIDGE->tc_count++;
+    BRIDGE->time_since_tc = 0;
+  }
+
+  BRIDGE->tcWhile_count+= change;
+}
+
+#define set_tcWhile(_val) \
+({ uint _oldval = tcWhile; tcWhile = _val; \
+   update_tcWhile_count(BRIDGE_ARGS, (tcWhile?1:0) - (_oldval?1:0)); })
+
+
+// 17.21.7
+static void newTcWhile(PORT_ARGS_PROTO)
+{
+  if (tcWhile == 0) {
+    if (sendRSTP /* CORR: sendRstp */) {
+      // Standard wants tcWhile rounded up
+      // We rounded HelloTime up when we received it.
+      //tcWhile = HelloTime + 1;
+      set_tcWhile(HelloTime + 1);
+
+      newInfo = TRUE;
+    }
+    else {
+      //tcWhile = rootTimes.max_age + rootTimes.forward_delay;
+      set_tcWhile(rootTimes.max_age + rootTimes.forward_delay);
+    }
+  }
+}
+
+// 17.21.8
+static RcvdInfo rcvInfo(PORT_ARGS_PROTO)
+{
+
+  if (bpduType == BPDUTypeTCN) {
+    // DIFF:
+    // 802.1D-2004 doesn't say explicitly what we should do in this case
+    // 802.1w-2001 says return OtherInfo, but just doing that won't ever
+    // set rcvdTcn, or cause any effect
+    // 802.1Q-2005 (MST, based on 802.1D-2004) says set rcvdTcn here
+    // Going by that, we set rcvdTcn and return OtherInfo
+
+    rcvdTcn = TRUE;
+    return OtherInfo;
+  }
+
+  /* Note: config BPDU conveys Designated Port Role */
+  if (bpduType == BPDUTypeConfig)
+    bpduRole = DesignatedPort;
+
+  msgPriority = bpduPriority;
+  msgTimes = bpduTimes;
+
+  if (bpduRole == DesignatedPort) {
+    // a)
+    if (CMP(msgPriority, <, portPriority) // 1)
+        || (CMP(msgPriority, ==, portPriority) // 2)
+            && CMP(msgTimes, !=, portTimes)))
+      return SuperiorDesignatedInfo;
+
+    // b)
+    if (CMP(msgPriority, ==, portPriority) && CMP(msgTimes, ==, portTimes))
+      return RepeatedDesignatedInfo;
+
+    // c)
+    if (CMP(msgPriority, >, portPriority))
+      return InferiorDesignatedInfo;
+  }
+
+  if ((bpduRole == RootPort || bpduRole == AltBackupPort) &&
+      CMP(msgPriority, >=, portPriority))
+    return InferiorRootAlternateInfo;
+
+  return OtherInfo;
+
+}
+
+// 17.21.9
+static void recordAgreement(PORT_ARGS_PROTO)
+{
+  if (rstpVersion && operPointToPointMAC && bpduAgreementFlag) {
+    agreed = TRUE;
+    proposing = FALSE;
+  }
+  else
+    agreed = FALSE;
+}
+
+// 17.21.10
+// No one sets disputed in 802.1D-2004, but see below.
+static void recordDispute(PORT_ARGS_PROTO)
+{
+  if (// bpduType == BPDUTypeRST && -- Implied since only RST has this flag
+      bpduLearningFlag) {
+    // DIFF: Going by 802.1Q-2005, and the remark at the end of
+    // 17.29.3 Designated Port states, about setting disputed,
+    // it looks like what we need is
+
+    disputed = TRUE;
+    agreed = FALSE;
+
+    // and not, as written in 17.21.10,
+    // agreed = TRUE;
+    // proposing = FALSE;
+  }
+}
+
+// 17.21.11
+static void recordProposal(PORT_ARGS_PROTO)
+{
+  if (bpduRole == DesignatedPort && bpduProposalFlag)
+    proposed = TRUE;
+}
+
+// 17.21.12
+static void recordPriority(PORT_ARGS_PROTO)
+{
+  portPriority = msgPriority;
+}
+
+#define MIN_COMPAT_HELLO_TIME  1
+
+// 17.21.13
+static void recordTimes(PORT_ARGS_PROTO)
+{
+  portTimes = msgTimes;
+  if (portTimes.hello_time < MIN_COMPAT_HELLO_TIME)
+    portTimes.hello_time = MIN_COMPAT_HELLO_TIME;
+}
+
+/* Per bridge */
+// 17.21.14
+static void setSyncTree(BRIDGE_ARGS_PROTO)
+{
+  ForAllPortDo(
+               FPORT->_sync = TRUE;
+               );
+}
+
+// 17.21.15
+static void setReRootTree(BRIDGE_ARGS_PROTO)
+{
+  ForAllPortDo(
+               FPORT->_reRoot = TRUE;
+               );
+
+}
+
+// 17.21.16
+static void setSelectedTree(BRIDGE_ARGS_PROTO)
+{
+  if (ForAllPort(FPORT->_reselect == FALSE))
+    ForAllPortDo(
+                 FPORT->_selected = TRUE;
+                 );
+}
+
+// 17.21.17
+static void setTcFlags(PORT_ARGS_PROTO)
+{
+  if (bpduType == BPDUTypeTCN)
+    rcvdTcn = TRUE;
+  else { // Only other types are Config and RST
+    //if (bpduType == BPDUTypeConfig || bpduType == BPDUTypeRST) {
+    if (bpduTcFlag)
+      rcvdTc = TRUE;
+    if (bpduTcAckFlag)
+      rcvdTcAck = TRUE;
+  }
+}
+
+// 17.21.18
+static void setTcPropTree(PORT_ARGS_PROTO)
+{
+  ForAllPortDo(
+               if (FPORT != PORT) FPORT->_tcProp = TRUE;
+               );
+}
+
+/***** txConfig(), txRstp() and txTcn() *****/
+
+/* To decide how much of the RawBPDU to actually transmit */
+#define offsetof(_TYPE, _MEMBER)  __builtin_offsetof (_TYPE, _MEMBER)
+//#define offsetof(_TYPE, _MEMBER) ((int)&((_TYPE *)0)->_MEMBER)
+
+/* To fill the times values in the BPDU in txConfig() and txRstp() */
+
+static void set_bpdu_time(uchar time[2], int t)
+{
+  time[0] = t;
+  time[1] = 0;
+}
+
+static void set_bpdu_times(RawBPDU *b, Times *t)
+{
+  set_bpdu_time(b->message_age, t->message_age);
+  set_bpdu_time(b->max_age, t->max_age);
+  set_bpdu_time(b->hello_time, t->hello_time);
+  set_bpdu_time(b->forward_delay, t->forward_delay);
+}
+
+// 17.21.19
+static void txConfig(PORT_ARGS_PROTO)
+{
+  RawBPDU b;
+
+  b.protocol_id = STP_PROTOCOL_ID;
+  b.version = 0;
+  b.type = BPDUTypeConfig;
+
+  b.priority = designatedPriority;
+
+  int flags = 0;
+  flags |= (tcWhile != 0) ? BPDU_FLAG_TC : 0;
+  flags |= (tcAck) ? BPDU_FLAG_TC_ACK : 0;
+  b.flags = flags;
+
+  set_bpdu_times(&b, &designatedTimes);
+
+  DEBUG("Sending Config BPDU\n");
+  STP_OUT_tx_bpdu(PORT->user_ref, &b, offsetof(RawBPDU, CONFIG_END));
+}
+
+// 17.21.20
+static void txRstp(PORT_ARGS_PROTO)
+{
+  RawBPDU b;
+
+  b.protocol_id = STP_PROTOCOL_ID;
+  b.version = 2;
+  b.type = BPDUTypeRST;
+
+  b.priority = designatedPriority;
+
+  int flags = 0;
+
+  int r = role;
+  if (r == AlternatePort || r == BackupPort)
+    r = AltBackupPort;
+
+  flags |= BPDU_FLAG_ROLE(r);
+
+  flags |= agree ? BPDU_FLAG_AGREEMENT : 0;
+  flags |= proposing ? BPDU_FLAG_PROPOSAL : 0;
+  flags |= (tcWhile != 0) ? BPDU_FLAG_TC : 0;
+  // tcAckFlag = 0;
+  flags |= learning ? BPDU_FLAG_LEARNING : 0;
+  flags |= forwarding ? BPDU_FLAG_FORWARDING : 0;
+
+  b.flags = flags;
+
+  set_bpdu_times(&b, &designatedTimes);
+
+  b.version1_len = 0;
+
+  DEBUG("Sending RST BPDU\n");
+  STP_OUT_tx_bpdu(PORT->user_ref, &b, offsetof(RawBPDU, RST_END));
+}
+
+// 17.21.21
+static void txTcn(PORT_ARGS_PROTO)
+{
+  RawBPDU b;
+
+  b.protocol_id = STP_PROTOCOL_ID;
+  b.version = 0;
+  b.type = BPDUTypeTCN;
+
+  DEBUG("Sending TCN BPDU\n");
+  STP_OUT_tx_bpdu(PORT->user_ref, &b, offsetof(RawBPDU, TCN_END));
+}
+
+// 17.21.22
+static void updtBPDUVersion(PORT_ARGS_PROTO)
+{
+  if ((bpduVersion == 0 || bpduVersion == 1)
+      && (bpduType == BPDUTypeTCN || bpduType == BPDUTypeConfig))
+    rcvdSTP = TRUE;
+  if (bpduType == BPDUTypeRST)
+    rcvdRSTP = TRUE;
+}
+
+// 17.21.23
+static void updtRcvdInfoWhile(PORT_ARGS_PROTO)
+{
+  if (portTimes.message_age + 1 <= portTimes.max_age)
+    rcvdInfoWhile = 3*portTimes.hello_time;
+  else
+    rcvdInfoWhile = 0;
+}
+
+// 17.21.24
+static void updtRoleDisabledTree(BRIDGE_ARGS_PROTO)
+{
+  ForAllPortDo(
+               FPORT->_selectedRole = DisabledPort;
+               );
+}
+
+// 17.21.25
+static void updtRolesTree(BRIDGE_ARGS_PROTO)
+{
+  // Computes Spanning Tree Priority vectors (17.5, 17.6) and timer values
+  /*
+    Sets:
+      bridge's rootTimes,
+      designatedPriority for each port, designatedTimes for each Port
+      For each port: selectedRole, possibly updtInfo
+  */
+
+  // a), b)
+  PriorityVector4 root_priority;
+  PortId root_port_id;
+  Port *root_port = NULL;
+
+  /* Initialize root_priority to computed BridgePriority = B:0:B:0:0 */
+  ZERO(root_priority);
+  ZERO(root_port_id);
+  root_priority.root_bridge_id = BridgeIdentifier;
+  root_priority.designated_bridge_id = BridgeIdentifier;
+
+  ForAllPortDo(Port *PORT = FPORT; // So we can use port var names directly
+               if (infoIs == Received &&
+                   CMP(portPriority.designated_bridge_id, !=, BridgeIdentifier)
+                   ) {
+                 // a) - root path priority vector
+                 PriorityVector4 root_path_priority = portPriority;
+                 root_path_priority.root_path_cost =
+                   path_cost_add(root_path_priority.root_path_cost,
+                                 PortPathCost);
+                 if (CMP(root_path_priority, <, root_priority)
+                     || (CMP(root_path_priority, ==, root_priority)
+                         && CMP(portId, <, root_port_id))) {
+                   root_priority = root_path_priority;
+                   root_port_id = portId;
+                   root_port = PORT;
+                 }
+               }
+               );
+  // Now root_priority is the minimum of BridgePriority and
+  // of all the root path priorities where designated bridge id is not our ID
+  // b)
+  rootPriority = root_priority;
+  rootPortId = root_port_id;
+
+  // c)
+  if (root_port != NULL) {
+    rootTimes = root_port->_portTimes;
+    rootTimes.message_age += 1; /* Rounded to whole second */
+  }
+  else
+    rootTimes = BridgeTimes;
+
+
+  ForAllPortDo(Port *PORT = FPORT;
+               // d)
+               designatedPriority.root_bridge_id =
+                 root_priority.root_bridge_id;
+               designatedPriority.root_path_cost =
+                 root_priority.root_path_cost;
+               designatedPriority.designated_bridge_id = BridgeIdentifier;
+               designatedPriority.designated_port_id = portId;
+
+               // e)
+               designatedTimes = rootTimes;
+               designatedTimes.hello_time = BridgeTimes.hello_time;
+               );
+
+
+  ForAllPortDo(Port *PORT = FPORT;
+               switch (infoIs) {
+               case Disabled: // f)
+                 selectedRole = DisabledPort;
+                 break;
+               case Aged: // g)
+                 updtInfo = TRUE;
+                 selectedRole = DesignatedPort;
+                 break;
+               case Mine: // h)
+                 selectedRole = DesignatedPort;
+                 if (CMP(designatedPriority, !=, portPriority) ||
+                     // Maybe below should be root_port->_portTimes instead
+                     // of designatedTimes, going literally by h)?
+                     // designatedTimes same as that that except for hello_time
+                     CMP(designatedTimes, !=, portTimes))
+                   updtInfo = TRUE;
+                 break;
+
+               case Received:
+                 if (PORT == root_port) { // i)
+                   selectedRole = RootPort;
+                   updtInfo = FALSE;
+                 }
+                 // In j), k), l) standard says designated priority is (or
+                 // is not) _higher_ than the port priority
+                 // Seems like it should mean _better_, numerically
+                 // higher is worse. We assume it means better, i.e.
+                 // numerically lower
+                 else if (CMP(designatedPriority, >=, portPriority)) {
+                   if (CMP(portPriority.designated_bridge_id,
+                           !=, BridgeIdentifier)) { // j)
+                     selectedRole = AlternatePort;
+                   }
+                   else { // k)
+                     /* portPriority.designated_bridge_id is our bridge */
+                     selectedRole = BackupPort;
+                   }
+                   updtInfo = FALSE; // for j) and k)
+                 }
+                 else { // l)
+                   /* Not root port and designatedPriority < portPriority */
+                   selectedRole = DesignatedPort;
+                   updtInfo = TRUE;
+                 }
+                 break;
+               default:
+                 ERROR("Unknown value for infoIs: %d\n", infoIs);
+               }
+                 DEBUG("Selected Role Set to: %d\n", selectedRole);
+               );
+}
+
+
+
+/* Other procedures we need */
+
+/* Call this whenever we set fdbFlush to TRUE. This will flush the
+   fdb data on the specfied port. */
+static void do_fdbFlush(PORT_ARGS_PROTO)
+{
+  if (rstpVersion) {
+    STP_OUT_port_fdb_flush(PORT->user_ref);
+    fdbFlush = FALSE; /* done */
+  }
+  else {
+    /* We need to reduce ageing time to FwdDelay for time FwdDelay */
+    /* Let us just flush them right away.
+       Bridge doesn't let us set ageing by port anyway */
+    STP_OUT_port_fdb_flush(PORT->user_ref);
+    fdbFlush = FALSE;
+  }
+}
+
+/**************************** STATE MACHINES *******************************/
+
+/*--------------- State machine diagrams ------------------------*/
+
+// 17.22 - Fig. 17-13
+
+#define STM_NAME PortTimers
+
+STATES(STN(ONE_SECOND), STN(TICK));
+
+#define dec(x) if (x) x = x - 1
+
+STM(PORT_ARGS_PROTO,
+
+    GC();
+
+    BG(
+       TR(UCT, ONE_SECOND);
+       );
+
+    ST(ONE_SECOND,
+       tick = FALSE;
+       ,
+       TR(tick == TRUE, TICK);
+       );
+
+    ST(TICK,
+       dec(helloWhen);
+       //dec(tcWhile);
+       if (tcWhile) {
+         set_tcWhile(tcWhile - 1);
+       }
+       dec(fdWhile);
+       dec(rcvdInfoWhile);
+       dec(rrWhile);
+       dec(rbWhile);
+       dec(mdelayWhile);
+       dec(edgeDelayWhile);
+       dec(txCount);
+       ,
+       TR(UCT, ONE_SECOND);
+       );
+
+    );
+
+#undef STM_NAME
+
+/*------------------------------------------------------------*/
+
+// 17.23 - Fig. 17-14
+
+#define STM_NAME PortReceive
+
+STATES(STN(DISCARD), STN(RECEIVE));
+
+STM(PORT_ARGS_PROTO,
+
+    GC(
+       TR((rcvdBpdu || (edgeDelayWhile != MigrateTime)) && !portEnabled,
+          DISCARD);
+       );
+
+    BG(
+       TR(UCT, DISCARD);
+       );
+
+    ST(DISCARD,
+       rcvdBpdu = rcvdRSTP = rcvdSTP = FALSE;
+       rcvdMsg = FALSE;
+       edgeDelayWhile = MigrateTime;
+       ,
+       TR(rcvdBpdu && portEnabled, RECEIVE);
+       );
+
+    ST(RECEIVE,
+       updtBPDUVersion(PORT_ARGS);
+       operEdge = rcvdBpdu = FALSE;
+       rcvdMsg = TRUE;
+       edgeDelayWhile = MigrateTime;
+       ,
+       TR(rcvdBpdu && portEnabled && !rcvdMsg, RECEIVE);
+       );
+
+    );
+
+#undef STM_NAME
+
+/*------------------------------------------------------------*/
+
+// 17.24 - Fig. 17-15
+
+#define STM_NAME PortProtocolMigration
+
+STATES(STN(CHECKING_RSTP), STN(SELECTING_STP), STN(SENSING));
+
+STM(PORT_ARGS_PROTO,
+
+    GC();
+
+    BG(
+       TR(UCT, CHECKING_RSTP);
+       );
+
+    ST(CHECKING_RSTP,
+       mcheck = FALSE;
+       sendRSTP = (rstpVersion);
+       mdelayWhile = MigrateTime;
+       ,
+       TR(mdelayWhile == 0, SENSING);
+       TR((mdelayWhile != MigrateTime) && !portEnabled, CHECKING_RSTP);
+       );
+
+    ST(SELECTING_STP,
+       sendRSTP = FALSE;
+       mdelayWhile = MigrateTime;
+       ,
+       TR(mdelayWhile == 0 || !portEnabled || mcheck, SENSING);
+       );
+
+    ST(SENSING,
+       rcvdRSTP = rcvdSTP = FALSE;
+       ,
+       TR(!portEnabled || mcheck || ((rstpVersion) && !sendRSTP && rcvdRSTP),
+          CHECKING_RSTP);
+       TR(sendRSTP && rcvdSTP, SELECTING_STP);
+       );
+
+    );
+
+#undef STM_NAME
+
+/*------------------------------------------------------------*/
+
+// 17.25 - Fig. 17-16
+
+#define STM_NAME BridgeDetection
+
+STATES(STN(EDGE), STN(NOT_EDGE));
+
+STM(PORT_ARGS_PROTO,
+
+    GC();
+
+    BG(
+       TR(AdminEdge, EDGE);
+       TR(!AdminEdge, NOT_EDGE);
+       );
+
+    ST(EDGE,
+       operEdge = TRUE;
+       ,
+       TR((!portEnabled && !AdminEdge) || !operEdge, NOT_EDGE);
+       );
+
+    ST(NOT_EDGE,
+       operEdge = FALSE;
+       ,
+       TR((!portEnabled && AdminEdge) ||
+          ((edgeDelayWhile == 0) && AutoEdge
+           && sendRSTP /* CORR: sendRstp */ && proposing),
+          EDGE);
+       );
+
+    );
+
+#undef STM_NAME
+
+/*------------------------------------------------------------*/
+
+// 17.26 - Fig. 17-17
+
+#define xc selected && !updtInfo
+#define xc2 newInfo && (txCount < TxHoldCount) && (helloWhen != 0)
+
+#define STM_NAME PortTransmit
+
+STATES(STN(TRANSMIT_INIT), STN(TRANSMIT_PERIODIC), STN(TRANSMIT_CONFIG),
+       STN(TRANSMIT_TCN), STN(TRANSMIT_RSTP), STN(IDLE));
+
+STM(PORT_ARGS_PROTO,
+
+    GC();
+
+    BG(
+       TR(UCT, TRANSMIT_INIT);
+       );
+
+    ST(TRANSMIT_INIT,
+       newInfo = TRUE;
+       txCount = 0;
+       ,
+       TR(UCT, IDLE);
+       );
+
+    ST(TRANSMIT_PERIODIC,
+       newInfo = newInfo || (role == DesignatedPort ||
+                             (role == RootPort && (tcWhile != 0)));
+       ,
+       TR(UCT, IDLE);
+       );
+
+    ST(TRANSMIT_CONFIG,
+       newInfo = FALSE;
+       txConfig(PORT_ARGS);
+       txCount += 1;
+       tcAck = FALSE;
+       ,
+       TR(UCT, IDLE);
+       );
+
+    ST(TRANSMIT_TCN,
+       newInfo = FALSE;
+       txTcn(PORT_ARGS);
+       txCount += 1;
+       ,
+       TR(UCT, IDLE);
+       );
+
+    ST(TRANSMIT_RSTP,
+       newInfo = FALSE;
+       txRstp(PORT_ARGS);
+       txCount += 1;
+       tcAck = FALSE;
+       ,
+       TR(UCT, IDLE);
+       );
+
+    ST(IDLE,
+       helloWhen = HelloTime;
+       ,
+       TR(helloWhen == 0 && xc, TRANSMIT_PERIODIC);
+       TR(sendRSTP && xc2 && xc, TRANSMIT_RSTP);
+       TR(!sendRSTP && role == RootPort && xc2 && xc, TRANSMIT_TCN);
+       TR(!sendRSTP && role == DesignatedPort && xc2 && xc, TRANSMIT_CONFIG);
+       );
+
+    );
+
+#undef STM_NAME
+
+/*------------------------------------------------------------*/
+
+// 17.27 - Fig. 17-18
+
+#define STM_NAME PortInformation
+
+STATES(STN(DISABLED), STN(AGED), STN(UPDATE),
+       STN(SUPERIOR_DESIGNATED), STN(REPEATED_DESIGNATED),
+       STN(INFERIOR_DESIGNATED), STN(NOT_DESIGNATED),
+       STN(OTHER), STN(CURRENT), STN(RECEIVE)
+       );
+
+STM(PORT_ARGS_PROTO,
+
+    GC(
+       TR(!portEnabled && (infoIs != Disabled), DISABLED);
+       );
+
+    BG(
+       TR(UCT, DISABLED);
+       );
+
+    ST(DISABLED,
+       rcvdMsg = FALSE;
+       proposing = proposed = agree = agreed = FALSE;
+       rcvdInfoWhile = 0;
+       infoIs = Disabled; reselect = TRUE; selected = FALSE;
+       ,
+       TR(portEnabled, AGED);
+       TR(rcvdMsg, DISABLED);
+       );
+
+    ST(AGED,
+       infoIs = Aged;
+       reselect = TRUE; selected = FALSE;
+       ,
+       TR(selected && updtInfo, UPDATE);
+       );
+
+    ST(UPDATE,
+       proposing = proposed = FALSE;
+       agreed = agreed && betterorsameInfo(PORT_ARGS);
+       synced = synced && agreed;
+       portPriority = designatedPriority;
+       portTimes = designatedTimes;
+       updtInfo = FALSE; infoIs = Mine; newInfo = TRUE;
+       ,
+       TR(UCT, CURRENT);
+       );
+
+    ST(SUPERIOR_DESIGNATED,
+       agreed = proposing = FALSE;
+       recordProposal(PORT_ARGS);
+       setTcFlags(PORT_ARGS);
+       agreed = agreed && betterorsameInfo(PORT_ARGS);
+       recordPriority(PORT_ARGS);
+       recordTimes(PORT_ARGS);
+       updtRcvdInfoWhile(PORT_ARGS);
+       infoIs = Received; reselect = TRUE; selected = FALSE;
+       rcvdMsg = FALSE;
+       ,
+       TR(UCT, CURRENT);
+       );
+
+    ST(REPEATED_DESIGNATED,
+       recordProposal(PORT_ARGS);
+       setTcFlags(PORT_ARGS);
+       updtRcvdInfoWhile(PORT_ARGS);
+       rcvdMsg = FALSE;
+       ,
+       TR(UCT, CURRENT);
+       );
+
+    ST(INFERIOR_DESIGNATED,
+       recordDispute(PORT_ARGS);
+       rcvdMsg = FALSE;
+       ,
+       TR(UCT, CURRENT);
+       );
+
+    ST(NOT_DESIGNATED,
+       recordAgreement(PORT_ARGS);
+       setTcFlags(PORT_ARGS);
+       rcvdMsg = FALSE;
+       ,
+       TR(UCT, CURRENT);
+       );
+
+    ST(OTHER,
+       rcvdMsg = FALSE;
+       ,
+       TR(UCT, CURRENT);
+       );
+
+    ST(CURRENT,
+       ,
+       TR(selected && updtInfo, UPDATE);
+       TR((infoIs == Received) && (rcvdInfoWhile == 0) &&
+          !updtInfo && !rcvdMsg,
+          AGED);
+       TR(rcvdMsg && !updtInfo, RECEIVE);
+       );
+
+    ST(RECEIVE,
+       rcvdInfo = rcvInfo(PORT_ARGS);
+       ,
+       TR(rcvdInfo == SuperiorDesignatedInfo, SUPERIOR_DESIGNATED);
+       TR(rcvdInfo == RepeatedDesignatedInfo, REPEATED_DESIGNATED);
+       TR(rcvdInfo == InferiorDesignatedInfo, INFERIOR_DESIGNATED);
+       TR(rcvdInfo == InferiorRootAlternateInfo, NOT_DESIGNATED);
+       TR(rcvdInfo == OtherInfo, OTHER);
+       );
+
+    );
+
+#undef STM_NAME
+
+/*------------------------------------------------------------*/
+
+// 17.28 - Fig. 17-19
+
+#define STM_NAME PortRoleSelection
+
+STATES(STN(INIT_BRIDGE), STN(ROLE_SELECTION));
+
+STM(BRIDGE_ARGS_PROTO,
+
+    GC();
+
+    BG(
+       TR(UCT, INIT_BRIDGE);
+       );
+
+    ST(INIT_BRIDGE,
+       updtRoleDisabledTree(BRIDGE_ARGS);
+       ,
+       TR(UCT, ROLE_SELECTION);
+       );
+
+    ST(ROLE_SELECTION,
+       clearReselectTree(BRIDGE_ARGS);
+       updtRolesTree(BRIDGE_ARGS);
+       setSelectedTree(BRIDGE_ARGS);
+       ,
+       TR(!ForAllPort(!FPORT->_reselect), ROLE_SELECTION);
+       );
+
+    );
+
+#undef STM_NAME
+
+/*------------------------------------------------------------*/
+
+// 17.29
+// 17.29.1 - Fig. 17-20  Disabled Port role transitions
+// 17.29.2 - Fig. 17-21  Root Port role transitions
+// 17.29.3 - Fig. 17-22  Designated Port role transitions
+// 17.29.4 - Fig. 17-23  Alternate and Backup Port role transitions
+
+#define STM_NAME PortRoleTransitions
+
+STATES(/* Disabled Port role transitions */
+       STN(INIT_PORT), STN(DISABLE_PORT), STN(DISABLED_PORT),
+       /* Root Port role transitions */
+       STN(ROOT_PROPOSED), STN(ROOT_AGREED), STN(ROOT_SYNCED),
+       STN(REROOT), STN(REROOTED),
+       STN(ROOT_LEARN), STN(ROOT_FORWARD), STN(ROOT_PORT),
+       /* Designated port role transitions */
+       STN(DESIGNATED_PROPOSE), STN(DESIGNATED_SYNCED),
+       STN(DESIGNATED_RETIRED), STN(DESIGNATED_DISCARD),
+       STN(DESIGNATED_LEARN), STN(DESIGNATED_FORWARD), STN(DESIGNATED_PORT),
+       /* Alternate and Backup Port role transitions */
+       STN(BLOCK_PORT), STN(ALTERNATE_PROPOSED), STN(ALTERNATE_AGREED),
+       STN(BACKUP_PORT), STN(ALTERNATE_PORT)
+       );
+
+STM(PORT_ARGS_PROTO,
+
+    GC(
+       // Let us put role != selectedRole first in the && of conditions
+       // Since this will be false in the stable state, so condition check
+       // will fail quicker.
+
+       /* Disabled Port role transitions */
+       TR((role != selectedRole) && (selectedRole == DisabledPort) && xc,
+          DISABLE_PORT);
+       /* Root Port role transitions */
+       TR((role != selectedRole) && (selectedRole == RootPort) && xc,
+          ROOT_PORT);
+       /* Designated port role transitions */
+       TR((role != selectedRole) && (selectedRole == DesignatedPort) && xc,
+          DESIGNATED_PORT);
+       /* Alternate and Backup Port role transitions */
+       TR((role !=selectedRole) && ((selectedRole == AlternatePort)
+                                    || (selectedRole == BackupPort)) && xc,
+          BLOCK_PORT);
+       );
+
+
+    /* Disabled Port role transitions */
+
+    BG(
+       TR(UCT, INIT_PORT);
+       );
+
+    ST(INIT_PORT,
+       role = DisabledPort;
+       learn = forward = FALSE;
+       synced = FALSE;
+       sync = reRoot = TRUE;
+       rrWhile = FwdDelay;
+       fdWhile= MaxAge;
+       rbWhile = 0;
+       ,
+       TR(UCT, DISABLE_PORT);
+       );
+
+    ST(DISABLE_PORT,
+       role = selectedRole;
+       learn = forward = FALSE;
+       ,
+       TR(!learning && !forwarding && xc, DISABLED_PORT);
+       );
+
+    ST(DISABLED_PORT,
+       fdWhile = MaxAge;
+       synced = TRUE;
+       rrWhile = 0;
+       sync = reRoot = FALSE;
+       ,
+       TR((fdWhile != MaxAge || sync || reRoot || !synced) && xc,
+          DISABLED_PORT);
+       );
+
+    /* Root Port role transitions */
+
+    ST(ROOT_PROPOSED,
+       setSyncTree(BRIDGE_ARGS);
+       proposed = FALSE;
+       ,
+       TR(UCT, ROOT_PORT);
+       );
+
+    ST(ROOT_AGREED,
+       proposed = sync = FALSE;
+       agree = TRUE;
+       newInfo = TRUE;
+       ,
+       TR(UCT, ROOT_PORT);
+       );
+
+    // This state is there in the 802.1Q-2005 state machine
+    ST(ROOT_SYNCED,
+       synced = TRUE;
+       sync = FALSE;
+       ,
+       TR(UCT, ROOT_PORT);
+       );
+
+    ST(REROOT,
+       setReRootTree(BRIDGE_ARGS);
+       ,
+       TR(UCT, ROOT_PORT);
+       );
+
+    ST(REROOTED,
+       reRoot = FALSE;
+       ,
+       TR(UCT, ROOT_PORT);
+       );
+
+    ST(ROOT_LEARN,
+       fdWhile = forwardDelay;
+       learn = TRUE;
+       ,
+       TR(UCT, ROOT_PORT);
+       );
+
+    ST(ROOT_FORWARD,
+       fdWhile = 0;
+       forward = TRUE;
+       ,
+       TR(UCT, ROOT_PORT);
+       );
+
+    ST(ROOT_PORT,
+       role = RootPort;
+       rrWhile = FwdDelay;
+       ,
+       TR(proposed && !agree && xc, ROOT_PROPOSED);
+       TR(((allSynced && !agree) || (proposed && agree)) && xc, ROOT_AGREED);
+       TR(((agreed && !synced) || (sync && synced)) && xc, ROOT_SYNCED);
+       TR(!forward && !reRoot && xc, REROOT);
+       TR(reRoot && forward && xc, REROOTED);
+       TR((fdWhile == 0 ||
+           ((reRooted && (rbWhile == 0)) && (rstpVersion))) && !learn && xc,
+          ROOT_LEARN);
+       TR((fdWhile == 0 || ((reRooted && (rbWhile == 0)) && (rstpVersion)))
+          && learn && !forward && xc,
+          ROOT_FORWARD);
+       TR(rrWhile != FwdDelay && xc, ROOT_PORT);
+       );
+
+
+    /* Designated port role transitions */
+
+    ST(DESIGNATED_PROPOSE,
+       proposing = TRUE;
+       edgeDelayWhile = EdgeDelay;
+       newInfo = TRUE;
+       ,
+       TR(UCT, DESIGNATED_PORT);
+       );
+
+    ST(DESIGNATED_SYNCED,
+       rrWhile = 0;
+       synced = TRUE;
+       sync = FALSE;
+       ,
+       TR(UCT, DESIGNATED_PORT);
+       );
+
+    ST(DESIGNATED_RETIRED,
+       reRoot = FALSE;
+       ,
+       TR(UCT, DESIGNATED_PORT);
+       );
+
+    ST(DESIGNATED_DISCARD,
+       learn = forward = disputed = FALSE;
+       fdWhile = forwardDelay;
+       ,
+       TR(UCT, DESIGNATED_PORT);
+       );
+
+    ST(DESIGNATED_LEARN,
+       learn = TRUE;
+       fdWhile = forwardDelay;
+       ,
+       TR(UCT, DESIGNATED_PORT);
+       );
+
+    ST(DESIGNATED_FORWARD,
+       forward = TRUE;
+       fdWhile = 0;
+       agreed = sendRSTP /* CORR: sendRstp */;
+       ,
+       TR(UCT, DESIGNATED_PORT);
+       );
+
+    ST(DESIGNATED_PORT,
+       role = DesignatedPort;
+       ,
+       TR(!forward && !agreed && !proposing && !operEdge && xc,
+          DESIGNATED_PROPOSE);
+       TR(((!learning && !forwarding && !synced)
+           || (agreed && !synced) || (operEdge && !synced)
+           || (sync && synced)) && xc,
+          DESIGNATED_SYNCED);
+       TR(rrWhile == 0 && reRoot && xc, DESIGNATED_RETIRED);
+       TR(((sync && !synced) || (reRoot && (rrWhile != 0)) || disputed)
+          && !operEdge && (learn || forward) && xc,
+          DESIGNATED_DISCARD);
+       TR(((fdWhile == 0) || agreed || operEdge)
+          && ((rrWhile ==0) || !reRoot) && !sync && !learn && xc,
+          DESIGNATED_LEARN);
+       TR(((fdWhile == 0) || agreed || operEdge)
+          && ((rrWhile ==0) || !reRoot) && !sync && (learn && !forward) && xc,
+          DESIGNATED_FORWARD);
+       );
+
+
+    /* Alternate and Backup Port role transitions */
+
+    ST(BLOCK_PORT,
+       role = selectedRole;
+       learn = forward = FALSE;
+       ,
+       TR(!learning && !forwarding && xc, ALTERNATE_PORT);
+       );
+
+    ST(ALTERNATE_PROPOSED,
+       setSyncTree(BRIDGE_ARGS);
+       proposed = FALSE;
+       ,
+       TR(UCT, ALTERNATE_PORT);
+       );
+
+    ST(ALTERNATE_AGREED,
+       proposed = FALSE;
+       agree = TRUE;
+       newInfo = TRUE;
+       ,
+       TR(UCT, ALTERNATE_PORT);
+       );
+
+    ST(BACKUP_PORT,
+       rbWhile = 2*HelloTime;
+       ,
+       TR(UCT, ALTERNATE_PORT);
+       );
+
+    ST(ALTERNATE_PORT,
+       // DIFF: std says FwdDelay, but that leads to infinite loop here
+       // when sendRSTP is set, since FwdDelay != forwardDelay then
+       // 802.1Q-2005 says forwardDelay here
+       fdWhile = forwardDelay;
+       synced = TRUE;
+       rrWhile = 0;
+       sync = reRoot = FALSE;
+       ,
+       TR(proposed && !agree && xc,
+          ALTERNATE_PROPOSED);
+       TR(((allSynced && !agree) || (proposed && agree)) && xc,
+          ALTERNATE_AGREED);
+       TR((rbWhile != 2*HelloTime) && (role == BackupPort) && xc,
+          BACKUP_PORT);
+       TR(((fdWhile != forwardDelay) || sync || reRoot || !synced) && xc,
+          ALTERNATE_PORT);
+       );
+
+    );
+
+#undef STM_NAME
+
+/*------------------------------------------------------------*/
+
+// 17.30 - Fig. 17-24
+
+#define STM_NAME PortStateTransition
+
+STATES(STN(DISCARDING), STN(LEARNING), STN(FORWARDING));
+
+STM(PORT_ARGS_PROTO,
+
+    GC();
+
+    BG(
+       TR(UCT, DISCARDING);
+       );
+
+    ST(DISCARDING,
+       disableLearning(PORT_ARGS);
+       learning = FALSE;
+       disableForwarding(PORT_ARGS);
+       forwarding = FALSE;
+       ,
+       TR(learn, LEARNING);
+       );
+
+    ST(LEARNING,
+       enableLearning(PORT_ARGS);
+       learning = TRUE;
+       ,
+       TR(!learn, DISCARDING);
+       TR(forward, FORWARDING);
+       );
+
+    ST(FORWARDING,
+       enableForwarding(PORT_ARGS);
+       forwarding = TRUE;
+       ,
+       TR(!forward, DISCARDING);
+       );
+
+    );
+
+#undef STM_NAME
+
+/*------------------------------------------------------------*/
+
+// 17.31 - Fig. 17-25
+
+#define STM_NAME TopologyChange
+
+STATES(STN(INACTIVE), STN(LEARNING), STN(DETECTED), STN(ACKNOWLEDGED),
+       STN(PROPAGATING), STN(NOTIFIED_TC), STN(NOTIFIED_TCN), STN(ACTIVE));
+
+STM(PORT_ARGS_PROTO,
+
+    GC();
+
+    BG(
+       TR(UCT, INACTIVE);
+       );
+
+    ST(INACTIVE,
+       fdbFlush = TRUE;
+       do_fdbFlush(PORT_ARGS); /* We add this to trigger the fdb flush */
+       //tcWhile = 0;
+       set_tcWhile(0);
+       tcAck = FALSE;
+       ,
+       TR(learn && !fdbFlush, LEARNING);
+       );
+
+    ST(LEARNING,
+       rcvdTc = rcvdTcn = rcvdTcAck = FALSE;
+       rcvdTc = tcProp = FALSE;
+       ,
+       TR(((role == RootPort) || (role == DesignatedPort))
+          && forward && !operEdge,
+          DETECTED);
+
+       TR((role != RootPort) &&
+          (role != DesignatedPort) &&
+          !(learn || learning) &&
+          !(rcvdTc || rcvdTcn || rcvdTcAck || tcProp),
+          INACTIVE);
+
+       TR(rcvdTc ||
+          rcvdTcn ||
+          rcvdTcAck ||
+          tcProp,
+          LEARNING);
+       );
+
+    ST(DETECTED,
+       newTcWhile(PORT_ARGS);
+       setTcPropTree(PORT_ARGS);
+       newInfo = TRUE;
+       ,
+       TR(UCT, ACTIVE);
+       );
+
+    ST(ACKNOWLEDGED,
+       //tcWhile = 0;
+       set_tcWhile(0);
+       rcvdTcAck = FALSE;
+       ,
+       TR(UCT, ACTIVE);
+       );
+
+    ST(PROPAGATING,
+       newTcWhile(PORT_ARGS);
+       fdbFlush = TRUE;
+       do_fdbFlush(PORT_ARGS); /* We add this to trigger the fdb flush */
+       tcProp = FALSE;
+       ,
+       TR(UCT, ACTIVE);
+       );
+
+    ST(NOTIFIED_TC,
+       rcvdTcn = rcvdTc = FALSE;
+       if (role == DesignatedPort) tcAck = TRUE;
+       /*
+         Fig 17-25 has setTcPropBridge();
+         Must mean setTcPropTree().
+         The 802.1w-2001 standard has setTcPropBridge() defined to be the
+         same thing as setTcPropTree() in 802.1D-2004, and also has
+         setTcPropBridge() in both DETECTED and NOTIFIED_TC states.
+         802.1D-2004 has setTcPropTree() in DETECTED state, but
+         setTcPropBridge() in NOTIFIED_TC state.
+       */
+       setTcPropTree(PORT_ARGS); /* CORR: setTcPropBridge() */
+       ,
+       TR(UCT, ACTIVE);
+       );
+
+    ST(NOTIFIED_TCN,
+       newTcWhile(PORT_ARGS);
+       ,
+       TR(UCT, NOTIFIED_TC);
+       );
+
+    ST(ACTIVE,
+       ,
+       TR(((role != RootPort) && (role != DesignatedPort) ) || operEdge,
+          LEARNING);
+       TR(rcvdTcAck, ACKNOWLEDGED);
+       TR(tcProp && !operEdge, PROPAGATING);
+       TR(rcvdTc, NOTIFIED_TC);
+       TR(rcvdTcn, NOTIFIED_TCN);
+       );
+
+    );
+
+#undef STM_NAME
+
+
+/***** Running the state machines *****/
+
+static STM_FUNC_DECL((*bridge_stms[NUM_BRIDGE_STATE_MACHINES]),
+                     BRIDGE_ARGS_PROTO)
+     = {
+       STM_FUNC(PortRoleSelection),
+     };
+
+static STM_FUNC_DECL((*port_stms[NUM_PORT_STATE_MACHINES]), PORT_ARGS_PROTO)
+     = {
+       STM_FUNC(PortTimers),
+       STM_FUNC(PortReceive),
+       STM_FUNC(PortProtocolMigration),
+       STM_FUNC(BridgeDetection),
+       STM_FUNC(PortTransmit),
+       STM_FUNC(PortInformation),
+       STM_FUNC(PortRoleTransitions),
+       STM_FUNC(PortStateTransition),
+       STM_FUNC(TopologyChange),
+     };
+
+static void state_machines_run(BRIDGE_ARGS_PROTO)
+{
+  if (!BRIDGE->stp_on)
+    return;
+
+  int i;
+  bool transitioned;
+  do {
+    transitioned = FALSE;
+    for (i = 0; i < NUM_BRIDGE_STATE_MACHINES; i++)
+      STM_RUN(&BRIDGE->state[i], &transitioned, bridge_stms[i], BRIDGE_ARGS);
+
+    ForAllPortDo(
+                 for (i = 0; i < NUM_PORT_STATE_MACHINES; i++)
+                   STM_RUN(&FPORT->state[i], &transitioned,
+                           port_stms[i], FPORT_ARGS);
+                 );
+  } while (transitioned);
+}
+
+static void state_machines_begin(BRIDGE_ARGS_PROTO)
+{
+  if (!BRIDGE->stp_on)
+    return;
+
+  int i;
+  for (i = 0; i < NUM_BRIDGE_STATE_MACHINES; i++)
+    STM_BEGIN(&BRIDGE->state[i], bridge_stms[i], BRIDGE_ARGS);
+
+  ForAllPortDo(
+               for (i = 0; i < NUM_PORT_STATE_MACHINES; i++)
+                 STM_BEGIN(&FPORT->state[i], port_stms[i], FPORT_ARGS);
+               );
+
+  /* Initialized. Now run them. */
+  state_machines_run(BRIDGE_ARGS);
+}
+
+void STP_IN_set_bridge_enable(Bridge *BRIDGE, unsigned int enabled)
+{
+  uint on = enabled?1:0;
+  if (BRIDGE->stp_on == on)
+    return;
+
+  BRIDGE->stp_on = on;
+
+  if (on)
+    state_machines_begin(BRIDGE_ARGS);
+  else {
+
+  }
+}
+
+
+/*
+  PortPathCost and operPointToPointMAC can change through configuration
+  as well as link notifications (when configured to auto).
+  Common functions to handle these.
+*/
+
+// 17.14 - Table 17-3, also NOTE 3
+static uint compute_pcost(int speed)
+{
+  /* speed is in MB/s*/
+  if (speed > 0)
+    return speed < 20000000 ? 20000000/speed : 1;
+  else
+    return 200000000;
+}
+
+/* Returns TRUE if PostPathCost changed */
+static bool check_port_path_cost(PORT_ARGS_PROTO)
+{
+  int pcost;
+  if (AdminPortPathCost == 0) {
+    pcost = compute_pcost(PORT->speed);
+  }
+  else {
+    pcost = AdminPortPathCost;
+  }
+
+  if (PortPathCost != pcost) {
+    PortPathCost = pcost;
+    selected = FALSE; reselect = TRUE; // 17.13
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+/* Returns TRUE if operPointToPointMAC changed */
+static bool check_port_p2p(PORT_ARGS_PROTO)
+{
+  bool p2p;
+  if (adminPointToPointMAC == P2PAuto) {
+    p2p = !!PORT->duplex;
+  }
+  else {
+    p2p = (adminPointToPointMAC == P2PForceTrue);
+  }
+
+  if (operPointToPointMAC != p2p) {
+    operPointToPointMAC = p2p;
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+/******************** Event notifications ******************************/
+
+void STP_IN_one_second(BRIDGE_ARGS_PROTO)
+{
+  if (BRIDGE->tcWhile_count == 0)
+    BRIDGE->time_since_tc++;
+
+  ForAllPortDo(
+               FPORT->_tick = TRUE;
+               );
+  state_machines_run(BRIDGE_ARGS);
+}
+
+void STP_IN_set_port_enable(Port *PORT, unsigned int enabled,
+                            unsigned int speed, unsigned int duplex)
+{
+  Bridge *BRIDGE = PORT->bridge;
+
+  bool changed = FALSE;
+
+  if (enabled) {
+    if (!portEnabled) {
+      portEnabled = TRUE;
+      changed = TRUE;
+    }
+
+    if (PORT->speed != speed) {
+      PORT->speed = speed;
+      if (check_port_path_cost(PORT_ARGS))
+        changed = TRUE;
+    }
+
+    if (PORT->duplex != duplex) {
+      PORT->duplex = duplex;
+      if (check_port_p2p(PORT_ARGS))
+        changed = TRUE;
+    }
+  }
+  else {
+    if (portEnabled) {
+      portEnabled = FALSE;
+      changed = TRUE;
+    }
+  }
+  if (changed)
+    state_machines_run(BRIDGE_ARGS);
+}
+
+/* Rounding up. What should we really be doing with fractional times? */
+static uint get_bpdu_time(const uchar time[2])
+{
+  uint t = (time[0] << 8) + time[1];
+  return (t + 0xff) >> 8;
+}
+
+
+void STP_IN_rx_bpdu(Port *PORT, const void *base, unsigned int len)
+{
+  Bridge *BRIDGE = PORT->bridge;
+  const RawBPDU *p = base;
+
+  if (!BRIDGE->stp_on)
+    return; // No action - else we might fail the next test
+
+  if (rcvdBpdu) {
+    ABORT("Port hasn't processed old BPDU\n");
+    return;
+  }
+
+  // 9.3.4 - Validate
+  if (len < 4)
+    return;
+  if (p->protocol_id != STP_PROTOCOL_ID)
+    return;
+
+  DEBUG("Received BPDU of type %d\n", p->type);
+
+  if (// 9.3.4 a)
+      (p->type == BPDUTypeConfig &&
+       len >= 35 &&
+       CMP(p->message_age, <, p->max_age) &&
+       !(CMP(p->priority.designated_bridge_id.bridge_address, ==,
+             BridgeIdentifier.bridge_address) &&
+         (port_id_to_uint(p->priority.designated_port_id) & 0xfff) ==
+         (port_id_to_uint(portId) & 0xfff)
+         ))
+      ||
+      // 9.3.4 b)
+      (p->type == BPDUTypeTCN)
+      ||
+      // 9.3.4 c)
+      (p->type == BPDUTypeRST && len >= 36)
+      ) {
+    /* BPDU has been validated */
+  }
+  else {
+    DEBUG("BPDU validation failed\n");
+    return;
+  }
+
+  // 9.3.4 d)
+  bpduVersion = p->version <= 2 ? p->version : 2;
+
+  bpduType = p->type;
+
+  bpduTcFlag =
+    (p->type != BPDUTypeTCN) ? ((p->flags & BPDU_FLAG_TC) != 0) : 0;
+  bpduProposalFlag =
+    (p->type == BPDUTypeRST) ? ((p->flags & BPDU_FLAG_PROPOSAL) != 0) : 0;
+  bpduRole =
+    (p->type == BPDUTypeRST) ? BPDU_FLAG_ROLE_GET(p->flags) : 0;
+  bpduLearningFlag =
+    (p->type == BPDUTypeRST) ? ((p->flags & BPDU_FLAG_LEARNING) != 0) : 0;
+  bpduForwardingFlag =
+    (p->type == BPDUTypeRST) ? ((p->flags & BPDU_FLAG_FORWARDING) != 0) : 0;
+  bpduAgreementFlag =
+    (p->type == BPDUTypeRST) ? ((p->flags & BPDU_FLAG_AGREEMENT) != 0) : 0;
+  bpduTcAckFlag =
+    (p->type != BPDUTypeTCN) ? ((p->flags & BPDU_FLAG_TC_ACK) != 0) : 0;
+
+  if (p->type == BPDUTypeTCN) {
+    ZERO(bpduPriority);
+    ZERO(bpduTimes);
+
+  }
+  else {
+    bpduPriority = p->priority;
+
+    bpduTimes.message_age = get_bpdu_time(p->message_age);
+    bpduTimes.max_age = get_bpdu_time(p->max_age);
+    bpduTimes.forward_delay = get_bpdu_time(p->forward_delay);
+    bpduTimes.hello_time = get_bpdu_time(p->hello_time);
+  }
+
+  rcvdBpdu = TRUE;
+
+  state_machines_run(BRIDGE_ARGS);
+
+}
+
+/************************** Configuration *************************/
+
+// 17.14
+static int check_times(BRIDGE_ARGS_PROTO, int max_age, int fwd_delay)
+{
+  if (2*(fwd_delay - 1) < max_age) {
+    ERROR("Configured BridgeTimes doesn't meet "
+          "2 * (Bridge Foward Delay - 1) >= Bridge Max Age\n");
+    return -1;
+  }
+
+  // This condition never fails, with hello_time == 2 && max_age >= 6.
+  // So no need to check.
+  //if (max_age < 2*(BridgeHelloTime + 1)) {
+  //  ERROR("Doesn't meet Bridge Max Age >= 2 * (Bridge Hello Time + 1) ");
+  //  return -1;
+  //}
+
+  return 0;
+}
+
+// 14.8.1.2
+int STP_IN_set_bridge_config(Bridge *BRIDGE, const STP_BridgeConfig *cfg)
+{
+  int r = 0;
+  bool changed = FALSE, init = FALSE;
+  /* First, validation */
+
+  if (cfg->set_bridge_protocol_version) {
+    if (cfg->bridge_protocol_version != 0 && cfg->bridge_protocol_version != 2) {
+      ERROR("Protocol version must be 0 or 2\n");
+      r = -1;
+    }
+  }
+
+  /* No validation of bridge_address. It can be any 6 bytes */
+
+  if (cfg->set_bridge_priority) {
+    // 17.14 - Table 17-2
+    if (cfg->bridge_priority & ~0xf000) {
+      ERROR("Bridge Priority restricted to 0-61440 in steps of 4096\n");
+      r = -1;
+    }
+  }
+
+  Times new_times = BridgeTimes;
+  bool set_times = FALSE;
+
+  if (cfg->set_bridge_hello_time) {
+    // 17.14 - Table 17-1
+    if (cfg->bridge_hello_time != BridgeHelloTime) {
+      ERROR("Hello Time cannot be changed from 2s in RSTP\n");
+      r = -1;
+    }
+    new_times.hello_time = cfg->bridge_hello_time;
+    set_times = TRUE;
+  }
+
+  if (cfg->set_bridge_max_age) {
+    // 17.14 - Table 17-1
+    if (cfg->bridge_max_age < 6 || cfg->bridge_max_age > 40) {
+      ERROR("Bridge Max Age must be between 6 and 40\n");
+      r = -1;
+    }
+    new_times.max_age = cfg->bridge_max_age;
+    set_times = TRUE;
+  }
+
+  if (cfg->set_bridge_forward_delay) {
+    // 17.14 - Table 17-1
+    if (cfg->bridge_forward_delay < 4 || cfg->bridge_forward_delay > 30) {
+      ERROR("Bridge Forward Delay must be between 4 and 30\n");
+      r = -1;
+    }
+    new_times.forward_delay = cfg->bridge_forward_delay;
+    set_times = TRUE;
+  }
+
+  if (check_times(BRIDGE_ARGS, new_times.max_age, new_times.forward_delay))
+    r = -1;
+
+  if (cfg->set_bridge_tx_hold_count) {
+    // 17.14 - Table 17-1
+    if (cfg->bridge_tx_hold_count < 1 || cfg->bridge_tx_hold_count > 10) {
+      ERROR("Transmit Hold Count must be between 1 and 10\n");
+      r = -1;
+    }
+  }
+
+  /* If not valid, return error */
+  if (r)
+    return r;
+
+  /* Set things */
+  if (cfg->set_bridge_protocol_version &&
+      ForceProtocolVersion != cfg->bridge_protocol_version) {
+
+    ForceProtocolVersion = cfg->bridge_protocol_version;
+
+    changed = TRUE;
+    // 17.13 a)
+    init = TRUE;
+  }
+
+  if (cfg->set_bridge_address &&
+      CMP(BridgeIdentifier.bridge_address, !=, cfg->bridge_address)) {
+
+    CPY(BridgeIdentifier.bridge_address, cfg->bridge_address);
+
+    changed = TRUE;
+    init = TRUE;
+  }
+
+  if (cfg->set_bridge_priority &&
+      ((BridgeIdentifierPriority[0] & 0xf0) << 8) != cfg->bridge_priority) {
+
+    BridgeIdentifierPriority[0] &= ~0xf0;
+    BridgeIdentifierPriority[0] |= cfg->bridge_priority >> 8;
+
+    changed = TRUE;
+    /* 802.1Q-2005, 12.8.1.3.4 c) (Management, setting bridge protocol params)
+       says this should be done for any management change, so we do this below.
+
+    // 17.13 b)
+    ForAllPortDo(
+                 FPORT->_selected = FALSE;
+                 FPORT->_reselect = TRUE;
+                 );
+    */
+  }
+
+  if (set_times &&
+      CMP(new_times, !=, BridgeTimes)) {
+
+    BridgeTimes = new_times;
+
+    changed = TRUE;
+  }
+
+  if (cfg->set_bridge_tx_hold_count &&
+      TxHoldCount != cfg->bridge_tx_hold_count) {
+
+    TxHoldCount = cfg->bridge_tx_hold_count;
+
+    changed = TRUE;
+    // 17.13 introduction
+    ForAllPortDo(
+                 FPORT->_txCount = 0;
+                 );
+  }
+
+  if (changed && BRIDGE->stp_on) {
+    // Do this for any change.
+    // Seems like 802.1Q-2005, 12.8.1.3.4 c) wants this.
+    /*
+       Otherwise we fail UNH rstp.op_D test 3.2 since when administratively
+       setting BridgeForwardDelay, etc, the values don't propagate from
+       rootTimes to designatedTimes immediately without this change.
+
+     */
+    ForAllPortDo(
+                 FPORT->_selected = FALSE;
+                 FPORT->_reselect = TRUE;
+                 );
+
+    if (init)
+      state_machines_begin(BRIDGE_ARGS);
+    else
+      state_machines_run(BRIDGE_ARGS);
+  }
+
+  return 0;
+}
+
+// 14.8.2.3
+int STP_IN_set_port_config(Port *PORT, const STP_PortConfig *cfg)
+{
+  Bridge *BRIDGE = PORT->bridge;
+
+  int r = 0;
+  bool changed = FALSE;
+  /* First, validation */
+
+  if (cfg->set_port_priority) {
+    // 17.14 - Table 17-2
+    if (cfg->port_priority & ~0xf0) {
+      ERROR("Port Priority restricted to 0-240 in steps of 16\n");
+      r = -1;
+    }
+  }
+
+  if (cfg->set_port_pathcost) {
+    // 17.14 - Note 3 and Table 17-3
+    if (cfg->port_pathcost > 200000000) {
+      ERROR("Port path cost restricted to 1-200,000,000 or 0 for auto\n");
+      r = -1;
+    }
+  }
+
+  if (cfg->set_port_admin_edge) {
+    if (cfg->port_admin_edge != 0 && cfg->port_admin_edge != 1) {
+      ERROR("Admin Edge must be 0 or 1\n");
+      r = -1;
+    }
+  }
+
+  if (cfg->set_port_auto_edge) {
+    if (cfg->port_auto_edge != 0 && cfg->port_auto_edge != 1) {
+      ERROR("Auto Edge must be 0 or 1\n");
+      r = -1;
+    }
+  }
+
+  if (cfg->set_port_admin_p2p) {
+    if (cfg->port_admin_p2p < 0 || cfg->port_admin_p2p > 2) {
+      ERROR("Admin P2P must be "
+            "0 (force false), 1 (force true), or 2 (auto)\n");
+      r = -1;
+    }
+  }
+
+  /* If not valid, return error */
+  if (r)
+    return r;
+
+  /* Set things */
+  if (cfg->set_port_priority &&
+      (portId.port_id[0] & 0xf0) != cfg->port_priority) {
+
+    portId.port_id[0] &= ~0xf0;
+    portId.port_id[0] |= cfg->port_priority;
+
+    changed = TRUE;
+    // 17.13 c)
+    selected = FALSE; reselect = TRUE;
+  }
+
+  if (cfg->set_port_pathcost &&
+      AdminPortPathCost != cfg->port_pathcost) {
+
+    AdminPortPathCost = cfg->port_pathcost;
+
+    if (check_port_path_cost(PORT_ARGS)) {
+      /* PortPathCost was changed */
+      changed = TRUE;
+      // 17.13 d) - Done by check_port_path_cost()
+      // selected = FALSE; reselect = TRUE;
+    }
+  }
+
+  if (cfg->set_port_admin_edge &&
+      AdminEdge != cfg->port_admin_edge) {
+
+    AdminEdge = cfg->port_admin_edge;
+
+    changed = TRUE;
+  }
+
+  if (cfg->set_port_auto_edge &&
+      AutoEdge != cfg->port_auto_edge) {
+
+    AutoEdge = cfg->port_auto_edge;
+
+    changed = TRUE;
+  }
+
+  if (cfg->set_port_admin_p2p &&
+      adminPointToPointMAC != cfg->port_admin_p2p) {
+
+    adminPointToPointMAC = cfg->port_admin_p2p;
+
+    if (check_port_p2p(PORT_ARGS)) {
+      /* operPointToPointMAC was changed */
+      changed = TRUE;
+    }
+  }
+
+  if (changed)
+    state_machines_run(BRIDGE_ARGS);
+
+  return 0;
+}
+
+/* To make this #define work */
+typedef STP_BridgeConfig  STP_bridgeConfig;
+typedef STP_PortConfig    STP_portConfig;
+
+#define SET_CFG(_type, _arg, _field, _value) \
+({                                                           \
+  STP_ ## _type ## Config cfg;                               \
+  ZERO(cfg);                                                 \
+  cfg._type ## _ ## _field = _value;                         \
+  cfg. set_ ## _type ## _ ## _field = 1;                     \
+  STP_IN_set_ ## _type ## _config(_arg, &cfg);               \
+})
+
+/* Single field Bridge config functions */
+
+int STP_IN_set_protocol_version(Bridge *BRIDGE, unsigned int version)
+{
+  return SET_CFG(bridge, BRIDGE, protocol_version, version);
+}
+
+int STP_IN_set_bridge_address(Bridge *BRIDGE, const STP_MacAddress *addr)
+{
+  return SET_CFG(bridge, BRIDGE, address, *addr);
+}
+
+int STP_IN_set_bridge_priority(Bridge *BRIDGE, unsigned int priority)
+{
+  return SET_CFG(bridge, BRIDGE, priority, priority);
+}
+
+int STP_IN_set_bridge_hello_time(Bridge *BRIDGE, unsigned int hello_time)
+{
+  return SET_CFG(bridge, BRIDGE, hello_time, hello_time);
+}
+
+int STP_IN_set_bridge_max_age(Bridge *BRIDGE, unsigned int max_age)
+{
+  return SET_CFG(bridge, BRIDGE, max_age, max_age);
+}
+
+
+int STP_IN_set_bridge_forward_delay(Bridge *BRIDGE, unsigned int fwd_delay)
+{
+  return SET_CFG(bridge, BRIDGE, forward_delay, fwd_delay);
+}
+
+
+int STP_IN_set_tx_hold_count(Bridge *BRIDGE, unsigned int count)
+{
+  return SET_CFG(bridge, BRIDGE, tx_hold_count, count);
+}
+
+
+/* Single field Port config functions */
+
+int STP_IN_set_port_priority(Port *PORT, unsigned int priority)
+{
+  return SET_CFG(port, PORT, priority, priority);
+}
+
+int STP_IN_set_port_pathcost(Port *PORT, unsigned int pcost)
+{
+  return SET_CFG(port, PORT, pathcost, pcost);
+}
+
+/* edge is 0 or 1 */
+int STP_IN_set_port_admin_edge(Port *PORT, unsigned int edge)
+{
+  return SET_CFG(port, PORT, admin_edge, edge);
+}
+
+int STP_IN_set_port_auto_edge(Port *PORT, unsigned int edge)
+{
+  return SET_CFG(port, PORT, auto_edge, edge);
+}
+
+int STP_IN_set_port_admin_p2p(Port *PORT, unsigned int p2p)
+{
+  return SET_CFG(port, PORT, admin_p2p, p2p);
+}
+
+/* Force migration check */
+// 14.8.2.4
+int STP_IN_port_mcheck(Port *PORT)
+{
+  Bridge *BRIDGE = PORT->bridge;
+
+  if (!BRIDGE->stp_on) {
+    ERROR("Bridge is down\n");
+    return -1;
+  }
+
+  if (rstpVersion) {
+    mcheck = TRUE;
+
+    state_machines_run(BRIDGE_ARGS);
+  }
+
+  return 0;
+}
+
+
+/********************* Status ************************/
+
+// 14.8.1.1
+void STP_IN_get_bridge_status(Bridge *BRIDGE, STP_BridgeStatus *status)
+{
+  CPY(status->bridge_id, BridgeIdentifier);
+
+  status->time_since_topology_change = BRIDGE->time_since_tc;
+  status->topology_change_count = BRIDGE->tc_count;
+  status->topology_change = BRIDGE->tcWhile_count?1:0;
+
+  CPY(status->designated_root, rootPriority.root_bridge_id);
+
+  status->root_path_cost = path_cost_to_uint(rootPriority.root_path_cost);
+
+  CPY(status->designated_bridge, rootPriority.designated_bridge_id);
+
+  status->root_port =
+    ((rootPortId.port_id[0] & 0x0f) << 8) + rootPortId.port_id[1];
+
+  status->max_age = rootTimes.max_age;
+  status->hello_time = rootTimes.hello_time;
+  status->forward_delay = rootTimes.forward_delay;
+
+  status->bridge_max_age = BridgeTimes.max_age;
+  status->bridge_hello_time = BridgeTimes.hello_time;
+  status->bridge_forward_delay = BridgeTimes.forward_delay;
+
+  status->tx_hold_count = TxHoldCount;
+
+  status->protocol_version = ForceProtocolVersion;
+
+  status->enabled = BRIDGE->stp_on;
+}
+
+// 14.8.2.1
+void STP_IN_get_port_status(Port *PORT, STP_PortStatus *status)
+{
+  status->uptime = -1; // XXX: Don't know
+
+  status->state = PORT->port_state_flags;
+  if (status->state & STP_PORT_STATE_FLAG_FORWARDING)
+    status->state &= ~STP_PORT_STATE_FLAG_LEARNING;
+
+  status->id = port_id_to_uint(portId);
+
+  // admin_path_cost is not in 14.18.2.1 but it is the one piece of config
+  // information missing there, so it is useful to have it.
+  status->admin_path_cost = AdminPortPathCost;
+  status->path_cost = PortPathCost;
+
+  CPY(status->designated_root, designatedPriority.root_bridge_id);
+  status->designated_cost =
+    path_cost_to_uint(designatedPriority.root_path_cost);
+  CPY(status->designated_bridge, designatedPriority.designated_bridge_id);
+  status->designated_port =
+    port_id_to_uint(designatedPriority.designated_port_id);
+
+  status->tc_ack = tcAck;
+  status->admin_edge_port = AdminEdge;
+  status->oper_edge_port = operEdge;
+  status->auto_edge_port = AutoEdge;
+
+  status->enabled = portEnabled;
+
+  status->admin_p2p = adminPointToPointMAC;
+  status->oper_p2p = operPointToPointMAC;
+}
+
+
+/**************** Creating and deleting ********************/
+
+/* user_ref is a pointer that the STP part uses to call the system part
+   Bridge is created as disabled. You need to enable it.
+ */
+Bridge *STP_IN_bridge_create(void *user_ref)
+{
+  Bridge *b = STP_OUT_mem_zalloc(sizeof(Bridge));
+
+  if (!b) {
+    ERROR("Couldn't allocate memory for bridge\n");
+    return NULL;
+  }
+
+  b->user_ref = user_ref;
+
+  Bridge *BRIDGE = b;
+
+  /* Default configuration values */
+  BridgeIdentifierPriority[0] = 0x80; // Default bridge priority = 32768
+  MigrateTime = 3;
+  BridgeHelloTime = 2;
+  BridgeMaxAge = 20;
+  BridgeForwardDelay = 15;
+  TxHoldCount = 6;
+  ForceProtocolVersion = 2;
+
+  return BRIDGE;
+}
+
+/* All ports will be deleted, don't reference them */
+void STP_IN_bridge_delete(Bridge *BRIDGE)
+{
+  /* Disable ports - for what it is worth */
+  ForAllPortDo(
+               FPORT->_portEnabled = FALSE;
+               );
+
+  state_machines_run(BRIDGE_ARGS);
+
+  /* Delete all ports */
+  while (BRIDGE->port_list)
+    STP_IN_port_delete(BRIDGE->port_list);
+
+  STP_OUT_mem_free(BRIDGE);
+}
+
+/* user_ref is a pointer that the STP part uses to call the system part
+   Port is created as disabled. You need to enable it.
+*/
+Port *STP_IN_port_create(Bridge *BRIDGE,
+                         unsigned int port_number, void *user_ref)
+{
+  if (port_number == 0 || (port_number & ~0x0fff)) {
+    ERROR("Port id should be in the range 1 - 1023\n");
+    return NULL;
+  }
+
+  Port *p = STP_OUT_mem_zalloc(sizeof(Port));
+
+  if (!p) {
+    ERROR("Couldn't allocate memory for port\n");
+    return NULL;
+  }
+
+  p->user_ref = user_ref;
+  p->bridge = BRIDGE;
+
+  p->port_next = BRIDGE->port_list;
+  BRIDGE->port_list = p;
+
+  Port *PORT = p;
+
+  portId.port_id[0] = 0x80 | (port_number >> 8); // Default port priority = 128
+  portId.port_id[1] = port_number & 0xff;
+
+  // Don't need zero assignments since we zalloc'ed
+
+  //AdminPathCost = 0;
+
+  adminPointToPointMAC = P2PAuto;
+
+  if (BRIDGE->stp_on) {
+    /* Try and initialize some things */
+    int i;
+    for (i = 0; i < NUM_PORT_STATE_MACHINES; i++)
+      STM_BEGIN(&PORT->state[i], port_stms[i], PORT_ARGS);
+
+    // selected = FALSE
+    reselect = TRUE;
+  }
+
+  return PORT;
+}
+
+void STP_IN_port_delete(Port *PORT)
+{
+  Bridge *BRIDGE = PORT->bridge;
+
+  /* Disable */
+  if (portEnabled) {
+    portEnabled = FALSE;
+    state_machines_run(BRIDGE_ARGS);
+  }
+
+  /* Find position in list */
+  Port **prev = &BRIDGE->port_list;
+  while (*prev != PORT && *prev != NULL)
+    prev = &(*prev)->port_next;
+
+  if (*prev == NULL)
+    ABORT("port not in its bridge's list\n");
+
+  /* Remove from list */
+  *prev = PORT->port_next;
+
+  STP_OUT_mem_free(PORT);
+}
+
diff --git a/rstp.h b/rstp.h
new file mode 100644 (file)
index 0000000..94c208a
--- /dev/null
+++ b/rstp.h
@@ -0,0 +1,226 @@
+#ifndef _RSTP_H
+#define _RSTP_H
+
+/*********************************************************************
+  This is an implementation of the Rapid Spanning Tree Protocol
+  based on the 802.1D-2004 standard.
+*********************************************************************/
+
+struct STP_Bridge_;
+typedef struct STP_Bridge_ STP_Bridge;
+struct STP_Port_;
+typedef struct STP_Port_ STP_Port;
+
+typedef struct STP_macaddr
+{
+  unsigned char addr[6];
+} STP_MacAddress;
+
+
+/****** Creating and deleting ******/
+
+/* user_ref is a pointer that the STP part uses to call the system part
+   Bridge is created as disabled. You need to enable it.
+ */
+STP_Bridge *STP_IN_bridge_create(void *user_ref);
+
+/* All ports will be deleted, don't reference them */
+void STP_IN_bridge_delete(STP_Bridge *);
+
+/* user_ref is a pointer that the STP part uses to call the system part
+   Port is created as disabled. You need to enable it.
+*/
+STP_Port *STP_IN_port_create(STP_Bridge *,
+                             unsigned int port_number, void *user_ref);
+
+void STP_IN_port_delete(STP_Port *);
+
+/****** enable/disable ******/
+
+/* When disabled, we can set config without any immediate effect.
+   State machine is reinitialized when we enable.
+*/
+void STP_IN_set_bridge_enable(STP_Bridge *, unsigned int enabled);
+
+/****** Configuration ******/
+
+typedef struct STP_BridgeConfig_
+{
+  int           set_bridge_protocol_version;
+  unsigned int   bridge_protocol_version;
+
+  /* Setting bridge address dynamically is not part of the standard
+     It is convenient for us to interface with Linux bridges where this
+     may happen. We reinit the state machine when this is changed.
+   */
+  unsigned int   set_bridge_address;
+  STP_MacAddress bridge_address;
+
+  unsigned int   set_bridge_priority;
+  unsigned int   bridge_priority;
+
+  unsigned int   set_bridge_hello_time;
+  unsigned int   bridge_hello_time;
+
+  unsigned int   set_bridge_max_age;
+  unsigned int   bridge_max_age;
+
+  unsigned int   set_bridge_forward_delay;
+  unsigned int   bridge_forward_delay;
+
+  unsigned int   set_bridge_tx_hold_count;
+  unsigned int   bridge_tx_hold_count;
+} STP_BridgeConfig;
+
+int STP_IN_set_bridge_config(STP_Bridge *, const STP_BridgeConfig *);
+
+/* Alternate forms to set single fields */
+
+/* 0 for STP, 2 for RSTP */
+int STP_IN_set_protocol_version(STP_Bridge *, unsigned int version);
+
+/* This reinitializes the bridge if bridge is enabled */
+int STP_IN_set_bridge_address(STP_Bridge *, const STP_MacAddress *);
+
+int STP_IN_set_bridge_priority(STP_Bridge *, unsigned int priority);
+
+/* Just for compatibility interfacing. Always fails */
+int STP_IN_set_bridge_hello_time(STP_Bridge *, unsigned int hello_time);
+
+int STP_IN_set_bridge_max_age(STP_Bridge *, unsigned int max_age);
+
+int STP_IN_set_bridge_forward_delay(STP_Bridge *, unsigned int fwd_delay);
+
+int STP_IN_set_tx_hold_count(STP_Bridge *, unsigned int count);
+
+
+typedef struct STP_PortConfig_
+{
+  unsigned int   set_port_priority;
+  unsigned int   port_priority;
+
+  unsigned int   set_port_pathcost;
+  unsigned int   port_pathcost;
+
+  unsigned int   set_port_admin_edge;
+  unsigned int   port_admin_edge;
+
+  unsigned int   set_port_auto_edge;
+  unsigned int   port_auto_edge;
+
+  unsigned int   set_port_admin_p2p;
+  unsigned int   port_admin_p2p;
+#define STP_ADMIN_P2P_FORCE_FALSE 0
+#define STP_ADMIN_P2P_FORCE_TRUE 1
+#define STP_ADMIN_P2P_AUTO 2
+} STP_PortConfig;
+
+int STP_IN_set_port_config(STP_Port *, const STP_PortConfig *);
+
+/* Alternate forms to set single fields */
+
+int STP_IN_set_port_priority(STP_Port *, unsigned int priority);
+
+int STP_IN_set_port_pathcost(STP_Port *, unsigned int pcost);
+
+/* edge is 0 or 1 - boolean */
+int STP_IN_set_port_admin_edge(STP_Port *, unsigned int edge);
+
+int STP_IN_set_port_auto_edge(STP_Port *, unsigned int edge);
+
+/* See STP_PortConfig struct for allowed values of p2p */
+int STP_IN_set_port_admin_p2p(STP_Port *, unsigned int p2p);
+
+
+/* Force migration check */
+int STP_IN_port_mcheck(STP_Port *);
+
+/****** Notifications ******/
+
+/* speed and duplex matter only if enabled */
+void STP_IN_set_port_enable(STP_Port *, unsigned int enabled,
+                            unsigned int speed, unsigned int duplex);
+
+/* No MAC or LLC header included */
+void STP_IN_rx_bpdu(STP_Port *, const void *base, unsigned int len);
+
+/* Should be called every second, triggers timer operation. */
+void STP_IN_one_second(STP_Bridge *);
+
+/****** Status ******/
+
+typedef struct STP_BridgeStatus_
+{
+  unsigned char bridge_id[8];
+  unsigned int time_since_topology_change;
+  unsigned int topology_change_count;
+  unsigned int topology_change; /* boolean */
+  unsigned char designated_root[8];
+  unsigned int root_path_cost;
+  /* Not in standard, but part of rootPriority */
+  unsigned char designated_bridge[8];
+  unsigned int root_port;
+  unsigned int max_age;
+  unsigned int hello_time;
+  unsigned int forward_delay;
+  unsigned int bridge_max_age;
+  unsigned int bridge_hello_time;
+  unsigned int bridge_forward_delay;
+  unsigned int tx_hold_count;
+  unsigned int protocol_version;
+  unsigned int enabled; /* Whether we are running state machines */
+} STP_BridgeStatus;
+
+void STP_IN_get_bridge_status(STP_Bridge *, STP_BridgeStatus *);
+
+typedef struct STP_PortStatus_
+{
+  unsigned int uptime;
+  unsigned int state;
+#define STP_PORT_STATE_DISCARDING    0
+#define STP_PORT_STATE_LEARNING      1
+#define STP_PORT_STATE_FORWARDING    2
+  unsigned int id;
+  unsigned int admin_path_cost;
+  unsigned int path_cost;
+  unsigned char designated_root[8];
+  unsigned int designated_cost;
+  unsigned char designated_bridge[8];
+  unsigned int designated_port;
+  unsigned int tc_ack; /* booleans */
+  unsigned int admin_edge_port;
+  unsigned int oper_edge_port;
+  unsigned int auto_edge_port;
+  unsigned int enabled; /* Is this MAC Enabled or MAC operational?
+                           It is false both if ifconfig <port> down
+                           and if the link status is down.
+                        */
+  unsigned int admin_p2p;
+  unsigned int oper_p2p;
+} STP_PortStatus;
+
+void STP_IN_get_port_status(STP_Port *, STP_PortStatus *);
+
+/****** Procedures to be provided by user ******/
+
+/* No MAC or LLC header included */
+void STP_OUT_tx_bpdu(void *port_user_ref, void *base, unsigned int len);
+
+#define STP_PORT_STATE_FLAG_LEARNING 1
+#define STP_PORT_STATE_FLAG_FORWARDING 2
+void STP_OUT_port_set_state(void *user_ref, unsigned int flags);
+
+void STP_OUT_port_fdb_flush(void *user_ref);
+
+#define STP_LOG_LEVEL_ERROR 1
+#define STP_LOG_LEVEL_DEBUG 10
+
+void STP_OUT_logmsg(void *br_user_ref, void *port_user_ref,
+                    int level, char *fmt, ...);
+
+
+void *STP_OUT_mem_zalloc(unsigned int size);
+
+void STP_OUT_mem_free(void *);
+
+#endif