#autoload

# spaces are valid instead of word ends, perhaps better to just do compset -q

local -a networks fields dirs protos relop
local -A subtypes flags
local values dir wlantype skip repeat=1 packet proto=0
local suf=']'

local WORD=$'[^ \0]##[ \0]##'

networks=(
  /$'[^/ \0]#'/
  \(
    /$'[ \0]'/ ': _message -e networks network'
    /$'mask[ \0]'/ ':masks:mask:(mask)'
    /$WORD/ ':netmasks:netmask:'
  \|
    /// /$WORD/ ': _message -e masks "netmask length (bits)"'
  \)
)
subtypes=(
  mgt 'assoc-req assoc-resp reassoc-req reassoc-resp probe-req probe-resp beacon atim disassoc auth deauth'
  ctl 'ps-poll rts cts ack cf-end cf-end-ack'
  data 'data data-cf-ack data-cf-poll data-cf-ack-poll null cf-ack cf-poll cf-ack-poll qos-data qos-data-cf-ack qos-data-cf-poll qos-data-cf-ack-poll qos qos-cf-poll and qos-cf-ack-poll'
)
flags=(
  len len
  tcp 'tcp-fin tcp-syn tcp-rst tcp-push tcp-ack tcp-urg'
  icmp 'icmp-echoreply icmp-unreach icmp-sourcequench icmp-redirect icmp-echo icmp-routeradvert icmp-routersolicit icmp-timxceed icmp-paramprob icmp-tstamp icmp-tstampreply icmp-ireq icmp-ireqreply icmp-maskreq icmp-maskreply'
)

case $OSTYPE in
  solaris*)
    fields=( ipaddr etheraddr atalkaddr ethertype rpc nofrag inet inet6 vlan-id )
    protos=( bootp dhcp dhcp6 apple pppoe ldap slp ospf )
    dirs=( from to )
    relop=( \^ % )
  ;|
  solaris2.<11->)
    fields+=( zone )
  ;|
  (free|open)bsd*) # pf(4) specific filters
    fields=( ifname on rnr rulenum srnr subruleset reason ruleset rset action )
  ;|
  ^(solaris|openbsd)*)
    protos+=( mpls netbeui iso geneve aarp ipx llc )
  ;|
  ^openbsd*)
    protos+=( ah esp sctp pppoed pppoes )
  ;|
  ^solaris*)
    protos+=( fddi wlan atalk stp lat moprc mopdl )
    relop=( '>>' '<<' )
  ;;
esac

compquote suf

# the regex is essentially:
# ( [not]* ( expression | [protocol]? [standalone-field | direction field ]? ) and|or ) *
# the proto variable ensures that and/or is not allowed if there is no
# protocol or field: it is one, the other or both but not neither

