#
#  Tests for parsing conditions in the new xlat_expr framework.
#
#  $Id: bfefd84f20c9f6cea84f68d963d12ec3dcbe0e8d $
#

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

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

xlat_purify ("foo
match ERROR offset 2: Unterminated string

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

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

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

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

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

#
#  0 - 111 is smaller than zero, and Service-Type is uint32.
#
xlat_purify (&Service-Type == 000-111)
match (&Service-Type == (0 - 111))

xlat_purify (ok FOO handled)
match ERROR offset 5: Invalid operator

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

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

#
# Re-enable when we have proper bareword xlat tokenization
#
#xlat_purify (ok == handled"foo")
#match ERROR offset 14 Unexpected text after condition

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

# sillyness is OK, but cleaned up.
#
#  We should really allow parsing of bare words, too?
#
#xlat_purify ((((((ok))))))
#match ok

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

#xlat_purify (!ok)
#match !ok

#xlat_purify !(ok)
#match !ok

xlat_purify !!true
match ERROR offset 2: Double operator is invalid

#xlat_purify !(!ok)
#match ok

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

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

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

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

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

#
#  '!' is higher precedence that '==', so the '!' applies just to the User-Name
#
xlat_purify (!&User-Name == &User-Password || &Filter-Id == &Reply-Message)
match ERROR offset 2: Operator '!' is only applied to the left hand side of the '==' operation, add (..) to evaluate the operation first

#
#  LHS is a boolean, which we then compare to a string
#
xlat_purify (!"foo" == "bar")
match ERROR offset 2: Operator '!' is only applied to the left hand side of the '==' operation, add (..) to evaluate the operation first

xlat_purify ((!"foo") == "bar")
match (!"foo" == "bar")

xlat_purify ((!"foo") == false)
match true

#
#  @todo - unit_test_attribute - add a flag which says to parse the FULL thing, or only parse part of it?
#
xlat_purify ((&User-Name == &Filter-Id) || (&Reply-Message == &User-Password)))
match Passed in 67 characters, but only parsed 66 characters

#
#  Truthy strings get omitted.
#
xlat_purify ('handled' && (&Packet-Type == Access-Challenge))
match (&Packet-Type == Access-Challenge)

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

# This is OK, without the braces
xlat_purify 'handled' && &Packet-Type == Access-Challenge
match (&Packet-Type == Access-Challenge)

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

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

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

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


#
#  We don't need to convert != to !(COND) for normal checks
#
xlat_purify &User-Name == &User-Password
match (&User-Name == &User-Password)

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

xlat_purify !&User-Name != &User-Password
match ERROR offset 1: Operator '!' is only applied to the left hand side of the '!=' operation, add (..) to evaluate the operation first

#
#  We allow a cast for the existence check.  Why not?
#
xlat_purify (ipv6addr)::1
match ::1

# new casts are allowed, too.
xlat_purify (ipv6addr)::1
match ::1

xlat_purify (ipv6addr)"xxx"
match ERROR offset 11: Failed to parse IPv6 address string "xxx"

#
#  Various casts
#
xlat_purify (ipaddr)&Filter-Id == &Framed-IP-Address
match ((ipaddr)&Filter-Id == &Framed-IP-Address)

#
#  Don't normalize things
#
xlat_purify (ipaddr)127.0.0.1 < &Framed-IP-Address
match (127.0.0.1 < &Framed-IP-Address)

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

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

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

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

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

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

# Automatic type inference means this is fine

#
#  @todo - peephole - resolve the RHS to the type of the LHS
#
xlat_purify &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.
#
xlat_purify &Event-Timestamp == "January 1, 2012 %{User-Name}"
match (&Event-Timestamp == "January 1, 2012 %{User-Name}")

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

# literals are parsed when the conditions are parsed
xlat_purify (integer)X == 1
match ERROR offset 10: Failed parsing string as type 'uint32'

#
#  @todo - parsing - resolution is delayed, so we don't know where in the input
#  string the RHS is.
#
xlat_purify &NAS-Port == X
match ERROR offset 1: Failed parsing string as type 'uint32'
#match ERROR offset 13: Failed parsing string as type 'uint32'

#
#  The RHS is a static string, so this gets mashed to a literal,
#  and then statically evaluated.
#
xlat_purify (ipaddr)127.0.0.1 == "127.0.0.1"
match true

# LHS is IPaddr, RHS is string (malformed IP address).
# We can only fail this at run-time.
xlat_purify (ipaddr)127.0.0.1 == "%md4(' 127.0.0.1')"
match (127.0.0.1 == "%md4(' 127.0.0.1')")

#
#  Bare %{...} is allowed.
#

