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

proto-dictionary radius
tmpl-rules allow_unresolved=yes allow_unknown=yes

condition &request.User-Name == &reply.User-Name
match (&request.User-Name == &reply.User-Name)

# All IP address literals should be parsed as prefixes
condition ("foo\
match ERROR offset 2: Unterminated string

condition ("foo
match ERROR offset 2: Unterminated string

condition ()
match ERROR offset 2: No operand found.  Expected &ref, literal, 'quoted literal', "%{expansion}", or enum value

condition (!)
match ERROR offset 3: No operand found.  Expected &ref, literal, 'quoted literal', "%{expansion}", or enum value

condition (|| b)
match ERROR offset 2: No operand found.  Expected &ref, literal, 'quoted literal', "%{expansion}", or enum value

condition ((ok || handled) foo)
match ERROR offset 18: Invalid operator

# escapes in names are illegal
condition (ok\ foo || handled)
match ERROR offset 4: Invalid operator

#
#  @todo - parsing - This is just a bare word comparison, and should be disallowed?
#
condition (Service-Type == 000-111)
match (Service-Type == (0 - 111))

#
#  @todo - parsing - many of these "invalid operator" errors should have additional code added to xlat_expr.c,
#  so that it finds out which specific parse problem it is, and gives a better error message.
#
condition (ok FOO handled)
match ERROR offset 5: Invalid operator

condition (ok !x handled)
match ERROR offset 5: Invalid operator

condition (ok =x handled)
match ERROR offset 5: Invalid operator

#
# Re-enable when we have proper bareword xlat tokenization
#
#condition (ok == handled"foo")
#match ERROR offset 14 Expected closing brace or logical operator

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

# sillyness is OK, but cleaned up.
condition ((((((ok))))))
match %expr.rcode('ok')

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

condition (!ok)
match !%expr.rcode('ok')

condition !(ok)
match !%expr.rcode('ok')

condition !!ok
match ERROR offset 2: Double operator is invalid

#
# @todo - peephole - do optimization to get rid of double negation?
#
condition !(!ok)
match !!%expr.rcode('ok')

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

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

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

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

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

#
#  @todo - parsing - unit_test_attribute should really check if we read the whole string
#
condition ((a == b) || (c == d)))
match ((a == b) || (c == d))
#match ERROR offset 23: Unexpected closing brace

condition (handled && (&Packet-Type == Access-Challenge))
match (%expr.rcode('handled') && (&Packet-Type == Access-Challenge))

# This is OK, without the braces
condition handled && &Packet-Type == Access-Challenge
match (%expr.rcode('handled') && (&Packet-Type == Access-Challenge))

# and this, though it's not a good idea.
condition handled &&&Packet-Type == Access-Challenge
match (%expr.rcode('handled') && (&Packet-Type == Access-Challenge))

condition &reply == &request
match ERROR offset 1: Cannot use list references in condition

condition &reply == "hello"
match ERROR offset 1: Cannot use list references in condition

condition "hello" == &reply
match ERROR offset 12: Cannot use list references in condition

condition &User-Name == &User-Password
match (&User-Name == &User-Password)

condition &User-Name != &User-Password
match (&User-Name != &User-Password)

condition !(&User-Name != &User-Password)
match !(&User-Name != &User-Password)

condition (ipv6addr)::1
match ::1

condition (ipaddr)&Filter-Id == &Framed-IP-Address
match ((ipaddr)&Filter-Id == &Framed-IP-Address)

#
#  We can automatically promote things as needed.  But if the
#  user forces incompatible types, then that's an error.
#
#  @todo - cast - this seems rather a bit wrong
#
condition (ipaddr)&Filter-Id == (uint) &Framed-IP-Address
match (((ipaddr)&Filter-Id == uint) & Framed-IP-Address)

#
#  @todo - cast - yeah, this is quite wrong.
#
condition (blerg)&Filter-Id == "foo"
match (blerg & (Filter-Id == "foo"))

#
#  Normalize things
#
condition (ipaddr)127.0.0.1 < &Framed-IP-Address
match (127.0.0.1 < &Framed-IP-Address)

# =* and !* are only for attrs / lists
condition "foo" !* bar
match ERROR offset 7: Invalid operator

condition "foo" =* bar
match ERROR offset 7: Invalid operator

# existence checks don't need the RHS
condition &User-Name =* bar
match ERROR offset 12: Invalid operator

condition &User-Name !* bar
match ERROR offset 12: Invalid operator

condition !&User-Name =* bar
match ERROR offset 13: Invalid operator

condition !&User-Name !* bar
match ERROR offset 13: Invalid operator

# redundant casts get squashed
condition (ipaddr)&Framed-IP-Address == 127.0.0.1
match (&Framed-IP-Address == 127.0.0.1)

condition (cidr)&Framed-IP-Address <= 192.168.0.0/16
match ((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
match (&Framed-IP-Address <= 192.168.0.0/16)

# string attributes must be string
condition &User-Name == "bob"
match (&User-Name == "bob")

condition &User-Name == 'bob'
match (&User-Name == 'bob')

#
#  @todo - parsing - bare-word bob isn't a valid enum name
#
condition &User-Name == bob
match (&User-Name == bob)

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

#
#  @todo - parsing - no automatic type casting
#
condition &Session-Timeout == '10'
match  (&Session-Timeout == '10')

# 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}"
match (&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
match ERROR offset 10: Failed parsing string as type 'uint32'

#
#  @todo - parsing - there is no enum named "X"
#
condition &NAS-Port == X
match (&NAS-Port == X)

#
#  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"
match true

condition (ipaddr)127.0.0.1 == "%md4(' 127.0.0.1')"
match (127.0.0.1 == "%md4(' 127.0.0.1')")

#
#  Bare %{...} is allowed.
#
condition (ipaddr)127.0.0.1 == %md4('127.0.0.1')
match (127.0.0.1 == %md4('127.0.0.1'))

condition (ipaddr)127.0.0.1 == %md4("SELECT user FROM table WHERE user='%{User-Name}'")
match (127.0.0.1 == %md4("SELECT user FROM table WHERE user='%{User-Name}'"))

condition (ether) 00:11:22:33:44:55 == "00:11:22:33:44:55"
match true

condition (ether) 00:11:22:33:44:55 == "%md4('00:11:22:33:44:55')"
match (00:11:22:33:44:55 == "%md4('00:11:22:33:44:55')")

condition (ether) 00:XX:22:33:44:55 == 00:11:22:33:44:55
match ERROR offset 12: Missing separator, expected ':'

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

condition 1
match 1

condition false
match false

condition 0
match 0

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

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

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

condition true || (&User-Name == "bob")
match true

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

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

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

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

condition ('foo' == 'foo')
match true

condition ("foo" == "%md4(' foo')")
match ("foo" == "%md4(' foo')")

condition ("foo bar" == "%md4(' foo')")
match ("foo bar" == "%md4(' foo')")

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

condition ("foo" == 'bar')
match 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")
match (&User-Name == "bob")

condition (&User-Name == "%md4(' blah')")
match (&User-Name == "%md4(' blah')")

condition (ipaddr)127.0.0.1 == 2130706433
match true

# /32 suffix should be trimmed for this type
condition (ipaddr)127.0.0.1/32 == 127.0.0.1
match true

condition (ipaddr)127.0.0.1/327 == 127.0.0.1
match ERROR offset 9: Invalid IPv4 mask length "/327".  Should be between 0-32

condition (ipaddr)127.0.0.1/32 == 127.0.0.1
match true

condition (/foo/)
match ERROR offset 2: Unexpected regular expression

#
#  Tests for (FOO).
#
condition (1)
match 1

condition (0)
match 0

condition (true)
match true

condition (false)
match false

condition ('')
match ''

condition ("")
match ""

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

condition ('a')
match 'a'

condition (a)
match ERROR offset 1: Unexpected text - attribute names must prefixed with '&'

#
#  Module return codes are OK
#
condition (ok)
match %expr.rcode('ok')

condition (handled)
match %expr.rcode('handled')

condition (fail)
match %expr.rcode('fail')

condition ("a")
match "a"

condition (&User-Name)
match &User-Name

#
#  Forbidden data types in cast
#
condition ((vsa)"foo" == &User-Name)
match ERROR offset 2: Invalid data type 'vsa' in cast

#
#  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
match ("foo" == &User-Name)

# This used to be expr, but expr isn't a builtin, so it failed...
condition (integer)"%md4(' 1 + 1')" < &NAS-Port
match ((uint32)"%md4(' 1 + 1')" < &NAS-Port)

#
#  The string gets parsed as an IP address.
#

condition &Filter-Id == &Framed-IP-Address
match (&Filter-Id == &Framed-IP-Address)

condition (ipaddr)127.0.0.1 == &Filter-Id
match (127.0.0.1 == &Filter-Id)

condition &Acct-Input-Octets64 == &request.Foo-Stuff-Bar
match (&Acct-Input-Octets64 == &request.Foo-Stuff-Bar)

condition &Acct-Input-Octets64 == &reply.Foo-Stuff-Bar
match (&Acct-Input-Octets64 == &reply.Foo-Stuff-Bar)

#
#  Casting attributes of different size
#
condition (ipaddr)&Acct-Input-Octets64 == &Framed-IP-Address
match ERROR offset 9: Cannot cast type 'uint64' to 'ipaddr'

#
#  LHS is a prefix, which _might_ be castable to an address
#  if the prefix is /32.  We don't know enough at compile time,
#  so this may be a run-time failure.
#
condition (ipaddr)&PMIP6-Home-IPv4-HoA == &Framed-IP-Address
match ((ipaddr)&PMIP6-Home-IPv4-HoA == &Framed-IP-Address)

# but these are allowed
condition (ether)&Acct-Input-Octets64 == "%interpreter('foo')"
match ((ether)&Acct-Input-Octets64 == "%interpreter('foo')")

condition (ipaddr)&Filter-Id == &Framed-IP-Address
match ((ipaddr)&Filter-Id == &Framed-IP-Address)

condition (ipaddr)&Class == &Framed-IP-Address
match ((ipaddr)&Class == &Framed-IP-Address)

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

condition &User-Name[1] == "bob"
match (&User-Name[1] == "bob")

condition &User-Name[n] == "bob"
match (&User-Name[n] == "bob")

#
#  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
match &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}"
match (&Acct-Input-Octets > "%{Session-Timeout}")

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

#
#  Parse OIDs into known attributes, where possible.
#
#  @todo - peephole - Perhaps this isn't done because the xlat name is taken from the
#  input, and not from the canonicalized names.
#
condition &26.24757.84.9.5.4 == 0x1a99
match  (&26.24757.84.9.5.4 == 0x1a99)
#match &Vendor-Specific.WiMAX.Packet-Flow-Descriptor-v2.Classifier.Src-Spec.Port == 6809

#
#  This OID is known, but the data is malformed.
#  Allow it so that we can look for malformed attributes
#  in packets.
#
condition &raw.26.24757.84.9.5.7 == 0x1a99
match (&raw.26.24757.84.9.5.7 == 0x1a99)
#match &raw.Vendor-Specific.WiMAX.Packet-Flow-Descriptor-v2.Classifier.Src-Spec.Assigned == 0x1a99

#  This one is really unknown
condition &26.24757.84.9.5.15 == 0x1a99
match (&26.24757.84.9.5.15 == 0x1a99)
#match &Vendor-Specific.WiMAX.Packet-Flow-Descriptor-v2.Classifier.Src-Spec.15 == 0x1a99

#
#  Invalid array references.
#
condition &User-Name[a] == 'bob'
match ERROR offset 12: Invalid array index

condition &User-Name == &Filter-Id[a]
match ERROR offset 26: Invalid array index

#
#  Bounds checks...
#
condition &User-Name[1001] == 'bob'
match ERROR offset 12: Invalid array index '1001' (should be between 0-1000)

condition &User-Name[-1] == 'bob'
match ERROR offset 12: Invalid array index

#
#  Sometimes the attribute/condition parser needs to fallback to bare words
#
#  @todo - bare word - this is likely treating the LHS as an enum which is impossible here
#
condition request.Foo == 'request.Foo'
match (request.Foo == 'request.Foo')

# Bareword compared with bareword is true
condition request.Foo+Bar == request.Foo+Bar
match (request.Foo+Bar == request.Foo+Bar)

condition &request.Foo+Bar == 'request.Foo+Bar'
match ((&request.Foo + Bar) == 'request.Foo+Bar')
#match ERROR offset 13: Unexpected text after attribute reference

#
#  @todo - bare word - this is wrong
#
condition 'request.Foo+d' == &request.Foo+Bar
match ('request.Foo+d' == (&request.Foo + Bar))
#match ERROR offset 32: Unexpected text after attribute reference

#  Attribute tags are not allowed for unknown attributes
condition &request.FooBar:0 == &request.FooBar
match ERROR offset 16: Unexpected text after attribute reference

condition &not-a-list:User-Name == &not-a-list:User-Name
match ERROR offset 12: Unexpected text after attribute reference

# . 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
match (&not-a-packet.User-Name == &not-a-packet.User-Name)

#
#  The LHS is a string with ASCII 5C 30 30 30 inside of it vs the RHS which should contain ASCII 0.
#
condition ('i have scary embedded things\000 inside me' == "i have scary embedded things\000 inside me")
match false

#
#  'Unknown' attributes which are defined in the main dictionary
#  should be resolved to their real names.
#
#  @todo - peephole - resolve it and change its name
#
condition &1 == 0x616263
match (&1 == 0x616263)

condition &26.11344.1 == 0x7f000001
match (&26.11344.1 == 0x7f000001)
#match &Vendor-Specific.FreeRADIUS.Proxied-To == 127.0.0.1

#
#  Escape the backslashes correctly
#  And print them correctly
#
condition &User-Name == '\\'
match (&User-Name == '\\')

condition &User-Name == "@|\\"
match (&User-Name == "@|\\")

condition &User-Name != "foo\nbar"
match (&User-Name != "foo\nbar")

#
#  We infer that the LHS is a prefix and the RHS is
#  and ipaddr without requiring an explicit cast.
#
condition 192.168.0.0/16 > 192.168.1.2
match true

condition (ipv4prefix)192.168.0.0/16 > 192.168.1.2
match true

condition (ipv4prefix)&NAS-IP-Address == 192.168.0.0/24
match ((ipv4prefix)&NAS-IP-Address == 192.168.0.0/24)

#
#  rewrite so that the attribute is on the LHS
#  and, move the cast to the attribute, as the RHS
#  is parsed as ipv4prefix
#
condition (ipv4prefix)192.168.0.0/24 > &NAS-IP-Address
match (192.168.0.0/24 > &NAS-IP-Address)

#
#  This is allowed and means "the list is not empty"
#
condition (&reply.)
match &reply.

#
#  Attributes with a protocol namespace
#
condition &radius.User-Name == 'bob'
match (&radius.User-Name == 'bob')

condition !(!(0))
match !!0

condition (true) && (false)
match false

#
#  More short-circuit evaluations
#
condition (&User-Name == "bob") && (false)
match false

#
#  This could return a string of the User-Name, *or* a boolean true.
#
condition &User-Name || true
match (&User-Name || true)

#
#  The evaluator does not know that the LHS returns a boolean, so it
#  can't optimize it.
#
condition (&User-Name == "bob") || (true)
match true

#
#  A && (B || C) is not the same as (A && B) || C, for 0/1/1
#
#	0 && (1 || 1) = 0 && 1 == 0
#	(0 && 1) || 1 = 0 || 1 == 1
#
condition (&User-Name == "bob") && ((&User-Password == "bob") || &EAP-Message)
match ((&User-Name == "bob") && ((&User-Password == "bob") || &EAP-Message))

#
#  Triple-quoted strings are parsed, but are printd as single-quoted ones.  Sue me.
#
condition (&User-Name == """bob "quoted" """)
match (&User-Name == "bob \"quoted\" ")


count
match 305
