VOL-1224: The ONU Adapter must support deletion of a Service Flow using the Open OMCI MEs
VOL-1389: Support Transparent C-tag handling in OpenOLT and BRCM OpenOMCI adapters

Change-Id: I7a3c89d68180fd94a744db6082ecdf695635c635
diff --git a/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu.py b/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu.py
index ad89dc8..8df0b09 100644
--- a/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu.py
+++ b/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu.py
@@ -56,7 +56,8 @@
             id=name,
             vendor_ids=['OPEN', 'ALCL', 'BRCM', 'TWSH', 'ALPH', 'ISKT', 'SFAA', 'BBSM'],
             adapter=name,
-            accepts_bulk_flow_update=True
+            accepts_bulk_flow_update=True,
+            accepts_add_remove_flow_updates=True
         )
     ]
 
@@ -208,7 +209,20 @@
         return handler.update_flow_table(device, flows.items)
 
     def update_flows_incrementally(self, device, flow_changes, group_changes):
-        raise NotImplementedError()
+        log.info('incremental-flow-update', device_id=device.id,
+                 flows=flow_changes, groups=group_changes)
+        # For now, there is no support for group changes
+        assert len(group_changes.to_add.items) == 0
+        assert len(group_changes.to_remove.items) == 0
+
+        handler = self.devices_handlers[device.id]
+        # Remove flows
+        if len(flow_changes.to_remove.items) != 0:
+            handler.remove_onu_flows(device, flow_changes.to_remove.items)
+
+        # Add flows
+        if len(flow_changes.to_add.items) != 0:
+            handler.add_onu_flows(device, flow_changes.to_add.items)
 
     def send_proxied_message(self, proxy_address, msg):
         log.debug('send-proxied-message', proxy_address=proxy_address, msg=msg)
diff --git a/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py b/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
index f2aa656..0f759f3 100644
--- a/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
+++ b/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
@@ -602,31 +602,254 @@
                 if _type is not None:
                     self.log.warn('ignoring-flow-with-ethType', ethType=_type)
                 elif _set_vlan_vid is None or _set_vlan_vid == 0:
-                    self.log.warn('ignorning-flow-that-does-not-set-vlanid')
+                    self.log.warn('ignoring-flow-that-does-not-set-vlanid')
                 else:
                     self.log.warn('set-vlanid', uni_id=uni_port.port_number, set_vlan_vid=_set_vlan_vid)
-                    self._add_vlan_filter_task(device, uni_port, _set_vlan_vid)
+                    self._do_vlan_filter_task(device, flow.cookie, add_tag=True,
+                                              uni_port=uni_port, _set_vlan_vid=_set_vlan_vid)
 
             except Exception as e:
                 self.log.exception('failed-to-install-flow', e=e, flow=flow)
 
+    def add_onu_flows(self, device, flows):
+        self.log.debug('function-entry', device=device, flows=flows)
 