_regex_arguments _bpf /$'[^\0]#\0'/ \( \
  /$'(not[ \0]#|![ \0]#|(\\\\|)\\([ \0]#)'/ ':operators:operator:(not ()' \# \
  \(\
    \(\
      \(\
        /"(0x[0-9a-f]##|[0-9]##|${(j.|.)${=flags}})"$'[ \0]#'/ -'((repeat != 2))' ":expressions:expression:compadd ${=flags[$packet]}" \
      \|\
        /'[a-z]##(\\|)\[[^\]]##(\\|)\]'$'[ \0]#'/ \
      \|\
        /'[a-z]##(\\|)\[[^:\]]##:'/ /'[]'/ ':sizes:field size (bytes):compadd -S "$suf" 1 2 4' \
      \|\
        /'tcp(\\|)\['/ -packet=tcp \
        /'[]'/ ':offsets:header offset:compadd -S "$suf " tcpflags' \
      \|\
        /'icmp(\\|)\['/ -packet=icmp \
        /'[]'/ ':offsets:header offset:compadd -S "$suf " icmptype icmpcode' \
      \|\
        /'[a-z]##(\\|)\['/ /'[]'/ ':offsets:offset:' \
      \)\
      \(\
        /$'(\\\\|)([<>=!](\\\\|)[<>=]|[<>&|=+*/%^-])[ \0]#'/ -'repeat=0' ":operators:operator:(+ - = != < > <= >= & | $relop and or)" \
        // ': _message -e expressions expression' \
      \|\
        // -'repeat=2' \
      \)\
    \) \# \
    // -'(( repeat == 2))' \
    // -'repeat=1' \
  \|\
    /$'ether[ \0]proto[ \0]'/ \
    /$WORD/ ':protocols:protocol:(\ip \ip6 \arp \rarp \atalk \aarp \dec \net \sca \lat \mopdl \moprc \iso \stp \ipx \netbeui)' \
  \|\
    /$'(less|greater)[ \0]'/ ':fields:field:(less greater)' \
    /$WORD/ ':numbers:length (bytes):' \
  \|\
    \(\
      /$'(tcp|udp|icmp|ether|ip|ip6|arp|rarp|decnet|bootp|dhcp|dhcp6|apple|pppoe|pppoed|ldap|ah|esp|slp|sctp|ospf|iso|clnp|esis|isis|atalk|aarp|iso|stp|ipx|netbeui|lat|moprc|mopdl)[ \0]'/ ":protocols:protocol qualifier:(tcp udp icmp ether tr ip ip6 arp rarp decnet $protos)" \
    \| /$'((fddi|tr|wlan)[ \0]|)'/ '-(( ++proto ))' \) \
    \(\
      /$'mpls[ \0]'/ \
      /$'((0x|)[0-9a-f]##[ \0]|)'/ ': _message -e labels "label number"' \
    \|\
      /$'geneve[ \0]'/ \
      /$'((0x|)[0-9a-f]##[ \0]|)'/ ': _message -e vnis "vni"' \
    \|\
      /$'pppoes[ \0]'/ \
      /$'((0x|)[0-9a-f]##[ \0]|)'/ ': _message -e session-ids "session id"' \
    \|\
      /$'proto[ \0]'/ ':fields:field:(proto)' \
      /$WORD/ ':protocols:protocol:(\icmp \icmp6 \igmp \igrp \pim \ah \esp \vrrp \udp \tcp)' \
    \|\
      /$'(broad|multi)cast[ \0]'/ ':fields:field:(broadcast multicast)' \
    \|\
      /$'type[ \0]'/ ':fields:field:(type)' \
      /$WORD/ -'wlantype=${match%?}' ':wlan-types:wlan type:(mgt ctl data)' \
      \(\
        /$'subtype[ \0]'/ ':fields:field:(subtype)' \
        /$WORD/ ':subtypes:subtype:compadd ${=subtypes[$wlantype]:-$subtypes}' \
      \| \)\
    \|\
      /$'protochain[ \0]'/ ':fields:field:(protochain)' \
      /$WORD/ ':protocols:protocol:' \
    \|\
      /$'vlan-id[ \0]'/ \
      /$WORD/ ':vlans:vlan:' \
    \|\
      /$'vlan[ \0]'/ ':fields:field:(vlan)' \
      \( /$WORD/ ': _message -e vlans vlan' \| \) \
    \|\
      \(\
        /$'(ra|ta|addr[1-4]|inbound|outbound)[ \0]'/ ":directions:direction qualifier:(src dst inbound outbound ra ta addr1 addr2 addr3 addr4 $dirs)" \
      \|\
        /$'(src|from|dst|to)[ \0]'/ -'values=${values:-hosts};dir=$match' \
        \(\
          /$'(or|and)[ \0]'/ ':operators:operator:(or and)' \
          /$'(src|dst)[ \0]'/ ':directions:direction qualifier:compadd ${${${${dir%?}:/dst/to}:/(src|from)/dst}:/to/src}' \
        \| \)\
      \| \)\
      \(\
        /$'(host|gateway)[ \0]'/ ":fields:field:(host gateway $fields)" \
        /$WORD/ -values=hosts ':hosts:host:_hosts' \
      \|\
        /$'inet(6|)[ \0]'/ \
        \( /$'host[ \0]'/ ':fields:field:(host)' \| \) \
        /$WORD/ -values=hosts ':hosts:host:_hosts' \
      \|\
        /$'ethertype[ \0]'/ \
        /$WORD/ ':numbers:number:' \
      \|\
        /$'(ipaddr|etheraddr|atalkaddr)[ \0]'/ \
        /$WORD/ ':addresses:address:' \
      \|\
        /$'llc[ \0]'/\
        /$'((s|u|rr|rnr|rej|ui|ua|disc|sabme|test|xid|frmr)[ \0]|)'/ ':types:type:(s u rr rnr rej ui ua disc sabme test xid frmr)' \
      \|\
        /$'(ifname|on)[ \0]'/ \
        /$WORD/ ':interfaces:interface:_net_interfaces' \
      \|\
        /$'(rnr|rulenum|srnr|subruleset)[ \0]'/ \
        /$WORD/ ':rules:rule number:' \
      \|\
        /$'reason[ \0]'/ \
        /$WORD/ ':reasons:reason:(match bad-offset fragment short normalize memory)' \
      \|\
        /$'(rset|ruleset)[ \0]'/ \
        /$WORD/ ':rule-sets:rule set:' \
      \|\
        /$'action[ \0]'/ \
        /$WORD/ ':actions:action:(pass block nat rdr binat scrub)' \
      \|\
        /$'rpc[ \0]'/ \
        \(\
          /$'[^, \0]##[ \0]'/ ':programs:rpc program:compadd -qS, - ${${(f)"$(</etc/rpc)"}%%[[:blank:]#]*}' \
        \|\
          /$'[^, \0]##,[^, \0]##,'/ /$'[^, \0]##[ \0]'/ ':procedures:procedure:' \
        \|\
          /$'[^, \0]##,'/ /$'[^, \0]##[ \0]'/ ':versions:version:' \
        \)\
      \|\
        /$'zone[ \0]'/ \
        /$WORD/ ':zones:zone:_zones' \
      \|\
        /$'port[ \0]'/ ':fields:field:(port)' \
        /$WORD/ -values=ports ':ports:port:_ports' \
      \|\
        /$'portrange[ \0]'/ -values=portranges ':fields:field:(portrange)' \
        /$'[^ \0-]##-'/ ':ports:port:_ports -S-' \
        /$WORD/ ':ports:port:_ports' \
      \|\
        /$'net[ \0]'/ -values='networks' ':fields:field:(net)' \
        $networks \
      \|\
        // -'[[ $values = hosts ]]' \
        /$WORD/ ':hosts:host:_hosts' \
      \|\
        // -'[[ $values = ports ]]' \
        /$WORD/ ':ports:port:_ports' \
      \|\
        // -'[[ $values = networks ]]' \
        $networks \
      \|\
        // -'[[ $values = portranges ]]' \
        /$'[^ \0-]##-'/ ':ports:port:_ports -S-' \
        /$WORD/ ':ports:port:_ports' \
      \|\
        // -'(( ++proto ))' \
      \)\
   \)\
  \)\
  // -'(( proto < 2 ))' \
  /$'(and|or|&&|\\|\\||\\))[ \0]'/ -proto=0 ':operators:operator:compadd and or \)' \) \#

_bpf "$@"
