#
#  Tests for parsing conditional expressions.
#
#  $Id$
#

#
#  A bunch of errors, in the order that the error strings
#  appear in parser.c
#


# All IP address literals should be parsed as prefixes
condition ("foo\
data ERROR offset 6 End of string after escape

condition ("foo
data ERROR offset 2 Unterminated string

condition ()
data ERROR offset 1 Empty string is invalid

condition (!)
data ERROR offset 2 Empty string is invalid

condition (foo == bar
data ERROR offset 11 No closing brace at end of string

condition (|| b)
data ERROR offset 1 Empty string is invalid

condition ((ok || handled) foo)
data ERROR offset 17 Unexpected text after condition

# escapes in names are illegal
condition (ok\ foo || handled)
data ERROR offset 3 Unexpected escape

condition (ok FOO handled)
data ERROR offset 4 Invalid text. Expected comparison operator

condition (ok !x handled)
data ERROR offset 4 Invalid operator

condition (ok =x handled)
data ERROR offset 4 Invalid operator

condition (ok =~ handled)
data ERROR offset 7 Expected regular expression

condition (ok == /foo/)
data ERROR offset 7 Unexpected regular expression

condition (ok == handled"foo")
data ERROR offset 14 Unexpected start of string

# And now we have a bunch of VALID conditions we want to parse.

# sillyness is OK
condition ((((((ok))))))
data ok

#
#  Extra braces get squashed
#
condition (&User-Name == &User-Password)
data &User-Name == &User-Password

condition (!ok)
data !ok

condition !(ok)
data !ok

condition !!ok
data ERROR offset 1 Double negation is invalid

condition !(!ok)
data ok

#
#  These next two are identical after normalization
#
condition (&User-Name == &User-Password || &Filter-Id == &Reply-Message)
data &User-Name == &User-Password || &Filter-Id == &Reply-Message

condition ((&User-Name == &User-Password) || (&Filter-Id == &Reply-Message))
data &User-Name == &User-Password || &Filter-Id == &Reply-Message

condition (!(&User-Name == &User-Password) || (&Filter-Id == &Reply-Message))
data !&User-Name == &User-Password || &Filter-Id == &Reply-Message

#  different from the previous ones.
condition (!((&User-Name == &User-Password) || (&Filter-Id == &Reply-Message)))
data !(&User-Name == &User-Password || &Filter-Id == &Reply-Message)

condition (!(&User-Name == &User-Password) || (&Filter-Id == &Reply-Message))
data !&User-Name == &User-Password || &Filter-Id == &Reply-Message

condition ((a == b) || (c == d)))
data ERROR offset 22 Unexpected closing brace

condition (handled && (Response-Packet-Type == Access-Challenge))
data handled && &Response-Packet-Type == Access-Challenge

# This is OK, without the braces
condition handled && &Response-Packet-Type == Access-Challenge
data handled && &Response-Packet-Type == Access-Challenge

# and this, though it's not a good idea.
condition handled &&&Response-Packet-Type == Access-Challenge
data handled && &Response-Packet-Type == Access-Challenge

condition /foo/ =~ bar
data ERROR offset 0 Conditional check cannot begin with a regular expression

condition reply == request
data ERROR offset 0 Cannot use list references in condition

condition reply == "hello"
data ERROR offset 0 Cannot use list references in condition

condition "hello" == reply
data ERROR offset 0 Cannot use list references in condition

condition request:User-Name == reply:User-Name
data &User-Name == &reply:User-Name

#
#  Convert !~ to !(COND) for regex
#
condition foo =~ /bar/
data foo =~ /bar/

condition foo !~ /bar/
data !foo =~ /bar/

condition !foo !~ /bar/
data foo =~ /bar/

#
#  Convert != to !(COND) for normal checks
#
condition &User-Name == &User-Password
data &User-Name == &User-Password

condition &User-Name != &User-Password
data !&User-Name == &User-Password

condition !&User-Name != &User-Password
data &User-Name == &User-Password

condition <ipv6addr>foo
data ERROR offset 0 Cannot do cast for existence check

condition <ipaddr>Filter-Id == &Framed-IP-Address
data <ipaddr>&Filter-Id == &Framed-IP-Address

condition <ipaddr>Filter-Id == <ipaddr>&Framed-IP-Address
data ERROR offset 21 Unnecessary cast

condition <ipaddr>Filter-Id == <integer>&Framed-IP-Address
data ERROR offset 21 Cannot cast to a different data type

condition <ipaddr>Filter-Id == <blerg>&Framed-IP-Address
data ERROR offset 22 Invalid data type in cast

#
#  Normalize things
#
condition <ipaddr>Filter-Id == "127.0.0.1"
data <ipaddr>&Filter-Id == '127.0.0.1'

condition <ipaddr>127.0.0.1 < &Framed-IP-Address
data &Framed-IP-Address > 127.0.0.1

# =* and !* are only for attrs / lists
condition "foo" !* bar
data ERROR offset 6 Cannot use !* on a string

condition "foo" =* bar
data ERROR offset 6 Cannot use =* on a string

# existence checks don't need the RHS
condition User-Name =* bar
data &User-Name

condition User-Name !* bar
data !&User-Name

condition !User-Name =* bar
data !&User-Name

condition !User-Name !* bar
data &User-Name

# redundant casts get squashed
condition <ipaddr>Framed-IP-Address == 127.0.0.1
data &Framed-IP-Address == 127.0.0.1

condition <cidr>Framed-IP-Address <= 192.168.0.0/16
data <ipv4prefix>&Framed-IP-Address <= 192.168.0.0/16

# All IP address literals should be parsed as prefixes
condition Framed-IP-Address <= 192.168.0.0/16
data <ipv4prefix>&Framed-IP-Address <= 192.168.0.0/16

# string attributes must be string
condition User-Name == "bob"
data &User-Name == "bob"

condition User-Name == `bob`
data &User-Name == `bob`

condition User-Name == 'bob'
data &User-Name == 'bob'

condition User-Name == bob
data ERROR offset 13 Must have string as value for attribute

# Integer (etc.) types must be "bare"
condition Session-Timeout == 10
data &Session-Timeout == 10

condition Session-Timeout == '10'
data ERROR offset 19 Value must be an unquoted string

# Except for dates, which can be humanly readable!
# This one is be an expansion, so it's left as-is.
condition Event-Timestamp == "January 1, 2012 %{blah}"
data &Event-Timestamp == "January 1, 2012 %{blah}"

# This one is NOT an expansion, so it's parsed into normal form
condition Event-Timestamp == 'January 1, 2012'
#data &Event-Timestamp == 'Jan  1 2012 00:00:00 EST'

# literals are parsed when the conditions are parsed
condition <integer>X == 1
data ERROR offset 9 Failed to parse field

condition NAS-Port == X
data ERROR offset 12 Failed to parse value for attribute

#
#  The RHS is a static string, so this gets mashed to a literal,
#  and then statically evaluated.
#
condition <ipaddr>127.0.0.1 == "127.0.0.1"
data true

condition <ipaddr>127.0.0.1 == "%{sql: 127.0.0.1}"
data <ipaddr>127.0.0.1 == "%{sql: 127.0.0.1}"

condition <ether> 00:11:22:33:44:55 == "00:11:22:33:44:55"
data true

condition <ether> 00:11:22:33:44:55 == "ff:11:22:33:44:55"
data false

condition <ether> 00:11:22:33:44:55 == "%{sql:00:11:22:33:44:55}"
data <ether>00:11:22:33:44:55 == "%{sql:00:11:22:33:44:55}"

condition <ether> 00:XX:22:33:44:55 == 00:11:22:33:44:55
data ERROR offset 8 Failed to parse field

#
#  Tests for boolean data types.
#
condition true
data true

condition 1
data true

condition false
data false

condition 0
data false

condition true && (User-Name == "bob")
data &User-Name == "bob"

condition false && (User-Name == "bob")
data false

condition false || (User-Name == "bob")
data &User-Name == "bob"

condition true || (User-Name == "bob")
data true

#
#  Both sides static data with a cast: evaluate at parse time.
#
condition <integer>20 < 100
data true

#
#  Both sides literal: evaluate at parse time
#
condition ('foo' == 'bar')
data false

condition ('foo' < 'bar')
data false

condition ('foo' > 'bar')
data true

condition ('foo' == 'foo')
data true

#
#  Double-quotes strings without expansions are literals
#
condition ("foo" == "%{sql: foo}")
data foo == "%{sql: foo}"

condition ("foo bar" == "%{sql: foo}")
data "foo bar" == "%{sql: foo}"

condition ("foo" == "bar")
data false

condition ("foo" == 'bar')
data false

#
#  The RHS gets parsed as a VPT_TYPE_DATA, which is
#  a double-quoted string.  Except that there's no '%'
#  in it, so it reverts back to a literal.
#
condition (&User-Name == "bob")
data &User-Name == "bob"

condition (&User-Name == "%{sql: blah}")
data &User-Name == "%{sql: blah}"

condition <ipaddr>127.0.0.1 == 2130706433
data true

# /32 suffix should be trimmed for this type
condition <ipaddr>127.0.0.1/32 == 127.0.0.1
data true

condition <ipaddr>127.0.0.1/327 == 127.0.0.1
data ERROR offset 8 Failed to parse field

condition <ipaddr>127.0.0.1/32 == 127.0.0.1
data true

condition (/foo/)
data ERROR offset 1 Conditional check cannot begin with a regular expression

#
#  Tests for (FOO).
#
condition (1)
data true

condition (0)
data false

condition (true)
data true

condition (false)
data false

condition ('')
data false

condition ("")
data false

#
#  Integers are true, as are non-zero strings
#
condition (4)
data true

condition ('a')
data true

condition (a)
data ERROR offset 1 Expected a module return code

#
#  Module return codes are OK
#
condition (ok)
data ok

condition (handled)
data handled

condition (fail)
data fail

condition ("a")
data true

condition (`a`)
data `a`

condition (User-name)
data &User-Name

#
#  Forbidden data types in cast
#
condition (<vsa>"foo" == &User-Name)
data ERROR offset 2 Forbidden data type in cast

#
#  Must have attribute references on the LHS of a condition.
#
condition ("foo" == &User-Name)
data ERROR offset 1 Cannot use attribute reference on right side of condition

#
#  If the LHS is a cast to a type, and the RHS is an attribute
#  of the same type, then re-write it so that the attribute
#  is on the LHS of the condition.
#
condition <string>"foo" == &User-Name
data &User-Name == "foo"

condition <integer>"%{expr: 1 + 1}" < &NAS-Port
data &NAS-Port > "%{expr: 1 + 1}"

condition &Filter-Id == &Framed-IP-Address
data ERROR offset 0 Attribute comparisons must be of the same data type

condition <ipaddr>127.0.0.1 == &Filter-Id
data ERROR offset 0 Attribute comparisons must be of the same data type

condition &Tmp-Integer64-0 == &request:Foo-Stuff-Bar
data &Tmp-Integer64-0 == &Foo-Stuff-Bar

condition &Tmp-Integer64-0 == &reply:Foo-Stuff-Bar
data &Tmp-Integer64-0 == &reply:Foo-Stuff-Bar

#
#  Casting attributes of different size
#
condition <ipaddr>&Tmp-Integer64-0 == &Framed-IP-Address
data ERROR offset 0 Cannot cast to attribute of incompatible size

condition <ipaddr>&PMIP6-Home-IPv4-HoA == &Framed-IP-Address
data ERROR offset 0 Cannot cast to attribute of incompatible size

# but these are allowed
condition <ether>&Tmp-Integer64-0 == "%{module: foo}"
data <ether>&Tmp-Integer64-0 == "%{module: foo}"

condition <ipaddr>&Filter-Id == &Framed-IP-Address
data <ipaddr>&Filter-Id == &Framed-IP-Address

condition <ipaddr>&Class == &Framed-IP-Address
data <ipaddr>&Class == &Framed-IP-Address

#
#  Tags of zero mean restrict to attributes with no tag
#
condition &Tunnel-Password:0 == "Hello"
data &Tunnel-Password:0 == "Hello"

condition &Tunnel-Password:1 == "Hello"
data &Tunnel-Password:1 == "Hello"

#
# Single quoted strings are left as-is
#
condition &Tunnel-Password:1 == 'Hello'
data &Tunnel-Password:1 == 'Hello'

#
#  zero offset into arrays get parsed and ignored
#
condition &User-Name[0] == "bob"
data &User-Name[0] == "bob"

condition &User-Name[1] == "bob"
data &User-Name[1] == "bob"

condition &User-Name[n] == "bob"
data &User-Name[n] == "bob"

condition &Tunnel-Password:1[0] == "Hello"
data &Tunnel-Password:1[0] == "Hello"

condition &Tunnel-Password:1[3] == "Hello"
data &Tunnel-Password:1[3] == "Hello"

#
#  This is allowed for pass2-fixups.  Foo-Bar MAY be an attribute.
#  If so allow it so that pass2 can fix it up.  Until then,
#  it's an unknown attribute
#
condition &Foo-Bar
data &Foo-Bar

#  Same types are optimized
#
#  FIXME: the tests don't currently run the "pass2" checks.
#  This test should really be:
#
#	data &Acct-Input-Octets > &Session-Timeout
#
condition &Acct-Input-Octets > "%{Session-Timeout}"
data &Acct-Input-Octets > "%{Session-Timeout}"

#  Separate types aren't optimized
condition &Acct-Input-Octets-64 > "%{Session-Timeout}"
data &Acct-Input-Octets-64 > "%{Session-Timeout}"

#
#  Parse OIDs into known attributes, where possible.
#
condition &Attr-26.24757.84.9.5.4 == 0x1a99
data &WiMAX-PFDv2-Src-Port == 6809

#
#  This OID is known, but the data is malformed.
#  This is disallowed.  Fix the configuration so that
#  it works.
#
condition &Attr-26.24757.84.9.5.7 == 0x1a99
data ERROR offset 27 Failed to parse value for attribute

#  This one is really unknown
condition &Attr-26.24757.84.9.5.15 == 0x1a99
data &Attr-26.24757.84.9.5.15 == 0x1a99

#
#  Invalid array references.
#
condition &User-Name[a] == 'bob'
data ERROR offset 11 Array index is not an integer

condition &User-Name == &Filter-Id[a]
data ERROR offset 25 Array index is not an integer

#
#  This one is still wrong.
#
condition User-Name[a] == 'bob'
data false

#
#  Bounds checks...
#
condition &User-Name[1001] == 'bob'
data ERROR offset 11 Invalid array reference '1001' (should be between 0-1000)

condition &User-Name[-1] == 'bob'
data ERROR offset 11 Invalid array reference '-1' (should be between 0-1000)

#
#  Tags
#
condition &Tunnel-Private-Group-Id:10 == 'test'
data &Tunnel-Private-Group-Id:10 == 'test'

condition &User-Name:10 == 'test'
data ERROR offset 10 Attribute 'User-Name' cannot have a tag

#
#  Tags are always wrong for attributes which aren't tagged.
#
condition &User-Name:0 == 'test'
data ERROR offset 10 Attribute 'User-Name' cannot have a tag

#
#  Bounds checks...
#
condition &Tunnel-Private-Group-Id:32 == 'test'
data ERROR offset 25 Invalid tag value '32' (should be between 0-31)

condition &request:Tunnel-Private-Group-Id:-1 == 'test'
data ERROR offset 33 Invalid tag value '-1' (should be between 0-31)

#
#  Sometimes the attribute/condition parser needs to fallback to bare words
#
condition request:Foo == 'request:Foo'
data true

condition request:Foo+Bar == request:Foo+Bar
data true

condition &request:Foo+Bar == 'request:Foo+Bar'
data ERROR offset 12 Unexpected text after unknown attr

condition 'request:Foo+d' == &request:Foo+Bar
data ERROR offset 31 Unexpected text after unknown attr

#  Attribute tags are not allowed for unknown attributes
condition &request:FooBar:0 == &request:FooBar
data ERROR offset 15 Unexpected text after unknown attr

condition &not-a-list:User-Name == &not-a-list:User-Name
data ERROR offset 1 Invalid list qualifier

# . is a valid dictionary name attribute, so we can't error out in pass1
condition &not-a-packet.User-Name == &not-a-packet.User-Name
data &not-a-packet.User-Name == &not-a-packet.User-Name

#
#  The LHS is a string with ASCII 5C 30 30 30 inside of it.
#
condition ('i have scary embedded things\000 inside me' == "i have scary embedded things\000 inside me")
data false

#
#  'Unknown' attributes which are defined in the main dictionary
#  should be resolved to their real names.
condition &Attr-1 == 'bar'
data &User-Name == 'bar'

condition &Vendor-11344-Attr-1 == 127.0.0.1
data &FreeRADIUS-Proxied-To == 127.0.0.1

condition &FreeRADIUS-Attr-1 == 127.0.0.1
data &FreeRADIUS-Proxied-To == 127.0.0.1

#
#  Escape the backslashes correctly
#  And print them correctly
#
condition &User-Name =~ /@|\\/
data &User-Name =~ /@|\\/

condition &User-Name == '\\'
data &User-Name == '\\'

condition &User-Name !~ /^foo\nbar$/
data !&User-Name =~ /^foo\nbar$/

condition &User-Name == "@|\\"
data &User-Name == "@|\\"

condition &User-Name != "foo\nbar"
data !&User-Name == "foo\nbar"

condition User-Name =~ /^([^\\]*)\\(.*)$/
data &User-Name =~ /^([^\\]*)\\(.*)$/

#
#  We require explicit casts
#
condition 192.168.0.0/16 > 192.168.1.2
data false

condition <ipv4prefix>192.168.0.0/16 > 192.168.1.2
data true

condition <ipv4prefix>&NAS-IP-Address == 192.168.0.0/24
data <ipv4prefix>&NAS-IP-Address == 192.168.0.0/24

condition <ipv4prefix>192.168.0.0/24 > &NAS-IP-Address
data <ipv4prefix>192.168.0.0/24 > &NAS-IP-Address

#
#  We add casts to the LHS if necessary
#
condition &NAS-IP-Address < &PMIP6-Home-IPv4-HoA
data <ipv4prefix>&NAS-IP-Address < &PMIP6-Home-IPv4-HoA

condition &NAS-IP-Address < 192.168/16
data <ipv4prefix>&NAS-IP-Address < 192.168.0.0/16

condition &NAS-IP-Address < "%{echo: 192.168/16}"
data <ipv4prefix>&NAS-IP-Address < "%{echo: 192.168/16}"

condition &NAS-IP-Address < `/bin/echo 192.168/16`
data <ipv4prefix>&NAS-IP-Address < `/bin/echo 192.168/16`