-    def _add_vlan_filter_task(self, device, uni_port, _set_vlan_vid):
-        assert uni_port is not None
+        #
+        # We need to proxy through the OLT to get to the ONU
+        # Configuration from here should be using OMCI
+        #
+        # self.log.info('bulk-flow-update', device_id=device.id, flows=flows)
+
+        # no point in pushing omci flows if the device isnt reachable
+        if device.connect_status != ConnectStatus.REACHABLE or \
+           device.admin_state != AdminState.ENABLED:
+            self.log.warn("device-disabled-or-offline-skipping-flow-update",
+                          admin=device.admin_state, connect=device.connect_status)
+            return
+
+        for flow in flows:
+            # if incoming flow contains cookie, then add to ONU
+            if flow.cookie:
+                _type = None
+                _port = None
+                _vlan_vid = None
+                _udp_dst = None
+                _udp_src = None
+                _ipv4_dst = None
+                _ipv4_src = None
+                _metadata = None
+                _output = None
+                _push_tpid = None
+                _field = None
+                _set_vlan_vid = None
+                self.log.debug("add-flow", device_id=device.id, flow=flow)
+
+                def is_downstream(port):
+                    return port == self._pon_port_number
+
+                def is_upstream(port):
+                    return not is_downstream(port)
+
+                try:
+                    _in_port = fd.get_in_port(flow)
+                    assert _in_port is not None
+
+                    _out_port = fd.get_out_port(flow)  # may be None
+
+                    if is_downstream(_in_port):
+                        self.log.debug('downstream-flow', in_port=_in_port, out_port=_out_port)
+                        uni_port = self.uni_port(_out_port)
+                    elif is_upstream(_in_port):
+                        self.log.debug('upstream-flow', in_port=_in_port, out_port=_out_port)
+                        uni_port = self.uni_port(_in_port)
+                    else:
+                        raise Exception('port should be 1 or 2 by our convention')
+
+                    self.log.debug('flow-ports', in_port=_in_port, out_port=_out_port, uni_port=str(uni_port))
+
+                    for field in fd.get_ofb_fields(flow):
+                        if field.type == fd.ETH_TYPE:
+                            _type = field.eth_type
+                            self.log.debug('field-type-eth-type',
+                                           eth_type=_type)
+
+                        elif field.type == fd.IP_PROTO:
+                            _proto = field.ip_proto
+                            self.log.debug('field-type-ip-proto',
+                                           ip_proto=_proto)
+
+                        elif field.type == fd.IN_PORT:
+                            _port = field.port
+                            self.log.debug('field-type-in-port',
+                                           in_port=_port)
+
+                        elif field.type == fd.VLAN_VID:
+                            _vlan_vid = field.vlan_vid & 0xfff
+                            self.log.debug('field-type-vlan-vid',
+                                           vlan=_vlan_vid)
+
+                        elif field.type == fd.VLAN_PCP:
+                            _vlan_pcp = field.vlan_pcp
+                            self.log.debug('field-type-vlan-pcp',
+                                           pcp=_vlan_pcp)
+
+                        elif field.type == fd.UDP_DST:
+                            _udp_dst = field.udp_dst
+                            self.log.debug('field-type-udp-dst',
+                                           udp_dst=_udp_dst)
+
+                        elif field.type == fd.UDP_SRC:
+                            _udp_src = field.udp_src
+                            self.log.debug('field-type-udp-src',
+                                           udp_src=_udp_src)
+
+                        elif field.type == fd.IPV4_DST:
+                            _ipv4_dst = field.ipv4_dst
+                            self.log.debug('field-type-ipv4-dst',
+                                           ipv4_dst=_ipv4_dst)
+
+                        elif field.type == fd.IPV4_SRC:
+                            _ipv4_src = field.ipv4_src
+                            self.log.debug('field-type-ipv4-src',
+                                           ipv4_dst=_ipv4_src)
+
+                        elif field.type == fd.METADATA:
+                            _metadata = field.table_metadata
+                            self.log.debug('field-type-metadata',
+                                           metadata=_metadata)
+
+                        else:
+                            raise NotImplementedError('field.type={}'.format(
+                                field.type))
+
+                    for action in fd.get_actions(flow):
+
+                        if action.type == fd.OUTPUT:
+                            _output = action.output.port
+                            self.log.debug('action-type-output',
+                                           output=_output, in_port=_in_port)
+
+                        elif action.type == fd.POP_VLAN:
+                            self.log.debug('action-type-pop-vlan',
+                                           in_port=_in_port)
+
+                        elif action.type == fd.PUSH_VLAN:
+                            _push_tpid = action.push.ethertype
+                            self.log.debug('action-type-push-vlan',
+                                           push_tpid=_push_tpid, in_port=_in_port)
+                            if action.push.ethertype != 0x8100:
+                                self.log.error('unhandled-tpid',
+                                               ethertype=action.push.ethertype)
+
+                        elif action.type == fd.SET_FIELD:
+                            _field = action.set_field.field.ofb_field
+                            assert (action.set_field.field.oxm_class ==
+                                    OFPXMC_OPENFLOW_BASIC)
+                            self.log.debug('action-type-set-field',
+                                           field=_field, in_port=_in_port)
+                            if _field.type == fd.VLAN_VID:
+                                _set_vlan_vid = _field.vlan_vid & 0xfff
+                                self.log.debug('set-field-type-vlan-vid',
+                                               vlan_vid=_set_vlan_vid)
+                            else:
+                                self.log.error('unsupported-action-set-field-type',
+                                               field_type=_field.type)
+                        else:
+                            self.log.error('unsupported-action-type',
+                                           action_type=action.type, in_port=_in_port)
+
+                    # TODO: We only set vlan omci flows.  Handle omci matching ethertypes at some point in another task
+                    if _type is not None:
+                        self.log.warn('ignoring-flow-with-ethType', ethType=_type)
+                    elif _set_vlan_vid is None or _set_vlan_vid == 0:
+                        self.log.warn('ignoring-flow-that-does-not-set-vlanid')
+                    else:
+                        self.log.warn('set-vlanid', uni_id=uni_port.port_number, set_vlan_vid=_set_vlan_vid)
+                        self._do_vlan_filter_task(device, flow.cookie, add_tag=True,
+                                                  uni_port=uni_port, _set_vlan_vid=_set_vlan_vid)
+
+                except Exception as e:
+                    self.log.exception('failed-to-install-flow', e=e, flow=flow)
+
+    def remove_onu_flows(self, device, flows):
+        self.log.debug('function-entry', device=device, flows=flows)
+
+        # no point in removing omci flows if the device isnt reachable
+        if device.connect_status != ConnectStatus.REACHABLE or \
+           device.admin_state != AdminState.ENABLED:
+            self.log.warn("device-disabled-or-offline-skipping-remove-flow",
+                          admin=device.admin_state, connect=device.connect_status)
+            return
+
+        for flow in flows:
+            # if incoming flow contains cookie, then remove from ONU
+            if flow.cookie:
+                self.log.debug("remove-flow", device_id=device.id, flow=flow)
+
+                def is_downstream(port):
+                    return port == self._pon_port_number
+
+                def is_upstream(port):
+                    return not is_downstream(port)
+
+                try:
+                    _in_port = fd.get_in_port(flow)
+                    assert _in_port is not None
+
+                    _out_port = fd.get_out_port(flow)  # may be None
+
+                    if is_downstream(_in_port):
+                        self.log.debug('downstream-flow', in_port=_in_port, out_port=_out_port)
+                        uni_port = self.uni_port(_out_port)
+                    elif is_upstream(_in_port):
+                        self.log.debug('upstream-flow', in_port=_in_port, out_port=_out_port)
+                        uni_port = self.uni_port(_in_port)
+                    else:
+                        raise Exception('port should be 1 or 2 by our convention')
+
+                    self.log.debug('flow-ports', in_port=_in_port, out_port=_out_port, uni_port=str(uni_port))
+
+                    # Deleting flow from ONU.
+                    self._do_vlan_filter_task(device, flow.cookie, add_tag=False, uni_port=uni_port)
+                except Exception as e:
+                    self.log.exception('failed-to-remove-flow', e=e)
+
+    def _do_vlan_filter_task(self, device, flow_cookie, add_tag=True, uni_port=None, _set_vlan_vid=None):
+        task_name = 'removing-vlan-tag'
+        if add_tag:
+            assert uni_port is not None
+            task_name = 'setting-vlan-tag'
 
         def success(_results):
