submodule infix-if-bridge {
  yang-version 1.1;
  belongs-to infix-interfaces {
    prefix infix-if;
  }
  import ietf-yang-types {
    prefix yang;
  }
  import iana-if-type {
    prefix ianaift;
  }
  import ietf-routing-types {
    prefix rt-types;
  }
  import ietf-interfaces {
    prefix if;
  }
  import ietf-ip {
    prefix ip;
  }
  import ieee802-dot1q-types {
    prefix dot1q-types;
  }
  import  infix-if-type {
    prefix infix-ift;
  }

  organization "KernelKit";
  contact      "kernelkit@googlegroups.com";
  description  "Linux bridge extension for ietf-interfaces.";

  revision 2025-10-28 {
    description "Prevent IP addresses on bridge ports.";
    reference "internal";
  }

  revision 2025-10-23 {
    description "Add WiFi interfaces to be able to be added to a bridge.";
    reference "internal";
  }

  revision 2025-01-08 {
    description "Add Spanning Tree Protocol (STP) support.

                 Move STP port state from the old `stp-state` to the
                 new `stp/cist/state`.

                 Drop the `default-priority` bridge port option, which
                 has never been supported but was accidentally
                 included in the model.";
    reference "internal";
  }

  revision 2024-11-28 {
    description "Drop must() expressions for IP addres on VLAN bridges.
                 If a bridge is untagged member of a VLAN it should be
                 possible to set an IP address on the bridge.

                 Allow bridge port settings that also apply to the
                 bridge itself (e.g., PVID) to be configured.";
    reference "internal";
  }
  revision 2024-11-15 {
    description "Fix pyang linter warnings, drop unused imports.";
    reference "internal";
  }
  revision 2024-08-26 {
    description "Improve must expressions for multicast.

                 Add a must expression forcing a port in a dot1q multicast-filter
                 to also belong to the VLAN. Also add must expression (for both
                 8021d and 8021q) to force snooping to be anabled when enable
                 multicast-filters";
    reference "internal";
  }
  revision 2024-03-28 {
    description "Rename mdb -> multicast-filters.

		 Add support for L2 multicast groups (MAC multicast).  They
                 are currently always in state 'permanent'.

	         Change vlan, multicast and mulitcast-filters containers to
                 presence containers.  This means multicast snooping is now
                 disabled by default when creating a bridge or adding a VLAN.

	         Global bridge multicast configuration must now be disabled
                 when adding VLANs to a bridge.  I.e., global multicast is
                 not inherited to VLANs in any way.";
    reference "internal";
  }
  revision 2024-03-06 {
    description "Drop default value for bridge-port PVID.  Should be possible
                 to drop untagged frames.

                 Add must() expression for VLAN memberships.  Listed ports must
                 belong to this bridge and cannot be untagged and tagged at the
                 same time.

                 Also, add must() expression to ensure VLAN filtering bridges
                 do not have any IP address set since the bridge is always only
                 a tagged member of VLANs.  Use a VLAN interfaces on top of the
                 bridge for IP addressing.";
    reference "internal";
  }
  revision 2024-02-19 {
    description "Add STP state to bridge port.";
    reference "internal";
  }
  revision 2023-12-02 {
    description "Extend bridge-port must expression to ensure a
                 bridge cannot be a bridge-port to itself.";
    reference "internal";
  }
  revision 2023-11-08 {
    description "Dropped support for configuring bridge pvid.
                 Bridge ports need explicit VLAN assignment.";
    reference "internal";
  }
  revision 2023-08-21 {
    description "Minor, lint ordering and add missing description.";
    reference "internal";
  }
  revision 2023-05-31 {
    description "Initial revision.";
    reference "internal";
  }

  /*
   * Features
   */

  feature vlan-filtering {
    description "Indicates if this bridge supports VLAN filtering.";
  }

  /*
   * Typedefs
   */

  typedef mac-multicast-address {
    description "Valid multicast address.";
    type string {
      pattern "[0-9A-Fa-f][13579BbDdFf](:[0-9A-Fa-f]{2}){5}";
    }
  }

  typedef ieee-reserved-groups {
    type union {
      type uint8 {
        range "0..15";
      }
      type enumeration {
        enum stp {
          value 0;
          description "Spanning Tree (STP/RSPT/MSTP).";
        }
        enum lacp {
          value 2;
          description "802.3 Slow Protocols, e.g., LACP.";
        }
        enum dot1x {
          value 3;
          description "802.1X Port-Based Network Access Control.";
        }
        enum lldp {
          value 14;
          description "802.1AB Link Layer Discovery Protocol (LLDP).";
        }
      }
    }
    description
      "This is a user-friendly enumeration of the different reserved IEEE
       reserved link-local multicast groups, in 01:80:C2:00:00:0X.";
  }

  typedef stp-protocol {
    description "Spanning Tree Protocol version.";
    type enumeration {
      enum stp {
        value 0;
        description "Spanning Tree Protocol.";
      }
      enum rstp {
        value 2;
        description "Rapid Spanning Tree Protocol.";
      }
      // enum mstp {
      //   value 3;
      //   description "Multiple Spanning Tree Protocol";
      // }
    }

    reference "IEEE 802.1Q-2018 13.7.2";
  }

  typedef stp-state {
    description "User-friendly enumeration of different bridge port operational states.";
    type enumeration {
      enum disabled {
        value 0;
        description "Port is in STP DISABLED state";
      }
      enum listening {
        value 1;
        description "Port is in STP LISTENING state";
      }
      enum learning {
        value 2;
        description "Port is in STP LEARNING state";
      }
      enum forwarding {
        value 3;
        description "Port is in STP FORWARDING state.";
      }
      enum blocking {
        value 4;
        description "Port is in STP BLOCKING state.";
      }
    }
  }

  typedef stp-port-role {
    description "Spanning Tree (STP) port role.";
    type enumeration {
      enum root {
        description "Root port.";
      }
      enum designated {
        description "Designated port.";
      }
      enum alternate {
        description "Alternate port.";
      }
      enum backup {
        description "Backup port.";
      }
      enum master {
        description "Master port.";
      }
      enum disabled {
        description "Disabled port.";
      }
    }
  }

  typedef stp-priority {
    type uint8 {
      range "0..15";
    }

    default 8;
  }

  typedef stp-system-id {
    type uint16 {
      range "0..4095";
    }
  }

  typedef stp-port-id {
    type uint16 {
      range "0..4095";
    }
  }

  typedef stp-bool-or-auto {
    type union {
      type enumeration {
        enum auto;
      }
      type boolean;
    }
    default auto;
  }

  typedef stp-path-cost {
    type union {
      type enumeration {
        enum auto;
      }
      type uint32 {
        range "0..200000000";
      }
    }
    default auto;
  }

  typedef querier-mode {
    description "Type of IGMP/MLD querier, recommend using 'auto'.";
    type enumeration {
      enum off {
        value 0;
        description "Never initiate IGMP/MLD queries.";
      }
      enum proxy {
        value 1;
        description "Send proxy queries if no better querier IP exists.";
      }
      enum auto {
        value 2;
        description "Participate in querier elections using the interface's address.";
      }
    }
  }

  typedef mrouter-port {
    description "Controls forwarding of known multicast on a port, recommend using 'auto'.";
    type enumeration {
      enum off {
        value 0;
        description "Very rarely needed, disables auto-detect, never forwards know multicast.";
      }
      enum auto {
        value 1;
        description "Auto detects any PIM- or MRDISC-capable multicast routers.";
      }
      enum permanent {
        value 2;
        description "Always forward known multicast, regardless of detected multicast routers.";
      }
    }
  }

  typedef mdb-state {
    description "Origin of mdb entry for a given port.";
    type enumeration {
      enum temporary {
        value 0;
        description "Learned from IGMP/MLD snooping.";
      }
      enum permanent {
        value 1;
        description "Static entry, from configuration.";
      }
    }
  }


  /*
   * Shared settings
   */

  grouping multicast {
    container multicast {
      presence multicast;
      description "Control multicast filtering and querier options in bridge.";

      leaf snooping {
        description "Control multicast snooping in bridge.

                   Enabled, IGMP and MLD snooping is used to automatically
                   handle multicast filtering.  By default all multicast is
                   forwarded, when an IGMP or MLD membership is received
                   only those groups are filtered.

                   Disabled, all multicast is treated as broadcast.  Not
                   even static MDB filters can be used in this mode.";
        type boolean;
        default true;
      }

      leaf querier {
        description "IGMP/MLD querier role.  Leave default as-is, or read on.

                   The querier role is usually the multicast router(s) on the
                   LAN.  In networks without a multicast router a switch can
                   take on this responsibility.

                   For a fully working multicast setup the LAN needs a querier.
                   If multiple queriers exist, a simple election is made -- the
                   device with the numerically lowest IP address is the winner,
                   execpt for source address 0.0.0.0 (IPv4), which is reserved
                   for 'proxy' queries and must never win an election.  Proxy
                   queries are like a stand-in for the real thing and mostly
                   work fine in all setups.

                   Some embedded and industrial devices do not send multicast
                   membership reports unless they receive a query, even worse,
                   some do not understand or misbehave with proxy queriers.
                   Hence the default 'auto' for this option.";
        type querier-mode;
        default auto;
      }

      leaf query-interval {
        description "Query interval when sending multicast queries.";
        type uint16 {
          range "1..1024";
        }
        default 125;
      }
    }
  }

  grouping multicast-filters {
    container multicast-filters {
      presence multicast;
      description "Bridge multicast database.";

      list multicast-filter {
        description "Multicast filter entry.";
        key "group";

        leaf group {
          description "IP or MAC multicast group address.";
          type union {
            type rt-types:ip-multicast-group-address;
            type mac-multicast-address;
          }
        }

        list ports {
          description "Port members of group.";
          must "state = 'permanent'" {
            error-message "State must be permanent for static multicast filters";
          }
          key "port";
          leaf port {
            must "not(deref(.)/../if:type = 'infix-ift:bridge') or re-match(../../group, '[0-9A-Fa-f][13579BbDdFf](:[0-9A-Fa-f]{2}){5}')" {
              error-message "Only MAC multicast is possible to add to host.";
            }
            description "Port with static or dynamic membership of group.";
            type if:interface-ref;
          }

          leaf state {
            description "State of membership, permanent or temporary.";
            type mdb-state;
            default permanent;
          }
        }
      }
    }
  }

  grouping stp-bridge-id {
    leaf priority {
      description "Priority.";
      type stp-priority;
    }

    leaf system-id {
      description "System ID.";
      type stp-system-id;
    }

    leaf address {
      description "MAC address.";
      type yang:mac-address;
    }
  }

  grouping stp-port-id {
    leaf priority {
      description "Priority.";
      type stp-priority;
    }

    leaf port-id {
      description "Port ID.";
      type stp-system-id;
    }
  }

  grouping stp-tree-state {
    container bridge-id {
      config false;
      uses stp-bridge-id;
    }

    container root-id {
      config false;
      uses stp-bridge-id;
    }

    leaf root-port {
      config false;
      description "Root port.";
      type if:interface-ref;
    }

    container topology-change {
      config false;

      leaf count {
        description "Cumulative number of topology changes.";
        type yang:counter32;
      }

      leaf in-progress {
        description "Topology change is currently in progress.";
        type boolean;
      }

      leaf port {
        description "Source port of the most recent topology change.";
        type if:interface-ref;
      }

      leaf time {
        description "The time of the most recent topology change.";
        type yang:date-and-time;
      }
    }
  }

  grouping stp-tree {
    leaf priority {
      description "Priority.";
      type stp-priority;
      reference "IEEE 802.1Q-2018 13.18";
    }

    uses stp-tree-state;
  }

  grouping stp-tree-port-state {
    container port-id {
      config false;
      uses stp-port-id;
    }

    leaf state {
      config false;
      description "Operational state.";
      type stp-state;
    }

    leaf role {
      config false;
      description "Role.";
      type stp-port-role;
    }

    leaf disputed {
      config false;
      description "Disputed.";
      type boolean;
    }

    container designated {
      config false;
      description "Designated bridge.";
      container bridge-id {
        uses stp-bridge-id;
      }
      container port-id {
        uses stp-port-id;
      }
    }
  }

  grouping stp-tree-port {
    leaf internal-path-cost {
      description "Internal path cost.";
      type stp-path-cost;
      reference "IEEE 802.1Q-2018 13.27.33";
    }

    leaf priority {
      description "Priority.";
      type stp-priority;
      reference "IEEE 802.1Q-2018 13.18";
    }

    uses stp-tree-port-state;
  }

  /*
   * Data Nodes
   */

  augment "/if:interfaces/if:interface" {
    when "derived-from-or-self(if:type,'ianaift:bridge')" {
      description "Only shown for if:type bridge";
    }

    description "Augment generic interfaces with a basic 802.1Q bridge.";

    container bridge {
      description "IEEE 802.1Q style bridge.";

      leaf-list ieee-group-forward {
        type ieee-reserved-groups;
        description
          "List of IEEE link-local protocols to forward, e.g., STP, LLDP";
      }
      choice  type {
        case ieee8021d {
          uses multicast;
          uses multicast-filters;
        }
        case ieee8021q {
          container vlans {
            presence vlans;
            if-feature "vlan-filtering";
            description "A VLAN filtering bridge has at least one VLAN.";

            leaf proto {
              type dot1q-types:dot1q-tag-type;
              default dot1q-types:c-vlan;
              description "Standard (1Q/c-vlan) or provider (1ad/s-vlan) bridge.";
            }

            list vlan {
              key "vid";
              description "List of VLANs associated with the Bridge.";

              leaf vid {
                type dot1q-types:vlanid;
                description "The VLAN identifier to which this entry applies.";
              }

              uses multicast;
              uses multicast-filters;

              leaf-list untagged {
                type if:interface-ref;
                description "The set of ports in the untagged set for VLAN.";
                must "current() = ../../../../if:name
                  or (/if:interfaces/if:interface[if:name = current()]/bridge-port/bridge = ../../../../if:name
                      and not(../tagged[contains(., current())]))" {
                  error-message "Port is not a member of this bridge or already set as tagged member in the same VLAN.";
                }
              }

              leaf-list tagged {
                description "The set of ports in the tagged set for VLAN.";
                type if:interface-ref;
                must "current() = ../../../../if:name
                  or (/if:interfaces/if:interface[if:name = current()]/bridge-port/bridge = ../../../../if:name
                      and not(../untagged[contains(., current())]))" {
                  error-message "Port is not a member of bridge or already untagged in the same VLAN.";
                }
              }
            }
          }
        }
      }

      container stp {
        presence stp;
        description "Spanning Tree Protocol";

        leaf force-protocol {
          description "Protocol version.";
          type stp-protocol;
          default rstp;
          reference "IEEE 802.1Q-2018 13.7.2";
        }

        leaf hello-time {
          description "Hello time.";
          type uint8 {
            range "1..10";
          }
          default 2;
          reference "IEEE 802.1Q-2018 13.25";
        }

        leaf forward-delay {
          description "Forward delay.";
          type uint8 {
            range "4..30";
          }
          default 15;
          reference "IEEE 802.1Q-2018 13.25";
        }

        leaf max-age {
          description "Max age.";
          type uint8 {
            range "6..40";
          }
          default 20;
          reference "IEEE 802.1Q-2018 13.25";
        }

        leaf transmit-hold-count {
          description "Transmit hold count.";
          type uint8 {
            range "1..10";
          }
          default 6;
          reference "IEEE 802.1Q-2018 13.25";
        }

        leaf max-hops {
          description "Max hops.";
          type uint8 {
            range "6..40";
          }
          default 20;
          reference "IEEE 802.1Q-2018 13.25";
        }

        container cist {
          description "Common and Internal Spanning Tree.";
          uses stp-tree;
        }

        // list mst {
        //   description "Multiple Spanning Tree Instance.";
        //   key "mstid";

        //   leaf mstid {
        //     description "The MSTI identifier to which this entry applies.";
        //     type dot1q-types:mstid-type;
        //   }

        //   uses stp-tree;

        //   leaf-list vlan {
        //     type leafref {
        //       path "/if:interfaces/interface/bridge/vlans/vlan/vid";
        //     }
        //   }
        // }
      }
    }
  }
  deviation "/if:interfaces/if:interface/infix-if:bridge/type/ieee8021d/multicast-filters/multicast-filter/ports/port" {
    deviate add {
      must "current() = ../../../../../if:name
        or /if:interfaces/if:interface[if:name = current()]/infix-if:bridge-port/bridge = ../../../../../if:name" {
        error-message "Port is not member of bridge";
      }
    }
  }
  deviation "/if:interfaces/if:interface/infix-if:bridge/type/ieee8021d/multicast-filters/multicast-filter" {
    deviate add {
      must "../../multicast/snooping = 'true'" {
        error-message "Multicast snooping is required when configure multicast-filters ";
      }
    }
  }

  deviation "/if:interfaces/if:interface/infix-if:bridge/type/ieee8021q/vlans/vlan/multicast-filters/multicast-filter/ports/port" {
    deviate add {
      must "current() = ../../../../../../../if:name
        or /if:interfaces/if:interface[if:name = current()]/infix-if:bridge-port/bridge = ../../../../../../../if:name" {
        error-message "Port is not member of bridge";
      }
    }
  }
  deviation "/if:interfaces/if:interface/infix-if:bridge/type/ieee8021q/vlans/vlan/multicast-filters/multicast-filter/ports/port" {
    deviate add {
      must "(../../../../untagged[contains(., current())]) or (../../../../tagged[contains(., current())])" {
        error-message "Port is not member of of VLAN";
      }
    }
  }
  deviation "/if:interfaces/if:interface/infix-if:bridge/type/ieee8021q/vlans/vlan/multicast-filters/multicast-filter" {
    deviate add {
      must "../../multicast/snooping = 'true'" {
        error-message "Multicast snooping is required when configure multicast-filters ";
      }
    }
  }

  grouping bridge-port-common {
    leaf pvid {
      if-feature "vlan-filtering";
      type dot1q-types:vlanid;
      description "The primary VID assigned to this bridge port.";
    }
  }

  grouping bridge-port-lower {
    leaf bridge {
      type if:interface-ref;
      must "deref(.)/../bridge and not(. = ../../if:name)" {
        error-message "Must refer to a bridge interface (and not itself).";
      }
      mandatory true;
      description "Bridge interface to which this interface is attached.";
    }

    container flood {
      description "Control flooding of unknown BUM traffic.";

      leaf broadcast {
        description "Flood unknown broadcast traffic on this port.";
        type boolean;
        default true;
      }

      leaf unicast {
        description "Flood unknown unicast traffic on this port.";
        type boolean;
        default true;
      }

      leaf multicast {
        description "Flood unknown multicast traffic on this port.

                         By default this option is enabled to allow MAC multicast
                         to coexist unregulated with filtering of IP multicast.

                         Flooding of IP multicast is done as long as the groups
                         remain 'unknown', i.e., while there are no MDB entries
                         set manually or automatically by IGMP/MLD.";
        type boolean;
        default true;
      }
    }

    container multicast {
      leaf fast-leave {
        description "Assume this port is attached to an end-device.

                         When enabled the bridge immediately cuts multicast
                         groups when receiving a membership leave report.
                         When disabled, group subscriptions linger until the
                         group specific queries time out.";
        type boolean;
      }

      leaf router {
        description "Forward all known multicast on this port.

                         Enable this for ports connected to a multicast router
                         that is not PIM or multicast router discovery (mrdisc)
                         capable.

                         This setting is also useful for legacy equipment that
                         does not support IGMP/MLD.  However, it is recommended
                         to instead set up static MDB entries for such ports.";
        type mrouter-port;
        default auto;
      }
    }

    container stp {
      description "Spanning Tree Protocol";
      reference "IEEE 802.1Q-2018 13.27";

      leaf edge {
        description "Edge.";
        type stp-bool-or-auto;
      }

      container cist {
        description "Common and Internal Spanning Tree.";
        uses stp-tree-port;

        leaf external-path-cost {
          description "External path cost.";
          type stp-path-cost;
          reference "IEEE 802.1Q-2018 13.27.25";
        }
      }

      // list mst {
      //   description "Multiple Spanning Tree Instance.";
      //   key "mstid";

      //   leaf mstid {
      //     description "The MSTI identifier to which this entry applies.";
      //     type dot1q-types:mstid-type;
      //   }

      //   uses stp-tree-port;
      // }

      container statistics {
        config false;

        leaf in-bpdus {
          type yang:counter64;
          units frames;
          description "Number of received BPDUs.";
        }
        leaf in-bpdus-filtered {
          type yang:counter64;
          units frames;
          description "Number of received, but filtered, BPDUs.";
        }
        leaf in-tcns {
          type yang:counter64;
          units frames;
          description "Number of received TCNs.";
        }

        leaf out-bpdus {
          type yang:counter64;
          units frames;
          description "Number of transmitted BPDUs.";
        }
        leaf out-tcns {
          type yang:counter64;
          units frames;
          description "Number of transmitted TCNs.";
        }

        leaf to-blocking {
          type yang:counter64;
          units transitions;
          description "Number of transitions into the blocking state.";
        }
        leaf to-forwarding {
          type yang:counter64;
          units transitions;
          description "Number of transitions into the forwarding state.";
        }
      }
    }
  }

  augment "/if:interfaces/if:interface/infix-if:port" {
    when "derived-from-or-self(if:type,'ianaift:bridge') or "+
         "derived-from-or-self(if:type,'ianaift:ethernetCsmacd') or "+
         "derived-from-or-self(if:type,'ianaift:ieee8023adLag') or "+
         "derived-from-or-self(if:type,'ianaift:l2vlan') or "+
         "derived-from-or-self(if:type,'ianaift:ilan') or "+
         "derived-from-or-self(if:type,'infix-ift:gretap') or "+
         "derived-from-or-self(if:type,'infix-ift:wifi') or "+
         "derived-from-or-self(if:type,'infix-ift:vxlan')" {
      description "Applies when a Bridge interface exists.";
    }

    description "Augments the interface model with the Bridge Port";

    case bridge-port {
      description "Extension of the IETF Interfaces model (RFC7223).";

      container bridge-port {
        must "not(../ip:ipv4/ip:address or ../ip:ipv6/ip:address)" {
          error-message "Bridge ports cannot have IP addresses configured.";
        }
        must "not(derived-from-or-self(../if:type, 'infix-ift:wifi')) or ../infix-if:wifi/infix-if:access-point" {
          error-message "WiFi interfaces can only be bridge ports when configured as Access Points.";
        }
        description "Bridge association and port specific settings.";
        uses bridge-port-common;
        uses bridge-port-lower {
          when "not(derived-from-or-self(../if:type, 'ianaift:bridge'))";
        }
      }
    }
  }
}
