@Override
  public void filter(FilteringObjective filter) {
    Instructions.OutputInstruction output;

    if (filter.meta() != null && !filter.meta().immediate().isEmpty()) {
      output =
          (Instructions.OutputInstruction)
              filter
                  .meta()
                  .immediate()
                  .stream()
                  .filter(t -> t.type().equals(Instruction.Type.OUTPUT))
                  .limit(1)
                  .findFirst()
                  .get();

      if (output == null || !output.port().equals(PortNumber.CONTROLLER)) {
        log.error("OLT can only filter packet to controller");
        fail(filter, ObjectiveError.UNSUPPORTED);
        return;
      }
    } else {
      fail(filter, ObjectiveError.BADPARAMS);
      return;
    }

    if (filter.key().type() != Criterion.Type.IN_PORT) {
      fail(filter, ObjectiveError.BADPARAMS);
      return;
    }

    EthTypeCriterion ethType =
        (EthTypeCriterion) filterForCriterion(filter.conditions(), Criterion.Type.ETH_TYPE);

    if (ethType == null) {
      fail(filter, ObjectiveError.BADPARAMS);
      return;
    }

    if (ethType.ethType().equals(EthType.EtherType.EAPOL.ethType())) {
      provisionEapol(filter, ethType, output);
    } else if (ethType.ethType().equals(EthType.EtherType.IPV4.ethType())) {
      IPProtocolCriterion ipProto =
          (IPProtocolCriterion) filterForCriterion(filter.conditions(), Criterion.Type.IP_PROTO);
      if (ipProto.protocol() == IPv4.PROTOCOL_IGMP) {
        provisionIgmp(filter, ethType, ipProto, output);
      } else {
        log.error("OLT can only filter igmp");
        fail(filter, ObjectiveError.UNSUPPORTED);
      }
    } else {
      log.error("OLT can only filter eapol and igmp");
      fail(filter, ObjectiveError.UNSUPPORTED);
    }
  }
  @Override
  // Dell switches need ETH_DST based match condition in all IP table entries.
  // So this method overrides the default spring-open behavior and adds
  // ETH_DST match condition while pushing IP table flow rules
  protected Collection<FlowRule> processSpecific(ForwardingObjective fwd) {
    log.debug("Processing specific");
    TrafficSelector selector = fwd.selector();
    EthTypeCriterion ethType = (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE);
    if ((ethType == null)
        || ((((short) ethType.ethType()) != Ethernet.TYPE_IPV4)
            && (((short) ethType.ethType()) != Ethernet.MPLS_UNICAST))) {
      log.debug("processSpecific: Unsupported " + "forwarding objective criteraia");
      fail(fwd, ObjectiveError.UNSUPPORTED);
      return Collections.emptySet();
    }

    TrafficSelector.Builder filteredSelectorBuilder = DefaultTrafficSelector.builder();
    int forTableId = -1;
    if (((short) ethType.ethType()) == Ethernet.TYPE_IPV4) {
      if (deviceTMac == null) {
        log.debug(
            "processSpecific: ETH_DST filtering "
                + "objective is not set which is required "
                + "before sending a IPv4 forwarding objective");
        // TODO: Map the error to more appropriate error code.
        fail(fwd, ObjectiveError.DEVICEMISSING);
        return Collections.emptySet();
      }
      filteredSelectorBuilder =
          filteredSelectorBuilder
              .matchEthType(Ethernet.TYPE_IPV4)
              .matchEthDst(deviceTMac)
              .matchIPDst(((IPCriterion) selector.getCriterion(Criterion.Type.IPV4_DST)).ip());
      forTableId = ipv4UnicastTableId;
      log.debug("processing IPv4 specific forwarding objective");
    } else {
      filteredSelectorBuilder =
          filteredSelectorBuilder
              .matchEthType(Ethernet.MPLS_UNICAST)
              .matchMplsLabel(
                  ((MplsCriterion) selector.getCriterion(Criterion.Type.MPLS_LABEL)).label());
      // TODO: Add Match for BoS
      // if (selector.getCriterion(Criterion.Type.MPLS_BOS) != null) {
      // }
      forTableId = mplsTableId;
      log.debug("processing MPLS specific forwarding objective");
    }

    TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
    if (fwd.treatment() != null) {
      for (Instruction i : fwd.treatment().allInstructions()) {
        treatmentBuilder.add(i);
      }
    }

    if (fwd.nextId() != null) {
      NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId());

      if (next != null) {
        GroupKey key = appKryo.deserialize(next.data());

        Group group = groupService.getGroup(deviceId, key);

        if (group == null) {
          log.warn("The group left!");
          fail(fwd, ObjectiveError.GROUPMISSING);
          return Collections.emptySet();
        }
        treatmentBuilder.group(group.id());
        log.debug("Adding OUTGROUP action");
      } else {
        log.warn("processSpecific: No associated next objective object");
        fail(fwd, ObjectiveError.GROUPMISSING);
        return Collections.emptySet();
      }
    }

    TrafficSelector filteredSelector = filteredSelectorBuilder.build();
    TrafficTreatment treatment = treatmentBuilder.transition(aclTableId).build();

    FlowRule.Builder ruleBuilder =
        DefaultFlowRule.builder()
            .fromApp(fwd.appId())
            .withPriority(fwd.priority())
            .forDevice(deviceId)
            .withSelector(filteredSelector)
            .withTreatment(treatment);

    if (fwd.permanent()) {
      ruleBuilder.makePermanent();
    } else {
      ruleBuilder.makeTemporary(fwd.timeout());
    }

    ruleBuilder.forTable(forTableId);
    return Collections.singletonList(ruleBuilder.build());
  }