-            self.log.info('vlan-tagging-success', uni_port=uni_port, vlan=_set_vlan_vid)
-            device.reason = 'omci-flows-pushed'
+            if add_tag:
+                self.log.info('vlan-tagging-success', _results=_results)
+                device.reason = 'omci-flows-pushed'
+                self.log.debug('Flow-addition-success', cookie=flow_cookie)
+            else:
+                self.log.info('vlan-untagging-success', _results=_results)
+                device.reason = 'omci-flows-deleted'
+                self.log.debug('Flow-removal-success', cookie=flow_cookie)
+
             self._vlan_filter_task = None
 
         def failure(_reason):
-            self.log.warn('vlan-tagging-failure', uni_port=uni_port, vlan=_set_vlan_vid)
-            device.reason = 'omci-flows-failed-retrying'
-            self._vlan_filter_task = reactor.callLater(_STARTUP_RETRY_WAIT,
-                                                       self._add_vlan_filter_task, device, uni_port, _set_vlan_vid)
+            if add_tag:
+                self.log.warn('vlan-tagging-failure', _reason=_reason)
+                device.reason = 'omci-flows-addition-failed-retrying'
+                self._vlan_filter_task = reactor.callLater(_STARTUP_RETRY_WAIT,
+                                                           self._do_vlan_filter_task, device, flow_cookie,
+                                                           add_tag=True, uni_port=uni_port,
+                                                           _set_vlan_vid=_set_vlan_vid)
+            else:
+                self.log.warn('vlan-untagging-failure', _reason=_reason)
+                device.reason = 'omci-flows-deletion-failed-retrying'
+                self._vlan_filter_task = reactor.callLater(_STARTUP_RETRY_WAIT,
+                                                           self._do_vlan_filter_task, device, flow_cookie,
+                                                           add_tag=False)
 