# Invalid cast from octets to ipaddr.
xlat_purify (ipaddr)127.0.0.1 == %md4('127.0.0.1')
match (127.0.0.1 == %md4('127.0.0.1'))

xlat_purify (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}'"))

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

# Invalid cast from octets to ether.
xlat_purify (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')")

xlat_purify (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.
#
xlat_purify true
match true

# @todo - parsing - for conditions, this should evaluate to "true".  However, this evaluation
#  only occurs in the condition code, and not in the xlat code!
xlat_purify 1
match 1

xlat_purify false
match false

xlat_purify 0
match 0

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

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

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

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

xlat_purify ('foo' == 'foo')
match true

#
#  MD4 hash is not equal to other things
#
xlat_purify ("foo" == "%md4(' foo')")
match false

xlat_purify ("foo bar" == "%md4(' foo')")
match false

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

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

xlat_purify (&User-Name == %md4(' blah'))
match (&User-Name == 0x544924d05ec4481925ba3749a096a0a7)

#  and without the double quotes.
xlat_purify (&User-Name == %md4(' blah'))
match (&User-Name == 0x544924d05ec4481925ba3749a096a0a7)

xlat_purify (ipaddr)127.0.0.1 == 2130706433
match true

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

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

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

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

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

xlat_purify (0)
match 0

xlat_purify (true)
match true

xlat_purify (false)
match false

xlat_purify ('')
match ''

xlat_purify ("")
match ""

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

xlat_purify ('a')
match 'a'

#xlat_purify (a)
#match ERROR offset 1: Expected a module return code

#
#  Module return codes are OK
#
#xlat_purify (ok)
#match ok

#xlat_purify (handled)
#match handled

#xlat_purify (fail)
#match fail

xlat_purify ("a")
match "a"

xlat_purify (&User-Name)
match &User-Name

#
#  Forbidden data types in cast
#
xlat_purify ((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.
#
xlat_purify (string)"foo" == &User-Name
match ("foo" == &User-Name)

# This used to be expr, but expr isn't a builtin, so it failed...

#
#  @todo - peephole - arguably this is a failed thing, we should get:
#
#  ERROR: Failed casting 0x002ade8665c69219ca16bd108d92c8d5 to data type uint32: Invalid cast from octets to uint32.  Source length 16 is greater than destination type size 4
#
xlat_purify (integer)"%md4(' 1 + 1')" < &NAS-Port
match ((uint32)"0x002ade8665c69219ca16bd108d92c8d5" < &NAS-Port)

#
#  The string gets parsed as an IP address.
#
xlat_purify &Filter-Id == &Framed-IP-Address
match (&Filter-Id == &Framed-IP-Address)

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

xlat_purify &Acct-Input-Octets64 == &request.Callback-Id
match (&Acct-Input-Octets64 == &request.Callback-Id)

xlat_purify &Acct-Input-Octets64 == &reply.Callback-Id
match (&Acct-Input-Octets64 == &reply.Callback-Id)

#
#  Casting attributes of different size
#
xlat_purify (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.
#
xlat_purify (ipaddr)&PMIP6-Home-IPv4-HoA == &Framed-IP-Address
match ((ipaddr)&PMIP6-Home-IPv4-HoA == &Framed-IP-Address)

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

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

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

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

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

xlat_purify &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
#
#xlat_purify &Foo-Bar
#match &Foo-Bar

xlat_purify &Acct-Input-Octets > "%{Session-Timeout}"
match (&Acct-Input-Octets > "%{Session-Timeout}")

xlat_purify &Acct-Input-Octets > &Session-Timeout
match (&Acct-Input-Octets > &Session-Timeout)

#  Separate types aren't optimized
xlat_purify &Acct-Input-Octets64 > &Session-Timeout
match (&Acct-Input-Octets64 > &Session-Timeout)

#
#  Parse OIDs into known attributes, where possible.
#
# @todo - peephole - resolve the unknown attribute to something real!
xlat_purify &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.
#
xlat_purify &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
xlat_purify &26.24757.84.9.5.15 == 0x1a99
match ERROR offset 18: Unknown attributes not allowed here
#match &Vendor-Specific.WiMAX.Packet-Flow-Descriptor-v2.Classifier.Src-Spec.15 == 0x1a99

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

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

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

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

#
#  attributes MUST be prefixed with '&'.
#
xlat_purify request.Foo == 'request.Foo'
match true

xlat_purify &request.Foo == 'request.Foo'
match ERROR offset 10: Attribute 'Foo' not found in namespace 'internal': Unresolved attributes are not allowed here
#match ERROR offset 10: Attribute 'Foo' not found.  Searched in: RADIUS, internal: Unresolved attributes are not allowed here

xlat_purify &not-a-list.User-Name == &not-a-list.User-Name
match ERROR offset 2: Attribute 'not' not found.  Searched in: RADIUS, internal: Unresolved attributes are not allowed here

# . is a valid dictionary name attribute, so we can't error out in pass1
xlat_purify &not-a-packet.User-Name == &not-a-packet.User-Name
match ERROR offset 2: Attribute 'not' not found.  Searched in: RADIUS, internal: Unresolved attributes are not allowed here

#
#  The LHS is a string with ASCII 5C 30 30 30 inside of it vs the RHS which should contain ASCII 0.
#
xlat_purify ('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 to something real
xlat_purify &1 == 0x616263
match (&1 == 0x616263)
#match (&User-Name == 'abc')

# @todo - peephole - resolve it to something real
#xlat_purify &26.11344.1 == 0x7f000001
#match &Vendor-Specific.FreeRADIUS.Proxied-To == 127.0.0.1

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

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

xlat_purify &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.
xlat_purify 192.168.0.0/16 > 192.168.1.2
match true


xlat_purify (ipv4prefix)192.168.0.0/16 > 192.168.1.2
match true

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

#
#  Don't rewrite so that the attribute is on the LHS
#  and, move the cast to the attribute, as the RHS
#  is parsed as ipv4prefix
#
xlat_purify (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"
#
xlat_purify (&reply)
match &reply

#
#  Expansions of environment variables
#  and empty strings
#
xlat_purify ("$ENV{SOMETHING_OR_OTHER}" == '')
match false

#
#  Attributes with a protocol namespace
#
# @todo - normalization - if the explicit namespace is the same as the implicit one, we can omit
# the explicit one?  But this is largely due to the printing fixes, where we just
# print the tmpl name as-is.
xlat_purify &radius.User-Name == 'bob'
match (&radius.User-Name == 'bob')
#match &User-Name == 'bob'

xlat_purify !(!(0))
match false

xlat_purify (true) && (false)
match false

#
#  Purify logical operators:
#
#  * TRUE OP EXPR --> TRUE
#  * FALSE OP EXPR --> EXPR
#
xlat_purify true || (&User-Name == "bob")
match true

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

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

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

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

xlat_purify (&User-Name == "bob") && (false) && (&User-Password == "hello")
match false

xlat_purify (&User-Name == "bob") && (&User-Password == "hello") && false
match false

xlat_purify (&User-Name == "bob") && (&User-Password == "hello") && (1 > 2)
match false

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

xlat_purify 1 || 2
match 1

xlat_purify 1 || 2 || (&User-Name == "bob")
match 1

xlat_purify (&User-Name == "bob") || 1 || 2
match 1

xlat_purify 1 && 2
match 2

#
#  Cases which always match should be omitted
#
xlat_purify (&User-Name == "bob") && true
match (&User-Name == "bob")

xlat_purify (&User-Name == "bob") && (&User-Password == "hello") && true
match ((&User-Name == "bob") && (&User-Password == "hello"))

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

xlat_purify (&User-Name == "bob") && (&User-Password == "hello") && ((&User-Name == "bob") || true)
match ((&User-Name == "bob") && (&User-Password == "hello"))

xlat_purify (&User-Name == "bob") && (&User-Password == "hello") && ((&User-Name == "bob") || (1 < 2))
match ((&User-Name == "bob") && (&User-Password == "hello"))


#
#  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
#
xlat_purify (&User-Name == "bob") && ((&User-Password == "bob") || &EAP-Message)
match ((&User-Name == "bob") && ((&User-Password == "bob") || &EAP-Message))

#
#  rcode tests
#
xlat_purify handled && (&User-Name == "bob")
match (%expr.rcode('handled') && (&User-Name == "bob"))

xlat_purify (&User-Name == "bob") && (&User-Password == "bob") && handled
match ((&User-Name == "bob") && (&User-Password == "bob") && %expr.rcode('handled'))

xlat_purify handledx
match ERROR offset 8: Invalid operator

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

#
#  Automatic casting
#
xlat_purify (192.168.0.1 == "192.168.0.1")
match true

xlat_purify (192.168.0.1 != "192.168.0.1")
match false

xlat_purify (192.168.0.1 != "192.168.0.2")
match true

#
#  Types are different, so they don't match.
#
xlat_purify (192.168.0.1 === "192.168.0.1")
match false

xlat_purify (192.168.0.1 !== "192.168.0.1")
match false

xlat_purify (192.168.0.1 !== "192.168.0.2")
match false

xlat_purify (192.168.0.1 === 192.168.0.1)
match true

xlat_purify (192.168.0.1 !== 192.168.0.1)
match false

xlat_purify (192.168.0.1 !== 192.168.0.2)
match true

count
match 324
