From b1a1a342504f8013907244c3a2728571e7839543 Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Wed, 16 Aug 2017 23:47:55 +0200 Subject: 41556: complete BPF (libpcap) filters --- Completion/Unix/Command/_ngrep | 33 ++++++ Completion/Unix/Command/_tcpdump | 177 ++++++++++++++++++------------- Completion/Unix/Type/_bpf_filters | 215 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 352 insertions(+), 73 deletions(-) create mode 100644 Completion/Unix/Command/_ngrep create mode 100644 Completion/Unix/Type/_bpf_filters (limited to 'Completion/Unix') diff --git a/Completion/Unix/Command/_ngrep b/Completion/Unix/Command/_ngrep new file mode 100644 index 000000000..924597826 --- /dev/null +++ b/Completion/Unix/Command/_ngrep @@ -0,0 +1,33 @@ +#compdef ngrep + +_arguments -s -S \ + '(- 1 *)-h[display help information]' \ + '(- 1 *)-V[display version information]' \ + "-q[be quiet (don't print packet reception hash marks)]" \ + '-e[show empty packets]' \ + '-i[ignore case]' \ + '-v[invert match]' \ + "-R[don't do privilege revocation logic]" \ + '(-W)-x[print in alternate hexdump format]' \ + '-X[interpret match expression as hexadecimal]' \ + '-w[word-regex (expression must match as a word)]' \ + "-p[don't go into promiscuous mode]" \ + '-l[make stdout line buffered]' \ + '-D[replay pcap_dumps with their recorded time intervals]' \ + '-t[print timestamp every time a packet is matched]' \ + '-T[print delta timestamp every time a packet is matched specify twice for delta from first match]' \ + "-M[don't do multi-line match (do single-line match instead)]" \ + '(-d -s)-I+[read packet stream from pcap format file]:file:_files' \ + '-O+[dump matched packets in pcap format file]:file:_files' \ + '-n+[look at only specified number of packets]:packets' \ + '-A+[dump specified number of context packets after a match]:packets' \ + '(-I)-s+[set the bpf caplen]:length (bytes) [65535]' \ + '-S+[set the upper limit on size of packets matched]:size (bytes)' \ + '(-x)-W+[set the dump format]:packet display format:(normal byline single none)' \ + '-c+[force the column width to the specified size]:columns' \ + '-P+[set the non-printable display char to what is specified]:character [.]' \ + '-F+[read the bpf filter from the specified file]:file:_files' \ + '-N[show sub protocol number]' \ + '(-I)-d+[use specified device instead of the pcap default]:interface:_net_interfaces' \ + '1: :_guard "^-*" pattern' \ + '*::expression:_bpf_filters' diff --git a/Completion/Unix/Command/_tcpdump b/Completion/Unix/Command/_tcpdump index 2c1d82226..4b9950fa5 100644 --- a/Completion/Unix/Command/_tcpdump +++ b/Completion/Unix/Command/_tcpdump @@ -1,17 +1,25 @@ #compdef tcpdump -typeset -A opt_args +local args ret=1 +local root +(( EUID )) && root='!' -_interfaces() { - local disp expl sep - _description interfaces expl 'network interface' - _net_interfaces "$expl[@]" - if zstyle -t ":completion:${curcontext}:interfaces" verbose; then - zstyle -s ":completion:${curcontext}:interfaces" list-separator sep || sep=-- - disp=( "any $sep capture on all interfaces" ) - compadd "$expl[@]" -ld disp any +_tcpdump_interfaces() { + local disp expl sep interfaces + [[ $OSTYPE != openbsd* ]] && + interfaces=( ${${${${(f)"$(_call_program interfaces tcpdump -D)"}#<->.}//[()]/}/ /:} ) + if (( $#interfaces )); then + _describe -t interfaces 'network interface' interfaces else - compadd "$expl[@]" any + _description interfaces expl 'network interface' + _net_interfaces "$expl[@]" + if zstyle -t ":completion:${curcontext}:interfaces" verbose; then + zstyle -s ":completion:${curcontext}:interfaces" list-separator sep || sep=-- + disp=( "any $sep capture on all interfaces" ) + compadd "$expl[@]" -ld disp any + else + compadd "$expl[@]" any + fi fi } @@ -40,43 +48,50 @@ _esp_secrets () { } _packet_types () { + local -a types types=( - 'cnfp[Cisco NetFlow protocol]' - 'rpc[Remote Procedure Call]' - 'rtp[Real-Time Applications protocol]' - 'rtcp[Real-Time Applications control protocol]' - 'vat[Visual Audio Tool]' - 'wb[distributed White Board]' + 'cnfp:Cisco NetFlow protocol' + 'rpc:Remote Procedure Call' + 'rtp:Real-Time Applications protocol' + 'rtcp:Real-Time Applications control protocol' + 'vat:Visual Audio Tool' + 'wb:distributed White Board' ) if [[ $OSTYPE = openbsd* ]]; then types+=( - 'sack[RFC 2018 TCP Selective Acknowledgements Options]' - 'vrrp[Virtual Router Redundancy Protocol]' - 'tcp[Transmission Control Protocol]' + 'sack:RFC 2018 TCP Selective Acknowledgements Options' + 'vrrp:Virtual Router Redundancy Protocol' + 'tcp:Transmission Control Protocol' ) else types+=( - 'aodv[Ad-hoc On-demand Distance Vector protocol]' - 'carp[Common Address Redundancy Protocol]' - 'radius[RADIUS]' - 'snmp[Simple Network Management Protocol]' - 'tftp[Trivial File Transfer Protocol]' - 'vxlan[Virtual eXtensible Local Area Network]' - 'zmtpl[ZeroMQ Message Transport Protocol]' + 'aodv:Ad-hoc On-demand Distance Vector protocol' + 'carp:Common Address Redundancy Protocol' + 'radius:RADIUS' + 'snmp:Simple Network Management Protocol' + 'tftp:Trivial File Transfer Protocol' + 'vxlan:Virtual eXtensible Local Area Network' + 'zmtpl:ZeroMQ Message Transport Protocol' ) fi - _values 'Packets type' $types + _describe -t packet-types 'packet type' types +} + +_time_stamp_types () { + local vals + vals=( ${${${(ps:\n :)"$(_call_program time-stamp-types tcpdump -J ${(kv)opt_args[(i)-i|--interface]} 2>&1)"}[2,-1]:#*cannot be set*}/ /:} ) + _describe -t time-stamp-types "time stamp type" vals } _data_link_types () { - if (( $+opt_args[-i] )); then - vals=( ${${${(s: :)"$(_call_program data-link-types tcpdump -L -i $opt_args[-i] 2>&1)"}[2,-1]}/ /:} ) - _describe -t data-link-types "data link types ($opt_args[-i])" vals && ret=0 - else - _values "Data link types (general)" \ - "EN10MB" \ - "LINUX_SLL" - fi + local vals expl + if (( $+opt_args[(i)-i|--interface] )); then + vals=( ${${${(s: :)"$(_call_program data-link-types tcpdump -L ${(kv)opt_args[(i)-i|--interface]} 2>&1)"}[2,-1]}/ /:} ) + _describe -t data-link-types "data link type (${(v)opt_args[(i)-i|--interface]})" vals + else + _wanted data-link-types expl "data link type (general)" \ + compadd EN10MB LINUX_SLL + fi } _bpf_filter () { @@ -84,79 +99,95 @@ _bpf_filter () { args=( '-A[print each packet in ASCII]' - '-c[exit after receiving specified number of packets]:number of packets' + '-c+[exit after receiving specified number of packets]:number of packets' '(-ddd)-d[dump the compiled packet-matching code in a human readable form]' '(-ddd)-dd[dump packet-matching code as a C program fragment]' '(-d -dd)-ddd[dump packet-matching code as decimal numbers (preceded with a count)]' "-E[decrypting IPsec ESP packets]:spi@ipaddr::algo\:secret:_esp_secrets" '-e[print the link-level header on each dump line]' - '-F[input file for the filter expression]:filter expression file:_files' + '-F+[specify input file for the filter expression]:filter expression file:_files' "-f[print 'foreign' IPv4 addresses numerically]" '-l[make stdout line buffered]' "-N[don't print domain name qualification of host names]" - "-n[don't convert addresses to names]" + "(-nn)-n[don't convert addresses to names]" "-O[don't run the packet-matching code optimizer]" - "-p[don't put the interface into promiscuous mode]" + '(-p --no-promiscuous-mode)'{-p,--no-promiscuous-mode}"[don't put the interface into promiscuous mode]" '-q[quick (quiet?) output]' - '-r[read packets from file]:input file:_files' - '-S[print absolute TCP sequence numbers]' - '-s[specify number of bytes of data to snarf from each packet]:number of bytes to snap' - '-T[interpret captured packets as specified type]:packet type:_packet_types' + '-r+[read packets from file]:input file:_files' + '(-S --absolute-tcp-sequence-numbers)'{-S,--absolute-tcp-sequence-numbers}'[print absolute TCP sequence numbers]' + '-T+[interpret captured packets as specified type]:packet type:_packet_types' "(-tt -ttt -tttt -ttttt)-t[don't print a timestamp on each dump line]" '(-t -ttt -tttt -ttttt)-tt[print an unformatted timestamp on each dump line]' '(-vv -vvv)-v[slightly more verbose output]' '(-v -vvv)-vv[more verbose output]' - '-w[write the raw packets to file]:output file:_files' + '-w+[write the raw packets to file]:output file:_files' '-X[print each packet (minus its link level header) in hex and ASCII]' '-x[print each packet (minus its link level header) in hex]' - '-y[set the data link type to use while capturing packets]:data link type:_data_link_types' - '*:BPF filter:_bpf_filter' + '(-y --linktype)'{-y+,--linktype=}'[set the data link type to use while capturing packets]: :_data_link_types' ) if [[ $OSTYPE = openbsd* ]]; then - args+=( + args=( + '-i+[specify interface]:interface:_tcpdump_interfaces' + - listd + '-L[list data link types for the interface]' + - capture + ${(R)args:#(|\*)(|\(*\))--*} # removes any long-options '(-n)-a[attempt to convert network and broadcast addresses to names]' '-D[select packet flowing in specified direction]:direction:(in out)' '-I[print the interface on each dump line]' '-o[print a guess of the possible operating system(s)]' + '-s+[specify amount of data to snarf from each packet]:length (bytes) [116]' '(-t -tt -tttt -ttttt)-ttt[print day and month in timestamp]' '(-t -tt -ttt -ttttt)-tttt[print timestamp difference between packets]' '(-t -tt -ttt -tttt)-ttttt[print timestamp difference since the first packet]' ) else - args+=( - '-B[specify the capture buffer size in KiB]:capture buffer size' + args=( + '(-i --interface -D --list-interfaces)'{-i+,--interface=}'[specify interface]:interface:_tcpdump_interfaces' + - listt + '(-J --list-time-stamp-types)'{-J,--list-time-stamp-types}'[list supported time stamp types]' + - listd + '(-L --list-data-link-types)'{-L,--list-data-link-types}'[list data link types for the interface]' + - capture + $args + '(-B --buffer-size)'{-B+,--buffer-size=}'[set the operating system capture buffer size]:size (kiB)' '-b[print the AS number in BGP packets in ASDOT notation]' - '-C[specify output file size in MB (10e6 bytes)]:output file size' - '(-* *)'-D'[print the list of the network interfaces available on the system]' - '-G[specify the interval to rotate the dump file in seconds]:dump file rotate seconds' + '-C+[specify output file size]:output file size (MB)' + '(-)'{-D,--list-interfaces}'[print the list of the network interfaces available on the system]' + '-G+[rotate dump file specified with -w at specified interval]:interval (seconds)' '-H[attempt to detect 802.11s draft mesh headers]' - '(-* *)-h[print version strings and a usage message]' + '(- *)'{-h,--help}'[display help information]' + '(- *)--version[display version information]' + '(-I --monitor-mode)'{-I,--monitor-mode}'[put the Wi-Fi interface in monitor mode]' + '--immediate-mode[deliver packets to tcpdump as soon as they arrive without buffering]' '-I[put the interface in monitor mode]' - '(-* *)-J[list the supported timestamp types]' - '-j[set the timestamp type]:timestamp type' - "-K[don't attempt to verify checksums]" - '*-m[load SMI MIB module definitions]:SMI MIB module definitions:_files' - '-M[shared secret for validating the digests in TCP segments with the TCP-MD5 option]:secret' - '-R[assume ESP/AH packets to be based on old specification (RFC1825 to RFC1829)]' - '(-t -tt -tttt)-ttt[print a delta (in micro-seconds) between current and previous line on each dump line]' - '(-t -tt -ttt)-tttt[print a timestamp in default format proceeded by date on each dump line]' - '(-t -tt -ttt -tttt)-ttttt[print a delta (micro-second resolution) since the first line on each dump line]' - '-U[make output packet-buffered when saving to file (-w)]' + '(-j --time-stamp-type)'{-j+,--time-stamp-type=}'[set the time stamp type for the capture]: :_time_stamp_types' + '--time-stamp-precision=[set the time stamp precision for the capture]:precision [micro]:(micro nano)' + '(-K --dont-verify-checksums)'{-K,--dont-verify-checksums}"[don't verify IP, TCP, or UDP checksums]" + '*-m+[load SMI MIB module definitions]:SMI MIB module definition:_files' + "(-n)-nn[don't convert protocol and port numbers to names]" + '-M+[specify shared secret for validating the digests in TCP segments with the TCP-MD5 option]:secret' + '(-# --number)'{-\#,--number}'[print an optional packet number at the beginning of the line]' + '(-O --no-optimize)'{-O,--no-optimize}"[don't run the packet-matching code optimizer]" + '(-s --snapshot-length)'{-s+,--snapshot-length=}'[specify amount of data to snarf from each packet]:length (bytes) [65535]' + '(-t -tt -tttt -ttttt)-ttt[print a delta (in micro-seconds) on each line since previous line]' + '(-t -tt -ttt -ttttt)-tttt[print a timestamp in default format preceded by date on each dump line]' + '(-t -tt -ttt -tttt)-ttttt[print a delta (in micro-seconds) on each line since first line]' + '(-U --packet-buffered)'{-U,--packet-buffered}'[make output packet-buffered when saving to file (-w)]' '-u[print undecoded NFS handles]' - '-V[Read a list of filenames from file]:file:_files' + '-V+[read a list of filenames from specified file]:file:_files' '(-v -vv)-vvv[most verbose output]' - '-W[limit the number of created files (-C)]:number of files' + '-W+[limit the number of created files (-C)]:number of files' '(-X)-XX[print each packet, including its link level header, in hex and ASCII]' '(-x)-xx[print each packet, including its link level header, in hex]' - '-Z[drops privileges (if root) and changes user ID (along with primary group)]:user:_users' - '-z[command to run after file rotation]:command:_command_names' + "${root}(-Z --relinquish-privileges)"{-Z+,--relinquish-privileges=}'[drop privileges and run as specified user]:user:_users' + '-z+[specify command to run on files (with -C or -G)]:command:_command_names -e' ) fi +[[ $OSTYPE = freebsd* ]] && args+=( + '-R[assume ESP/AH packets to be based on old specification (RFC1825 to RFC1829)]' +) -_arguments : \ - '-i[interface]:interface:_interfaces' \ - - optL \ - '-L[list the known data link types for the interface]' \ - - default \ - $args +_arguments -s $args \ + '*::BPF filter:= _bpf_filters' diff --git a/Completion/Unix/Type/_bpf_filters b/Completion/Unix/Type/_bpf_filters new file mode 100644 index 000000000..c62481e09 --- /dev/null +++ b/Completion/Unix/Type/_bpf_filters @@ -0,0 +1,215 @@ +# 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)"$(