-        self.log.info('setting-vlan-tag')
-        self._vlan_filter_task = BrcmVlanFilterTask(self.omci_agent, self.device_id, uni_port, _set_vlan_vid)
+        self.log.info(task_name)
+        self._vlan_filter_task = BrcmVlanFilterTask(self.omci_agent, self.device_id, uni_port, _set_vlan_vid,
+                                                    add_tag=add_tag)
         self._deferred = self._onu_omci_device.task_runner.queue_task(self._vlan_filter_task)
         self._deferred.addCallbacks(success, failure)
 
diff --git a/voltha/adapters/brcm_openomci_onu/omci/brcm_vlan_filter_task.py b/voltha/adapters/brcm_openomci_onu/omci/brcm_vlan_filter_task.py
index 6c665c7..2ed8f33 100644
--- a/voltha/adapters/brcm_openomci_onu/omci/brcm_vlan_filter_task.py
+++ b/voltha/adapters/brcm_openomci_onu/omci/brcm_vlan_filter_task.py
@@ -18,9 +18,12 @@
 from twisted.internet.defer import inlineCallbacks, failure, returnValue
 from voltha.extensions.omci.omci_defs import ReasonCodes, EntityOperations
 from voltha.extensions.omci.omci_me import *
+from voltha.adapters.brcm_openomci_onu.uni_port import UniType
+from voltha.adapters.brcm_openomci_onu.pon_port import BRDCM_DEFAULT_VLAN, DEFAULT_TPID
 
 RC = ReasonCodes
 OP = EntityOperations
+RESERVED_VLAN = 4095
 
 
 class BrcmVlanFilterException(Exception):
@@ -34,13 +37,16 @@
     task_priority = 200
     name = "Broadcom VLAN Filter Task"
 
-    def __init__(self, omci_agent, device_id, uni_port, set_vlan_id, priority=task_priority):
+    def __init__(self, omci_agent, device_id, uni_port, set_vlan_id, add_tag=True,
+                 priority=task_priority):
         """
         Class initialization
 
         :param omci_agent: (OmciAdapterAgent) OMCI Adapter agent
         :param device_id: (str) ONU Device ID
+        :param uni_port: (UniPort) UNI port
         :param set_vlan_id: (int) VLAN to filter for and set
+        :param add_tag: (bool) Flag to identify VLAN Tagging or Untagging
         :param priority: (int) OpenOMCI Task priority (0..255) 255 is the highest
         """
 
@@ -57,6 +63,12 @@
         self._results = None
         self._local_deferred = None
         self._config = self._device.configuration
+        self._add_tag = add_tag
+
+        # Port numbers
+        self._input_tpid = DEFAULT_TPID
+        self._output_tpid = DEFAULT_TPID
+        self._cvid = BRDCM_DEFAULT_VLAN
 
     def cancel_deferred(self):
         super(BrcmVlanFilterTask, self).cancel_deferred()
@@ -73,117 +85,197 @@
         Start Vlan Tagging Task
         """
         super(BrcmVlanFilterTask, self).start()
-        self._local_deferred = reactor.callLater(0, self.perform_vlan_tagging)
+        self._local_deferred = reactor.callLater(0, self.perform_vlan_tagging, add_tag=self._add_tag)
 
     @inlineCallbacks
-    def perform_vlan_tagging(self):
+    def perform_vlan_tagging(self, add_tag=True):
         """
         Perform the vlan tagging
         """
-        self.log.info('setting-vlan-tagging')
+        if add_tag:
+            self.log.info('setting-vlan-tagging')
+        else:
+            self.log.info('removing-vlan-tagging')
 
         try:
             # TODO: parameterize these from the handler, or objects in the handler
             # TODO: make this a member of the onu gem port or the uni port
             _mac_bridge_service_profile_entity_id = 0x201
             _mac_bridge_port_ani_entity_id = 0x2102  # TODO: can we just use the entity id from the anis list?
+
+            vlan_tagging_entity_id = _mac_bridge_port_ani_entity_id + self._uni_port.mac_bridge_port_num
+            extended_vlan_tagging_entity_id = _mac_bridge_service_profile_entity_id + \
+                self._uni_port.mac_bridge_port_num
+
             # Delete bridge ani side vlan filter
-            msg = VlanTaggingFilterDataFrame(_mac_bridge_port_ani_entity_id + self._uni_port.mac_bridge_port_num)
-            frame = msg.delete()
-            self.log.debug('openomci-msg', omci_msg=msg)
-            self.strobe_watchdog()
-            results = yield self._device.omci_cc.send(frame)
-            self.check_status_and_state(results, 'flow-delete-vlan-tagging-filter-data')
+            yield self._send_msg(VlanTaggingFilterDataFrame(vlan_tagging_entity_id), 'delete',
+                                 'flow-delete-vlan-tagging-filter-data')
 
-            # Re-Create bridge ani side vlan filter
-            msg = VlanTaggingFilterDataFrame(
-                _mac_bridge_port_ani_entity_id + self._uni_port.mac_bridge_port_num,  # Entity ID
-                vlan_tcis=[self._set_vlan_id],  # VLAN IDs
-                forward_operation=0x10
-            )
-            frame = msg.create()
-            self.log.debug('openomci-msg', omci_msg=msg)
-            self.strobe_watchdog()
-            results = yield self._device.omci_cc.send(frame)
-            self.check_status_and_state(results, 'flow-create-vlan-tagging-filter-data')
+            forward_operation = 0x10  # VID investigation
+            # When the PUSH VLAN is RESERVED_VLAN (4095), let ONU be transparent
+            if self._set_vlan_id == RESERVED_VLAN:
+                forward_operation = 0x00  # no investigation, ONU transparent
 
-            # Re-Create bridge ani side vlan filter
-
-            # Update uni side extended vlan filter
-            # filter for untagged
-            # probably for eapol
-            # TODO: Create constants for the operation values.  See omci spec
-            attributes = dict(
-                received_frame_vlan_tagging_operation_table=
-                VlanTaggingOperation(
-                    filter_outer_priority=15,
-                    filter_outer_vid=4096,
-                    filter_outer_tpid_de=0,
-
-                    filter_inner_priority=15,
-                    filter_inner_vid=4096,
-                    filter_inner_tpid_de=0,
-                    filter_ether_type=0,
-
-                    treatment_tags_to_remove=0,
-                    treatment_outer_priority=15,
-                    treatment_outer_vid=0,
-                    treatment_outer_tpid_de=0,
-
-                    treatment_inner_priority=0,
-                    treatment_inner_vid=self._set_vlan_id,
-                    treatment_inner_tpid_de=4
+            if add_tag:
+                # Re-Create bridge ani side vlan filter
+                msg = VlanTaggingFilterDataFrame(
+                    vlan_tagging_entity_id,  # Entity ID
+                    vlan_tcis=[self._set_vlan_id],  # VLAN IDs
+                    forward_operation=forward_operation
                 )
-            )
-            msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
-                _mac_bridge_service_profile_entity_id + self._uni_port.mac_bridge_port_num,  # Bridge Entity ID
-                attributes=attributes  # See above
-            )
-            frame = msg.set()
-            self.log.debug('openomci-msg', omci_msg=msg)
-            self.strobe_watchdog()
-            results = yield self._device.omci_cc.send(frame)
-            self.check_status_and_state(results,
-                                        'flow-set-ext-vlan-tagging-op-config-data-untagged')
+                yield self._send_msg(msg, 'create', 'flow-create-vlan-tagging-filter-data')
+            else:
+                # Delete bridge ani side vlan filter
+                msg = VlanTaggingFilterDataFrame(
+                    vlan_tagging_entity_id  # Entity ID
+                )
+                yield self._send_msg(msg, 'delete', 'flow-delete-vlan-tagging-filter-data')
 
-            # Update uni side extended vlan filter
-            # filter for vlan 0
-            # TODO: Create constants for the operation values.  See omci spec
-            attributes = dict(
-                received_frame_vlan_tagging_operation_table=
-                VlanTaggingOperation(
+            # Delete uni side extended vlan filter
+            msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
+                extended_vlan_tagging_entity_id  # Bridge Entity ID
+            )
+            yield self._send_msg(msg, 'delete', 'flow-delete-ext-vlan-tagging-op-config-data')
+
+            # Create uni side extended vlan filter
+            if add_tag:
+                # When flow is removed and immediately re-added tech_profile specific task is not re-played, hence
+                # Extended VLAN Tagging Operation configuration which is part of tech_profile specific task is not
+                # getting created. To create it, we do Extended VLAN Tagging Operation configuration here.
+                # TODO: do this for all uni/ports...
+                # TODO: magic.  static variable for assoc_type
+
+                omci_cc = self._device.omci_cc
+                # default to PPTP
+                if self._uni_port.type is UniType.VEIP:
+                    association_type = 10
+                elif self._uni_port.type is UniType.PPTP:
+                    association_type = 2
+                else:
+                    association_type = 2
+
+                attributes = dict(
+                    association_type=association_type,  # Assoc Type, PPTP/VEIP Ethernet UNI
+                    associated_me_pointer=self._uni_port.entity_id,  # Assoc ME, PPTP/VEIP Entity Id
+
+                    # See VOL-1311 - Need to set table during create to avoid exception
+                    # trying to read back table during post-create-read-missing-attributes
+                    # But, because this is a R/W attribute. Some ONU may not accept the
+                    # value during create. It is repeated again in a set below.
+                    input_tpid=self._input_tpid,  # input TPID
+                    output_tpid=self._output_tpid,  # output TPID
+                )
+
+                msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
+                    extended_vlan_tagging_entity_id,  # Bridge Entity ID
+                    attributes=attributes
+                )
+                yield self._send_msg(msg, 'create', 'create-extended-vlan-tagging-operation-configuration-data')
+
+                attributes = dict(
+                    # Specifies the TPIDs in use and that operations in the downstream direction are
+                    # inverse to the operations in the upstream direction
+                    input_tpid=self._input_tpid,  # input TPID
+                    output_tpid=self._output_tpid,  # output TPID
+                    downstream_mode=0,  # inverse of upstream
+                )
+
+                msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
+                    extended_vlan_tagging_entity_id,  # Bridge Entity ID
+                    attributes=attributes
+                )
+                yield self._send_msg(msg, 'set', 'set-extended-vlan-tagging-operation-configuration-data')
+
+                # parameters: Entity Id ( 0x900), Filter Inner Vlan Id(0x1000-4096,do not filter on Inner vid,
+                #             Treatment Inner Vlan Id : 2
+
+                # Update uni side extended vlan filter
+                # filter for untagged
+                # probably for eapol
+                # TODO: lots of magic
+                # TODO: magic 0x1000 / 4096?
+                attributes = self._generate_attributes(
                     filter_outer_priority=15,  # This entry is not a double-tag rule
                     filter_outer_vid=4096,  # Do not filter on the outer VID value
                     filter_outer_tpid_de=0,  # Do not filter on the outer TPID field
 
-                    filter_inner_priority=8,  # Filter on inner vlan
-                    filter_inner_vid=0x0,  # Look for vlan 0
-                    filter_inner_tpid_de=0,  # Do not filter on inner TPID field
-                    filter_ether_type=0,  # Do not filter on EtherType
+                    filter_inner_priority=15, filter_inner_vid=4096, filter_inner_tpid_de=0, filter_ether_type=0,
+                    treatment_tags_to_remove=0, treatment_outer_priority=15, treatment_outer_vid=0,
+                    treatment_outer_tpid_de=0, treatment_inner_priority=0, treatment_inner_vid=self._cvid,
+                    treatment_inner_tpid_de=4)
 
-                    treatment_tags_to_remove=1,
-                    treatment_outer_priority=15,
-                    treatment_outer_vid=0,
-                    treatment_outer_tpid_de=0,
-
-                    treatment_inner_priority=8,  # Add an inner tag and insert this value as the priority
-                    treatment_inner_vid=self._set_vlan_id,  # use this value as the VID in the inner VLAN tag
-                    treatment_inner_tpid_de=4,  # set TPID
+                msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
+                    extended_vlan_tagging_entity_id,  # Bridge Entity ID
+                    attributes=attributes
                 )
-            )
-            msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
-                _mac_bridge_service_profile_entity_id + self._uni_port.mac_bridge_port_num,  # Bridge Entity ID
-                attributes=attributes  # See above
-            )
-            frame = msg.set()
-            self.log.debug('openomci-msg', omci_msg=msg)
-            self.strobe_watchdog()
-            results = yield self._device.omci_cc.send(frame)
-            self.check_status_and_state(results,
-                                        'flow-set-ext-vlan-tagging-op-config-data-zero-tagged')
+                yield self._send_msg(msg, 'set', 'set-extended-vlan-tagging-operation-configuration-data-table')
+
+                if self._set_vlan_id == RESERVED_VLAN:
+                    # Transparently send any single tagged packet.
+                    # Any other specific rules will take priority over this
+                    attributes = self._generate_attributes(
+                        filter_outer_priority=15, filter_outer_vid=4096, filter_outer_tpid_de=0,
+                        filter_inner_priority=14, filter_inner_vid=4096, filter_inner_tpid_de=0, filter_ether_type=0,
+                        treatment_tags_to_remove=0, treatment_outer_priority=15, treatment_outer_vid=0,
+                        treatment_outer_tpid_de=0, treatment_inner_priority=15, treatment_inner_vid=0,
+                        treatment_inner_tpid_de=4)
+
+                    msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
+                        extended_vlan_tagging_entity_id,  # Bridge Entity ID
+                        attributes=attributes  # See above
+                    )
+                    yield self._send_msg(msg, 'set',
+                                         'flow-set-ext-vlan-tagging-op-config-data-single-tag-fwd-transparent')
+                else:
+                    # Update uni side extended vlan filter
+                    # filter for untagged
+                    # probably for eapol
+                    # TODO: Create constants for the operation values.  See omci spec
+                    attributes = self._generate_attributes(
+                        filter_outer_priority=15, filter_outer_vid=4096, filter_outer_tpid_de=0,
+                        filter_inner_priority=15, filter_inner_vid=4096, filter_inner_tpid_de=0, filter_ether_type=0,
+                        treatment_tags_to_remove=0, treatment_outer_priority=15, treatment_outer_vid=0,
+                        treatment_outer_tpid_de=0, treatment_inner_priority=0, treatment_inner_vid=self._set_vlan_id,
+                        treatment_inner_tpid_de=4)
+
+                    msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
+                        extended_vlan_tagging_entity_id,  # Bridge Entity ID
+                        attributes=attributes  # See above
+                    )
+                    yield self._send_msg(msg, 'set', 'flow-set-ext-vlan-tagging-op-config-data-untagged')
+
+                    # Update uni side extended vlan filter
+                    # filter for vlan 0
+                    # TODO: Create constants for the operation values.  See omci spec
+                    attributes = self._generate_attributes(
+                        filter_outer_priority=15,  # This entry is not a double-tag rule
+                        filter_outer_vid=4096,  # Do not filter on the outer VID value
+                        filter_outer_tpid_de=0,  # Do not filter on the outer TPID field
+
+                        filter_inner_priority=8,  # Filter on inner vlan
+                        filter_inner_vid=0x0,  # Look for vlan 0
+                        filter_inner_tpid_de=0,  # Do not filter on inner TPID field
+                        filter_ether_type=0,  # Do not filter on EtherType
+
+                        treatment_tags_to_remove=1, treatment_outer_priority=15, treatment_outer_vid=0,
+                        treatment_outer_tpid_de=0,
+
+                        treatment_inner_priority=8,  # Add an inner tag and insert this value as the priority
+                        treatment_inner_vid=self._set_vlan_id,  # use this value as the VID in the inner VLAN tag
+                        treatment_inner_tpid_de=4)  # set TPID
+
+                    msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
+                        extended_vlan_tagging_entity_id,  # Bridge Entity ID
+                        attributes=attributes  # See above
+                    )
+                    yield self._send_msg(msg, 'set', 'flow-set-ext-vlan-tagging-op-config-data-zero-tagged')
+            else:
+                msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
+                    extended_vlan_tagging_entity_id  # Bridge Entity ID
+                )
+                yield self._send_msg(msg, 'delete', 'flow-delete-ext-vlan-tagging-op-config-data')
 
             self.deferred.callback(self)
-
         except Exception as e:
             self.log.exception('setting-vlan-tagging', e=e)
             self.deferred.errback(failure.Failure(e))
@@ -214,3 +306,53 @@
 
         elif status == RC.InstanceExists:
             return False
+
+    @inlineCallbacks
+    def _send_msg(self, msg, operation, vlan_tagging_operation_msg):
+        """
+        Send frame to ONU.
+
+        :param msg: (VlanTaggingFilterDataFrame/ExtendedVlanTaggingOperationConfigurationDataFrame) message used
+        to generate OMCI frame
+        :param operation: (str) type of CUD(Create/Update/Delete) operation
+        :param vlan_tagging_operation_msg: (str) what operation was being performed
+        """
+        if operation == 'create':
+            frame = msg.create()
+        elif operation == 'set':
+            frame = msg.set()
+        else:
+            frame = msg.delete()
+        self.log.debug('openomci-msg', omci_msg=msg)
+        self.strobe_watchdog()
+        results = yield self._device.omci_cc.send(frame)
+        self.check_status_and_state(results, vlan_tagging_operation_msg)
+
+    def _generate_attributes(self, **kwargs):
+        """
+        Generate ExtendedVlanTaggingOperation attributes
+
+        :return: (dict) ExtendedVlanTaggingOperation attributes dictinary
+        """
+        return dict(
+            received_frame_vlan_tagging_operation_table=
+            VlanTaggingOperation(
+                filter_outer_priority=kwargs['filter_outer_priority'],
+                filter_outer_vid=kwargs['filter_outer_vid'],
+                filter_outer_tpid_de=kwargs['filter_outer_tpid_de'],
+
+                filter_inner_priority=kwargs['filter_inner_priority'],
+                filter_inner_vid=kwargs['filter_inner_vid'],
+                filter_inner_tpid_de=kwargs['filter_inner_tpid_de'],
+                filter_ether_type=kwargs['filter_ether_type'],
+
+                treatment_tags_to_remove=kwargs['treatment_tags_to_remove'],
+                treatment_outer_priority=kwargs['treatment_outer_priority'],
+                treatment_outer_vid=kwargs['treatment_outer_vid'],
+                treatment_outer_tpid_de=kwargs['treatment_outer_tpid_de'],
+
+                treatment_inner_priority=kwargs['treatment_inner_priority'],
+                treatment_inner_vid=kwargs['treatment_inner_vid'],
+                treatment_inner_tpid_de=kwargs['treatment_inner_tpid_de'],
+            )
+        )
diff --git a/voltha/adapters/openolt/openolt_flow_mgr.py b/voltha/adapters/openolt/openolt_flow_mgr.py
index acfcbfd..99d387c 100644
--- a/voltha/adapters/openolt/openolt_flow_mgr.py
+++ b/voltha/adapters/openolt/openolt_flow_mgr.py
@@ -40,6 +40,7 @@
 
 # FIXME - see also BRDCM_DEFAULT_VLAN in broadcom_onu.py
 DEFAULT_MGMT_VLAN = 4091
+RESERVED_VLAN = 4095
 
 # Openolt Flow
 UPSTREAM = "upstream"
@@ -803,10 +804,12 @@
             classifier.eth_type = classifier_info[ETH_TYPE]
         if IP_PROTO in classifier_info:
             classifier.ip_proto = classifier_info[IP_PROTO]
-        if VLAN_VID in classifier_info:
-            classifier.o_vid = classifier_info[VLAN_VID]
-        if METADATA in classifier_info:
-            classifier.i_vid = classifier_info[METADATA]
+        if VLAN_VID in classifier_info and \
+                classifier_info[VLAN_VID] != RESERVED_VLAN:
+             classifier.o_vid = classifier_info[VLAN_VID]
+        if METADATA in classifier_info and \
+                classifier_info[METADATA] != RESERVED_VLAN:
+             classifier.i_vid = classifier_info[METADATA]
         if VLAN_PCP in classifier_info:
             classifier.o_pbits = classifier_info[VLAN_PCP]
         if UDP_SRC in classifier_info:
diff --git a/voltha/extensions/omci/omci_me.py b/voltha/extensions/omci/omci_me.py
index a8a2d05..990cf8f 100644
--- a/voltha/extensions/omci/omci_me.py
+++ b/voltha/extensions/omci/omci_me.py
@@ -75,7 +75,7 @@
     of its point of attachment, the specified tagging operations refer to the
      upstream direction.
     """
-    def __init__(self, entity_id, attributes):
+    def __init__(self, entity_id, attributes=None):
         """
         :param entity_id: (int) This attribute uniquely identifies each instance of
                                 this managed entity. Its value is the same as that