summaryrefslogtreecommitdiffstats
path: root/src/tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/tests')
-rw-r--r--src/tests/.gitignore12
-rw-r--r--src/tests/Makefile317
-rw-r--r--src/tests/README29
-rw-r--r--src/tests/all.mk78
-rw-r--r--src/tests/auth/all.mk119
-rw-r--r--src/tests/auth/chap3
-rw-r--r--src/tests/auth/chap.attrs4
-rw-r--r--src/tests/auth/chap_header7
-rw-r--r--src/tests/auth/chap_header.attrs4
-rw-r--r--src/tests/auth/digest3
-rw-r--r--src/tests/auth/digest.attrs25
-rw-r--r--src/tests/auth/md5_password7
-rw-r--r--src/tests/auth/md5_password.attrs4
-rw-r--r--src/tests/auth/password_with_header7
-rw-r--r--src/tests/auth/password_with_header.attrs4
-rw-r--r--src/tests/auth/password_without_header7
-rw-r--r--src/tests/auth/password_without_header.attrs4
-rw-r--r--src/tests/auth/radiusd.conf50
-rw-r--r--src/tests/auth/user_password3
-rw-r--r--src/tests/auth/user_password.attrs4
-rw-r--r--src/tests/auth/wimax3
-rw-r--r--src/tests/auth/wimax.attrs30
-rw-r--r--src/tests/bob2
-rw-r--r--src/tests/comp128-1vectors1024
-rw-r--r--src/tests/comp128-2vectors1024
-rw-r--r--src/tests/comp128-3vectors1024
-rw-r--r--src/tests/config/test.conf114
-rw-r--r--src/tests/dictionary.test11
-rw-r--r--src/tests/digest-01/digest-auth-MD528
-rw-r--r--src/tests/digest-01/digest-auth-MD5_Sess23
-rw-r--r--src/tests/digest-01/digest-auth-int23
-rw-r--r--src/tests/digest-01/digest-auth-noalgo21
-rw-r--r--src/tests/digest-01/digest-auth_int-MD523
-rw-r--r--src/tests/digest-01/digest-auth_int-MD5_Sess24
-rw-r--r--src/tests/digest-01/digest-auth_int-noalgo22
-rw-r--r--src/tests/digest-01/digest-md5-sess21
-rw-r--r--src/tests/eap-fast.conf15
-rw-r--r--src/tests/eap-md5.conf10
-rw-r--r--src/tests/eap-mschapv2.conf10
-rw-r--r--src/tests/eap-pwd.conf11
-rw-r--r--src/tests/eap-tls.conf19
-rw-r--r--src/tests/eap-ttls-eap-mschapv2.conf17
-rw-r--r--src/tests/eap-ttls-eap-tls.conf15
-rw-r--r--src/tests/eap-ttls-mschapv2.conf12
-rw-r--r--src/tests/eap-ttls-pap.conf12
-rw-r--r--src/tests/eapcrypto-01/eapcrypto-out.txt63
-rw-r--r--src/tests/eapmd5-01/client.gdb5
-rw-r--r--src/tests/eapmd5-01/client.sh14
-rw-r--r--src/tests/eapmd5-01/req.txt8
-rw-r--r--src/tests/eapsim-02/check.gdb3
-rw-r--r--src/tests/eapsim-02/client.sh14
-rw-r--r--src/tests/eapsim-02/eapsim-in.txt59
-rw-r--r--src/tests/eapsim-02/eapsim-out.txt161
-rw-r--r--src/tests/eapsim-02/req.txt8
-rw-r--r--src/tests/eapsim-03/check.gdb2
-rw-r--r--src/tests/eapsim-03/client.sh6
-rw-r--r--src/tests/eapsim-03/eapsim-cooked.txt169
-rw-r--r--src/tests/eapsim-03/eapsim-in.txt17
-rw-r--r--src/tests/eapsim-03/eapsim-out.txt169
-rw-r--r--src/tests/eapsim-03/eapsim-sanitize.sed21
-rw-r--r--src/tests/eapsim-03/radiusd-example.txt1506
-rw-r--r--src/tests/eapsim-03/users-example.txt34
-rw-r--r--src/tests/eapsim-04/client.sh6
-rw-r--r--src/tests/eapsim-04/eapsim-cooked.txt169
-rw-r--r--src/tests/eapsim-04/eapsim-in.txt17
-rw-r--r--src/tests/eapsim-04/myvectors.txt136
-rw-r--r--src/tests/eapsim-04/users.txt17
-rw-r--r--src/tests/eapsim-05/check.gdb2
-rw-r--r--src/tests/eapsim-05/client.sh6
-rw-r--r--src/tests/eapsim-05/description.txt2
-rw-r--r--src/tests/eapsim-05/eapsim-cooked.txt148
-rw-r--r--src/tests/eapsim-05/eapsim-in.txt15
-rw-r--r--src/tests/eapsim-05/eapsim-out.txt148
-rw-r--r--src/tests/eapsim-05/eapsim-raw.txt148
-rw-r--r--src/tests/eapsim-05/eapsim-sanitize.sed10
-rw-r--r--src/tests/eapsim-06/check.gdb2
-rw-r--r--src/tests/eapsim-06/client.sh6
-rw-r--r--src/tests/eapsim-06/description.txt24
-rw-r--r--src/tests/eapsim-06/eapsim-cooked.txt184
-rw-r--r--src/tests/eapsim-06/eapsim-in.txt15
-rw-r--r--src/tests/eapsim-06/eapsim-out.txt184
-rw-r--r--src/tests/eapsim-06/eapsim-raw.txt184
-rw-r--r--src/tests/eapsim-06/simtriplets.dat5
-rw-r--r--src/tests/example.com5
-rw-r--r--src/tests/fips186-02/description.txt5
-rw-r--r--src/tests/fips186-02/fips186-2.txt9
-rw-r--r--src/tests/hmac-md5-01/digest1.txt1
-rw-r--r--src/tests/hmac-sha1-01/digest1.txt1
-rw-r--r--src/tests/keywords/3gpp19
-rw-r--r--src/tests/keywords/README.md43
-rw-r--r--src/tests/keywords/all.mk123
-rw-r--r--src/tests/keywords/array53
-rw-r--r--src/tests/keywords/base64141
-rw-r--r--src/tests/keywords/break-error11
-rw-r--r--src/tests/keywords/cache229
-rw-r--r--src/tests/keywords/case-attr-error20
-rw-r--r--src/tests/keywords/case-empty23
-rw-r--r--src/tests/keywords/case-empty-string25
-rw-r--r--src/tests/keywords/case-list19
-rw-r--r--src/tests/keywords/cast-byte25
-rw-r--r--src/tests/keywords/cast-integer25
-rw-r--r--src/tests/keywords/cast-ipaddr442
-rw-r--r--src/tests/keywords/cast-short25
-rw-r--r--src/tests/keywords/cmp20
-rw-r--r--src/tests/keywords/cmp-ipaddr20
-rw-r--r--src/tests/keywords/comments48
-rw-r--r--src/tests/keywords/count-error11
-rw-r--r--src/tests/keywords/crypt151
-rw-r--r--src/tests/keywords/default-input.attrs11
-rw-r--r--src/tests/keywords/else-error14
-rw-r--r--src/tests/keywords/escape67
-rw-r--r--src/tests/keywords/escape-sequences95
-rw-r--r--src/tests/keywords/expand39
-rw-r--r--src/tests/keywords/expr108
-rw-r--r--src/tests/keywords/foreach5
-rw-r--r--src/tests/keywords/foreach-break73
-rw-r--r--src/tests/keywords/foreach-break-246
-rw-r--r--src/tests/keywords/foreach-break-344
-rw-r--r--src/tests/keywords/foreach-break-444
-rw-r--r--src/tests/keywords/foreach-break.attrs18
-rw-r--r--src/tests/keywords/foreach-error5
-rw-r--r--src/tests/keywords/foreach-isolation38
-rw-r--r--src/tests/keywords/foreach-list5
-rw-r--r--src/tests/keywords/foreach-list.attrs21
-rw-r--r--src/tests/keywords/foreach-nested9
-rw-r--r--src/tests/keywords/foreach-nested.attrs25
-rw-r--r--src/tests/keywords/foreach-regex26
-rw-r--r--src/tests/keywords/foreach-regex.attrs16
-rw-r--r--src/tests/keywords/foreach-return52
-rw-r--r--src/tests/keywords/foreach-varied-depth43
-rw-r--r--src/tests/keywords/foreach.attrs18
-rw-r--r--src/tests/keywords/hex141
-rw-r--r--src/tests/keywords/if10
-rw-r--r--src/tests/keywords/if-bob15
-rw-r--r--src/tests/keywords/if-else15
-rw-r--r--src/tests/keywords/if-elsif19
-rw-r--r--src/tests/keywords/if-multivalue173
-rw-r--r--src/tests/keywords/if-paircmp27
-rw-r--r--src/tests/keywords/if-rcode-error11
-rw-r--r--src/tests/keywords/if-regex-bad-attribute21
-rw-r--r--src/tests/keywords/if-regex-error12
-rw-r--r--src/tests/keywords/if-regex-match183
-rw-r--r--src/tests/keywords/if-regex-match-comp149
-rw-r--r--src/tests/keywords/if-regex-match-comp.attrs7
-rw-r--r--src/tests/keywords/if-regex-match-named117
-rw-r--r--src/tests/keywords/if-regex-match-named.attrs6
-rw-r--r--src/tests/keywords/if-regex-match.attrs7
-rw-r--r--src/tests/keywords/if-regex-multivalue26
-rw-r--r--src/tests/keywords/if-skip42
-rw-r--r--src/tests/keywords/integer209
-rw-r--r--src/tests/keywords/ipaddr51
-rw-r--r--src/tests/keywords/ipaddr-error10
-rw-r--r--src/tests/keywords/ipaddr.attrs12
-rw-r--r--src/tests/keywords/ipprefix52
-rw-r--r--src/tests/keywords/length155
-rw-r--r--src/tests/keywords/load-balance97
-rw-r--r--src/tests/keywords/log7
-rw-r--r--src/tests/keywords/map-xlat25
-rw-r--r--src/tests/keywords/md458
-rw-r--r--src/tests/keywords/md560
-rw-r--r--src/tests/keywords/module-failure-message40
-rw-r--r--src/tests/keywords/ok-return13
-rw-r--r--src/tests/keywords/ok-return.attrs4
-rw-r--r--src/tests/keywords/pad62
-rw-r--r--src/tests/keywords/pairs42
-rw-r--r--src/tests/keywords/pap146
-rw-r--r--src/tests/keywords/pap-ssha2114
-rw-r--r--src/tests/keywords/radiusd.conf127
-rw-r--r--src/tests/keywords/redundant17
-rw-r--r--src/tests/keywords/redundant-error6
-rw-r--r--src/tests/keywords/redundant-load-balance65
-rw-r--r--src/tests/keywords/redundant-redundant73
-rw-r--r--src/tests/keywords/regex-escape29
-rw-r--r--src/tests/keywords/regex-lhs27
-rw-r--r--src/tests/keywords/return33
-rw-r--r--src/tests/keywords/return-group22
-rw-r--r--src/tests/keywords/return-group.attrs4
-rw-r--r--src/tests/keywords/return-section35
-rw-r--r--src/tests/keywords/sha160
-rw-r--r--src/tests/keywords/sha281
-rw-r--r--src/tests/keywords/smash6
-rw-r--r--src/tests/keywords/string19
-rw-r--r--src/tests/keywords/substring418
-rw-r--r--src/tests/keywords/switch19
-rw-r--r--src/tests/keywords/switch-attr-cast34
-rw-r--r--src/tests/keywords/switch-attr-cmp36
-rw-r--r--src/tests/keywords/switch-default22
-rw-r--r--src/tests/keywords/switch-escape43
-rw-r--r--src/tests/keywords/switch-nodefault22
-rw-r--r--src/tests/keywords/switch-value-error29
-rw-r--r--src/tests/keywords/switch-value-error227
-rw-r--r--src/tests/keywords/switch-virtual23
-rw-r--r--src/tests/keywords/switch-xlat-error17
-rw-r--r--src/tests/keywords/truncation109
-rw-r--r--src/tests/keywords/unknown84
-rw-r--r--src/tests/keywords/unknown-if8
-rw-r--r--src/tests/keywords/unknown-name15
-rw-r--r--src/tests/keywords/unknown-update6
-rw-r--r--src/tests/keywords/update7
-rw-r--r--src/tests/keywords/update-add-ref-index118
-rw-r--r--src/tests/keywords/update-add-ref-tag118
-rw-r--r--src/tests/keywords/update-all9
-rw-r--r--src/tests/keywords/update-array63
-rw-r--r--src/tests/keywords/update-delete40
-rw-r--r--src/tests/keywords/update-error9
-rw-r--r--src/tests/keywords/update-error-29
-rw-r--r--src/tests/keywords/update-error-310
-rw-r--r--src/tests/keywords/update-exec94
-rw-r--r--src/tests/keywords/update-filter75
-rw-r--r--src/tests/keywords/update-index52
-rw-r--r--src/tests/keywords/update-list-error19
-rw-r--r--src/tests/keywords/update-operator85
-rw-r--r--src/tests/keywords/update-prepend65
-rw-r--r--src/tests/keywords/update-remove-any50
-rw-r--r--src/tests/keywords/update-remove-index100
-rw-r--r--src/tests/keywords/update-remove-list40
-rw-r--r--src/tests/keywords/update-remove-tag275
-rw-r--r--src/tests/keywords/update-remove-value116
-rw-r--r--src/tests/keywords/update-tag176
-rw-r--r--src/tests/keywords/update-xlat61
-rw-r--r--src/tests/keywords/urlquote50
-rw-r--r--src/tests/keywords/virtual12
-rw-r--r--src/tests/keywords/virtual-exists12
-rw-r--r--src/tests/keywords/virtual-load-balance14
-rw-r--r--src/tests/keywords/virtual-rhs16
-rw-r--r--src/tests/keywords/virtual_policy15
-rw-r--r--src/tests/keywords/wimax31
-rw-r--r--src/tests/keywords/wimax-comboip19
-rw-r--r--src/tests/keywords/with_dots19
-rw-r--r--src/tests/keywords/xlat-attr62
-rw-r--r--src/tests/keywords/xlat-attr-index53
-rw-r--r--src/tests/keywords/xlat-attr-tag225
-rw-r--r--src/tests/keywords/xlat-concat40
-rw-r--r--src/tests/keywords/xlat-error12
-rw-r--r--src/tests/keywords/xlat-explode91
-rw-r--r--src/tests/keywords/xlat-list64
-rw-r--r--src/tests/keywords/xlat-octets36
-rw-r--r--src/tests/keywords/xlat-virtual-attr131
-rw-r--r--src/tests/map/all.mk1
-rw-r--r--src/tests/map/base6
-rw-r--r--src/tests/map/base.out5
-rw-r--r--src/tests/map/count-error6
-rw-r--r--src/tests/map/count-list-error6
-rw-r--r--src/tests/map/map_tests.mk50
-rw-r--r--src/tests/map/map_unit.c219
-rw-r--r--src/tests/map/map_unit.mk5
-rw-r--r--src/tests/modules/README.rst18
-rw-r--r--src/tests/modules/all.mk40
-rw-r--r--src/tests/modules/always/all.mk3
-rw-r--r--src/tests/modules/always/module.conf7
-rw-r--r--src/tests/modules/always/replace.unlang11
-rw-r--r--src/tests/modules/always/set_rcode.unlang44
-rw-r--r--src/tests/modules/always/set_status_dead.unlang18
-rw-r--r--src/tests/modules/always/set_status_revive.unlang28
-rw-r--r--src/tests/modules/cache/rbtree/all.mk2
-rw-r--r--src/tests/modules/default-input.attrs11
-rw-r--r--src/tests/modules/files/addcontrol.attrs13
-rw-r--r--src/tests/modules/files/addcontrol.unlang8
-rw-r--r--src/tests/modules/files/addreply.attrs12
-rw-r--r--src/tests/modules/files/addreply.unlang4
-rw-r--r--src/tests/modules/files/all.mk3
-rw-r--r--src/tests/modules/files/authorize92
-rw-r--r--src/tests/modules/files/bob.attrs11
-rw-r--r--src/tests/modules/files/bob.unlang4
-rw-r--r--src/tests/modules/files/doug.attrs11
-rw-r--r--src/tests/modules/files/doug.unlang4
-rw-r--r--src/tests/modules/files/fall-through.attrs11
-rw-r--r--src/tests/modules/files/fall-through.unlang4
-rw-r--r--src/tests/modules/files/filterreply.attrs10
-rw-r--r--src/tests/modules/files/filterreply.unlang4
-rw-r--r--src/tests/modules/files/module.conf9
-rw-r--r--src/tests/modules/files/subreply.attrs12
-rw-r--r--src/tests/modules/files/subreply.unlang4
-rw-r--r--src/tests/modules/json/all.mk3
-rw-r--r--src/tests/modules/json/encode.attrs13
-rw-r--r--src/tests/modules/json/encode.unlang233
-rw-r--r--src/tests/modules/json/module.conf150
-rw-r--r--src/tests/modules/ldap/acct.attrs35
-rw-r--r--src/tests/modules/ldap/acct.unlang23
-rw-r--r--src/tests/modules/ldap/all.mk8
-rw-r--r--src/tests/modules/ldap/auth.attrs15
-rw-r--r--src/tests/modules/ldap/auth.unlang72
l---------src/tests/modules/ldap/example.com.ldif1
-rw-r--r--src/tests/modules/ldap/groups_rfc2307bis.attrs15
-rw-r--r--src/tests/modules/ldap/groups_rfc2307bis.unlang41
-rw-r--r--src/tests/modules/ldap/module.conf537
-rw-r--r--src/tests/modules/pap/all.mk3
-rw-r--r--src/tests/modules/pap/module.conf1
-rw-r--r--src/tests/modules/pap/pbkfd2_dig_big.attrs11
-rw-r--r--src/tests/modules/pap/pbkfd2_dig_big.unlang17
-rw-r--r--src/tests/modules/pap/pbkfd2_dig_small.attrs11
-rw-r--r--src/tests/modules/pap/pbkfd2_dig_small.unlang17
-rw-r--r--src/tests/modules/pap/pbkfd2_iter0.attrs11
-rw-r--r--src/tests/modules/pap/pbkfd2_iter0.unlang17
-rw-r--r--src/tests/modules/pap/pbkfd2_iter1.attrs11
-rw-r--r--src/tests/modules/pap/pbkfd2_iter1.unlang17
-rw-r--r--src/tests/modules/pap/pbkfd2_iter1000.attrs11
-rw-r--r--src/tests/modules/pap/pbkfd2_iter1000.unlang17
-rw-r--r--src/tests/modules/pap/pbkfd2_iter100000.attrs10
-rw-r--r--src/tests/modules/pap/pbkfd2_iter100000.unlang17
-rw-r--r--src/tests/modules/pap/pbkfd2_iter_big.attrs11
-rw-r--r--src/tests/modules/pap/pbkfd2_iter_big.unlang17
-rw-r--r--src/tests/modules/pap/pbkfd2_iter_miss.attrs11
-rw-r--r--src/tests/modules/pap/pbkfd2_iter_miss.unlang19
-rw-r--r--src/tests/modules/pap/pbkfd2_iter_small.attrs11
-rw-r--r--src/tests/modules/pap/pbkfd2_iter_small.unlang19
-rw-r--r--src/tests/modules/pap/pbkfd2_passlib.attrs10
-rw-r--r--src/tests/modules/pap/pbkfd2_passlib.unlang20
-rw-r--r--src/tests/modules/pap/pbkfd2_salt0.attrs10
-rw-r--r--src/tests/modules/pap/pbkfd2_salt0.unlang19
-rw-r--r--src/tests/modules/pap/pbkfd2_salt1.attrs10
-rw-r--r--src/tests/modules/pap/pbkfd2_salt1.unlang17
-rw-r--r--src/tests/modules/pap/pbkfd2_salt1024.attrs10
-rw-r--r--src/tests/modules/pap/pbkfd2_salt1024.unlang17
-rw-r--r--src/tests/modules/pap/pbkfd2_salt64.attrs10
-rw-r--r--src/tests/modules/pap/pbkfd2_salt64.unlang17
-rw-r--r--src/tests/modules/pap/pbkfd2_salt_big.attrs10
-rw-r--r--src/tests/modules/pap/pbkfd2_salt_big.unlang17
-rw-r--r--src/tests/modules/pap/pbkfd2_salt_small.attrs10
-rw-r--r--src/tests/modules/pap/pbkfd2_salt_small.unlang17
-rw-r--r--src/tests/modules/pap/pbkfd2_sha1.attrs10
-rw-r--r--src/tests/modules/pap/pbkfd2_sha1.unlang17
-rw-r--r--src/tests/modules/pap/pbkfd2_sha2_224.attrs10
-rw-r--r--src/tests/modules/pap/pbkfd2_sha2_224.unlang17
-rw-r--r--src/tests/modules/pap/pbkfd2_sha2_256.attrs10
-rw-r--r--src/tests/modules/pap/pbkfd2_sha2_256.unlang17
-rw-r--r--src/tests/modules/pap/pbkfd2_sha2_384.attrs10
-rw-r--r--src/tests/modules/pap/pbkfd2_sha2_384.unlang17
-rw-r--r--src/tests/modules/pap/pbkfd2_sha2_512.attrs10
-rw-r--r--src/tests/modules/pap/pbkfd2_sha2_512.unlang17
-rw-r--r--src/tests/modules/preprocess/all.mk3
-rw-r--r--src/tests/modules/preprocess/hints2
-rw-r--r--src/tests/modules/preprocess/huntgroups0
-rw-r--r--src/tests/modules/preprocess/module.conf4
-rw-r--r--src/tests/modules/preprocess/xlat.attrs12
-rw-r--r--src/tests/modules/preprocess/xlat.unlang14
-rw-r--r--src/tests/modules/radiusd.conf103
-rw-r--r--src/tests/modules/sql/.gitignore1
-rw-r--r--src/tests/modules/sql/acct_0_start.attrs37
-rw-r--r--src/tests/modules/sql/acct_0_start.unlang40
-rw-r--r--src/tests/modules/sql/acct_1_update.attrs37
-rw-r--r--src/tests/modules/sql/acct_1_update.unlang30
-rw-r--r--src/tests/modules/sql/acct_2_stop.attrs38
-rw-r--r--src/tests/modules/sql/acct_2_stop.unlang40
-rw-r--r--src/tests/modules/sql/acct_start_conflict.attrs37
-rw-r--r--src/tests/modules/sql/acct_start_conflict.unlang76
-rw-r--r--src/tests/modules/sql/acct_update_no_start.attrs37
-rw-r--r--src/tests/modules/sql/acct_update_no_start.unlang40
-rw-r--r--src/tests/modules/sql/auth.attrs12
-rw-r--r--src/tests/modules/sql/auth.unlang39
-rw-r--r--src/tests/modules/sql/reject.attrs12
-rw-r--r--src/tests/modules/sql/reject.unlang39
-rw-r--r--src/tests/modules/sql_mysql/.gitignore1
l---------src/tests/modules/sql_mysql/acct_0_start.attrs1
l---------src/tests/modules/sql_mysql/acct_0_start.unlang1
l---------src/tests/modules/sql_mysql/acct_1_update.attrs1
l---------src/tests/modules/sql_mysql/acct_1_update.unlang1
l---------src/tests/modules/sql_mysql/acct_2_stop.attrs1
l---------src/tests/modules/sql_mysql/acct_2_stop.unlang1
l---------src/tests/modules/sql_mysql/acct_start_conflict.attrs1
l---------src/tests/modules/sql_mysql/acct_start_conflict.unlang1
l---------src/tests/modules/sql_mysql/acct_update_no_start.attrs1
l---------src/tests/modules/sql_mysql/acct_update_no_start.unlang1
-rw-r--r--src/tests/modules/sql_mysql/all.mk6
l---------src/tests/modules/sql_mysql/auth.attrs1
l---------src/tests/modules/sql_mysql/auth.unlang1
-rw-r--r--src/tests/modules/sql_mysql/module.conf53
l---------src/tests/modules/sql_mysql/reject.attrs1
l---------src/tests/modules/sql_mysql/reject.unlang1
-rw-r--r--src/tests/modules/sql_postgresql/.gitignore1
l---------src/tests/modules/sql_postgresql/acct_0_start.attrs1
l---------src/tests/modules/sql_postgresql/acct_0_start.unlang1
l---------src/tests/modules/sql_postgresql/acct_1_update.attrs1
l---------src/tests/modules/sql_postgresql/acct_1_update.unlang1
l---------src/tests/modules/sql_postgresql/acct_2_stop.attrs1
l---------src/tests/modules/sql_postgresql/acct_2_stop.unlang1
l---------src/tests/modules/sql_postgresql/acct_start_conflict.attrs1
l---------src/tests/modules/sql_postgresql/acct_start_conflict.unlang1
l---------src/tests/modules/sql_postgresql/acct_update_no_start.attrs1
l---------src/tests/modules/sql_postgresql/acct_update_no_start.unlang1
-rw-r--r--src/tests/modules/sql_postgresql/all.mk6
l---------src/tests/modules/sql_postgresql/auth.attrs1
l---------src/tests/modules/sql_postgresql/auth.unlang1
-rw-r--r--src/tests/modules/sql_postgresql/module.conf52
l---------src/tests/modules/sql_postgresql/reject.attrs1
l---------src/tests/modules/sql_postgresql/reject.unlang1
-rw-r--r--src/tests/modules/sql_sqlite/.gitignore1
l---------src/tests/modules/sql_sqlite/acct_0_start.attrs1
l---------src/tests/modules/sql_sqlite/acct_0_start.unlang1
l---------src/tests/modules/sql_sqlite/acct_1_update.attrs1
l---------src/tests/modules/sql_sqlite/acct_1_update.unlang1
l---------src/tests/modules/sql_sqlite/acct_2_stop.attrs1
l---------src/tests/modules/sql_sqlite/acct_2_stop.unlang1
l---------src/tests/modules/sql_sqlite/acct_start_conflict.attrs1
l---------src/tests/modules/sql_sqlite/acct_start_conflict.unlang1
l---------src/tests/modules/sql_sqlite/acct_update_no_start.attrs1
l---------src/tests/modules/sql_sqlite/acct_update_no_start.unlang1
-rw-r--r--src/tests/modules/sql_sqlite/all.mk3
l---------src/tests/modules/sql_sqlite/auth.attrs1
l---------src/tests/modules/sql_sqlite/auth.unlang1
-rw-r--r--src/tests/modules/sql_sqlite/module.conf52
l---------src/tests/modules/sql_sqlite/reject.attrs1
l---------src/tests/modules/sql_sqlite/reject.unlang1
-rw-r--r--src/tests/modules/test.mk165
-rw-r--r--src/tests/modules/unbound/all.mk3
-rw-r--r--src/tests/modules/unbound/dns.attrs11
-rw-r--r--src/tests/modules/unbound/dns.unlang53
-rw-r--r--src/tests/modules/unbound/module.conf4
-rw-r--r--src/tests/modules/unbound/unbound.conf6
-rw-r--r--src/tests/mschapv116
-rw-r--r--src/tests/panic.gdb4
-rw-r--r--src/tests/peap-client-mschapv2.conf18
-rw-r--r--src/tests/peap-eap-tls.conf14
-rw-r--r--src/tests/peap-mschapv2.conf13
-rw-r--r--src/tests/proxy.conf61
-rw-r--r--src/tests/radiusd.mk115
-rw-r--r--src/tests/radsec/.gitignore6
-rw-r--r--src/tests/radsec/1.basic-auth.reply2
-rw-r--r--src/tests/radsec/1.basic-auth.request3
-rw-r--r--src/tests/radsec/2.ipaddrudp-coa.reply4
-rw-r--r--src/tests/radsec/2.ipaddrudp-coa.request3
-rw-r--r--src/tests/radsec/3.homepooludp-coa.reply4
-rw-r--r--src/tests/radsec/3.homepooludp-coa.request2
-rw-r--r--src/tests/radsec/4.homepooltls-coa.reply4
-rw-r--r--src/tests/radsec/4.homepooltls-coa.request2
-rw-r--r--src/tests/radsec/5.singletunnel_proxy-coa.reply6
-rw-r--r--src/tests/radsec/5.singletunnel_proxy-coa.request2
-rw-r--r--src/tests/radsec/6.singletunnel_originate-coa.reply4
-rw-r--r--src/tests/radsec/6.singletunnel_originate-coa.request2
-rw-r--r--src/tests/radsec/7.coareply-auth.reply4
-rw-r--r--src/tests/radsec/7.coareply-auth.request2
-rw-r--r--src/tests/radsec/Makefile10
-rw-r--r--src/tests/radsec/README.rst103
-rw-r--r--src/tests/radsec/all.mk150
-rw-r--r--src/tests/radsec/config-coa/main.conf.template37
-rw-r--r--src/tests/radsec/config-home/main.conf322
-rw-r--r--src/tests/radsec/config-proxy/main.conf.template207
-rwxr-xr-xsrc/tests/radsec/runtest.sh83
-rw-r--r--src/tests/rbmonkey.c250
-rw-r--r--src/tests/rbmonkey.mk7
-rwxr-xr-xsrc/tests/runtests.sh51
-rw-r--r--src/tests/salt-test-server/.gitignore8
-rw-r--r--src/tests/salt-test-server/README3
-rwxr-xr-xsrc/tests/salt-test-server/build.sh1
-rw-r--r--src/tests/salt-test-server/salt/iptable.sls13
-rw-r--r--src/tests/salt-test-server/salt/iptables15
-rw-r--r--src/tests/salt-test-server/salt/ldap.sls41
-rw-r--r--src/tests/salt-test-server/salt/ldap/base.ldif80
-rw-r--r--src/tests/salt-test-server/salt/ldap/base2.ldif81
-rw-r--r--src/tests/salt-test-server/salt/ldap/schema_freeradius.ldif76
-rw-r--r--src/tests/salt-test-server/salt/mysql.sls74
-rw-r--r--src/tests/salt-test-server/salt/mysql/schema.sql150
-rw-r--r--src/tests/salt-test-server/salt/mysql/setup.sql18
-rw-r--r--src/tests/salt-test-server/salt/ntp.sls22
-rw-r--r--src/tests/salt-test-server/salt/postgres.sls71
-rw-r--r--src/tests/salt-test-server/salt/postgres/schema.sql183
-rw-r--r--src/tests/salt-test-server/salt/postgres/setup.sql21
-rw-r--r--src/tests/salt-test-server/salt/top.sls7
-rw-r--r--src/tests/salt-test-server/salt_config/master12
-rw-r--r--src/tests/salt-test-server/salt_config/roster4
-rw-r--r--src/tests/sql_nas_table/all.mk78
-rw-r--r--src/tests/sql_nas_table/auth.txt2
-rw-r--r--src/tests/sql_nas_table/clients.sql1
-rw-r--r--src/tests/sql_nas_table/config/radiusd.conf143
-rw-r--r--src/tests/stripped.example.com5
-rw-r--r--src/tests/test.example.com7
-rw-r--r--src/tests/tests.gdb9
-rw-r--r--src/tests/unit/all.mk53
-rw-r--r--src/tests/unit/ascend.txt5
-rw-r--r--src/tests/unit/condition.txt679
-rw-r--r--src/tests/unit/dhcp.txt44
-rw-r--r--src/tests/unit/eapol_key_msg.txt14
-rw-r--r--src/tests/unit/errors.txt17
-rw-r--r--src/tests/unit/escape.txt74
-rw-r--r--src/tests/unit/extended.txt103
-rw-r--r--src/tests/unit/lucent.txt11
-rw-r--r--src/tests/unit/rfc.txt204
-rw-r--r--src/tests/unit/rfc4849.txt49
-rw-r--r--src/tests/unit/tunnel.txt87
-rw-r--r--src/tests/unit/vendor.txt48
-rw-r--r--src/tests/unit/wimax.txt171
-rw-r--r--src/tests/unit/xlat.txt142
-rw-r--r--src/tests/xlat/all.mk57
-rw-r--r--src/tests/xlat/expr.txt20
-rw-r--r--src/tests/xlat/radiusd.conf37
485 files changed, 24372 insertions, 0 deletions
diff --git a/src/tests/.gitignore b/src/tests/.gitignore
new file mode 100644
index 0000000..a019664
--- /dev/null
+++ b/src/tests/.gitignore
@@ -0,0 +1,12 @@
+.cache
+.foo
+.bar
+.request
+dictionary
+test.conf
+radius.log
+radiusd.pid
+eapol_test
+tlscache
+config/eap-test
+config/eap-test-inner-tunnel
diff --git a/src/tests/Makefile b/src/tests/Makefile
new file mode 100644
index 0000000..fe836c6
--- /dev/null
+++ b/src/tests/Makefile
@@ -0,0 +1,317 @@
+# -*- makefile -*-
+##
+## Makefile -- Build and run tests for the server.
+##
+## http://www.freeradius.org/
+## $Id$
+##
+#
+include ../../Make.inc
+
+BUILD_PATH := $(top_builddir)/build
+
+#
+# Build eapol_test if requested to.
+#
+.PHONY: eapol_test
+eapol_test: $(BUILD_PATH)/tests/eapol_test/eapol_test.mk
+ @echo EAPOL_TEST=$(EAPOL_TEST)
+
+#
+# If we're doing anything resembling EAP, then make sure that
+# EAPOL_TEST is defined.
+#
+ifneq "(findstring eap,$(MAKECMDGOALS))" ""
+$(BUILD_PATH)/tests/eapol_test:
+ @mkdir -p $@
+
+TEST_PATH := $(top_builddir)/src/tests
+DICT_PATH := $(TEST_PATH)
+BIN_PATH := $(BUILD_PATH)/bin/local
+RADIUSD_BIN := $(BIN_PATH)/radiusd
+
+ifeq "$(DICT_PATH)" "$(TEST_PATH)"
+LIB_PATH := $(BUILD_PATH)/lib/local/.libs/
+DYLD_LIBRARY_PATH := $(DYLD_LIBRARY_PATH):$(LIB_PATH)
+export DYLD_LIBRARY_PATH
+endif
+
+ifneq "$(OPENSSL_LIBS)" ""
+#
+# Build eapol_test, and cache its output. Note that EAPOL_TEST may not be
+# defined, so we have to run the shell script for the second line, too.
+#
+# Normal expansion will still run the script if EAPOL_TEST_BIN is
+# set but empty, which we don't want.
+#
+ifeq "$(EAPOL_TEST_BIN)" ""
+override EAPOL_TEST_BIN := $(shell $(top_builddir)/scripts/ci/eapol_test-build.sh)
+endif
+
+$(BUILD_PATH)/tests/eapol_test/eapol_test.mk: | $(BUILD_PATH)/tests/eapol_test
+ @echo "EAPOL_TEST=$(EAPOL_TEST_BIN)" > $@
+ @echo "TLS1_3=$(shell openssl ciphers -s -v 'ECDHE:!COMPLEMENTOFDEFAULT'| grep -q 'TLSv1.3' && echo yes)" >> $@
+ @echo "OPENSSL_OK=$(shell openssl version | grep -v ' 1\.0' >/dev/null && echo yes)" >> $@
+ @echo "OPENSSL3_OK=$(shell openssl version | grep -q ' OpenSSL 3\.0' && echo yes)" >> $@
+else
+#
+# No OpenSSL means that we don't even try to build eapol_test
+#
+.PHONY: $(BUILD_PATH)/tests/eapol_test/eapol_test.mk
+$(BUILD_PATH)/tests/eapol_test/eapol_test.mk: | $(BUILD_PATH)/tests/eapol_test
+ @touch $@
+endif
+
+-include $(BUILD_PATH)/tests/eapol_test/eapol_test.mk
+endif
+
+#
+# OpenSSL 1.0.x doesn't support cipher_list="DEFAULT@SECLEVEL=1"
+#
+# If the variable is empty, then OpenSSL isn't OK.
+#
+ifeq "$(OPENSSL_OK)" ""
+SECLEVEL=
+else
+SECLEVEL=@SECLEVEL=1
+endif
+
+#
+# For OpenSSL 3.0.x, as described in https://github.com/openssl/openssl/blob/master/doc/man7/migration_guide.pod
+#
+# "The security strength of SHA1 and MD5 based signatures in TLS has been reduced.
+# This results in SSL 3, TLS 1.0, TLS 1.1 and DTLS 1.0 no longer working at the
+# default security level of 1 and instead requires security level 0."
+#
+ifeq "$(OPENSSL3_OK)" "yes"
+SECLEVEL=@SECLEVEL=0
+endif
+
+RADDB_PATH := $(top_builddir)/raddb/
+
+TESTS = mschapv1 digest-01/digest* \
+ test.example.com
+
+PORT = 12340
+ACCTPORT = $(shell expr $(PORT) + 1)
+
+# example.com stripped.example.com
+
+SECRET = testing123
+
+.PHONY: all eap dictionary clean
+
+#
+# Build the directory for testing the server
+#
+all: tests
+
+clean:
+ @rm -f test.conf dictionary *.ok *.log $(BUILD_DIR)/tests/eap
+
+dictionary:
+ @echo "# test dictionary. Do not install. Delete at any time." > dictionary; \
+ echo '$$INCLUDE ' $(top_builddir)/share/dictionary >> dictionary; \
+ echo '$$INCLUDE ' $(top_builddir)/src/tests/dictionary.test >> dictionary; \
+ if [ "$(DICT_PATH)" = "$(TEST_PATH)" ]; then \
+ echo '$$INCLUDE ' $(top_builddir)/share/dictionary.dhcp >> dictionary; \
+ echo '$$INCLUDE ' $(top_builddir)/share/dictionary.vqp >> dictionary; \
+ fi
+
+test.conf: dictionary config/eap-test
+ @echo "# test configuration file. Do not install. Delete at any time." > $@
+ @if [ -n "$(LIB_PATH)" ]; then \
+ echo "libdir =" $(LIB_PATH) >> $@; \
+ fi
+ @echo "testdir =" $(TEST_PATH) >> $@
+ @echo 'logdir = $${testdir}' >> $@
+ @echo "maindir =" $(RADDB_PATH) >> $@
+ @echo 'radacctdir = $${testdir}' >> $@
+ @echo 'pidfile = $${testdir}/radiusd.pid' >> $@
+ @echo 'panic_action = "gdb -batch -x $${testdir}/panic.gdb %e %p > $${testdir}/gdb.log 2>&1; cat $${testdir}/gdb.log"' >> $@
+ @echo 'security {' >> $@
+ @echo ' allow_vulnerable_openssl = yes' >> $@
+ @echo '}' >> $@
+ @echo >> $@
+ @echo 'modconfdir = $${maindir}mods-config' >> $@
+ @echo 'certdir = $${maindir}/certs' >> $@
+ @echo 'cadir = $${maindir}/certs' >> $@
+ @echo '$$INCLUDE $${testdir}/config/' >> $@
+ @echo '$$INCLUDE $${maindir}/radiusd.conf' >> $@
+
+#
+# Rename "inner-tunnel", and ensure that it only uses the "eap-test" module.
+#
+config/eap-test-inner-tunnel: $(RADDB_PATH)sites-available/inner-tunnel
+ @sed 's/eap/eap-test/;s/server inner-tunnel/server eap-test-inner-tunnel/' < $< > $@
+
+#
+# * Same renames as above
+# * enable caching
+# * uncomment caching directory
+# * set the minimum TLS version to 1.0 for testing
+# * set the maximum TLS version to 1.2 or 1.3, depending if 1.3 is available
+# * always enable TLS 1.3 for the tests, via the super-secret magic flag.
+# * tell OpenSSL to enable insecure ciphers TLS 1.0 and TLS 1.1
+#
+config/eap-test: $(RADDB_PATH)mods-available/eap config/eap-test-inner-tunnel
+ @sed -e 's/eap {/eap eap-test {/' \
+ -e 's/= inner-tunnel/= eap-test-inner-tunnel/;s/use_tunneled_reply = no/use_tunneled_reply = yes/' \
+ -e 's/enable = no/enable = yes/' \
+ -e 's/^\(.*\)persist_dir =/ persist_dir =/' \
+ -e 's/tls_min_version = "1.2"/tls_min_version = "1.0"/' \
+ -e '$(if $(TLS1_3),s/tls_max_version = "1.2"/tls_max_version = "1.3"/)' \
+ -e 's/cipher_list = "DEFAULT"/cipher_list = "DEFAULT${SECLEVEL}"/' \
+ < $< > $@
+
+radiusd.pid: test.conf
+ @rm -rf $(TEST_PATH)/gdb.log $(TEST_PATH)/radius.log $(TEST_PATH)/tlscache
+ @mkdir -p $(TEST_PATH)/tlscache
+ @printf "Starting server... "
+ @if ! $(RADIUSD_BIN) -Pxxxxml $(TEST_PATH)/radius.log -d ${top_builddir}/src/tests -n test -i 127.0.0.1 -p $(PORT) -D $(DICT_PATH); then \
+ echo "failed"; \
+ echo "Last log entries were:"; \
+ tail -n 20 "$(TEST_PATH)/radius.log"; \
+ fi
+ @echo "ok"
+
+# We can't make this depend on radiusd.pid, because then make will create
+# radiusd.pid when we make radiusd.kill, which we don't want.
+.PHONY: radiusd.kill
+radiusd.kill:
+ @if [ -f radiusd.pid ]; then \
+ ret=0; \
+ if ! ps `cat $(TEST_PATH)/radiusd.pid` >/dev/null 2>&1; then \
+ rm -f radiusd.pid; \
+ echo "FreeRADIUS terminated during test"; \
+ echo "GDB output was:"; \
+ cat "$(TEST_PATH)/gdb.log"; \
+ echo "Last log entries were:"; \
+ tail -n 20 $(TEST_PATH)/radius.log; \
+ ret=1; \
+ fi; \
+ if ! kill -TERM `cat $(TEST_PATH)/radiusd.pid` >/dev/null 2>&1; then \
+ ret=1; \
+ fi; \
+ exit $$ret; \
+ fi
+ @rm -f radiusd.pid
+
+#
+# Run eapol_test if it exists and we built with openssl support.
+# Otherwise do nothing.
+#
+ifneq "$(EAPOL_TEST)" ""
+EAP_FILES = eap-md5.conf
+EAP_TLS_FILES = eap-ttls-pap.conf eap-ttls-mschapv2.conf peap-mschapv2.conf
+EAP_TLS_VERSIONS = 1.1 1.2
+EAP_TLS_DISABLE_STRING = tls_disable_tlsv1_0=1 tls_disable_tlsv1_1=1 tls_disable_tlsv1_2=1
+
+ifneq "$(TLS1_3)" ""
+EAP_TLS_VERSIONS += 1.3
+EAP_TLS_DISABLE_STRING += tls_disable_tlsv1_3=1
+endif
+
+.PHONY: $(BUILD_PATH)/tests/eap
+$(BUILD_PATH)/tests/eap:
+ @mkdir -p $@
+
+.PHONY: clean.tests.eap
+clean.tests.eap:
+ @rm -rf $(BUILD_PATH)/tests/eap config/tlscache config/eap-test config/eap-test-inner-tunnel
+
+#
+# Set target-specific variables, so that the later shell scripts are rather more understandable.
+#
+# MD5 doesn't use MPPE keys
+#
+$(BUILD_PATH)/tests/eap/%.ok: NO_MPPE = $(filter eap-md5,$(basename $(notdir $@)))
+$(BUILD_PATH)/tests/eap/%.ok: CMD = $(EAPOL_TEST) -c $< -p $(PORT) -s $(SECRET) $(if $(NO_MPPE),-n)
+$(BUILD_PATH)/tests/eap/%.ok: LOG = $(patsubst %.ok,%,$@).log
+
+$(BUILD_PATH)/tests/eap/%.ok: $(top_builddir)/src/tests/%.conf | radiusd.kill $(BUILD_PATH)/tests/eap radiusd.pid radiusd.kill
+ @printf 'EAPOL_TEST %s ' $(notdir $(patsubst %.conf,%,$<))
+ @if ! $(CMD) > $(LOG) 2>&1; then \
+ echo " - " FAILED - command failed; \
+ echo ">>> cmd -" $(CMD); \
+ echo ">>> log -" $(LOG); \
+ echo "===================="; \
+ tail -10 $(LOG); \
+ echo "===================="; \
+ $(MAKE) radiusd.kill; \
+ exit 1; \
+ fi
+ @echo
+ @touch $@
+
+#
+# Don't run the full TLS version tests for CI post-install.
+#
+ifneq "$(prefix)" ""
+#
+# ${1} is the config file
+# ${2} is the TLS version to use.
+#
+# Update the phase1 configuration to enable/disable various TLS versions
+# insert an OpenSSL cipher configuration line by cloning "password" and editing it.
+#
+define EAP_TLS_CONFIG
+$(BUILD_PATH)/tests/eap/${1}-${2}.conf: $(top_builddir)/src/tests/${1}.conf
+ @sed -e 's/phase1="/phase1="$(subst $(subst .,_,${2})=1,$(subst .,_,${2})=0,$(EAP_TLS_DISABLE_STRING)) /' \
+ -e '/password/s/^//p; /password/s/^.*/ openssl_ciphers="DEFAULT${SECLEVEL}"/' \
+ < $$< > $$@
+
+$(BUILD_PATH)/tests/eap/${1}-${2}.ok: $(BUILD_PATH)/tests/eap/${1}-${2}.conf
+ @printf 'EAPOL_TEST %s' $$(notdir $$(patsubst %.ok,%,$$@))
+ @if ! $$(CMD) -r 1 > $$(LOG) 2>&1; then \
+ echo " - " FAILED - command failed; \
+ echo ">>> cmd -" $$(CMD) -r 1; \
+ echo ">>> log -" $$(LOG); \
+ echo "===================="; \
+ tail -10 $$(LOG); \
+ echo "===================="; \
+ $(MAKE) radiusd.kill; \
+ exit 1; \
+ elif ! grep -q '^SSL: Using TLS version TLSv${2}$$$$' $$(patsubst %.ok,%,$$@).log; then \
+ echo " - " FAILED - not using TLS version ${2}; \
+ echo ">>> cmd -" $$(CMD) -r 1; \
+ echo ">>> log -" $$(LOG); \
+ $(MAKE) radiusd.kill; \
+ exit 1; \
+ elif ! grep -q '^OpenSSL: Handshake finished - resumed=1$$$$' $$(patsubst %.ok,%,$$@).log; then \
+ echo " - " FAILED - did not use resumption; \
+ echo ">>> cmd -" $$(CMD) -r 1; \
+ echo ">>> log -" $$(LOG); \
+ $(MAKE) radiusd.kill; \
+ exit 1; \
+ fi
+ @echo
+ @touch $$@
+
+# EAP-FAST doesn't do TLS 1.3
+ifneq "${1}-${2}" "eap-fast-1.3"
+EAP_TLS_VERSION_FILES += $(BUILD_PATH)/tests/eap/${1}-${2}.ok
+endif
+endef
+
+$(foreach FILE,$(patsubst %.conf,%,$(EAP_TLS_FILES)),$(foreach TLS,$(EAP_TLS_VERSIONS),$(eval $(call EAP_TLS_CONFIG,${FILE},${TLS}))))
+endif # there's no "prefix", so we don't run the full EAP tests
+
+EAPOL_OK_FILES := $(sort $(addprefix $(BUILD_PATH)/tests/eap/,$(patsubst %.conf,%.ok, $(notdir $(EAP_TLS_FILES) $(EAP_FILES)))) $(EAP_TLS_VERSION_FILES))
+
+tests.eap: $(EAPOL_OK_FILES) | radiusd.kill radiusd.pid
+ @$(MAKE) radiusd.kill
+
+endif # we have eapol_test built
+
+# kill the server (if it's running)
+# start the server
+# run the tests (ignoring any failures)
+# kill the server
+# remove the changes to raddb/
+tests: test.conf | radiusd.kill radiusd.pid
+ @chmod a+x runtests.sh
+ @BIN_PATH="$(BIN_PATH)" PORT="$(PORT)" ./runtests.sh $(TESTS)
+ifneq "$(EAPOL_TEST)" ""
+ @$(MAKE) tests.eap
+endif
diff --git a/src/tests/README b/src/tests/README
new file mode 100644
index 0000000..4055d5d
--- /dev/null
+++ b/src/tests/README
@@ -0,0 +1,29 @@
+ This is a preliminary test harness for the server.
+
+$ make tests
+
+ makes all of the tests
+
+config/*
+
+ virtual server configuration that is used for the tests
+
+unit/*
+ unit tests. Mostly parsing tests, to see if we can parse
+ conditions, xlat expansions, RADIUS attributes, etc.
+
+xlat/*
+ dynamic expansions. All stand-alone ones.
+ e.g. can we turn %{md5:fooo} into the MD5 hash?
+
+keywords/*
+ tests of "unlang" keywords. Both parsing and functionality.
+
+auth/*
+ tests of authentication methods.
+
+modules/*
+ tests for individual modules
+
+In general, just placing files of the correct format in a directory
+will cause them to be picked up by the test harness. \ No newline at end of file
diff --git a/src/tests/all.mk b/src/tests/all.mk
new file mode 100644
index 0000000..142772b
--- /dev/null
+++ b/src/tests/all.mk
@@ -0,0 +1,78 @@
+SUBMAKEFILES := rbmonkey.mk unit/all.mk map/all.mk xlat/all.mk keywords/all.mk auth/all.mk modules/all.mk sql_nas_table/all.mk
+PORT := 12340
+SECRET := testing123
+DICT_PATH := $(top_srcdir)/share
+
+#
+# Include all of the autoconf definitions into the Make variable space
+#
+-include $(BUILD_DIR)/tests/keywords/autoconf.h.mk
+
+#
+# Pull all of the autoconf stuff into here.
+#
+$(BUILD_DIR)/tests/keywords/autoconf.h.mk: src/include/autoconf.h
+ @grep '^#define' $^ | sed 's/#define /AC_/;s/ / := /' > $@
+
+######################################################################
+#
+# Generic rules to set up the tests
+#
+# Use $(eval $(call TEST_BOOTSTRAP))
+#
+######################################################################
+define TEST_BOOTSTRAP
+
+#
+# The test files are files without extensions.
+#
+OUTPUT.$(TEST) := $(patsubst %/,%,$(subst $(top_srcdir)/src,$(BUILD_DIR),$(abspath $(DIR))))
+OUTPUT := $$(OUTPUT.$(TEST))
+
+#
+# Create the output directory
+#
+$$(OUTPUT.$(TEST)):
+ $${Q}mkdir -p $$@
+
+#
+# All of the output files depend on the input files
+#
+FILES.$(TEST) := $(addprefix $$(OUTPUT.$(TEST))/,$(sort $(FILES)))
+
+#
+# The output files also depend on the directory
+# and on the previous test.
+#
+$$(FILES.$(TEST)): | $$(OUTPUT.$(TEST))
+
+#
+# Make sure that the output files depend on the input.
+# This way if the input file doesn't exist, we get a
+# build error. Without this rule, the test target
+# would just get re-built every time, no matter what.
+#
+$(foreach x, $(FILES), $(eval $$(OUTPUT.$(TEST))/$x: $(DIR)/$x))
+
+#
+# We have a real file that's created if all of the tests pass.
+#
+$(BUILD_DIR)/tests/$(TEST): $$(FILES.$(TEST))
+ $${Q}touch $$@
+
+#
+# For simplicity, we create a phony target so that the poor developer
+# doesn't need to remember path names
+#
+$(TEST): $(BUILD_DIR)/tests/$(TEST)
+
+#
+# Clean the output directory and files.
+#
+.PHONY: clean.$(TEST)
+clean.$(TEST):
+ $${Q}rm -rf $$(OUTPUT.$(TEST))
+ $${Q}rm -f $$(BUILD_DIR)/tests/$(TEST)
+
+clean.test: clean.$(TEST)
+endef
diff --git a/src/tests/auth/all.mk b/src/tests/auth/all.mk
new file mode 100644
index 0000000..284033f
--- /dev/null
+++ b/src/tests/auth/all.mk
@@ -0,0 +1,119 @@
+#
+# Unit tests for authentication
+#
+
+#
+# The test files are files without extensions.
+# The list is unordered. The order is added in the next step by looking
+# at precursors.
+#
+AUTH_FILES := $(filter-out %.conf %.md %.attrs %.mk %~ %.rej,$(subst $(DIR)/,,$(wildcard $(DIR)/*)))
+
+#
+# Create the output directory
+#
+.PHONY: $(BUILD_DIR)/tests/auth
+$(BUILD_DIR)/tests/auth:
+ @mkdir -p $@
+
+#
+# Find which input files are needed by the tests
+# strip out the ones which exist
+# move the filenames to the build directory.
+#
+AUTH_EXISTS := $(addprefix $(DIR)/,$(addsuffix .attrs,$(AUTH_FILES)))
+AUTH_NEEDS := $(filter-out $(wildcard $(AUTH_EXISTS)),$(AUTH_EXISTS))
+AUTH := $(subst $(DIR),$(BUILD_DIR)/tests/auth,$(AUTH_NEEDS))
+
+AUTH_HAS := $(filter $(wildcard $(AUTH_EXISTS)),$(AUTH_EXISTS))
+AUTH_COPY := $(subst $(DIR),$(BUILD_DIR)/tests/auth,$(AUTH_NEEDS))
+
+#
+# For each file, look for precursor test.
+# Ensure that each test depends on its precursors.
+#
+-include $(BUILD_DIR)/tests/auth/depends.mk
+
+$(BUILD_DIR)/tests/auth/depends.mk: $(addprefix $(DIR)/,$(AUTH_FILES)) | $(BUILD_DIR)/tests/auth
+ @rm -f $@
+ @for x in $^; do \
+ y=`grep 'PRE: ' $$x | sed 's/.*://;s/ / /g;s, , $(BUILD_DIR)/tests/auth/,g'`; \
+ if [ "$$y" != "" ]; then \
+ z=`echo $$x | sed 's,src/,$(BUILD_DIR)/',`; \
+ echo "$$z: $$y" >> $@; \
+ echo "" >> $@; \
+ fi \
+ done
+#
+# These ones get copied over from the default input
+#
+$(AUTH): $(DIR)/default-input.attrs | $(BUILD_DIR)/tests/auth
+ @cp $< $@
+
+#
+# These ones get copied over from their original files
+#
+$(BUILD_DIR)/tests/auth/%.attrs: $(DIR)/%.attrs | $(BUILD_DIR)/tests/auth
+ @cp $< $@
+
+#
+# Don't auto-remove the files copied by the rule just above.
+# It's unnecessary, and it clutters the output with crap.
+#
+.PRECIOUS: $(BUILD_DIR)/tests/auth/%.attrs raddb/mods-enabled/wimax
+
+AUTH_MODULES := $(shell grep -- mods-enabled src/tests/auth/radiusd.conf | sed 's,.*/,,')
+AUTH_RADDB := $(addprefix raddb/mods-enabled/,$(AUTH_MODULES))
+AUTH_LIBS := $(addsuffix .la,$(addprefix rlm_,$(AUTH_MODULES)))
+
+#
+# Files in the output dir depend on the unit tests
+#
+# src/tests/auth/FOO unlang for the test
+# src/tests/auth/FOO.attrs input RADIUS and output filter
+# build/tests/auth/FOO updated if the test succeeds
+# build/tests/auth/FOO.log debug output for the test
+#
+# Auto-depend on modules via $(shell grep INCLUDE $(DIR)/radiusd.conf | grep mods-enabled | sed 's/.*}/raddb/'))
+#
+# If the test fails, then look for ERROR in the input. No error
+# means it's unexpected, so we die.
+#
+# Otherwise, check the log file for a parse error which matches the
+# ERROR line in the input.
+#
+$(BUILD_DIR)/tests/auth/%: $(DIR)/% $(BUILD_DIR)/tests/auth/%.attrs $(TESTBINDIR)/unittest | $(BUILD_DIR)/tests/auth $(AUTH_RADDB) $(AUTH_LIBS) build.raddb
+ @echo UNIT-TEST $(notdir $@)
+ @if ! TESTDIR=$(notdir $@) $(TESTBIN)/unittest -D share -d src/tests/auth/ -i $@.attrs -f $@.attrs -xxx > $@.log 2>&1; then \
+ if ! grep ERROR $< 2>&1 > /dev/null; then \
+ cat $@.log; \
+ echo "# $@.log"; \
+ echo "TESTDIR=$(notdir $@) $(TESTBIN)/unittest -D share -d src/tests/auth/ -i $@.attrs -f $@.attrs -xxx > $@.log 2>&1"; \
+ exit 1; \
+ fi; \
+ FOUND=$$(grep ^$< $@.log | head -1 | sed 's/:.*//;s/.*\[//;s/\].*//'); \
+ EXPECTED=$$(grep -n ERROR $< | sed 's/:.*//'); \
+ if [ "$$EXPECTED" != "$$FOUND" ]; then \
+ cat $@.log; \
+ echo "# $@.log"; \
+ echo "TESTDIR=$(notdir $@) $(TESTBIN)/unittest -D share -d src/tests/auth/ -i $@.attrs -f $@.attrs -xxx > $@.log 2>&1"; \
+ exit 1; \
+ fi \
+ fi
+ @touch $@
+
+#
+# Get all of the unit test output files
+#
+TESTS.AUTH_FILES := $(addprefix $(BUILD_DIR)/tests/auth/,$(AUTH_FILES))
+
+#
+# Depend on the output files, and create the directory first.
+#
+tests.auth: $(TESTS.AUTH_FILES)
+
+$(TESTS.AUTH_FILES): $(TESTS.KEYWORDS_FILES)
+
+.PHONY: clean.tests.auth
+clean.tests.auth:
+ @rm -rf $(BUILD_DIR)/tests/auth/
diff --git a/src/tests/auth/chap b/src/tests/auth/chap
new file mode 100644
index 0000000..648546a
--- /dev/null
+++ b/src/tests/auth/chap
@@ -0,0 +1,3 @@
+#
+# Password is already set in radiusd.conf
+#
diff --git a/src/tests/auth/chap.attrs b/src/tests/auth/chap.attrs
new file mode 100644
index 0000000..04df463
--- /dev/null
+++ b/src/tests/auth/chap.attrs
@@ -0,0 +1,4 @@
+User-Name = "bob",
+CHAP-Password := "hello"
+
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/auth/chap_header b/src/tests/auth/chap_header
new file mode 100644
index 0000000..13c3c9b
--- /dev/null
+++ b/src/tests/auth/chap_header
@@ -0,0 +1,7 @@
+#
+# over-ride password set in radiusd.conf
+#
+update control {
+ Cleartext-Password -= 'hello'
+ Password-With-Header := 'oracle01'
+}
diff --git a/src/tests/auth/chap_header.attrs b/src/tests/auth/chap_header.attrs
new file mode 100644
index 0000000..9f815c7
--- /dev/null
+++ b/src/tests/auth/chap_header.attrs
@@ -0,0 +1,4 @@
+User-Name = "bob"
+CHAP-Password = 'oracle01'
+
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/auth/digest b/src/tests/auth/digest
new file mode 100644
index 0000000..4858977
--- /dev/null
+++ b/src/tests/auth/digest
@@ -0,0 +1,3 @@
+update control {
+ Cleartext-Password := "zanzibar"
+}
diff --git a/src/tests/auth/digest.attrs b/src/tests/auth/digest.attrs
new file mode 100644
index 0000000..2d32aa0
--- /dev/null
+++ b/src/tests/auth/digest.attrs
@@ -0,0 +1,25 @@
+#
+# http://ftp6.us.freebsd.org/pub/rfc/internet-drafts/draft-smith-sipping-auth-examples-01.txt
+#
+# 3.5.2
+#
+#
+# In the "users" file: bob Cleartext-Password := "zanzibar"
+#
+# TESTS 1
+#
+User-Name = "bob",
+Digest-Response = "bdbeebb2da6adb6bca02599c2239e192"
+Digest-Realm = "biloxi.com",
+Digest-Nonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093",
+Digest-Method = "INVITE",
+Digest-URI = "sip:bob@biloxi.com",
+Digest-Algorithm = "MD5",
+Digest-User-Name = "bob",
+Digest-QOP = "auth-int",
+Digest-Nonce-Count = "00000001",
+Digest-CNonce = "0a4f113b",
+Digest-Body-Digest = "c1ed018b8ec4a3b170c0921f5b564e48",
+Message-Authenticator = ""
+
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/auth/md5_password b/src/tests/auth/md5_password
new file mode 100644
index 0000000..4376056
--- /dev/null
+++ b/src/tests/auth/md5_password
@@ -0,0 +1,7 @@
+#
+# over-ride password set in radiusd.conf
+#
+update control {
+ Cleartext-Password -= ANY
+ Password-With-Header := '{md5}5d41402abc4b2a76b9719d911017c592'
+}
diff --git a/src/tests/auth/md5_password.attrs b/src/tests/auth/md5_password.attrs
new file mode 100644
index 0000000..65b967b
--- /dev/null
+++ b/src/tests/auth/md5_password.attrs
@@ -0,0 +1,4 @@
+User-Name = "bob"
+User-Password = "hello"
+
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/auth/password_with_header b/src/tests/auth/password_with_header
new file mode 100644
index 0000000..08993ab
--- /dev/null
+++ b/src/tests/auth/password_with_header
@@ -0,0 +1,7 @@
+#
+# over-ride password set in radiusd.conf
+#
+update control {
+ Cleartext-Password -= 'hello'
+ Password-With-Header := '{clear}hello'
+}
diff --git a/src/tests/auth/password_with_header.attrs b/src/tests/auth/password_with_header.attrs
new file mode 100644
index 0000000..65b967b
--- /dev/null
+++ b/src/tests/auth/password_with_header.attrs
@@ -0,0 +1,4 @@
+User-Name = "bob"
+User-Password = "hello"
+
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/auth/password_without_header b/src/tests/auth/password_without_header
new file mode 100644
index 0000000..6fab6b3
--- /dev/null
+++ b/src/tests/auth/password_without_header
@@ -0,0 +1,7 @@
+#
+# over-ride password set in radiusd.conf
+#
+update control {
+ Cleartext-Password -= 'hello'
+ Password-With-Header := 'hello'
+}
diff --git a/src/tests/auth/password_without_header.attrs b/src/tests/auth/password_without_header.attrs
new file mode 100644
index 0000000..65b967b
--- /dev/null
+++ b/src/tests/auth/password_without_header.attrs
@@ -0,0 +1,4 @@
+User-Name = "bob"
+User-Password = "hello"
+
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/auth/radiusd.conf b/src/tests/auth/radiusd.conf
new file mode 100644
index 0000000..2d822e7
--- /dev/null
+++ b/src/tests/auth/radiusd.conf
@@ -0,0 +1,50 @@
+#
+# Minimal radiusd.conf for testing keywords
+#
+
+raddb = raddb
+testdir = src/tests/auth
+
+modconfdir = ${raddb}/mods-config
+
+# Only for testing!
+# Setting this on a production system is a BAD IDEA.
+security {
+ allow_vulnerable_openssl = yes
+}
+
+modules {
+ $INCLUDE ${raddb}/mods-enabled/always
+
+ $INCLUDE ${raddb}/mods-enabled/pap
+
+ $INCLUDE ${raddb}/mods-enabled/chap
+
+ $INCLUDE ${raddb}/mods-enabled/expr
+
+ $INCLUDE ${raddb}/mods-enabled/digest
+}
+
+server default {
+ authorize {
+ update control {
+ Cleartext-Password := 'hello'
+ }
+
+ #
+ # Include the test file specified by the
+ # KEYWORD environment variable.
+ #
+ $INCLUDE ${testdir}/$ENV{TESTDIR}
+
+ digest
+ chap
+ pap
+ }
+
+ authenticate {
+ digest
+ pap
+ chap
+ }
+}
diff --git a/src/tests/auth/user_password b/src/tests/auth/user_password
new file mode 100644
index 0000000..648546a
--- /dev/null
+++ b/src/tests/auth/user_password
@@ -0,0 +1,3 @@
+#
+# Password is already set in radiusd.conf
+#
diff --git a/src/tests/auth/user_password.attrs b/src/tests/auth/user_password.attrs
new file mode 100644
index 0000000..65b967b
--- /dev/null
+++ b/src/tests/auth/user_password.attrs
@@ -0,0 +1,4 @@
+User-Name = "bob"
+User-Password = "hello"
+
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/auth/wimax b/src/tests/auth/wimax
new file mode 100644
index 0000000..648546a
--- /dev/null
+++ b/src/tests/auth/wimax
@@ -0,0 +1,3 @@
+#
+# Password is already set in radiusd.conf
+#
diff --git a/src/tests/auth/wimax.attrs b/src/tests/auth/wimax.attrs
new file mode 100644
index 0000000..38ec09a
--- /dev/null
+++ b/src/tests/auth/wimax.attrs
@@ -0,0 +1,30 @@
+#
+# Tests for WiMAX attributes
+#
+# TESTS 1
+#
+User-Name = "bob"
+User-Password = "hello"
+WiMAX-GMT-Timezone-offset = -1
+WiMAX-AAA-Session-Id = 0x01020304
+WiMAX-hHA-IP-MIP4 = 192.0.2.1
+#
+# Manually encoded capability
+#
+WiMAX-Capability = 0x01ff45454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545040301
+#
+# Automatically encoded capability
+#
+WiMAX-Accounting-Capabilities = 2
+WiMAX-Release = "1.0"
+WiMAX-Packet-Data-Flow-Id = 1
+#
+# Long string
+#
+WiMAX-Hotline-Indicator = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxaaaaaaaaaabbbbbbbbbbcccccccccc123"
+WiMAX-Service-Data-Flow-Id = 2
+WiMAX-hHA-IP-MIP4 = 192.0.2.2
+
+
+# and the response
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/bob b/src/tests/bob
new file mode 100644
index 0000000..f9398ad
--- /dev/null
+++ b/src/tests/bob
@@ -0,0 +1,2 @@
+User-Name = "bob"
+User-Password = "bob"
diff --git a/src/tests/comp128-1vectors b/src/tests/comp128-1vectors
new file mode 100644
index 0000000..47178ed
--- /dev/null
+++ b/src/tests/comp128-1vectors
@@ -0,0 +1,1024 @@
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,00000000000000000000000000000000,0E9FF8FF24119D2D4ED18C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,00102030405060708090A0B0C0D0E0F0,A9D961ADC7633CE8768C4800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,C0AED303A34148CBBFA06C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,000102030405060708090A0B0C0D0E0F,5D2A043E67B7C5C7C3356C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EC40BAAD8DC6C2ABF2FC5A37199CACFC,69A197C79ECD0880B4FA4400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,77E0979917EF8AB46623627304D4FDB0,F06210B51F2B2840B5B9BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D4098AE8B464C6EF8CFFDF8091966D32,40CE18CB876EC6E1117EE400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5A9844D086BDB6F3466520C65C1DED96,1A7D588387F00CA0D16E8400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0E9AC39114CFAD5DB1AF451CD4925C6B,EDF6CF7142ED0CA258A35400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7DFE76CA758728499049922D9999F3AF,8E5C547A5E075CB7450A7C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,41BA2412C0A0757F207C76E4BD1C087E,84E634B4AAF7218FCEF62C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,47B768859A5C4B74ACCEF8034C05C14D,582002FDFE5A21E5324F3C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,649DFCE8376E5D387358EFEB3BC26420,5CB28C9696F57A5DEDE9B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7E1E3DF0017D23C4B7BD6741FFDAE7FB,CC427CDD4FF449DE836D6800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4CC4819EE8EBEBB77082003130437D26,D70813EF55034FD892A30400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,184CA804BBF60010DBA18A0CA67A1140,91915ABA811DACDB3C63B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,36322EC763D3EF2524FA010827AB4985,DCF8CC8E39D352D893B4F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0A6818AC118827B49F0052A00285A264,9B18B467AEE55F507C2F0C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4F1D98A1E9F7B24A014857A885201C2E,C60C37B6115385CB1B06A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,437DFD43130B6DFE4F966C65CB4FC2FB,71CC9B531B3962E345259400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8EDCB9698F072904907BD54A38EB1BC8,D184E6EEF6CAA297DAC18C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D3FD8F3572797889F67E3A1D514F497F,5709C69BD0E9DDDF548AFC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,96E4A40675892C8DC56B43A0EC8D01A5,329E4182ECDD71496EA36000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A1CE5E775FB44A8E69C20B41FB50EF2D,F8B015A9EE2CD767DF8C1000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BCCBF4CF67883B274932C5153F5DBB47,04818B54332E3DB7C425AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D2BE79D308E612C6560DF304DE6714C6,655D7AC7F912EF41A61E6400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DBC53E6F3533AB92B40A4475FEC6926D,2AD30CF0D317AC66FBB58C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,914677C71251C24D0817BEE04D62F160,1C6A028C89509ECAD23E0800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,68184B14C236E1902B8BA7D55A2C4802,08020BC1869B99426317B800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AB92831C5582DE547C365A1FD0DB10FF,7D4990DAEC7C78F649256C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B9F471D0BF3488E979CA32A9B0CEDAED,9EB85CA06490DCFCF3E81C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,57C6A34EE6F4094E45F592F4A2E91A42,74BFF2650C1E7224C50F7000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,48EB9924EA123F5551D7808033252D0A,BCAABA24B488514B6B64B800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2E3DA0CCA62877FAE90D46B3D46821B2,5FCF387BAC1F0BB3A32BB000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D8B169F83205DC67ED400C76EF13CBEE,9590280673C10EB6813D9400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,622AFB53FD779E2E5B7BA1948BA6E0ED,A0F227CFB0675C2B7D49C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,928BB857020EDE6A455E4C29CFB2F3F7,A0174B9864C18A544D14A400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0B4C4273C2AC3B5F736AE5E38E0B3BDD,1DE7570907B7F7D463F57C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F6E444BF0108B3DFC3C345B1753AD4CC,B27188972C9EB3EDED04E000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5B99D79D041C45ADE9DF566C7E917A45,4F97851A31C0027130527400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A60313DEB989CCE02A1DE3F9F998E3A5,02FDCA64A58DF99A9329B800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B6FE696DAB580697FBAC3372171B966F,3A1CF01528BF7F62E9858400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B83B757200FBA3F2FC3E716BB17D279B,603E583982951DF901896800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C0A72FB3E2F34FEAA42BA088BC65CD1F,F8D4AFDCF0A58ACA2E4FDC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CB808D16DF8F0433589205F94E4426DD,6BAD345C95356B42CB012400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EDEA4ED36D797C45ED846631C9BE6689,EF518A2581ED200804BD4400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,365648888E5060E74BDF2D0E80808D81,2428AA249B7D99D7E8E3B800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2F5B73E278E21F78F6905479699B38C7,E842741E2F010248304F4400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,27F33379F46FA6B79A8FCB3BE272B203,205D4882650E55576E8F0000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4D750388464F723D5DF5DC529D3283E4,B63569746D455BC2DD6E7000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,42DF5BC8EE095DDA5876CE2DA1115F17,D18E9C5D768BA02AC4DE9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,67308B554C4AE8B47E73AE5DBA99E0D5,AFB2548347F7156B2CF51C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8AFBDEA2C397126A19123C74CC128D93,6A3A83DDE0F3EC903D0FFC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9531239AAA0A92A028928DF2534B5E2F,828CB170F682351F8B12F800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B81109133236BB1766F9382C5079AEBC,0E8B6C96AB146429DA0BB800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9585D18C8B51865F34D993EDDCFD060C,7FB814F9D11A71889F98B400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,99C2ED37A50F4BFA55C3337F3423B2F7,E5D6C5185D36E3580F65A800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B848670F782B3D6E602227DD5AA2CECA,98665AAEFC034B3CBD0E0400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,065D815DFFC12FE9AFDFFF9C18D7B2CD,E75104250C17A09B49251800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EC66ABA966FC847CDA806849A3756894,382DA8392ED16008E4955C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F055F2334857BE9BEF0CC91D951A15E7,CE55A765C01AD634C84E0800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5EECBC15306DD047FB81D93C56E494C5,32BE31BE3BBC2B65D153C000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,22DF1FCE06AE53574265BCF27457D19C,071F37C9E6292165A838F800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,13285484F075311FAB14A161428780D5,4559832C31EF85E69D274000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,147AC147195A638DA3AC6943747B13F0,3BE416AF4EBDED1E394D6800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0176166D0B478308D40672FD319EDBC7,D169CF01446B750BE3BBF000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B87C0832954FA9FB0B2D4D3E598D65D6,B2FACF9EDA52A08F728C1C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,39C3780BD8C80162F501F9FB054EDEB2,E5CC8C96ED0A128174097C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,93917101570BA3BA1BFA7DBCD69E6C1D,8908AC2FCCB8A07513E51C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F8B260CE5C93997B3C2F25E2357248C7,7348E29D60AD1D96F6EE5800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9DE59942143B23D07D4557F84E3E3963,36E03DE0B4586214299F6800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,43817BC21197B07CD1D3EABEB9A07DBA,680D0BBB17CAE8529813FC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BEDA13A0BD90BEA122F2C418C1321062,5003676AABA367BE767B3000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,92F3B8C11949E0815CD9DC672A47DE91,08F7BA4E3080B206068A3000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D03B447FAA0861B9E5E118BEC2073D9C,2710726FA8FF6A3803037800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1B89A9B8EB7FBCD86B3FBD1F2B86F2B2,63D988615E9112BE8D7CE400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5EB9756CC95B90A03C43758C1401900C,D76784339661137160199C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3E187CA6B643251ECB4F10DEA1F767E3,5E173C4AC2408481D90BB400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,80D04BCDB7CBCEC3115537FE85145328,F1EB7D9BA62D4895E9983800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2A4698EC2A871ED36053E56308D71798,FAF015C6DDE2A1826A0EC000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,667CBB8B0F868872637EBED8C6E289F4,678198B9179DE9AB98A2EC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7C689482AF142439B75A91B7165ECC4C,37D8DB63C76B7B2C8E3FEC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B07A66BAE751E06884F791303CD9FD69,9EC1BCE089388D8FC00DCC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C7BEE476315DEE169046A713073338CC,42EAFBD4BAF6C0ED5EA0F400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,18DBC0A8B049C880EA48D146A62AD0E8,69C445AF5EA400BFD64E3C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2B461A2898079028CFB527520217BA0D,12179D15353140E1810DAC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,47FE5DBECDE438E5298248B5704420A7,3F1774546CDC1569AFBF7000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3634B628DE0287DCB87B21C8084DF43C,345F6EFA1C457B7C56037C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,71DB88176E8890CD6E0493424A00FA8C,01079AC45A0D984972BAEC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,073FCFBFFACA47395734C54C8C476E11,218323C292CAC44089A44800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,19645F973B4B29F0228CB4427E65D894,C321F36C0A3A563A6ED89000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,83563B002DA2721008A75CFDDF36D82C,6DF9FBB17FC44848D1696800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3D6DA0BD7178D13E9FCEF132E9AAE39B,F91E3DD46BFDC0C801DBC800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C50C2C1352905704F2CA4DF172DA003D,E275EE5E9C189FDC4805B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DFD1188C409EAB1CF875A2DAB46F003A,30ED76B8B8D98FFC73D2B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DA249F66D539B76CFF6A10A38B98C9B3,E76C07BA833CDB9981010000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,41164FCCFF44634967586B797EA6487E,75F98006562C775397BDC000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,161305517807E4A4ABBEF8FBB3044C15,F393E6417AA099E7821C4800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1A43220311334360FD5DF3E5A8CBE057,CAD7D1AABA924B87FE447C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,188FD0FEA7A740DD5E7D60CB433D853E,BDD09A5FE431237C31A90000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,649FEB547DA8C8969EB42BB4EFE33378,6FC2615D7A22A2E896B5C000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DE5391FC6660ED178EE077AC2B6BC1F7,CBAB6F451612429F260F4000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FD0D3721DDCB2BE59FBAACC278F920CF,EDBC9EC4A4FDA07F6FCBB000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,49945330B249F1EC10F00E11D314350F,8E67AE3A3554310A3C8CD400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B47155CD8AD25E5D2F004EFEA0F72E70,5D669582C339BA9D32B60000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3F6FD204AD680436733AF549D5313196,0DFEA155575A9C8CA4F57000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,09B3BE30B7ECF8481BF7AB7D28FC1BD2,124A222E0C6C4C3A9C282400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,04D11DB975883C574DBD7774DAB904C4,E243465E4022DDE576021800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FA8C7850F0308A1D2650CDE5B29C1B66,AF3CCBFC787056562D522C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F8EB99DBBE8520EF4A86988E9B933143,55A7FAFAF4D8E539E4115000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BD0658FBFA69E77F373BA04E79619E58,0DC5FA323C84A04CF0BA6400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7652A528236DDA50C8D82737EB9B90E3,2E3F7C8CE1AFCA609B62D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8170037C74AE45C24EC09E41B4D2464F,55C9EC14FD56BFAC16A05000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,84FE114A18B4BDAC82DA6F5BA2DADE4F,3B33A37444DBB76D805C9000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,09D893A12772B3F9455514E3A50BB905,8575EC3C79BEA7A753DEF400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D6194277687F3F79E9E48614ADA0DFB7,05F2CFF1A0DEB7D166C26400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E8E52FA06585D1CE6BC29E6F9483A622,489E29F51F79A2DC2EA46800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5B4A722DA80ED01444A003039A9197FA,84AF1D967FAE2F7F34677400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,276AE82AF0EC1F9478FBB68B457E1DA6,88D6EBBAB9E8C84B55BFD800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CBD2A4CE843B952FFD80E3C9F21034C0,79CE4C2DA0045CF7D1C23400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,99F284D22F72F788D3F4129746503A3D,AF91784EEE542A871995A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FAD8B5FD7B889C3F36A267400DBFB131,91165950063D39DDEA796000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E9707B9D713F062DD2AF8FCE011CEFCE,7CE7B9040D0B5954274C5000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A1A8ACBAF0DECF6813478CFFEF342408,CF8DC0AC814C2AAAD477B400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1ED877F777C197F60E55318211897293,E449AD57A0C77F2D35B53C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,28EE90BB4F37F5D300B1E5BCB89B4FDE,C7D81EF8E8DC7DDE34E1D800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8B57B9568F799BD75BEAB79DAD833265,F8AC59252E4AB1F469225000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EB34FE1C38595A82888A4753ADE0FD80,FAE3BAB54718A74E5A6E4800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C2F1C922230840AB370211FB4C6CDE27,AF432068508846BB05B35000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7EB2BF57BB399741608E33CA070818E1,FE95822EB8561B5F9DC8D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A91138BD3801BE38A8E18B470D1FAF0B,D20AA5C2450BA956C4196000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,555871767F65C440A556F01171F43E8A,BF13C406EB3E0D21470D6400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9710002398B71C7F19480141835DB207,1428A58AD5BE14D21843E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A5D49EF6EF84356623664E7A5A44B289,081D6A1378B3BE718B569000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0E74D2CFB728666A8DE0152D6F5F1767,2A496945D2EA8F5C6FCEEC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8FF3FEE077290D92A82850F7442365FE,8DA7B3E1D781E85F9D7A9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4E9E877BA791A4AF7365E3AFF4AA0DE8,CF8517FA1B5269E553942800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0EC139E142A3B9CFBD610B1E0BB00ED2,3BB88451DA999BDABCFC9000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7EAD301F96A4F0D354E3C5CC836C1039,D113F34A1A13B74AB6AA3000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5636CAF8C382546DAA5780FDAD296800,2E377EC555884D9AD7CE8400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2476AC17FD5332046CF0BBC80FFAA766,E215C786E939C5A3F4D21000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EF96A0306FFD72A0B0869E4E5ED0A4A1,615631AE931D404E3BF93400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C09D609EDF571B1863505C5D65D26CA9,D54DF4BDA4AAE73CA7BFF000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D1A8C6545EBB37EAFF6CFCA054AC1EAC,19D432959633D7CD4ED93400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,26E334FEFD60E9C5BFE2204CC8CB01EC,891CE41D1B8280F6973B9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5808E12CB8BBB95FE7D47B1CEA43AC72,7D801C0E0A2B66139B044800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EF8D125A481566594E34D8532DF416C7,30DBCC499221A8A6E871E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7483D4EABCE5DEC707E177A81EF1F1A6,84C07BE8412B33B2C19F4800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C9288F1597B409FE379A218E21297777,D4B3E78C08453CFE9C000000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8303FAD2A86DF3FE7F68E79CF18E5B7B,2BC30881291A723EFF5A0800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EB96D3BCED1CABB35F8566323B9E7124,C637E2FF86FEE93BCEAD1000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8AE59A69F2808EE1B4211E1E77BDB654,8E518878B361082CF1267400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,948E3231F2AEBCD5D70DBC8CEC2FA000,A74C0FCCDD15F545FF44E000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,213F6E332F2E74B6CFF792718713F6E5,F0AC582F9F1E81CAF2F11800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,822D13B3EEE04CBC9C59A19FC077BF4C,B661ECA5719465CBCB14D000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,22D07AF183C33D9A7EEAB36211F853B1,60843547E8A027A1943F7000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5A30220376D80D728F3A5DEAC5C1D8C3,0C68D00D0A8D848A76517000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7C9EEAD19FCC2767BAE7FC689E152E9A,30164343516A0A6CCFFB4400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F5682E9663D37F80532742F0D895D9E3,C82518A5F8D502FB3397FC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,296BF0D43CA901E0AAD35B518C00084D,3C6F4B6F7921281C6EF9C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AE11EAF7E8E14A71DCC31ED072BC111A,2300B5EB4368C09530D03C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,897A26D60B5F3C924C973CFD92099792,07BFB9AD600A6F1D5D26A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C0EB7E92C28C07453711C712208EA590,A3717B4C776007A446D9D800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,81023ABDE598612DE71519D2B9C63A24,4602A9ED205DD7A2ECD32800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F3E8BD8CBF53F016EE4EB00AE83D4920,E7A2D4E1CA7C49956A3C9800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,26F7C827FDCC3176E87F119834D4B53B,95A75369165D6286763B9000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,704098ED590876D47D7FB9819B4AF98A,7F066B8959754DAA571CD000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BBE7CC6254B8122112C6CB0FDE8C364A,28334373ED0F1853843AB800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,03904F78B55EAD92BF5DC50BC1A9F39D,3BB4CA0F29FB0672A9784C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F459EADEE2D0EB4A55E0C36308A63AF2,43FF3300C374CBAAD8028000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,52AF9DB388B23F6FA74356A29BF4B732,9FC8199C5F8EB4D758360000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D855AD7AFDD44ABD84585C3864ECA1C1,9069CDE31AD036C790295400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AA660FD307D56EE038043DCB9929E6F9,EA388366D578EDB605EC4000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B0E54AE3FE4444AACE332DAC7A929C9C,221230927E0AE42C7B42B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8BE801918B64B94116AD7AA68195C3B2,BB5B34E1D9947779D8E09400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B612A5F7CB7CB5D0E99BB2EDDCFE548B,812EDCC8B038D73F2B542800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,786983EB5AE4433D69F095AD831FF114,C26CB10613A8AA8251133400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,099E770F15C696CFDF524D664D72DA0F,F8709AADD870573D692B7400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AF5AC99C606C009909E53CC35A189B45,5E892BCED45CA3567FF31C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,06099CD46C22639D0772FFB31EFF23C5,8DB982A8C70F838BEAB80C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BBA047D917B57C94A9672AB0DF38E61D,B9B06F0BAAECC986306FFC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,411665C7BF534CA2A0C7D5E9D7226496,38AD2C60F63D797726948400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0618B2AB97CA8B35E4BBE86B0E4117F7,503A8A7A498BDA8DB57B0800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,72C36F2A86A3CE233D2069460A461E1B,2E5727AC9EC2268F6A91C000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8282380BBCEF8A400CC57664464ABFBE,17F5613CFA97F0809867A800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,047045DBCEDE8DB595EBE7B49BD2CC22,5F7F207639DF0513F7BBC400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6B320FD047FA0D10C12EE410060A36BC,FB9FD72243690ABB840E7000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DB2CFBC1F0A6306895C40E5ABBD5FC4E,5BBFBF1700E67071610B5000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B540AAB41A2605210F8692B84A6056E7,EA7D118636B1AEDC88555400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,33338E1F721FD08B28221864346672DD,55C519E2884947DBF7CADC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B1227506EE577601CAE6FDB8560C3714,BA2C2B744DB37954C2A27400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AD68B66CD27F228827E1D81D1BD18965,3C209AAAAEA085BF8D89C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8618DE016FAEA99C3B4EDA224F92F008,52A92A58FC80FA4C238D8800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C0B136695B67AC610D884B10EA0FA91D,B959070045CD802F504A3400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6BD4F77DFF589FBDBDCBD709C0722EB5,F05861F9688B9B437CD38C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CCEA499C750A2BE5D572497AE3C42B45,FD8072A32652BB6624D39C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,21086C88705EB412DD348CAAF3E9477B,D3C78ECD5D669D924B7FBC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,955EFEE1D729A8E8A81B65456B60ECE1,A4A5FEA37064F0E7A9EE3400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,49CE6392F5F7E42B8778A385542CE9BA,E5C0197447F7A380C194D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D59A64A00283C3D684446BBD5203ED77,0E20987E3D59D24D96B13C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9EC7CB569F8F822308EFC4F9667058A0,7AA573410D4CE7805A971400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,631184C7DFE39F17501100987EBEEEB3,7E0435EFA49196BDD162EC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5DB2A97EA52772F4FBE6EBFF6B0BA41F,027DF7FE2157C137C966B400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6E070F316F73D196F56FEB995DE1B112,660065D8EBD54B7B86849C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,48342C9F13562F518F2A69E426201EF0,18876000621EECAD97DF8400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4D447EF42099C09F389049A87F9F8820,C92920E59F351664F7C79000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5612ECBE66133D8EF8AD9B85C9DF9F89,4268B479FF738DF714F64C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4479D5694B9AED0F44AA36ECC4B70D34,6F2467A3B64CAE1E89625C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A85CAA9B29F28BCA024D16AFF481012C,BC18CD6B0CAC773A0CCD5400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A2D8EE169F13CE527C75833395926474,360AC1DA86A569FD1C4CB000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9367387612A62FC0DADDEACA88946938,99078BAAE7D907DBE29B6400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,12374AF4213D4B18BAFFF164B3FD93AF,E12A21A0C882F6C8B12AD000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7777D20DA4C2805FB672F2E7B6BD70DF,C6BBBC2EDC99EC77C2FA8400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FF87B8268C91C2D0CA99B6A73DE942DF,858C22D7D549ADB1BC060C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D880189E1CD7D91A77165BBBFE752828,671E94443799AB38DBDA1800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A2525084E0E318561A42F4E983B920E6,D430AE973C352DB68640A800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FB997C519995347B1A6125330A90BF5F,089C7083E5ECFAD7DAA11400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B182E694FDF17FD7CFC726DE1AAA80B1,18683615DCACDC4B79D44400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F1553F3A28DD03B4DEB84ABD9D0A3F2B,DD91A1D1C71FEAD0A2595C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,38DDCC7056DF77C9EEF380A723A8AD83,DADCEC05DC82CDD74BE91C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CF6CDCC3D89ECC0475F759622B328F57,C0F75E2253F6774EB8425000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0735EB4A365CF9B00BB85DA566021059,C1DE8D6497EAB4A2CF7AF400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4C93FE9E30F98903DE52C0581032FACD,0B3BDCB34E07641B36985800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DCB9D086B0E46508D0C1FE2E1AD6623D,212B147796DD9E304F8C1400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ADA34F00A2A985B0744DA24E138A6AD6,3D77D6C13B6738C782B3E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DF71DE337AE2787FA6AFC405FA58D434,7372CF2B02715B600EFD8000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A2F4DACA111B16ADA44323552A973563,12DE30EDE1D36C67FCAE5000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9247F0B48A4D5194113F964C07DD831B,76565FCA75F3A1B064419C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,39A8CC4F8255046F206D767C8117B9BF,E219AAE9DAD7B37E7EDE0800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A4FB62150860AE93943C6359E4B4C2E7,7C0C164480A44A4DD7DD5000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DA98B46B10F9259B2774613CFEBBF7BA,C7336D60F25BC80A9A54AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6134D5747E08C4197AA825C67CD09A64,37BAF4A3E83A2A615592F800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,02B67B24FB4855F490F119A8ED4BDCEC,E95290ADEC013547BAA66800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C028EBBFE824D1FB9B093B86EB051A08,4626EDA51BAED0AC9532E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B98956E8C4FC8FF2230F41089B9B55E4,E6AF4B55C6DA8C40DDFCE000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1759F0517314526EF0F084155E9D24D6,F88275D77D48785075CE2C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,818F3F03006190218122A2A29BE76EA1,872820D68AC8C488B87A3000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,82C39BDD7EF771B9945D7B398517DE0D,C90CC93C31F8AB01ABB09800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DA539F773D7AD125AEAFB99FE0D2741D,F00769FE8F4B138DF5C9A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,71424D509E2E622EDFD3996BCB518F35,72BADB1920B5920CD44C4C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,69C294ADF010BB06EFA05E2F54310F1F,B29B249C2EC8BBCF01B22400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,08558A4D279984C75B9553DC9E3D2556,B94A6CE01E9844AAF61F7800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1F8FDF96D481A9BDE05A2FDEC2A321E5,187358184836E42DF6ADEC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4363F070C9252D5A07683432F3970E9E,4853CF0C1D4AE424F6F9DC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,36090CAC4DC5B58EFB5A1FFAFEEC48D3,551213305EF13C71137C2800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,111DA689819D635FE3A658460AE136A4,28A3D5EF7A99071D0949B800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BB283743E81082E8134E3E537873DA82,698447E19E464D548BECB000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,23F2C78A3DB7C54022B827BBEB2FDEDD,3D4FCE9E534CF42AB3C1BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A0DFC63065CDD59FF97BDB43DDCE9967,4471E003AE9D6F331416A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,87ED4CCC8B6E94684953184537CC1A52,26E9D421C1A8F6687A1C1C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D39173448EDFA2F4F1C4E59587BB263E,FFE059647FF0C8699A791800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,30EE2A8D2E7AF20C1B9826E774FB16E1,E4B6BDE3F842E79C6A41E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F16A4867C3316CF0A9C09FD50C5ACF71,E52581AAFEAF943A7E130400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0610D1FD0164D1352338E0FEA351E2CB,8D7E4DB5CA64B590F446B800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EECC5EDAC58AA8CF020D2D83D86E38E8,CFE185A93812CC2A08F73400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CED8E1CD712F4760587335F5FF164ED8,23112CD5BFF1558A17C90C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,61EB7F4DD16AAB1D27D7B12728ECFF09,359617BC1B8612B341AA5400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5FE10BCADA993532EA7AB1238B6B0ACD,78257D016158BA82CDDE3400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D5940C92AAB7BB89E8BEEFCEF34FAD63,E3D6758A0178F38E72BB9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1BEA9E235C5B7ECE309E3CAE8AF151BD,5BA81B9D417CDA46946CA400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DAAFB30B99E309579A38227B16FE5C0F,5328067F6C6210A43E411000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6C81434FEE82C124912D2F5F4A0022D7,7AA28F178B99D93C668AD800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F57CC7A65EB4369E0D457C8F79CA4114,6F8EEF679DF8DDD24D2A2000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,790C8BB19A93A4EDDFCEFF96EB1DA357,57ADBE7CB987956666326000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,03B2E33A8396ACC7EEACC882AED71E7A,98FC326CA7FC657BB131E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2C0975C6BF224A734047B28185528D2F,61AB5628359F92BA7AB09C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,873C693B10236468C4A142074911F111,6C7AD95B726CD0C902C3AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BF8DDD2F2634A51D08FDF6F90107BD04,97F17A2D7349E3B872B57800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7E8BF02547F38C95643897D5D575F6B9,7BF6276405AE0317D23D5800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4A2BAEB93E90D9313E794BC106853294,A517B8E019812C7F42EC4800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A29A413D8420CFE4A0399946FAA6D1F4,BA1614FEF80C9A567C6C6400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BB8DBD4022734A5BB00E50F7A86F9568,231874E4F79C03FB60AE1800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B94FD66004111947B6FC02E70F82F5B0,961014A6B1EEC0750EF6BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B8E33AA731F8B530E2CB6B4B6DED38D9,1425EBA776ABD3C6C4688000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3A1C4F081597FC7A88E3461DC643F9AE,2C56ECBDF4360ADC9BA4E000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7CFA44ECF817B55295E8F60FDA3D5912,624C50C6536EE49D6C3A4400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,298A9667CB620C32BFFE8F70D0350363,99AFF69080619D9DBDE1C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,65BE0781E67A46F434EA21F6F38E4E0E,E0D3D6849A17D52D9F497400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,68F24E0C77777E7D39CD542BDAA5EDC1,7CB1BFC29BE8E273AA8C7C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6069C7334582DA4C3273E0ADCAE8E480,975DB219B89280E427B87C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8DA7AFD6C226AA61055384D3B2BE7E87,5A3603B2E209E6C34F8CB400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8E8D9D3622A34DEA88BA8D6E7F81C401,B13A3E6AC4CB0D5CEEEFD000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,542024F443086C123567A12E681B4BC2,E0E35FDC94F0A066C77B4000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,72F63EB6B53171079F98C1A3F6417ECD,6D4932E9FC8C2CCC80674C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C699BAA060ABA8086ED2582138F25A1A,7742C66D4B3FC700623A3000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CC1A5E8A32566A560777CAA60EA4DAE0,05572877BF34525CA402A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,630E0A65FD189DDE95CE1C5BEA7B79E4,222256AEA87BC488CEBE3000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7FB0F5F6B96A2CD1C6314BEFD33E3461,7D6641842923CFB68BA25C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A30BECB3742AFF6EF82D4864DA6DA432,6EDFE93F5BF4A8BE7520F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,68C392DFEB34243DC45CA5112C7B1E3F,231D86C30824AA03A6B94C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C1B3CB650742498D73CB633F1BC1EACF,D54AAC27C4D26C05487E4C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6326E055E883D4BAE9AA4C2C150255EF,3F71A7EE677099AE6EB32400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,81AFF35D10DD0122DDD8A08A0A173EFA,E68AF50EBE86863868EFFC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A39F138FE330BFBCB7E6A9B67D00C7DF,FEBCE30B861BDD29C095B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2A0E15800C2086E341BD9BE00770F860,BB46EAEF62348E952A3AF400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D05D0C0DC0A8D3D128D6E18EB5C0A3DF,F96E8E1EDA080CA1A7D61800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9F5F056A9041BD41A00F0E7CEFB4A612,B7557C98F0DA32E59CCAD800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3747E9BCE992A392460CEB6F01BFA975,8A1DD67538586E7FD411B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AF92F06EC27968491D4EB46DC83FF644,5C4DB60C5A3E1BCFF6C6FC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,765AC73AD7834792BF3A18A56D51932B,5652F0557D4F4C5F33305800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A0FB2D27F274247E88666E03048739FA,7BE3F59F5A14AF8E25EAE400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B6358FD3EC378FE155594E8CB85CF4D6,2C216B03B8EE640B7827A400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B06649CAC926D9F0E2308D70115AE1C6,288BE7C8D092A199D71C2C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,76274A06460DBF5E2E4646BB9816B44F,3DC7F6DAB19EF29B7EDFC800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BED6BB15E97F9E777836EEE92E8FBAB8,E4DE3040F51A034DB42A5400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0F2AC88BCD5192DD8871223FA5D002C7,29D9A9F1C7CD8DA75DDCE000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3BEBB8AD82610C4962604967718C6E4C,095B0A65995FDB444FA51400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B22BD1AB07FD4DF3EA6C94D421B2BDC0,77969D457D8B3B324C447800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9E75844E4BCC1465B38BCDB854FE147B,F7D399019EC2855DDBB6AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F02E5CEAEE0745683A75DE33E9E79BE8,2DAAD1213DCBFF4241F23000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1ADE7A15E36B2271E618E35824003BCE,72168E07C397F5631FD42800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,07A2681347106106A1D416C3EB20B79C,6E11DD36D3BCE4CA2ACAEC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,36EBA429004EB96ABBE0F14572446AD2,A996965C2361869954C62800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CA5B91005468214844E0E2A7F5B7AAD4,0A5AB72D7605640AD277D000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AC2315A4749E01A924721216F803977F,5D4852D8A3E7DACB583C2000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C03E17FD332001D28D394F1B40D3546D,939968E673F5AC7A1F78B800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6FA3F958542A795C72CBBBC3FD334F01,C4E3E994D191899E0024CC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7ACAB3D0CC89E5679B6C745ACD3D1822,4B7507876A82AE89250F5000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,41BD9F89D46496A85F8D2855B0313844,96AADFCDB942515831CE7400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,885714B0574F319A71948831C90B0889,16463275ED381B8B033CB000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FBEC83CF9D10C6BD5FD6AB6931C2DF44,26C14B7EC005AEB83BD2D000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2F602DAAF79101BE2838642065DAE18A,3D828E60798F6877A6CD0400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FD9BD8C74457B15617063D6525ECB6AB,B42DF8D97E39F4609E822800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,479C04ADF27FE0B28B760375F86A3100,FF284A00A7053BF526170000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A1836CA02DB03EF6163874415520C636,C669AA3EEBB3E76833A46000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7CF4B6B1905F2B4B2A24DEAF39DE2D96,357AAEE48666AAB9E6389000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8DCCB8D29BCB3E1F6C0019D491DAFFF6,4160CE94AB7D912F9AF6C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A15AE2663BBAFDF1F969BDE8C84D3BBB,A59DB40FD499792BEBBCD800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,892C6E166C6D11FE1C42D20F86404A0E,9D0B328B83BEF9F874968000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,31C3BBE4B06E87C231E5F734111CC661,50EB14E7F07852431D608800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4A6B48C7070F1E64881B61A4B6EF9F8F,99F1EDF56EB873C810914400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7A2930F6688CDD9D37849C3B2B0856E7,B8BABF3B94E49B426DF6C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AF0AB32F219D7ABD3BA7ED5C1FBAB869,BC4460DFC7C6C98DB6F4D000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,40A2055D8738BDDE940503885D11EDA6,8F7B813DAE73B8D7A0DA3800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D4213EE3E46820B1E7808B782EFF1E6B,8492C81C1E616AE5AF699400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1EBAC09FD03B944E405A2767CC52271B,F23A7C4E82532EF97161FC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A199A02D1A11E43FA042185253F7DAA2,799C343425A9AB9D10698800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8EFFF1BF4695138FA35857ACE9287997,C9C4CEEEDF47434881201000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CC4E4B603D4682CC8FA804E6AAD65CCD,AFBC13D658BA6B0993C5D800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E026413D8A32FEFD5F3CB77AD74CBBB3,507DDC432EF1DFCD95713C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4B73F664DA00B2D1D3946F3A58AB1D93,C6A1E344E333AF7EE0211000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E867FFF55682259400BCBCAF3ADB8CE8,E29A1CA488DE57D02B4E9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3242CD109B2DE38BC582FEB1E5E2BBD6,CFAA6F0ADE0A1496D250A400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B755D3ACE3B389010CCAB8310738E084,E77BFA25EC55F9F35B67D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8714E5D1CEB605098C3DC3DC5D01D69C,C710A159C4BE312E6DD12400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8A23264F26EB6D899265E6B21B40D849,87F1114B6C50427E40AFB400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2B124EE3F6D923E6A4E1580953FB773C,8C97D67E177C4C1011A94400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B31AC9479956A491400AE50E2682C238,DF553F80727F1888DAECBC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1909431199EE7A44296DD7C68431ED2D,BF839D489ACD726E0C964C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A9401C211D054F35300A80EF9A0DE775,C86CCEF18CB393E2D1080800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E57000D7D7FCE91D84D803C14A9EA9B6,1F818D301438528E44689C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7CAB835272DFD4259DD6E5AA0FFB7CFC,014A371AF4C12A534463E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4B013CB4FC14DFA049B76C6E1C1F35A9,F75C8E77A029F715ACBC7800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4E11FEE1A070A1E8887BD8652E924070,B3E80ACB16B415D3A4B27400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5035CD99EE98F84839DCDFEAA3FF60A2,2A30B018C91925824A033400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,869559F9E084CB43FD29BA04010DF774,1B80A1592103CCE0333ADC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,26B1EA7E2650A73F28FC35A403635466,FA9A9E1A2B5A90C4B7295400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CBE67FCDF468291A9907193F0897F265,C03CBAFCEE43CC200658F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0348D66B461D5BCE79B46A373453DD3A,CCBE7E66D7ACAEF1F0B55000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,518AA0D4085C9847707500B24197B6A8,F7DB9D47F3BD3C60FB397C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,50AC5F40DE8259EEFA2F50AC1DAABDFB,96932C47CC67DE1623967400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,552FB592DF830F4888CDA466B2A927C0,C2C1D6D4E18C0DC604EFE400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E2BB10A14F5BE16D7D7F2E947AF4333F,5BA229B1795B0A123AD22800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9233F9C1736AEBC3C174E515EAE36107,7C82371F4F6EC44152366000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4AD6C86685F605B99ECFAD1D55D0535F,DC34A617B98D52B318C35800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,449916D9A1DA7E291F48B07452064593,D12D11519E617795BE2DC400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,77605ACD7148D2C3636D6A782300FB95,9239B6F7AD0468E1B8B5B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F740355FDDFCEBDA6E3535D8A219D3FA,B049B7550A441FE345347C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C1A813B24BA270D433EFE9A6EC935C70,1C7759BBA563FA66C4D9B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0D475FA3BA4CBC732151AF1C1AB55954,A34BBEB09F64827B927AD800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DBFB33671579E69C54CC90741ADAE794,5940EF1C4B8020F4A5DE9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,434B3CF7B020D9EE6E5829B87C15F776,9342353443059BC7BBB0C000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,40F2E0CAF7F693DDD605AE21E629A99C,8E8CE97A05F4E936330ED400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C863F8254231D458116A0E78DE82DCD5,2F78C563E6AB4474A470E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,89BA7605A3B7A8CB0B60A93F00926F9F,BD73B0C1FD24DA1D9A78EC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B40A3534EFF09BBB4E1674F6CD686583,A489F380A749D2C99CEF2000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DE6ABB547A80164584F19BB81D5B5985,736995CE7725C99F4A55CC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,744255C89986091DF9223DF6BDA31F13,DAB16E6FC8908F2B10652000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E106C07F8AD119AA4EA7D2A0D9C22C30,4C9403ACDA17A0733AD70000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5398CCED5DD6A335D8AC924A730701E5,D54A777CBCB2F42E2E480400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FA4BCAA8D9D13A04D89CCE7D5E80C0D9,DAD82F5062584E29E09B9000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C1164BAE09775E74A1D6499ADB2259FE,103058A5996C13EB573A8800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E0D3228E5A7CB033F32FA785F9EE6B18,E1795497A53A2DF98F7E1800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BDD19B8096384D186B36151FF21F5365,0318AD041D484894D6372C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A08B3C2D17E0606A49034974C336EEEF,0EB0D91837B25881C95E0C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4B8E4E36C3742615D63F2E397146EB73,891FCCCAA5CC9BFC8E927800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E818C06F8F7B2E6F6B32304EB477822B,64F16D8259A5AC6A43CEE400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7109C9D5C62A64D58ECF6FD23C6B0340,85237EE4D438354692EE0C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,592E8CFC156A61778B3D0E4240849EE9,0D29A236335E8AA9B622BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EEE768188A1318CE04D91AC8B8FF8862,EA9AC41E17F1F5DDE88A3000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7A0A0B1F35EF34520D86E09BA894A677,F4D8AC77FB3C4859A2BF5400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4A668C8A9B94DBF27340937095C3E759,1427C39FD52D1949BB485000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,57E730905D26E9617981474A51BA9D8B,49276A2EC63351B7CDD3AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5B361D566F88DA2B6B75F18B5032A696,1A6158CDBC7A79FB57FE2C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,16E07D0C1B67B176BA2FE4EAB0BEA590,3EB4FB87472864E8F8EF7400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,84E31AB35D3D2B79657E4A604E1CB1B4,8D0C3E2DFC864A897876C000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E3F8242FE1750D3C6546F1FA9B8C70E7,FE202F834DAB7D1B91190000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5AE8532AC676006FC919E9AF6355D00C,9516F98C444443E655352400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A9509E7B74D2D49F1BC88DC311AB55C0,925551FEFFB8C4127047F400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C3E113DF2F79D6E7199B6E323C27AC27,3B1BAF967BA8A79303E20800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1F80302F5E2E3490E5BF6B14C2A82FFB,E943F301AF257E7A2265A400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1E255DA8EE7EAC5C8F469905644166A0,23A53176B4E0115360D1C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,870B59B5A8AD858BD9E8AF222EC2B2BE,5BB9AD7462713852484CF800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,46D2662F936324893426FE52C96052B2,BE34A735E3A557C17F330400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7822EC77044045B9A19DB793D3F50E6D,2B42F40E208FC3E700DF8800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E7C6102BE479FAB7135005749582556F,1998B5A4F8BE6BF7FDE1F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F1E6F6BB3A7F3511E13E28C70B31F30A,2DE010A3EB3E12E8635A0400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4226FB50B2205E8E1B0FCE9C5FCF9F87,454537140131FB1AECE00000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,14DAE2893E5DEE46E9BC32420487D083,64404B7FB3839466C65D0400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,82B85B0C3EC123A5CE60F2E0A293DF44,2F8CB911404FCB5A50742800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,49BDA1CB7DFA102F46514A381A002570,5524C9FB7F55FBD4D6330000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,56FD653DD3708EE5EFF295FA2F206CE2,EE127E16883B3E65F4A4A400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F98A948E3FCE5E5409ECB724C7507199,3ACF16C679FAE5B121CD8C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ECCA2D5D97C4A7B908512664ABBC579F,B4E22BE1BE385B4B1307AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5BE7160B02F0CA8C20B887663ED0F605,C7EFF71D2B44CB10E9428C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D74AD239D11A06F9591E366D7C15937F,D60F219AA40D44F4C55AA800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FE536AA01092A82012ECA3A22007D478,431696D2FFE08EECB4D9A400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CD465A20768B32011D00DDD5B4C86B5A,1447818D2AD72F9B6A018800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,731A3E85A3EAF95E4EE205BE06209162,50F499B6DBD107D582C67000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7B6EC21FE5E59EE3203813614A753FCD,BACAA7FE68367944264D2800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A5E1699FF99C2263D352ED394D9E61B5,1BFB5EB2D1037F5EB9E95400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,085036B1587164DDDB7A15D38B3B7727,4E739226C3528DE3DAC0BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B3295F3945CD4E3A5A07610986B0E7C7,C4EF694963D801AA40671400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2518F37AC7124F41562A52D6D7ABB113,B8EDE2D5CBF8ACCF1342C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,91050DF8DCB43D2659CDC70E78D13E5A,C290CD4978BD1267093B2800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C7C18666F49FF1075E46EFFEACA9FFDD,E9499E7F110771B569054C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DEF71CC9437573D166764D7F9E216EA9,B83E9B48194B43DB7937A800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C923CD7AD3DAD1BB3FF5B376978F622C,77F2AD8926487A80D785C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,078DFA9BF3697BA52C5FE0AB29CF31D3,757AEF05130E3548AE8DE000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FA750184CBF8ACE84A5A3DD63FB0E820,68C4EAD165D5FAE44A9EB000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4C1AEB6C1322903EE5EC13F935EC95F8,996700760C40CE6BA6164C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BF096BA843214BA16011A3B4D45C4F5A,1FD24181BA1DECF441218C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A82812F0BEEC054FA2809FAC0F64B570,ECCD419B3BDCE8E9F6A50000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,539D30EFEA7B949821334B87BBCA78A9,48B4D30B1EF623792801D800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4AFFE94260AC58BB1DA39998D2451A0B,880E8243A6FB4F3C9547FC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8104D4FA54F766E2C84E7D381F9100E5,7D5600114B1506CD0B1BA400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5A4A97705548D19BEF2C0842A42C60A0,8593038543A80B0AFC790000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,26ABBA139E866BD2CE6BFFB197E8C0D0,C5CEB7378AEF12A1D6A41800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1AB0AA185E7ECF37CCB2C6EF04DB4F3C,EA1B76E9B763C4256E21DC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3DF812538FCE077057224B85ABDAD113,08D89B0B14CD51A459E1B800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A9B7D016441230D11217ADC999873DD9,59603D45E3C8F77D79CD4C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1A2366A935C097B272914A05299D9793,722126643278038F1B1E5800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,64CDF5C0CD70F5B72E8A246709DD8FD9,78829F65E09A51EC92B6C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0D3CD841D24FC1698A20337D11448AE8,36DC921D1FDD637EBFA03400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D44B55312926BF14D6AA11AA07EEAAEE,CC4E2F51987ACC47A9FA3800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1E9051EC096D4595B5EA72AA10CB050D,268B8F75749FE37776341000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,043C11E52AC74B30B09DA3172839DCCE,FFA615AE41A2B10416992000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,39717B75F15055CD964DDC49108E0DE7,271D6737E21890510DE9E000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8D66BD55543E7560F9ADC1D28859D457,7060CC2D162CC9BA481A2C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,839A1A33E3D416E138B235A294ABB86E,C2B784275267037A60FFEC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,67A9C7F1E4D2CBA5AE5B42A1FF81F49C,E774EC8A7227015D2F1E5400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,46C4533742AA6CE9BA0262E932891758,D67AA5488DDE1BEE99E9A800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AEC3F4523959382BD115CCE442B035A4,2D18094C4967705887C5A800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,46E6E616E09CB7E55DD39394A5DC3761,1957DF25DF612BD4B53EC000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8072491CB20007BA6CC7E24A4FC23A2D,A84FFCB88DF8E54030774000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,700384AE8C942CA60C9A615E0FB93C30,4B8142C4954953A8D13B6800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9951C3E75C5ECF9FFC7566AA9D0031F7,809B1DCBEB889A1FB9E73400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6F610E49FC29DD5EA3A445F304085CDF,822CC2F7447B2E62C2579800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6A4CE86DAAEA928FB124EC3DA5CA3EBB,EDE83ACEB36404FFEFB76C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C7D6EFED35F071D2F2462160CAED7F19,E575EED6081EA7B77FF6C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,29DD5372BEDC0236BD8ED88ADDD0C02B,45153BCDDB552C6F666CC800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,54F41A2C39E1D9B01BF950D5CA208166,1AD31A927B0C6D38B620AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DAB9695550243EAB561253A7C907959A,3BE487266A9ADAF6FA2A9800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B209462A12C58C787B0706BDC6E071FF,571445F58FE2B1E49F6A0000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,885264A8B2BA53283791FCC17BED5776,E6431B1E31B01187581D3800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7CB3743A2F7DB26C8F025865EB42FA4D,08B8DF66B2B00CC064AF2800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4BCFF07D51A538D69A1BD5D1BD46800F,1128215EBD7FCB32AE078800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,64A07031848C50B7A455919202D920DC,B67A7909187BA6B7DEA1A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,000F68298F34759CAB5F8BA364593FC5,8B70B69633E3756CD5A22800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AE8782A3BFE932D19D6C028FE65938E0,874B9B39689E36259BA12400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5FBD8DE9DB7E7378BC32417528417345,30DA2A8928EE101E49859400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FEB9C37A35F9E331241078CB35A2056F,0F20CB8D72AB7ECDD6A1CC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,12BC5563EEF599DE3FC325CF7B7FA3B7,AEDDD76C2932016DB66BF400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EBA08765670A1137D8D1876F115BEA06,2E830A587D43AB1AB1FB9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,435C0F9B79DEF7A007DF1177FF846394,DA7F625720EBFDF1EB8F4400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B0843767FA1549B0850D51AD8BE3488A,C65EC97D459BA53821677800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,67BBEF0293C80DB85A2E5316D2E266A9,E8FC816A328F80CE1CBC6C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0E0CE1586E32A187F9A825A28EE3D5A1,BC595580C882523D332C1C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2E63F86CC94FBCF1E5B013108F5E1E95,EEE860FA5FA7BFB372D1E000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2E7D53F55D07D935E2BC91636A00A8D4,B0FF535B497CDE59E244F400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3BA09096F5B0145242F8542D9BF1DF2C,3756FB796F8D771B42977C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5A9C6694C964B25FB8DFD93C3B9F46D6,D3769FEFC8A2EA673C502800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D06567B86C6CDBF5D47DEC7A8F6C6731,AE631A624BA4DC2F594D7000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7928E74CA1EDC169BDAA19ED05D67F0F,B2CDB710887C45154C093000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,88DB7A8B9956FEFCE812FCC84D07CA2D,6DF91317548F4064908BF000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,14E0A13922478E323B00B93E642FE73D,7E7815BCC48D0C82F7219800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,58BB7507A0AEE3197BEB8530588FAA99,F71C10995E59E39439344C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2F6752C2E43C8C1FD5ADEF1FDF5F2E0D,FB1CC235B949996895079800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,76759ED260D948F7E00063B3DBF8BC4D,B4F96FC1DBF0375BBC740400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6E7F5FE64A5BD9CA55231204C5F6C83D,98D40A3B7F6764618E72A800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7F760DA49107FAEBF8A8B814074678EE,A03E72AC97B2F8EE2808B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,097141EA7B38A481AB72103FE96F978F,6475FE67A94CF3660E18D800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,13D724201521ECD7F0B5D63907AF3DFF,0B6A33B2C9D0BE2D7A638800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8F3126186456611FFA524FA250CC1A77,5C5E9D11C6ADD407ACF68400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D1B6B5A60FC228BB38E51AB14F696C42,273E6EF03CABF21EC12CE400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,10981549EBDA3223E8D820CAC9169A26,B5C389CF85BAF5F062AC9400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9C9C6E50E1C0A703F51511A7B6CFEA28,23DEB00140BAA6B3379A8400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FF75FB5E7877446089C27EC1A62EB922,04E38D6813864C5FB85FD400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F735791998CB0AF5F52A65E60A8B5C96,831E4825135C99143BCA4000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DCE0F36E9BF4A6F9345490F0B6CACFD1,8794B636018607C7BB901000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F9D41DB085ADDF8FADAB0DF687513C9B,120215A9948729BC5C1E6800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1331BF559F492A317E5950A8F7F7AD78,229F583637B4C17DE9380400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,678800573C8219DBD720BF0161848962,2F579B45B8368F88AE19D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CF6441B4CB4FB1ECF042EB6D61EF9285,0E653124AE23AACB626DE000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3708063897CA8A1F36BB6CD36836528E,9AFD7A2CC10934D5A7337800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D91D9DBD32EFE0A16B9B112E941C7567,FCFD143039A3A167DC878C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DB7CFAA8A86B47A4D3D1F6A727CB76DC,B4AF398DB4DE17A5AF00A800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CD5D71BF49107DEBDD94576C8E4E1869,D9367B9C59A080AC95C66C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1F228A9A66DA0602188A72171A3DF008,930DDC0BECFF7D99DACF4800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,01A0F2374215A532E3ABC5FFD6CC951B,BDB55A3E93BC6099A3545C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D3770E3A7867FA05020037EAD52E23D2,31BCD31C93EE77B1CA5D5400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F98A68F3C5C5076CC908EF36EF41C438,463BA41C3683DD974FDEEC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,51C9B75E2AFCDC3392122D6B9C57DF91,CB1A46457B9A64DA0D97D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8467B23766BB51982A1378B4C2973A6B,76D6A09C5D35F0B51DEA5C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A33A014149C8472F04C7B546F35506CE,2475B8089D2497E764532C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4D8EC57C97246E24B6D631040BCA1939,DEE3FF6970851AD21422FC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,39D756DEFB4B005ACEE292C396D74B58,6F3FF477D18954D1863D1C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D14C6D8641E7436D3534AADE2907AB1C,256F2874813E19140D865000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,275154585B5D49A9DE635124DAF16F2E,EAD982C122C47FE01088E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2919B272EB6933E0F6111ED30F7CB49C,E55E2F5D0DFB3A6201419400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D826E72C9CD9200418707FE58F0712F2,5F4DB5348AB8734712FE3400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,702D868C60837D32C415C02DCA5E92A7,846679260D71291DF3D83C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D45A204033D99A424C794011586869C0,B45C84D24D0B3EE3F1BE9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6E2B9A5D424B8EB8788208642C0D9325,1587161E9729C59F48E36400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,215245679D97996331CEDE6C6BA812C5,E763DAD3BC40C2B2B5A13C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DA33695C843B2BC13A05D0FDF53E8AE6,4B61EFA3570EEA3213B92400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0A7EC9ED67E2CF1681D66291271E4AB7,4B98A3DCCBBBB0F20669EC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C366BD09AE8419BABB3A1DF026B826B6,D8974D2E5421CC65F1532C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3A20FD98582782E2467A64F4582C6FFA,08B88181628EAE2E94F33C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,91961EDA843915E90E24DBC2EDDF2B99,55EC4785C219C49B07A7F800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3114A2B04AB507BFB1F2064EF4D46807,AE20B2539DC263383EF48C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AF0200DEB592D09F9828D205F7BF3522,B57F0C011EE7BF1B16514C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C18282D779F5F8854CB407443D8A23C5,18F5CF7BC9F9F448DA358400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1AD7CCE70A6778AA86D63EF43143854E,356192FF2F5B9C560BB06000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F147FFB56F664D8A2587D8F8BA338246,9BAD90BCB877572D1B258800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3ACF8A37EC96B1125AA54EBD1B6CD804,8CE5FCF8031EB8C6EC1A5800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F4C93664DB8E72ADB0E578F14E3B92A7,102EAE991DCA6637C331E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6984BD91AEB7413902C478D5DB6BE14D,679D8A7CE0B34ED219432C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DCD8184D03278915FDE4BD63B943B954,01181DA58C2739AC1D800400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2E2EEC6C03258D817EB65EB2C60BF80D,896667EC0AB4B79EF744A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,707E87CC60B31BAB981AC7FB6BBD6100,812115149198B2653D7F8400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,37E7F27C19223C0BF4A46974F72CD221,B3EDF0F63CF9C42C33327000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A7C272423B0336C9A72BCDF4F6028617,51717330C0812799C3060400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3D4D311D218A0DC45B84936C6F4F08A2,C96008F90673970556634000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DFBF17DAB39CC5F2E8CB34D43DE46B34,DB52DDB44FAAF20B44482000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B5B897581DC42C93F688C6F2F595BB49,4D3F63DC19CB8644F39FE000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B49596D0E69308390858414C49A950A4,8A820754E33C95E4B612C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B611FD6DD1BB62638939447C16B8D93E,FC7441F7E49561F317C00000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E59DD928BAB2FC9C637AC15FC8E0B85E,227CC47074FFD676EFDC4000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4FE1F8C8D7361B139B5BB339A8743386,26F1B3CBC7A4F1AA7E3F6C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A52AB013AEA6AB771B129ACE61538B66,34B834857D32B40B56E4A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1EF42894FC1CFA402BDE02BC63DD3414,27336F3A6A03CD844AFB4800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F675AE5D40B508470DEA22C2FEBF5F92,FF892595BA5778DC0F9BB400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AD3963E2A83FCF9BFC35070D9DFBEF7B,F13262FD09B13B6C5C9A3000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AD3D8E0EAD4694D27D053C02A16F45F4,07BA12021F3E3A194AC6B800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F29222F9683359A965DFF368D37B51FF,B7B1C5C506CC44F3C4485400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CC74FE5763AEC95F6A5116B09A103DD5,4EAB8703F75261F275353000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D5C7CC3C0651B7C7B590EA93F1F99E87,06B64422EC2EF03E4C989800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5E3E18F946CD52E5584557FFDCF9DB39,B34C74616450993462222C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9244B53E99AF01CFFDF48AE1827ECC52,1C8DACBD94BEF8882DCF8000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8ADF54EC31A84CFA1C9C85BA7DAC5987,F0769F9571F363F8A9AE6000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1C0155307746E33656D84F1C6874216A,3B24753DD4871CB2B9D8E000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,572776074B2ABF9D474A1E90BE3D4282,0B18859500C77987E51B5000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,530B25F37BAB56582E93D7CA49BC29C6,0973DCFE0827721BAA3E0000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BF7D2196EDFFDD0102F87BC73E736647,BF29D98555DF5294E4754C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3F62ED8028BB9326C7B352D1BDCC2E3E,FBE9C7A4C80FA177D2D86000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D4E5FD7028BE3A1A2E6317D8914B5DE0,7D8EE0F3F6E6855579A60800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FB3930B4E6145ECF7D9A1D1795EB97EB,37F8240A0381D6B9CE73DC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,682BA717279864F9D69A0DAB66309BC7,4D52F954B8A35A692B378000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,171FCE31DF97AADF7BC8493BF62EA86B,B615010CBF2A027D4A339C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7E4959E557C3FA3CCE5086E899008942,2A3518C0E04528F3EEECB400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F507773FB6BA07E47C38EC65AF4383C0,53016AF6C302AEB44752DC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,695D6FE782777103621CC4839A53541C,44CCA8780CE27A3D09F9DC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B6EB3DB8AF175499640AD17205535EC4,1D67DA38C8FB1DBA40515800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C9E6AB88664A3A4484416CBE2B6EE949,EB3D0289B8CF2D0A83558800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0F7A74C7EB58F951BFAF9298E1443D83,785ED07D2CD8A61F0566AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EEF8E1CE329497FB7BD85D563BBE6BCB,41E2A6F93E425714E42DAC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2FFE06996CFA3E20587292A057655B4C,DD387EBA7CCEFEF871866800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0717F68DB23B333C77C94BF4186D80E0,394CB35755B97C69D2A7D000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,03A4141A41E464C133BC5F14AC2913CA,E0EAA20C0F3F4D87B7235000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6077B034845B4256457E83B664A74CA8,0CE359E4114FC0A803D1D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,68FCF6C5DB5652ABC69FD422A726D433,C24AA6ABE418FE0A60E67800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,40277E59AA7BA0E34D15D319949890A2,18A153DEAB2EBA2B767EFC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,511D2A6A97DC190350BEAC49CA985029,9CB1773E34708EADB1C1D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8B56006DB8F26BFE637FC23707BE420B,BFFE0C23A994AA0CBEF03400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,521CD0CE210A03345901F362CB1F986C,6944936261F98CF9ABE2C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FF7D43D1B7EFA2C53CC441D24BAECB14,E44046764E65634D42E44400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4D86EDDA6B38C68CA72442D8387D2583,75ECBEFF42E7DB42CA865400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AB459EBE37BBE976ECE0C4E3802616F2,D96C3C8BA204BC6FD63F2800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C06373086CEF4472426AD6B5B2E2FA2A,BDF4C22C5CC2CF26D8DDC000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A66CE48F4A404587EB7E89E4F860BC84,47C3D89877180AEC4C6A7000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,21F4E49CA39F7055CB48B4823EB60B3A,BC2167B0C50AFCFA1BE6F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3BE307147B80BBC470A4EE967197DE9D,7496873601D14DB5C2478000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,005DE6083AE188A9D439F822DD91E276,6071EADC41997021B64AAC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BECBBEC3D9904615659398F0D10A37AD,474C529A92B020F450016000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,54D2238C5A49876C5BFFE2A40CBC1A9D,F6D12C50F2E0017FDF843800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7372C7A89131767CF0212ECF024E7B7E,E4AAEFA2950A3051BE6F2800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,610E52FF15507BA26BD65CB9D0D4E3C8,8FE60B0263BE14B2F9C22000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7B9567760B977B8098C11E1FE7327D0D,3CB229AF10E7A1BCCCF44C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,489D7EDD44A94F7181B77229B54B300E,E0A662E1E544275C163B2400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1C7FDD0042BEEAC4F81D4078E7D49684,DF3FABD2A77D7A38B12D3800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B89805DCA011CEF194D9BC04E600DF8A,2809BF985F423E17D5226C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F69BFB96B5DA8EA19624ADC9214D07E1,3FFB94087F5371C97E71BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3E815D34509F9E7F5B17CA9183715AF3,F7631C95DEB4E06D93B4D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F0180208C1424F65A3E14669F47D613A,F2B1D81E4DE0F12F6CB4C000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E09FEA06947ECA5FC976D87591F2407D,883EF20CCCD5798235C6A800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2E3E936B2185A5D41D4B60C3F60103F7,2D7558E040DCC3E416F6D000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,91A782D36FA443B0A55E994F2985D575,AE21774C32D634AF2DD6C000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,656B1598FDA639EA759A30E493215A63,DCA5B8DBC5F9D3AD06243400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1D68E1C70480D6F432983FA46950B0C2,E2E90ACF8F3FA04C8F2F3C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FD9C4B27B6EB1149D7B085F4212D0E29,AAB9A8E82E9D321DFDF15C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D72A0E8CDD5895CD0186AE81672DA156,D763625C8AE8803D42F6EC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7BC002176E2443396926FB46CE765E31,4F107FE42A13EE45CBE1F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5F5A7DD615BCE9ABF8B8C4C18DAF3362,5480BD66256E4D220C265400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9251A19FE41CB4D412470F6BFA32594F,C12429A97C548C5D5C736000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ADF090518F75202F8C857DF5F67E977D,B1B2AA978F65BE9679673000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3BCA17276CDEEAD058D7E81402CBC5C1,FA6A82608DDDB93A253CA800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A9A5679904E6C5BEC951D7ACD820F2E1,F8BE9E7706EFCF25FC611400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D86F518E58B52001BDE85856AF7CD0AA,55422D987556CD82926AF400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DC7868C9100C79E2B3881C384CA53D96,9558B50239B91FBBD6975000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5C1B8F86FD31CD5AF76883B10B72CCF1,E60CA6EF142C1460C889E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,28B6D8DADD3B4B3EEE8E621418581BB3,23F88A84D08A65011D399000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,390324C804995A305DBF491ABB8688C8,793C4846BADEDE452720B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,520B67DEF9F337D1D308F6F061C02C9C,F723F0299B214C0A078FD400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,71645B154FCBE3651D1CDE8C7CF796D1,1278C555355F61C26C42F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,98D733A8D5B85CB86F6B77E4A1100AA8,AE9E9A20CA2DDEC396D11000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,96B9EFFF2554579915EAA550CF286282,8722ED19BFB98E2B02596C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B0A7AA237867FC0A6EDEC4EA924F0E76,43DE54F954EC06730AB7E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E2537DFE6BE739991E00F01A91C14AB3,8ECABB5CE3C850D98AB35C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,239DC2BC8D0FC8496D1D144E776FDBF7,728F074A77A2BAFDEC446800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9E05E050CBD1FBA7E47F5516CD629CCB,CEBE1841EBC48571D47E2C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,99EB8B1C74AD8EF6CF437A3F449ABD82,4C8CAAB0C8DB3F3B83169800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B5030C866A721D7F572682A3F4BC0E0E,5A7EDF1309B160CC094D8000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4A9CC5E01B3E0B9367D61906A98A8E36,9E9C0DA2F4875275063BDC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E5EFED823C5C56635464AF5D77D1EA7A,5CA79206290A4215ADA1F400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,17537A5411BE7B9D3BE694F0A00CB003,97DBE50AAB9E002325660000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1553DEC3F5D7437F931A7E3FFD2D3895,720564A61EF48C79C7060800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,80389F9463503F6D7BC84FEE369B3441,7FFC982DCEDAB72846277000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,91731582928DD18742BA511C8A69D96C,AF84A1CBCD7F562D82079000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DD162A3B971CC81DA240792EC494CA61,AD67C7E4DE3A1184C7D05400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,616CB3A22573F18BA4B3195CBED514D0,F621DFB8E16745C9D2FA9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0627642E6A047D17A484C389F02759F6,671092182F8E066311FFD400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D1A8BF8A390F007FA74AB0C906A04424,96910363C1AEB39F2C900800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A20ECD8792D687E6DB8A9C15804C1E28,512536CC6F779446C7781000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,68437AD4CF4B415A62D91E1715383460,AC5944DEF8E834A5FF584000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,34B144A234CC6D88DABBFBFD3BF0D641,0607F2FBC96FCF993C02F400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1F6B9CD1B07A45DA011B186A9A49D955,9DBE594329CDE953436AB000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9894F828ECCA7C552079C9AAE88D9B9A,F811F54BC2C4C25465F90000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,34D08C7BA29FB2769B6976908587ED2B,98B2F0558B98A1E82904BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B920FCF9AC6CE5287253EE1B5A494298,667D8BD0FE5ECBDFE5EB3000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B6E2575D1808FD3EEB1998C3C7081578,B8DDEAEC9B7EE402217DD800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C110AB32005A4529AE28C513508746DD,32A6F986809F82322D150C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6E8AAE52AE48C4D086E101D4E41AF83C,A1C8B39F74229E7ED303C000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B8576446856AF5AAF2971B3A07BA4D4E,95B199FD57B55E23032F9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,490A65C2382350CA352D6B635390E523,C5126E4AFE2ABD865C220C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,560F1B8F7E2A4DBE23074E00502E9AA0,1B88135A06542E17C2F47C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,499A9D53933F23775FD61D2DFAACCDD7,7D4082DE2537462B02F15400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F5E7FFB677243810C4803256599D4241,530B0E0BD87EDD716E153C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DC15DBB314B0CD7506004914E043F197,368892055BF4C3C5A2FFF800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,02389CE3320403735EDF37B2900E9C1D,6B08D52290AC82AEBD093400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1F604FC5F56E05FCADAFA7A5669D82B4,432BA76333EF9B1478694400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,406F17386B826A696E5A8621B4C64C89,CEBC3E39E68D1B32D5897000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,479ABC18F6D16B0F7978D4638EF73A45,307CDDD712924E63A1604000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B30687C4213B0BAD2DA74555E5367DDF,ED1BBD009A424EB868743800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A181D5E15070BD7AF14D9FFD438B3B32,2F970EF02DBDCBCE7A190C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,66FF86200758B8A9A482F0FE70D915A5,47F72F255F9325BC3D0A6C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0DF1DE28AD3D61B91C931E5FF8E3F8FD,51CA1FC9D919ED3B3AAD5400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F08BF58595FE0B5CB7BF3E5A41218A6E,C949FB245F5C658E0BC94C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,82D5768A2BDE39F46D1D5DEAAB5BC7F8,E296D7A4DCE9ECC5E31ED000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,14D57135B4E0271887B55F21186E18FD,89628738B1EF82D8E1337800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,948BEC877348E7E2E31809E05CDF5820,FF0ED6732FDFFF1C806DD400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F1F8493DF7A794583AA8349A6F1EFE60,61AF0321F23AF53ED07FBC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A5EEF0BA46587DB2FA26B6E98D8B70FF,EB09CB2A3282DE77C2617400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A405F323FECD92761D28595CE51452B6,6269C86B54930F7E44CE8800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3BB3261A20BD9240D869AF801B835026,9694EE49A2FB6EB720319800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5672886C169F6E6031B255F365A4D0A1,4C858BA479E037BF6717AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,457968CF1C8368F5DEC7C4F5587F751E,D0A5F466F249D242AB913400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4969F1CC1C3BF44EDA2A562A2A23B49D,12204C89DFDD613AFC7F6C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5212E266A3C77E8A53A6BB6874E2C60C,FAEB70C36410349A97107800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,038B136CC82B6B42C119935062E4A261,0477BA0D961A3C86ACDB6000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5B8C70223689BC1BDDF9301DEEB735F8,6AB5841AA9C8361BA8D8B400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,817BA76F795465AA2B84B41568CACCC9,7BDB894633548CDC295F1800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AC3394BA708D35F36FB7B418EE82663A,3526F2B1D0D626E69C359C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,22830ACF5C70D6185B65DD4997CB5B97,8308B10D37DA0816FA480400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BB14AF8263324A4877D168BE38A11EBF,7C909A2034ABF14520172400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0F221FA4767A827000039281E2F0B623,05D4E1D1B316AD965B0F8800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E33F7CE6359780A5B781AD215AC04225,0C20FBE9688A08D5A4AFC400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DD23B02BDFEB88895CB0D777AB18F345,4DE5BA94E859BDDE79707C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,223BA98DB77F1626EF6EC92CFE960192,1CC629F32F8A3C8C651CE400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AE306E78099F816287D71F3FA11B56DF,4E11CC726B804F906BEFB800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,72C611D0DCC5D2F9A50BCE28457E0A45,BBC45738032A40FAFE2D3800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,15CC6B6797BFADA19F2594AE921FB59A,B91B68D15D4CB4B1F6B81400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E5D2DB764E7B7AAA92C357D12742462C,5F0D2A79A730E47464303800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,213195ACFE845F0C93166A631DF49487,777D5706979E2DF50B87F400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,63E24B12DE2CB25ECB5E29C12E37B23D,2E14C4CD4D545C3845B3D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5CB0F5D2197CB8AB853A4B4A445744D0,8A1CB38FBB7B7864D34C0800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2CE903C3134ECABBE8EE2C64C3B97708,627F02569E66F7745A408C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EA8FE58201AD8EC836D6857CD2067F6C,3A3FFA2D4BD6B3AEA2457000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C8D41275DF89DD183F0B197D03631D91,F46167B49B652589D09B0C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3CC77CB1F3531657711B9AFE5A2DE496,FDA30B4A453BC0A64F545400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7EB3302AB5736F9F6263D208F7B156DE,16E3E2881889EDAECE661400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0CA7BC43A7ED76CDD9F1B5D604681F36,9A78C7D867967E438C82C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,66A1412F61A52D5DC5B5E75C25DE320E,83134957B95983C900965400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F8788F948A75EFEA809BBC88485F42C6,3E0B59E31A7301B5DAA91400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,03D1DBA98E61ADB76A2220CAF0B3B65E,E890AFED9844B8E585E42800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,54E139044195600843E4F5BE24B6F94C,5CAE0DC4017FA09193E75400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,335FA06290E50E22BA6FE18659A33E8E,61728763E21154C63A774400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AAC3EA583360E5AAE4ABE89544A7FC95,68267B6CA1F4FC14FBBE0000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,30E32E729F6F642AEF4F8D60D14D5A3C,11FE5C0791B39824AF7D0C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CD956CF0CBAB6371688B79DC03CC6A4C,735D2BC22F4BCF7C997F3400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0423A8252D20DD174AA12315521CF7E2,E79960B97245596EB85F1800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FBC5D937BCA0C80344DCF1ADCF5418B7,641039C40F7D88B4F80A9400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EECF953EE957E1AEC36C333D96C4B79D,863270F7C46B5368715F3800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,20EAEBA061FBF323C97A9576F5B98823,056407E1F0F39CA05C9EE800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3FCA5A7ED535ADC2975B0AC8BF3C33E4,8FD8F9E15C99ED3E447C5000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F0D0B816E086CC3EA3D43120784787D8,0F3BEBC2239E37F7CD3A8800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E589F677639F03501C626FFCB66DBF24,CCB15E8AFC80F0C47405B400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,285C1F14D941F891CDD764C99F74A194,59B7284F9C34C1D0AADA5800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A100BC6B2ED37CE78590A68DF5FD1E80,B62567A9C4B173E9484F1800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,277E707A632759DF2E91A7CF9DD8239E,6EE6CFC460CF8B9A72B62800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,402298E5140D9CAF7A2EF0A93645B3F7,F24528D20569BD1B0A899400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9D70DFC32D6FD29FA8D59D9B2DB3728C,0DA1A2EEA64F163CBCAA1C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,03D12BEDE33CE608992001A74A5F635D,BC5444F7CDE90291DA579000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FA4264789EFD43E18B42A91C0E7F0EA4,4D9D225C5ADFB5F935F8B400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,44F0B60206FC2F63C509F39314945F6B,162A33E151A57F946EEBC800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A5A8262D4D61908E0A351E9FACFF2598,29170C989D1635558A2AB400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4F10746C8B3C354EC9C6AD89160A8CEC,9221BD0BEDDCDD876A8B2400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7B8FA87477A8355D4E6B466875259A0C,A9D47FF8B26F8D850DA42000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,139D34702695F7F899D4277F976C4DB2,D0894A14604E1A119AED4000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F37E60C2DE408D0713D7F440F1A83D5F,2B510C7C054D520AE17FA400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,01612C3477433ABCAF80F1EA10FEF447,C63E5660871DC933954BA000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B4B6BA348510101A88441C0FC598E1A2,4C557C4D91C42CB1F6DE8C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,14AF8D19C6A6843220DE463DD1F4040C,5DF913F98AB001E257562400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7CD5E50611433EA1D7518D586B0340DE,9CC76EFFDDAFA5BE93956C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,438FDCC8CE3353C7FA6DC4AE551245B2,8AA23A7F909799E1A48B5800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2643BC18643B60879735A7269D7A987D,B20311DCB8250460671B7800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6C1C7D1AE5DDB83D3FB416F123FA8479,0556F88B3B2380EA17F2AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,908E8B49B17E4D326F0EF9F2FA78E9E0,73AA4D65F48EE6701DA5D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8A99572093A23F95F6F156055A3BF203,F7C15FE541421D63CB43E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B08DB4290BB9D0626D42F40640008681,A1FC07C0F9C9E9BAF82B0800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D33CABEE42647A28791D7835A5117F7D,98D2BDB8B0CE56FF0A894800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0CF607B09D5CF5648A5760BD989610C9,74497807105D4DE16315F400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A79D44E3CC21609D11F76E1886F96A40,0749DB4851E2DADAD3558800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,51D6A4620DD7CC555FD3E4F747372E95,0EE5EE0883CE6C487F2C3C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,362ACBAF2C62995783EDE6E59BFF20CE,95C52F28543431597B575C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E5132B79FA67FF47EF4A4833E386D824,4B6C9DDBF7F7CAB3FC6F2C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,85CE93A1D0F99CC8643B2677A4D7FA86,6838227C63559D72F44A0000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,22DE20DE6E49773C57CAC9C656B829EC,D10A413DFD6091FD5860AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6821F332FEC94BA3FDFC5A28E00798DA,2EC42120EB8A00F280ABC800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,121D37973388925EB5E2EFC823364F76,CD6EF47B7E1A977D7AD12800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,08E99209A84B799EBA03D41F90AE6CFE,451988D9FBF0F56AE1E4D800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9CCD9AB7B846A5D131EA3101ED7C0C53,50A09516683D11B0FDEF4400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C5E0EA76C92046E29DC279AFDA1A322E,B294359F547F3600C5388000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,78CFB7FC779B3EB22851DA5F62E744A7,07CC6B3182E2F5B4702BC800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C63763E26E9C8522E6B7F9289FC7D698,6C734634305C25448525CC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E29F892D23A70DF6B8508DBEA91D9136,83DDB4B393695E2533512800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B35F94865F655C238C3D9F66475FDC32,704192C30D69066BAF661C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,45CCDF4039F895685118FED30C06AEAE,363AB01A5A7241E22F8B5C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AB3169DE2F4DDF04026BD0ACD2E3451D,C0AC072DFAE2EC32C2999800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7A14DBDADB21AAB6A4003C7AC01D7125,5F834BC537A730AB0EEB8C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,617F61AC87D671863E362AEACA2BED3A,3C1E23A8E2B7EEEF33C91000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5CCF9E16ABCEB48A69DBD47A2FEE8672,2969D626F8420DB0BF69A400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6599362E11E40AC1A88066222A558B73,2D00FD820B0C7B7989117800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,80EA0F12F82D2DD7EEF81E049D37DC65,DC8F89B7A5E2AF4DE4806400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5E2FAED83F035D661FE77BC67543A049,84195BF5DFDB89C1B4BAC400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1FB738D838DCC4BF409E6E1D56DD99F7,F1689606C624FEFFFB0A7800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3E0EDD907FB98168CFAD52D5808DDD37,8AABE70745D44A883440C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6C501F7A7540ABDC4EB9EDB0FED7E057,1C33E9180596D96109807C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F0BF880A9D8E4EB1EB2F7E247B232F74,8E7D30368A91BEFC889B7800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,013256BE8A64ECA0C38B9A8C75DBDDDA,019878985319AE79A9415C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,69AEA9AEC878E38BD7F6B578C29B378C,0EF6E7E3B0CFDF4836FB9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D1B6919926246CCA1772026E06C0118B,9790275EF92C9E5237E55800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,56AD747544BE89B96E3C019E3BCFB816,807845368088630A09E6CC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,407212D0EC94C39EEE6827F01A2C36E9,65A4B46B47E6F9F57709DC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B9FD2AF36D032C2991B3484D05B8670D,022B158A362CBB5961F82C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C45319D93B49A80B4C5CAF4D103C4E78,D3560CD730F10ED5A68EB000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,55D91D1D0CA6D2B8EEB0BFA39D580ADD,385482EA7FD09919766B9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CB11070876E8ED38788C0DF9C6402593,31EBD1645D48C8093E625C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BB49D9350BD6A5376CD56D7F00216D5D,F083D58403D218D829958800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D40873B3A9E3DBA1D6CF0D0C65EA07F1,57A134B56E18C3CA535C4400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,36BA58B48BF81CFDA44935D5706F9471,BEA90DC973B3B9622A54B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,78BF252E31F76C743E1A971CA702E346,B61CA967FFB8D341FDF4A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5F7F83D2D484580E8E64E8420DADE3B0,7FE00E37C99029A79FC09400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CF116FE8B95E674F722938B93995345B,0632A6B77B739705E58E9400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,57AA9B1142E72573AE6702118BDCC5B3,A997A317FEA200A1BF17C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,26A312470472B997C50D6C2AFA39822C,566586A7B4A9C4B04E947400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,06E8CC2E32C5AB23375735C1D2A9F38F,B07A1E6B1A1487FD1EF35400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,50C57DA189F9DEF15F4FB0CD4B9367D0,B7E3F20B6A83D4DF00291C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4675E3E185C5A2D9A35A35563781D4D7,A7CE1ED6968E6808EE2B5800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0C79AE9CCE6754C73AFFC48F3F8D3BD0,E6D686E63824A2F14AC3D800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E4BBB2B496FFD177FA57CA441D308129,1AF150442D8695156841B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0B1A154C651756BB5716C454444574E7,B36458807808C000DFBB8000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2B320C674D7BFE9FE97D648BCDD05E7C,D2FFF90A6C9514E69B2E8000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,475CC377554E9BC27F3F519942A91C14,452FDF7EB7007DB022391C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E00CE4A90F93475BF1FCECFF48467B7A,60CB8ECD24A6EE06D57F8400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,815178A0C659B18945639C4B07EB0881,6A8192E5D3B693C59C483000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FC17646BC2F358901FB26E4E43675920,5B0B39552E99A25B5DA35C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F18F45FBCEC63FFA649513806CE900B3,7187DA52E1B11801CBFF9400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F75C41529E232A43FC926C70E5D5FF63,E9FD91449755E2EC5BBCE800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D52BA065FF224D2F8F07D40319A50370,D852250AB403426CF8DDB800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E1D2E56B09E4BCEC51BF417A4C372779,A3E804521A96B079E58A1C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7058BA383C163C684C6C505B73091FF9,F79BFFD0A222CDAC8AF9D800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,464E181491382AFF807712047648C94E,79A87B43D7DF0D925B844800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C2104EC03923F8FAD45AD1E0993A5D87,7E1781F3283B2F6A881E3800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4AC0C77D93E0CF377031647CA3EF7183,FD17B7555745CB85E67E7C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B8BA33423199B92D0B8BEDB68321067D,D88C3C98B96CEE6FD7073C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5B719D887EC6A78D24E03725D73B8C95,CD7B4AD85CDC4460C5850C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E2357B769EFC0A4BF1764630CB7E8CCD,2781A4EBC32671EDB7D11400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6BAA811BDC5A84AD77F89D0C1CA38E73,6EA0886076F38D7F7E5DB000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E256E3F599C5D59E5CE419047E45A475,7A5E2D93E70C0DE9E24BFC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,448D90B6D0728D6A8C709364659B285B,3A1BA05A8ADC9D9A9406F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,48AEB674DAE14A861C1ED47E106E1F09,1D179FAD2440BE476484F800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4ADFBEB3D17620818F10A2CFF5A2C685,90FAC65A6510EE987BBE7C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,97F5994BB37BC1B109E8F85612A9EB71,AC0E414EF10CA9E6EC12B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B5B58A750CAA75DB6F7DB327D33C7029,4AC405C9508DD4AD0D2D1000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5A9FC980135340C2148F7EA1397E0A3A,E490CC8B50EFD553525E5000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2C654B096CEE17996842D414ADAB0F65,5E202941D3B94A97F801F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BFD42E6AF05531E36472ACECEDCABB01,105900508885332F7AB27400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2F1D6FCA47D9614EDBEB3C7BBD0F3FBF,D56F3C13E6E5A823AF345400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0CFCB9E4A854AFAB012A3773659D3497,E2A8D0A7CDD8208BDFDB8400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0EB2F1479B898574FD5A005BAE2469ED,FDFB5EC18859067F746E3000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3DDE3DD627701D3C82E231BD8E6D3B9A,C1D1AAD8ADD808A3531C4C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,83C59B2CCA5852317B0CDE7DD7271839,0C921E18ADA831C196F4C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9F15C1D01CAEAABB0F3BE6F9F945009D,0B5D77E367E003B2E9B57000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D643477293F78185BC9BB8CF56618B68,0D2D4C255FAFECC099C98800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0133D361C9C3F74659D49017CA4688A1,87E097A69DEAC904FCFBC000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4BD21978E31D1A932F52D1CA52BA51C1,D69E4C5478984C99E36D2800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E7A3119A8A14EB0176450391EAB94BE4,DCFEDA85A75B49C6AB08D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5A602B0877A593FCA1E6ABD00D6E577A,51D275F3D93C3663FD457400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2DC13BB009B7D17955912233A638CC40,DD5585D4D69C6308668B1800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0DAF5353573B8D7BA15EE778D10C5B96,AA61BA1FDEE572BF3991B400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,561FAF93B17A26208F5C25563F89CEB3,16886D3D2E82D27F07206C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2E8477F09EF1EF1467422355C9D7A5F4,8EC6BB8B18B4F0ABF7E80C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,344CA2A796654AD2120F2C8DF91E398A,D8426EB27CADF76A1343B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,62B925E165726437836A148714266725,F9C2F257432181710C074400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,40D9F437C6B977D4905EE57051219409,032DD9FD9672471482AA0000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,704164C86890924F54FC23C80780D43E,C6AEF0A6161616D7E369FC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C50859A7E4B893CF0152540D662AF6F5,8455EBF400BE39D837EC0800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,45678BD0518687ACE0382776502063CF,6D078470B0A19D4F11483C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,87BF9B0EB9C4C02733B2A70235FEF789,3C7E311DA59EE7194C1B5800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,417AB0CF8C02E4F88A48595F037612C7,9863ED9CDB97E9858E59B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EDB9E22D193705D5B296A1E5208A543E,970D6DF96335F191FF096000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8CEE30C5B579DE823EF219572C580C14,BB8CE2D7396C694C2000B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FB2DD7777D14FA51725B84E2ECCC25D6,608BF57DFCDCF781BB81CC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2B80BF5CC2BB2257EE7AA0E3389A311A,5ABF98CB0FCA9AB95180EC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2DF3CA5DE617D1A3D8AED5D3FC35A5F4,62597C3ACC2A14FAFD627C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F42124E89A2BB0750289A4D309483D85,4975BE2A554B9AF2127A6000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,467423D8CDA747D5D8FE6176A8D4C3D6,B2BBBA88E0573F00D2317C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6AF7F5B6F08B68B634571E2F98DF77BB,36BAD2C7F49D0B5F41D8E000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7ADC92106ABFB875E218C86116F338D7,0FD01030AEE5744760059400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B9D359A4859A227A18E95DF83A54CDDE,19CD5BF3D090E55029D4C000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,79A726C514C2F388828D95637C68C9B9,FF505A95CF143009E4963000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B718DD87A174CD9D97C1D44866543D69,2E70D35BE3F4B667ED283C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,33E0321C0110429FCF617D2047C41392,9762E08AD5AEAA8B22572400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E1E72B0717BCA40E8207B235ED337F98,E0808C7A299F7CA6DCB62400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,70BCA904F49B289684D72B21234D158E,5BEAE8368903EC2C83024C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C8CA2F03DA9FA62C87D75B1ADBA601CE,627D983FDE0091839D772C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E6695765D71EA944A33BD8A24BD2BBC5,9E10BCDDED967DD8E5BB6000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E28036071370022361A292C6FF0346DA,11C94697FF0322B813332C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C304A0719C863BF255FB766A34CAF6D5,987F746AF4BE1B5EEBDB4000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1E6D0DA456CACFFCAC1763F513C780E8,02DC9C8664003C31E81B8400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5D87DC287D64CE316AF1FF5D22CE6403,7C7ED05AB3FFAB5BA46B2C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B93E2C36F43B75F7759F1856158A0C81,195F8C4631C7F5827A12AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0BA82A946103A44B59AED9E1BC99D964,2C8ECBBF4E05CCE5CACE9000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,386543B2FBFC04636770631E42992CAA,643B71AD00861745ADF29C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E4127B8924B007BE05A1870E623BE0EF,73556CA9245832D5B30FE000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F1DE95ECE729F864307D8F5863D092FC,BB3FCBF94116C640D0CDB400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F8A1F1E112909D704F71B0A3D48BAEB0,D18D8B60803CCC7502C4D800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,25C76F0AB71C9E65AB99665DD8DA3BAD,33B8951FE810F71564DB6800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5E5DDAFE67E23B856D1E46A5B27A7DE4,C7AD38CFF61BC5FB6E2EE400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,52F4B4C77A10F819DD8874C18EA7438D,668E45DE6168820CF57E4800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,54CD69AB3026B3E74752904CBAC11A02,D8D074AC1BEBC91C6BEAD800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0813F4A8DDDE2BF0E5B68B00EAA6F30E,DA326497C3DE92A4E085B800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,36A30D53982D5D85BA13190315AD58AC,F32AF63EAC691B345BA92400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B63D3E9052CB3CC5F2C3FF2837EEA5F2,CC27BCAABE8F8142BA484000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,879F9AF719FE477E14B3EB0BD77B3E43,F4B2F1CB81A170610AEF6800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C054F0CD7C31B847AB7175439A458178,86C839BF0E031ED064908C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,79C74644F0C6602375F08868AFFBD268,06CB29729A358897B3FCCC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B8498EB5B4FC739BF5FC50B616406DC4,1D639210EC64B69D3F8D0C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9CF9A35D8621E232FBC13045589B2E8F,E7C76E017BCEF9269F2A9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A18C21324C93123FE7D9CAD2A1E9A559,11CBC47301F850DF3596A400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3C765FAFFEFF8A17DE76C687A72EDCF8,71853FB74D540B83AFD52000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2DF8807494703ADDFFFEDE81D4F3FBB1,B61A4D177B172354FB810C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2B66F399537C587B74FEA82BB015D19A,76356D3B5F027674C6672800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4D73AE14411A2C7B935A9D507F5BA68C,80151CE2F0D60D694150CC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A29D56B89D350DD4D9F841F0EBC8BD91,9D9361719374D3F972E79000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ED9E597AC0D022A3CF8EB6920EFEB2E0,9488F7AB56E42346AFEC7400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A7299CAAD6C44A24111BD8B94456C322,ADAAAE15F05A4F697DA29000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CAF5329EBC9A22F39181985473DEF108,47E4B7D7B82F7DB5AF04D800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7538BDF5E86F3529A0384446EA8D1EF4,4EF96C48D339F2B15226B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,07F5D01354A04BA9273CFC82D483B86B,E4A07AA41947F5F230582800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1E0083C5FC227C3B612C6BED4C5A332D,63108DD54C79331CE2992400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A2683E60D1405F0866B833E7FC83E915,B84678DF144CD28DAAE14800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D8D8698128890BAC2B82D5B418199ADA,E46F5C5CB48D6E5597D24800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,81EE1065B330FB2C21039D8779FC7C75,3351A55C82A76D1ED1E1A400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,47B5ADB0550E2ECC4CE6874AF2E6418F,91B69B1470354B6526073000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,13E38B62E73CCBDF5D7242C08D2A89F5,85EC66414C029772FBDE6400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,70235F0E7337C84322AB96446FC1A833,D8E94765B1CFC78F829EE400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8237581EB1ECD66A8BF30AA3E35D4F3E,32E73E377DBA84E7D4B52800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A6F441642D4A71FD69ECC6FCED8BB068,A890FD9C9FCC796DADC97000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,40F87D956379B82554DB7DBC26D71D5D,D141EC8AA94E5946EAB2C000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E88727F888CD6A47E59B29ECF8569CEE,C158C34537B52903F9AE0800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E1DAC3F3A141E99F953BABA2CAC38A87,9943AC9F705789775289A400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,502D8301980BB00F04D7E5F6CEEDC373,5DCAA4B437C8B14C0E45B400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,100488093CB374C14B49608936D33DB1,EAFA1391EECEC311171E4400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E5EE4EBA9C0E79F25CC2A5F6F84603AB,CEE1079CEF85234D879E0400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B90106F8F0D039F5315A3700BA690960,697CDF0D338C49FC6A048C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A63875F2FE66C8BB50398A4904DD3404,C0CEF845910957FCC6FF6800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FD5D01A15B8C285C55778253D39BD134,8340DA915B2194E9F1B80400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7DB1C19BE6C2F2A50ECCD76376307BB1,88CADD268FC87AFDC061A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,256A9046143884EB2F67AB14850175D2,DF413B7B297755E841789000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ECAB0399732B45954989A362CBA0813C,5139BCF5B900F6229764A800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CC402D3C33D0138FE69E8E9F8F6506B4,36313C1C57A3D5A0E051E000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0A5AF91FD7F5CD1737564E0751280729,AE070C1E5A14535629EF1800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,95995007FF5EA7F154152F3C23799DF8,E984C168AFE2954852384400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7EF782AA97A03CA95FF486CF6F78B32E,6630B29A9777970BB7930800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C5C2906DC49A264B7EDEF71B2C8FBBE3,8406FFCBE4D0CD1738D46000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5D8CB8FCB97763EBF582B0F95F002AD7,577F959095EA213D7EE47C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F5834BF4F437366625F452E94261CFCA,3769CD4D6616561D0F241800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,772B4CDF899FFBF9DB9EA0B97A7D2DD8,B4580063FDBF1DD3133A6400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,91929148C60545B3F745CF6A56D41C27,30547EB2AF6A55D5FA6AD000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E932DD627A9466F58E252394D5D04901,6307C0244560E2EAC34AA800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F0E3394DE2E75CB2BCD3DD4BB13C194E,153C8946A3A747E6D3690400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F28A7D1B8156803234B4E37F42CA737A,9942F6D5B52517A6E7ECAC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0BF0A68F6E43F56DE7B99FD0C9996FE1,D813A8A1C33DC3AD9D9E2C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6E9EA7EBAB43E8B6162A9F17E5B70323,B62C69C63EC30374212B8400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B384329A87DC6F129E4EF6D0E68C2301,14BB46A6BA52828796C20C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6BCF32F418AEFA69610B2C4094269CB5,EC1DD1ACBB3ED4D568E65C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7C03029166C297966572C62861BB8F33,98328BF8D1F41B4DEADF1C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0529B36F238288BE3EC577E3B0D1C13F,2A2444BF54404D080F7F4C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2A41C97D2C33749FDB1C42122948B81E,C0CC4F6902D81E7A63AD9800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6C5D4556093D3320B02C80C100685F74,4113BFDFB09C3748A6292800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0B3BBA35D7A5CF7C6D449AFFE93F0A50,91CDEB2BBDE6AB9166797800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A2CFA1C03AD05C6DCD97B74A9D5281F5,B4256C9DEEE49675ABCB0000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EE3BA1ACCB7420FB7CBC03496EDA075C,5BE80F2FDAE1AA9C3FBD5C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6CEA40CCDBBF40EFD818F246A23BAF49,AA6F04E7EF961C5C1A541800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,87593AF0C2AFD9FB1578F47853492BF2,A7682DFE1623709AEE063800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F8A639319C14F1C89B0ABB64D136BF9A,29514A6DE21DEB47B6301400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F08E8ED904F99242ED85964F7A95F831,2BF2009F485BEC2D798AB800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0485854AFA9A042C61A572D1F73C0410,8FA71A3E177267E955474C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,237D5850161F65F6CE1420D4EBD0B28E,7A210D3999C5CB711E241C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9C31EBB62770179203514FBDDAC77D7D,7C17F19E8BE20EB76E377400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E8FFF12A2CE3BDF4B7F9E620B548CAF0,522CA15B72EF64BBE4A40C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,50BF459B3924B07E86AEF6CA47565754,EE64879FDAA0FBFB1CFED000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7F04BA9D7DD3D5166495C802E6500D69,6298C400A3E7FAF2A82FD800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7BA0CB4AC96C1128CDE8F341CE06B047,9133D5BF5B17D7B431887800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,621D834BE928B3AA065B709F69AA56D8,953881D2A2AFD50BBEDF1000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,78C27AB36F475466FB70160194D686C3,63ECB79DA075E9B878094000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9BE76B1C0303CC8B952CA8629D175501,8C9B8A40631A160DF30F3800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8DC6F2C6DE887B8F9B15080959534E47,C0BBB3320D464F854B6B4C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E542B7C86F2FF2F3BB95DB61FBC62DEB,850FEDBCED96F8BA42975C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,271A9EE3C4A98425E1D13EF0A2BF8273,7146E8F7627885F6AD611000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,07824A66771D764DC131E363CA9AAC5A,10A20AB726F6006363C36C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8B2AFB87CDCFB8A28F01FC12BC0B0527,ACC2A8320B12002C9E4D4C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8196A5C8F15421B49DC08FCF11FBB647,C0A1622EA312CC03175B6000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5D231C176472B07E38E0BE3C61FA7C2F,290F0F22E828A4FE264D5000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,52B4CE41BD869756BC1263BECDABA56A,74255DFA0AA6776B9BC87800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,391FB21E6ABEBA5B3CB77ED2DD8F6533,186CCDD8573F3D7AF2565C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9EC0E3582BAD0C0B43B31D3BC33811F8,EE6820780F53A730BB586000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,47F1CFCA01E1E5FA976ED5F26E24D8C2,B5F12C39244D2D4093305400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,09B3752125A129F41A84094DA47F4CE0,7788E20F4FAC8E370C4F3C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,21C5BD892C7E1DD2DA55A3DA45467715,D73214EA4B34C5DBAFD59800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B5BCE233AA9565E3D27A21F2B7546E20,13CC2525B05358D8ED654C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,33BBDEEB22FA51437A27E6E0544CF0E1,19F94C1807DA37BB6B1C9000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FCCBDE2E5168DC0D0FEDCA996D77AD68,482D6B562A13AB4C1B67E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,783BBA207619F1CBB9044EAE5BAE2E10,BB61051A9C9DEF0F1DBFF000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8C0E1361E509CA0004A420A41E1D4849,16038E532CFAC7AA80048000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7AA6E63449724DCC8DE68E8D5AFA5301,73BABF62AE9E467A4D867C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D07B9311627AF8F541BBE37E593F7D61,7A9C8872C238B5C2DC8C4C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A195A7F22246F5389492B1D0631B6A67,0B17240185622BCCEB6D8000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,97ECEEF59A9483051A945B6EED4C78F2,E97F75B682E889ABE2294400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4B9D254F1523FA70CE4BBFB5544C4BEF,A08C4FEEEFA9238722AAD800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B9F4375D97DD4078504EE625364CF8B3,CDCE8299491D913D7BB8D800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,696BFCC93CC4CC64B2EB5E826C8C2219,09835434E7139BF0B90D2C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B77A6C4A0F884C434F4A6E4FDA14276E,A6AEFF174AFA09654D14B800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7C6DA8FCE206CC03FA59EFAACD0BD907,6CB2E37A215DAD381A787400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1CA7C35309FB7ECE5D5219E176B50BD6,7B2008A5489593D7BA70E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C388FC5D8BAEC91F29005CB86695E682,46952E2042FD4BA728BB1000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DC2DBAAD2F34798AAC7D6BE151DC0A69,7B56F0DB0ADA555E3CBBE800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F1B2A9F3482BB5F7C4884A021E072128,D11EC8DADC1E76064D794C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AB9437BC42C29EF5EA57F5738AE7A629,ACDC6CC57CF7E950C5FEC800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A779F9F9247D0F86CA92F36EB3D428A9,63707C9FB8BF67C7BC68D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8102941773B0B7D0B69B63CD55E81501,6C6A24096571C1CF59BBA000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D9A6F2E81D56C4BB6E73E2E508558889,D083A100B004B33198152C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,20EBD97079542ACE342699F03004242B,D7A46BFB9150D08712629000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FC5E3DA672DEE281AD27E32122B9B36D,86E78B56EADD64C2A54F6800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1E9F77D590ACEAD911C0D11EEC7FC2C8,606B827B6D73312287D9F800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,05440E31DEB3885973ABA57B59343041,D175AD1E110F9457575E6400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8AF82442372F760DA698E78909810CCF,094CEBA17CEA543968351400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8B231C433B53C9EE33FE1B4544631161,920A60F7EDAAC3AF94963800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E2DA294961B34299A5CF7F83F58B6A06,6D5BCE65DF1B786765FD4400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9EEE58193DA40E3ACA6427F53210C456,6CCA116693779FBE108BF800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,91893F03898BB4893837EDA553AFCEE3,446395A9D20D15EC20546800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7FD56A37A515E98523EE17593BFDA8DA,4386D1476506EFA5D67D7C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3A57EBE1E2500B892E31681023A931DC,94A83A69B6087F355241E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,62C0673D8DF88A630BABD27D89DAEF9C,41863287B5A85F9F29648800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1BDB6CD049C2B27D6F56BF1FF602407E,6499BEECA5277FED7B616C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,16402A54957BAEB151703031790B88B8,CDB52ABF00EA032B9261BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9A317167BA191CECA41534D6FA504AFB,FB27A373B24DF6955A0DC000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F140B40C1559E82231122649271B3C0B,4630E6390D0833A29A2E1000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0DA9978679975EF89FEC8DD578B75DE7,B172798D19F1270657081000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E5E0A8C4B055F3437044E355476F3AB4,449528393C21EDFD77DBBC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,48833CA3136C59F16A35D53D373A44D0,3CC0E89847E648210C501C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6233C40196F570765EB55E159D8E5F80,B974B4B9410C682D04BEE400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8A4199A244B9AB048A65F2C5AA4228D2,A8DDEB16790C84D4B6CD2400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1468A20CFE51B3CE82359D4F2AF6941C,BA10D23CA1874A38FE2A1C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3A3CB47062BFBE36FCD6257EE7D72F88,A2A9149381F57E10C9545C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F5A5363DEE1BDA47986041C41C8E34AA,6CD228F7E612AE9F8241E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,944956CA8654D73D294421DA6EA62C51,987C0884830A27B7C10F1800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,14F62C203D2627EE9093FDEA95BCABE2,52FC66E7FCB74AC837FAF000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,50C11C4253D2DAB982CF8711104412CB,6DEEA62B0D68F378006FE800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E8DC47A4D50019F1CD5DEC362D2ED9A2,7461F7817B8FC81E5BD2A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CEB6BD0BFB463953A646FFB41B1AADAE,21CA435081939219D163B400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,13A2DA5D51A79E03A0E1EE945870DF4F,AC77A6CA0E2C0D6D39C7B800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,990AF9AE967A25B431EC263897441315,92BD9D744C43028DA5395000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0B6D77887C93ECD7EA31CF77EE405DDD,8FAEC162B88C9B7AC0507400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,65AC3F7C936506792B81399D617C4B01,633653B209F9D44D2D805800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A06050078CCEBAC82FF4E6C70C3C7E66,37DDEEBC33A3EBEFB9080400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,209D949802205B077CBE94D11666254F,D90C1F730DCD5A0851461800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,96E1732AD1EEC3849E86F2EF1896BB6F,E15CFB24B0DF81A7DCE1FC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5C46CFF71BC7C0649C57B66B11BC791B,0BEC49EE8AD752847955F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A38AD60A6CF2B1F19327E145EDF31A03,2C065B8EEF112FB3D641FC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A36EAC16FE6DF1D04F03921D429DA125,DA932D16F576FBD408B37000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1AD321016CBFF532341C3126C84A4F31,476572A6736CA32E0F5EDC00
diff --git a/src/tests/comp128-2vectors b/src/tests/comp128-2vectors
new file mode 100644
index 0000000..13438e7
--- /dev/null
+++ b/src/tests/comp128-2vectors
@@ -0,0 +1,1024 @@
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,00000000000000000000000000000000,34B4225BF16B96E118A85800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,00102030405060708090A0B0C0D0E0F0,A892A8EFD6D33E3650372C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,F699F0BABA87114F0350BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,000102030405060708090A0B0C0D0E0F,A5B4C7CA0514C4E1B25CBC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F918FB140A2D63E10B9B3354C93D5816,C1AD6FE372383E0D6AAC7C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CBFE7E4156F94F1E6ECA59C194A9BED4,3DEF079216A74B97B4ADC800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B65CB9DDC667BBDA3F493FB4BA2CA2E9,A846E7EA48C2E85F1C115800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,23E0F6ED14ED2596A3B11453786E7C2C,73280CD483E7DCDE26F94400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CB5CD183A1D26D1CF75D5987D20CAE75,055DF715899926BD0D6A0400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1E99C6D07A0914E101FC2094406FB46F,0A500680A860B1678C749400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F9213F7452CC291C6B64F990DFC219CE,6273439D25CA01E321505C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,21C5CCC4CDDF8D016F82788BF2863718,404F028B1F31A67A71734400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1561F279CC3FC8D7FDF7703C3755003B,7081294CCCF3AF9D87CBBC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,360EBBC222F116936F55772663FE0131,D30CD39641B7771E2CDA2800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D7FDC4BEFF3E172607BCAFD0FFB8414E,656222C3E481A1AE7B42BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D832114007244CEB3B05AA50F60A1DC3,EAE9BC2A4D1DD802E9ADF000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CCB8CAA7DC27AF527F75385BC9CC943A,2A43B559362A6DDED82E4800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,48BD98472929D5D66FDEF005FD1B67D5,0EA9FCAF684D3416C187C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,97A67270ABC7807CF3F7D2D668D9EF2C,510DA1D26FA933C86D0D2C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5CF1FC3916B9631D04E763003CB0AFB1,EE2E83CF08A00B0A1966C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,18F91C80B5CDC5A5A95A187EF66AB0C0,0182E8AFDC4092FEE382F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,248B68303F09476AE2E72342636E6D9E,D78DF0808D19FBF8B703D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,340D4544F8559C61EDF7F4216E70C576,2480A75BF304AC7AC73F3800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CCFE030B6436D949082ACA45077C9193,85A18AEA5B9BC2B7A376F400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3D0754700F6A1D2C704921EC67AF3ED2,2D337465329B474F67975800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9165BF37C63326F8CE9CD03F9A1378BC,DCAEA3C710D0D67DD5B3F400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9DCFA432DFC4F1FF19A441F32A5D8597,F010B99B63B7E6F75DE6C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1DE9D2AC71DF86E5A599017A692CB4B8,EB93C952E8C616D6C2979400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,261326154E44C794F5A91935182CFF40,B27DE0650A1FC35AB045EC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3F299BB3EE46BBB4ADB5493B4A2CBE5E,67838DEDAA2242B6BB237800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CFD033BE76FE261250042457B40445A2,863CA702CF08A47B3CBD5800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FCCF9EE60A80EC9F7BE413A50CE60869,14D1B0D8254D474C999CC000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A966B4A739358CFD6EF22A9A660785DF,6894BA1EF235BFA46A0E7000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C1F8AD801A7441908B0597E186EBF2C0,C4E9F13F0C19A76F7EC9CC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ACDF541A969C68703E5DE37ED0B7CA09,832463CBF662B2A72D4BF400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,274F7ECBD2AF639D9479FF4C73815E1E,D1D5AA0F491ED442716D4C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3E4FC9C886E4E5A8EE89AF61231857CB,323C4D161E55610BCB0CCC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E0F4130624669860131BD80D85A3F1F2,07DAEF70D14DFB9988C5D800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DD8C39429A107F263B2A5F596A501905,1417946345034965053FB000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A95D7E54A42604BAB557A0BDA4F50CC1,D1683BA6DF50CAC2508CA400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,140C6662233AD66D5AC825D80A9A8272,A0FAA92970DC0E98AB406000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EEA61DDED13D0E0F2DCCB71ADD551B7B,7240AF352D215FE4B4762C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E4C84D1860B03C3DC2010FB6AB3E4C47,30576C1054878023B505D800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,747AF0FE3B3E903AEB6191EA01DC9F4E,4CBAD22E8500464CFD7E4800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E607B88BE14E5CEE43594EF51A0725BB,1D79DBF69D7E2350225D9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4002BDDC83EF54ABAE5442B0A8E976B9,6CB6D7673428D2D2E84BC800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AB8B27FE3F278CFDAE085F236A0A28E5,06407FCB02EBF61123C57000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6883B190F8EC18305BAAF6429E817BC4,C17A6B9AB444C1A9DD448800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,18DC6B25398F64FAA4A740C617E9D591,F88F9FA28DE0500403AC6400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2735F287D3AEDDD35C204628322D3899,00F5692A5F350599985AC400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5FD7E3F81F3671F547B94D9F6BE7A348,FC9DCBBB4FD0580F9D196C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4A316F08F912FC534D8E204129C78974,855C814274800A5C45B3B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,960CB8BE1B5025F3EBBB55EB0D79890E,E1CAF3B52DDA529D0B694000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,282461C3FEF2ACC7F53E640AAA8279FA,52F8D1327B2A1B5319025800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BA4DF10402E2E5956DE8DA1886CF14E3,DB2D97F66B679B4D12661800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,33B82F56356D44D6A790ADD4F4CE9FDC,694EF7DB3E35F50726159800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,01FDE145E42A4A0F7CC97B8CF4A216C1,4391900FCA30173E63A39C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,61259899F937B7403B2EC70ED9D32384,F51BAF50F8CE1A64DCD25000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0E6F0DC32693A7EF677C37BC44CE8600,C110634B037031745F401800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1283E739566C46FC1041C2EDF46ACA63,4C74EFAB8A6522A010F09C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CE7B368B575950EE69D82D3CEEF59E7D,FE9FF88A94F0A490335EFC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DB0D3AE84486ED465CFD507F6B321E2F,76D1679FAABBBF1FD3E79000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BBA5E23EED1E43EB3AFCE2ABC5FDF71F,022E2C5CFE2B4C3EBDC41000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B0B9EDB4BB979E3CA9630F8CCC3FCAA2,1775CD2F9C4A6DCE9503C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4A68C3FBD3A74468D486308DD1412D31,77249441DF2502778FF3E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9AB0D6DCD8251E4BBB78BFACF5823C7A,E7499452B5C45C1A7D15BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B62C6CD3A87425B29DA8E226B28C3617,D4A1FC8107EFFD9ABDB01400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A094A045959D9A589F3BBD28D908CBAE,A20ADDC80477942E1B390800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3C76356D4AB3CDEB6AF36AE27653676F,F192C4B354CFDC97186CD400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,04ADB833E08A91A26BA932A9BE9DC4FE,A86826C01F6B291290F21C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0CD25169C9CE123475F394ED818CDACC,F0219A28F767E219F029D800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,758F9B775D80F66C2F46F45C13D14EA6,A20754DA43C43FC4C387D800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,501418ED3CFC481399ABE1522CE9FB1E,42764FF7641AB6D55C796000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2D7333EFC9B3DB86C43ED4A440BB02D3,9AE0F630A52EE42058852C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BEE9E64A0165F7EDEDED06BB7AC0D11C,B436CC2DA8B4E083D0967400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A342BA3D24D7CE59E44C5F1E6F3F28AB,47BC33CA9A6D94596C8B0C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,423580E3B8B42BE68E2FCF0805A369A7,34A0F45FE614F578B9504400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C81DF1E755DCA874A97FE2C8A2030A45,2B5D9AE2B659D2920CAF9800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,00B065F6BBD7142C0D810D81348143E1,0CB323266C1993A44D335000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D0DE9876CCA615D16BE541659FD8E0A8,F313600462BC4C2BC056DC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1C02F9836A1C0C334AC68873C1A95DE2,E913C4B6BEDC8F011042CC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CC2686858921BD2A970F3364C1BD8154,CD6AB5F8BE1967E637F11C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EC5A44CE58F654A751AD1F87C7468E22,E5B372CDD142BA7523ADE800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F7B93A2ECA12B2F0A7AD2B550781A311,AC1D56004C3DB4248A2B9800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,95A26343BDAE219F9A77E2D68B8D9715,B8E2274BA75A01A1B844FC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D9EF207AF47750EBF69E81669A0D7168,90BE7292A33AFF976AF50000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CB095420C757A88E23E845F90010CE60,5D5267614E4868BC9019EC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,43DA2FBE69364674ABE7A67EA1079F7A,11602A06755795316CC2D000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9F3145932F5712D7767278650BF038C3,62F7DA68B860F0EAC85AF000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6AC7C8825DE45A64EE61E175A4212CA2,1564273CFC0D44D3AA813000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A81B3F8E35F3B77A681E581E81992245,DEE4D4C1C210AD3A88208400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DA6EDF93A83A4F3390C7A74448F3EA58,DADEFCC4C903978B5D968000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FEABCD9A4566FAB5CCC225649A9B51E8,990248308A158336AD8CB400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F5A9320721897F9C4010CC26806DCC6C,4E9483834C11A3258B735400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6A3BD4D5FC62586DA382EEB13EC5B98B,C0BC1F742AF062B2C57F6800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,657502B51422A9DF5D02215A3211720D,ACD94B7C1B42B827ABB4B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E601807D685AC33884F10BEE2B0F7155,8E555A2E5483666FF8E9EC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D081597E0807CDCC345E64EE06A656EE,252F83F942D3CBF225A5BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AE911B26A2AAAE1690541AD7C4AEC12C,C09DFAD45B847939AA6F5C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D32460B8EA2DE870AEEF9591E2B945E0,415F30A507DFB65D21E92C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3EF290EAC6ED5506430691F6652AD256,BFDA7F8A4AA126AD5D832C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,96E50BC3013669B9453CECED3379BB9E,3127EBB45E2170FF88B6B800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,50CD8707C942E07374A604419F328627,997A146971E7F9F173930000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F5D436A55F52CF178EC755D9B851C2A5,526DA1545DB57E300BD2AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,59D00634939E1296D628D762D098359B,29CB71E3C1D6D8F598FCC800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A50B3BB3252C3C9E16C12396F3894607,A1107E2B8910AAD9F4098400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,89F8344B973576425ABDFE3466A7C99F,3335275BFB59F6DD11903C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,17095FF0861CCDBBE7EC9DDD97CF9FD8,1952EB05C39002CB2533F800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,00319ECA71633B48AF1CF6050FF612EC,8DD0BD48A4287D3987225800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FE2BC8ECEC5406BAD2992A903A516162,4DF20DADD02875F1F6DB8C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2719B57237F55AA6F967EFAFFAB491D3,E9584869A08303981DDF3000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E9895484AC5350C6020D47BECFE5FB53,183CE56373954D52E0BFF000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2E4CCA78B5FD8FEAE8F593DA046F7D39,B2EAAA3360B2AABD8923C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F3B1A9F9D5F83540037EA0EA78A8DE90,12147C976D7BB13BFE9BCC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DD32C4C62D3CE942B5FFBEB6D4A5F2F8,45407DABB4B14DDFC8FA5800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B34F7F0BD6B2D777C0817F82E3052AC3,FDC03BCD2DBD999561344800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,83780E5C93048E6A2A060346ED2BC544,6AF2A4F18731A281B75B5C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B64A86EE50BB1C02134F11CB431C5069,36AC5A0B2CB95F686E3F1000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FCFEB28D9732B937496FE96CD11B24B1,E23CB44D586FE9FD19FCF000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,61664DE891B324D7C1402E7B80C68B17,EE3A9940C7A09853F8D9FC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8B173E8CA34CF9DA56DBE336196941EA,F703154C9730C5AA58FBD800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4D21217A48E9EFE24B4704EC10B85C6D,D56C16F7F97B8CEC06BEF800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FFC69A387F0435BD58C13FCB913F60DB,5F63F9197F0028ABC70F4000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AEDEB9AEF7408837DD4BE51498ECA4AC,1216B27EBE081A8B58880000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,14A053B1F95E86448F73D2FFEFC7FE3B,2B9BD41CABF325CB93BD8C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6881901AF0FE3333627FEE84795A70FA,651BD7D5B6DB33C5D1B0EC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,196577B50A4C9BF7A208E4533219D468,5E251C34B40822FCC476C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2ED5196DC635F8603196BE9549BED8AF,BA9B3440ABEF67B108421C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FE09F4C92966BA06163AA644D6CE5D7D,E07AA3D335E25B9303671000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,18A117FDE347F64B3CA01F0F10659596,4059BD729905578A62A29000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6877CF2C157251B5A97904F8EB337B8B,2B1A0A31984ED54C1D3E4C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4236F749801A93FE1FD95324A24CE1C8,2092ADDFF5D50EE60E4ADC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B9A4020F7EFDA03BCFB46FE31200708E,80685309ED5215AD37C5C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,574B8C41C4594499D037D3BF5084F167,218AF13232BA26852E95B800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5D7E99E0D35742E0D80A85F243606F28,1B54FEDD20031F7C11533000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,40EB7A728783CFB17363744C8960C966,ABBD5EC9F78951FC73C11C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6C7826A1F1B40323CF57C7909A83E2D2,91C94F666B08EBA4EF7E0C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,61E2456FB48EFBDB946DF0A3F298BF41,9E39FFDC1C8573E4DF224800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0B3BE969A08BF4CE4BE98D6CC0BC7309,152512315F2CD6A3EC1D3400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A6C41AB03860DD83608098111AAEB4E9,57A99A60C2B9DA9E26C85C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6CD88694601229E809C13775C4E761E5,18017C263C41817B9ED6A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,53F16F6A6B6D2513CBBD0D63CBCFF990,115CC0B613458C1F17DA3400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4FAC9C89DEAD012D875350CCCD6DF9B0,3CA402F00396AE4AE375F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,61C960DAE03E40FC1F56BC1C71D37E06,4E5128E99E89BC59D51BE000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4DD845C43A81DF92E90B83504E159B85,35C382EFF558E66A0822F400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2315D98737F65C690EE46743434DC239,36A2E24DC3B5834D198F2400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A63680047A7750B42313F2D3B878662A,C3D10EB4D222A75C63383800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3934F208F33EB583661F36869C73FBA8,890536E7B8FEB54E97526400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4A9F494AE875A5314FC40DEEB9DB9985,A14AE3B8501B7B5885947800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E6BC8770CA7ADC109185683CF4666D9F,69748080DBE0959AACCDBC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D2EBF8E248B694CFFD41BA7428668159,1401C448FD719A421363C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A7AF83C2D4448241547B3425A8CD34C0,5A8B14DBC9E50CD5C3616000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D95594E46451C524B586913F80965237,830C1CF57919CF7C3CDC2800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C00B2713CC2E4E2355DA502E1B521001,FDDFA77D186A5B4BDBD12400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0B8D598E6F13F1236DACC84E456F0E01,B471887B4A040637264DE800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C652FDF2912983742E177C0EA30F2E61,C42F84FD9CD6E7FC37536400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6B655E5F7B56B51D7E254A9AE69E44C8,48721AE298AF66D7CDCC9000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5A900B611A5B17D82D4A2EC8E6544090,F4A51C7F4E896653E5140000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,554C91377E43AD28215056E8AEA5DF4C,B566C947F785B9B830080400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,54725198690B36F0C6A4DE05B0E79E47,BBBBDA64D8B739E6D5038C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0BF03AF40DED5898A8D1C657846ECDE3,4EE99919522D100349F9F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,971B0989CDE6313ACE7690BD2C2BEE09,590684B1A33F255BB9D40400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,32E1BE35019575931694A33B7B0AA049,BAA62EC15FCD29F7BCC9A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7B601512FB8D061C1B6F7FA423733F8C,82C1C97CC83A920346C47800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F0A3B5BDC514E4F20060D83A61A9C711,684138DD3D04B3B15E11CC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4F2F20EEE54D82CBA6FA087B242CA6A8,7F445E9AF2735B2C29E33000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8F357399AAD7B59AD790D55C55FBE3CC,86DB5E7D9F1E83A870067000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,511141F30BE586D039080ECD3822F2EA,D358B8FFEBE9FF38F0EF6800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B62E2C043BDCC5A1741ECE60DDE598CC,D5E1BBBA61272C8E9375E000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1F0F950D094BF35F9657D68E5018B9A8,BD37BD448CF9A5DDD7E87000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DE30802335604EDE8F943169A72E6A68,EC184C8434883302B00D1400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3DBB9938C7C4B14E1D5D07DD616B4E0E,AC5A0D23DA694B6128547C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D26257D4C9CE19B09774A9E0C86E335A,4C8C3DA2D021243593C7EC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B7CD31F340EE67C49D651E1C3042D2C1,A20D985A6C900427C48CB000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D763E4A31FCD50F595E4666BD91B8EA1,3BAD6E389FF168688284A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,41DEF6924472D84434CE2C7D96881F3A,B0EE0C304599D3AC58018800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0CDEFB71C93D7B5EBBD6CFD85EDF9C12,7C225B5EE5301B2F71E1B800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C2A41CBC63F2F8D34FF1E47FC9B7660C,DF4D16A7EFB8B8BB68450000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7F490D52DF1330100CC6D4EEAF8253EF,C501BF91F2BFF6B811393400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,124A768E3593AF51297D2740AD0200A9,4CE1215F737876BD5E89C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,39B8F95D2406756066A53C4E705F5190,017D2D2F27C605B783B7E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1F4511450C91823B333BF3372B1616CB,7EDE3A2F55D86D2692B00800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0762815735FF7B22BB73A35F2B98BCF3,C5F15A6612CE3312682BF400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3E3A81D6C95B5BE0866EA8E077241131,0F8E225A21CEE88570BF8800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CDC633D112AB14B86799836799589EE0,41D8946A92C3F5DD4D4F5400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E1655B3EADFAD31AD5A077A22DF28AFC,16D609AEE5FC4A24CCDD4000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C73E501E952DD572189853B6BB173A4D,A9E890D3DC0B1D4A0B295C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A1D2F09DF9A48878D0124C469BD7F413,35DF82BD4899DC7437B6D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BE1CA7C52223B34080503DDD9F84FB7E,8449AFF4E3C8F31BD76DFC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,44D343E09285E36C1ACC9494753A8584,624DB556732775366D708C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,61D91B4E360F4F5C7EA25DFFDB081AA7,8276AF99F7EF234573014400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FDC7AD732965351CEC95D5AF89254921,7E1DB73398B30B957DF8CC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1A944BAAFE83C95B9349782F5974B663,5A51D44FBD0A2BAE38195400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2941043DC4CD9975CCD5B57B11055787,5A506CD76E9E008F4141C000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E5547C0BE6192FDC79A0E77EC8E7D726,6CCCCB1940F444EBD2FDCC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A53F937180266161BEFF102A4AF616C5,5287128B3152083765EA9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9A15EFCB21BF0E8002FEDADF8542D8B9,4EFF43E5E4F8FEE77F8FFC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,99F84D1074D0E4A89AB99E79894D990E,01A6BEC2FB1491F16AD90800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7C09FC1D74F44970D826CD75BD76E4C6,4AB57C72DE6516B54FDFA000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,34C365219A6CB004FFAC21098E9BF4F8,E8DACB225DA9120AE8B7F800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,64938FBDE2C7C8BD27DE7DE71D936F8F,EE4AE4535176DB9BF2B7D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D6F53EF3D8213BD92E370F9565119A36,D3F4E0550CA140F329096000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A3728A2E35B9109737BF41A7CE3BA88E,AD7857ACE57489FCE8475400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C5A46566E17F20CBB22ACBA0829B3A35,53188D1D79B9AC71E12B7C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,49C327A37B243C502211059ADC2B928E,92D49AE27BAF11CD61DF3400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4C013269FB730DD9B4B87867F285692E,7F977070217337C997B27C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C08BDB2794795CFA4AB1626D23AA49EB,4248294453FCAC270C119400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6650F32A56DB39C02711D06D361AE5A2,AC1869DD89ECFC26E19C9800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DD10CB3DEA808A7D0C1F666AA3FDBED7,15CBA9F20FCA32D7F03C8400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C088FC4AABC5D08B3B842E90883EF20C,B64B2FB41EB4CEA224AAD400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9C3658205884DDE93389FC595DA95F3F,A9C1907D30E26D51BA496C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D386F98BEC1386870DD353EF8BEFD0EC,9F2DAB5417E2537C78DF1400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,32D13296BD30E931C2EF187B9C07E676,099E66F116225F2E5FCA5400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1B3F10C760DB7647994D6C1AB8B0E27C,88B59F43AB9F746C3AC3D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7F56EFEA97E53ADBD8A28E6784D4454C,A3A64F72AD0A3D1414876800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DC15015AF77CA3472D9C722ED2C6765D,202ECCA412CA0903D2E46C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5DF5D4D07734814F50D24C28D4D133D6,75445158435F68E6138F0800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0AA8ECCADE7C3DDE1E33CCCA904F87A4,7C36F6063668D0871E35C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D5DA6CFA0EA9C893E0C31EAB8F07C265,A4B70423146ED051AB678C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CA0DAB9743D4641EDA047DDCE7AECCA3,43B666DCFFAF925608CA3000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C8B74AC8B6096E5884601F4E1DDBD174,1E8E15E0F1743837DB3CC000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5F60A4B2AC4B3FE14259CFF7B9B9B9DA,6EB7E1212271898211BB8C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E3CD19D5573DFFD6E493C4DD7337758F,13130B7A1E66FF00ADD89C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9836A1AD51EE33A05C1AB926D13177F3,803B815E3D4A015D69AE0800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2A96EE958A39E9F57FD81B873F32F0C6,82825219B94D4148364CEC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DEF7C00F0ED8BFA5E29FB0316BA10293,C1599BCEA9E04812F33B3800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CF8A6B0BCC3285827DB9D471479A515A,60236043F506676EE874A400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4332F66E5C8FE9B2A374A807ADD3489C,E76B4BD39247441C69731800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B6B1A9D4F4B82AC72D820EF143F52BF1,BFCE82DD74C8ABF9F4C3F800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,41955313F015B3E391555E7EFD20D94A,CFAE1992CBAFDC38113E1400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,99F3AF88D90DA2AB226EEDA86816B73C,385E9AABC4B633422F7F4000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E497BC27A587261123B53562665A0421,EA6B9CAF5418DEE2B0C48C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A881F3D91DFBBB74427980D76B6331B2,E1258AFE12F8EFE020B58C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8A5FB6205C5BBA89FE76DF2D32C9667D,04608F77B0B37BC1645E1800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E282B8E50A4C89F996967FE3C97D4376,A34FA42A7429F439A09F7C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1BEA1A3C719CFF988D49FEB1E66BD4C5,58095EA3DFF0E4F55C649400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D21207460046447600836E81933C2987,5D3B6182D866461F597F5800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,86DC003A1E7352404118C4A6BDF522D2,522B03EB0142EB27B813C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FFF92D1920F3AAAB2ECE380817DB739C,8D193BADBFAB72BF8AB02800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6D169D93541BDE4543214B3905A573F9,3026B85156E7E48078DD4800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5E8B626C2461A81224E29EE751E578E4,DCEC75A33337B20D084DB000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E5DF990790E1F9D245F0840E90C845F9,5D43D9C00D5DFEEDA9632400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4F1733D5BDDB0FC3BCFF3A095EA63FAD,C3190679E66A34EF6A7FB800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,47E6B7544D18B2FE7A6910368080CF69,8AFAC9EA29F6FD5EF3CBF400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A015E3BC2CC8B20D1F9BD245BE00D935,6F8DDCB4C9CECC28493E8000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,850745B3A941130D04F76F07BB7DBD2D,CE82AA1D16E2D020A1BE0000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F9EE213796256B136B6503C06FECB90E,BCB277A4EF1AA561D1341000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,326CD7C58707E7D934772FA1A14E16FA,B8DE63537B49F9985ACFCC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8D495E6E54EEDDDF0EEF870FE4952568,CC5F16CD030F0E8CD1A58800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,74E8879783B60C994471D29F7D06168B,376BC13CA72BB41E5C3DDC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9EC9382241ED63D96B4D1C32344E1AE0,D83B4BECB683510AB6948400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F35E046CB410739F29DDBE66DD5F37D5,1A5A5308BA48B041DB62D800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,286C8D04A4E136AD2871F6173640D84A,7A89F034584E9F8550028C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A16B18F2C459C143EEFF761842BB578F,484459E01603A7A52320AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A8188E3D3E1C1BB3193E45E3D300FDE2,C1B8C534338CF19DE6EC4800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5C04D2F4663E98094BBF664207CA5AD8,35187E89549EBFCC31894800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,21637583D2915E0AC78C44BF8D6B8529,73D4A069B4EBE3A7F9B3C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7D30DF857DAED4EBC23AEFF59558B1B1,1905B164D13A1493F43E7800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0761A9113C550E71D4A001D6F2651E62,191662FEB0B15243BAB64000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1DF95FF090CAF75AC07F888E70F60BCD,58EB8C5A5CECAAF234DDB800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2A4E9F6AAE94DE7A282FE59371E75034,14B519C690ACA88942B1A800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,50A4BA3A673775C82A273690ED2FE960,92EB9C62BF57A3462A081400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,89E484024EF1C40A108D261A39298AFD,2F2903D5DC9FFC4AAA17DC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,10DCA6BDA94F80E7239120EDF9231AC7,67B830467AA2C76B8A83B400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,70A961DF5CCF1C45D26AF50541C3ECCB,B19AC7232C7E7ACEDFF6F800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7B338E1357DBAF14CB0D1AB035928F71,83EE66B91C1CFAFBE8C5A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F68F8CB5C1EAD184564C0B518D2DF608,35E056CBD8D692CB54555400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A0B4F049C406ED5FCC1AE8FC690BD520,9556CEF79B478627D5D4E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9E8F808AAC6301867025A4C7B247FBD8,58950816A5768A4FCCCFFC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AC5DAB1E100A506A6114696EDE6A064F,94A4116760CE7D8897D08400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8601AA0166D16DBE7320C9158D0700EF,F4E00766FA137211703F6000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7D0E95EBA4BD16E3C6B538FB723FAB93,3D231451211EACBA3DD42800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EB41290ABEDE397978C898B02219908D,5EB704A737FD1C97848CC400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,230DAD3A95D90EC20EBC8EC573781B06,CC0A164A00B54DBF2BA99400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0769460FA069E0DE021693AF2B923CB3,4AD863E830BE2F6013FD8C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DCF8186B27A4D60588375B514EE8217B,6BA6F69D924AB5B806C22400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E17C33D2F1B42CC9CC4BB68C372B86AB,A315B3B4BBF2AFACF82EB400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C74127FCD69A7DEDAF3EC080DD794C2B,A288120EC5E5D774E60EE800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D148DEDDC094E552A75B362858807E76,037A7E48A6E230437E1AE400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,91F0824B24B3190318CB89B0758B6821,D608EA0088C201318F3A4400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7F5D92535D449D7412E16856C9A9CC83,8C126E8E0155CAF47607AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,22D7AFB76E71CF4AA209B7B5B8711551,7C7707A33EB1CAC94FE51C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8848CEADDCF76EC13D5D5309594E7A58,7E141BD3C032FAAADDF1C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,76D1736CF997CBD643A242F805917ADE,4A422DED87839DE4F8639800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,96B7E7197A05CBDDE68CD566E833F907,62E205A674974554C7F5A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,928AFCA1A1DB27340BC5689F9ADCC3FD,9A7A16C8AAA1A89D28CBAC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,566561B80926991BC84FF3D782C43533,4C67249BE59008455BAD8400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AD7CCE408FE4D908234C4B9B7AD4586F,54FF6E91AEFCACE32C6B0400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,270BE9B26BFD8E8F399D63CE342C3AD6,BD3B66DBED973FD195CA9800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6B09662F3C200C2079B15CCA03EE8550,BAA793152333F7B78DE07400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,52A0DE611C43E8A2AA08C73B79122EF0,8B619DFBFDCA21ED3246A800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,089249127245C7364E2DA1F4B12B4174,1BB9BECD4723DAB665D9B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,78A4E8D674E7A9BAF1A2EEC2BCA52C83,1F4A8220DC3B790194AB3000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D69D8E89A9DB9FDF5008A490DCA35932,97F23433085310FEF92B0800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,303EC59372B82FEEF9776A77B89AD1EF,D62B685B0469F2DCB8431000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BF25B6FBA7D5B955F81A196F603DB6DD,9EEF5B576A0D5904CFE16400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A034EDBF71F3789C7AD0B7DD3E2983B7,F3955289B3B925D317E41C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0F64D014BCA2B53B1FB6C5267C6AA869,176AD9DFEF9FD87C1B26FC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E8A8E6665D25A32DEFED1BBFE032178D,635E89FD4FAAFAEE81A6E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D61E52FB7BDDB6B08D79E5727701E327,1A8FC1CFEAC2D83C73BEE800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F526D40E8A4791776F76B754BCB6002E,42230C725900932FEEB32C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,33274BEAE8FCC71C0547FCD8370F53D1,A647CFBD2B6015A5071F2800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,71B260044E7818F625536196647D609D,9F6CAE8F4BA7E18167D1F800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,494D5B1DD29C4CE82825A0DE60D75B28,747D3A2FD2456A63434BB400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D57BC3C7180DC541ABFE858D10462A05,171FA63B591C6B7002CB9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,75CDED92F2DA05D5F5A180371FE51F0C,8D6064E886661C7493535400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0A188392250D0CDB92FD3E224332F7B0,788924D7FA3700DBC29A5800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,15D4AC837FC73BA6C4A0FE899D09718F,7CCF880D84106A31F5F07400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AC453FA3FDB5BF207156144B24AD1E7B,F9DEC85BC2B8D3996D094800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,71F2AD4E06BB7F988C5004A0357F6970,DF76F1CA8D1203DCFB8F5400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9ACF6CF19BB44FEB4D95541AFC4A14A0,261B9A333A06FCCEA0D19000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3E26329310742282F2B9D1E3DC25033A,15020ADE64D319AA40610000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A6DDCDBA584140D229FB839D398F78A7,3073F2C778550F75EC707000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D3AD058E5268095511BEA6976BA94D02,6C2CC3C6B0325705D3393800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E4F0FF1E5296A93BDC84A3B0E03CD043,5DF3547DC840423AD633F800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DC5F59D24CD8961B0872CD4264F240E3,4038666247DF1CBE432D0000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BC9CE92196AEFEDB4A9C93D9CA8A006C,3EA4747DE9A8B930EC780C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A7FDBBF43164D9C679C09F010ED57120,A0D97A4D4B329DAC3C1D9400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,885F62DAEC03DC5C7C3B6D2985F1989F,8E8C3BB5767DEB1A84B8AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2BF7BCEA0DAEEFED5F1B9BF67C59C42F,3E83E6A4BDA567C0C1C37C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9A90204CA9EBEDDB0E8E270C232430F4,FE65162A4E81EAD4C0FAF000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,983A16E1E9F06C1E16CA66D0024295A8,A31960B3BCA0409A77D76400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,894A65F0863621F6F4B0F7D2DB52AF61,EABDE0292515ACBA40C8E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F12067DCF27C55BBEE33B0856124CD88,82F1FFC3EB594F53CDF39400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,81821C13A89580DAEFA775D37D75C58E,6DE6DC3F4AA7EBCF3BF69400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,296F16BACCB5B66E5316A21A82E603AC,CC5D42E68A362BC4EB60E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5AD7081575AB7E65BA8DF2DE3B6B7909,CEC011CC38CA5ED64CB93000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,84AB79B6A3A1F005563210592BE8604B,63D53A5C888A72E5A18E0000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,06DB39540FFAFC31D275F3E56EB15824,88AEDFAFE13E7632EA7CC000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9E38292FDB67242052D0A445014910D0,41BBF707586B1FCCCDE90400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A371E96FAD96E1545D8E8808A07E07E3,6A58E0D91F95F6DBB18CCC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BB10FDDCFD8C047EB0B51E96048CE459,2DC85575F29CA4F11FC36C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5B4307D70E43F25A9CFFFD56370BBC94,9F3DD6CE52DAA89234550C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,42FA4C38D32C541EC2F1AD27DECA6C67,13D083584650096DF3789C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B8FDEDEDCB6E23CF14E6EEA25DC419F7,09F8ED29BB3C6C40A9196800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2F83B170565F4977A89E3E09477CA1B3,E1053E83AD4F056439AF0000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7C2EC5FEA03C78F65D7D711DE30D76A6,CC39D08EBE81752FB84A6000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1EE80FBE225CD938D1800459985B61EA,688E5F40E8C20BCD7E90AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0F3D06972170FCE7CE9C7511293846D9,D90B439D68E4F9229AF30000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B15AAFD4778B30CBEBA6123A2DCF242D,416073391C2A09D498896000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F39B05FDE23CD3513FC1F9426CFFF0C0,C833CCF0BA49B3417F93C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D778CA42BBA1BF18D77DFF99BBEC939B,F3878F86134C23289EFE4C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E54F1991A18C175859242DB288BC581D,589458131E50C6D50B946400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3C5BEB096F9D117872C665B719EE1477,C39175020428B806D442F400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9412AB06825CA3411BBE178480A7D1EB,70B4716B4A43ADD6217EA400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,06EBAED1B90E8A2D25C3AB1973905D5D,ECB251D81D64096720ABA800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FD229FC2ABD4D5CB1E24FD63EA77AD66,4E74DCC86725C9249311E000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D87E8FE1157A93AC00DD5D2948ECCAA3,4EB7C5F4A40577BA424F0C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,30884DEB37B796F54A3C04F3A86F010D,3A2F6B2DB3E7387C9B85F400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1BAC80F40908D0C03313C33CAE26F5DC,9646ED009469732EC3EBCC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CD302E7008E2683A7244AADB9417E501,7839F2AE68BC906AFC06E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1348D0CD2910306D9118E9796E303C1E,FED0DE5F032BB902E4354C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,70C3FB8FB3CA6942BD7C1DF5EDB10180,7E9B37DB18F1AFC4D28BB000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A4150041B47229B850660FDB84B65828,D8A37D0FB9B2675AAFB9DC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5D67CF90EB0C63FB4951C40B5F91259E,7CFDC3D570F1E7C38BBE5000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,88017172C4612F91C89EC43868C6FDF8,4E838FF2E0008654C5CF0000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,23B4A866497F2C4A67353B396936CFC1,3FA08D495D5063E30EB6F400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ED2550ACBCC9821894FBDF3DBDC93297,A45019C0B1E78F60BA837C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,43C6573D014B23C691BB0DFD2040E9C9,2901DE457C41F792D77D2C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AD204E6AF70B423ED521A4B3AEBF3358,AF6620C0D42EC71B29135000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EBF14EDEEC94BE21F6E2521841B71B4F,C32B0095A611EE1FCF8A0800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E4354B7F94D37F8974E8912FE63E65ED,A8844009E0A67032FCB39C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5F5F2AD08E071C324A2A0A6FFC77B441,61BB6EBDFFEA61A3DA29E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FAA048F624863639327522D41B31D392,971B2475B2806A4741497400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E98C633FB91EE72FC4F0CB18119A67E1,FBDAE08872C1CCEC77EA8800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6A8815193A45732FF6CC06AFD49F9316,F26191BB253A328C7567B400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FC8FF54ABE8958DB01DBE87E8178A69C,E691DDCFEF1AB2B6B069A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,027469E5A3803DC4C2876D95C1C678C6,2438B8CBCA2A88A931EB9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5159283F05722548C74A737741CD6509,D46ADFFFCB068AF092F12000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6AC3B272DD8CC8BB745A8D0B36C211A1,DDFF97F3223A4B6BC232F800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8AA83372D1D5EC88F72ED111E27A448E,2E229F0F997B25213222F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3E7608319DCB2A0E65EC9AE2084EA982,C8B76DFD9A01A240E1B5AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2841232946DE470D2FF755C70C19905E,DDAA67B52DCB035E9A2CF400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A944542DF2373E12823C99DEA8DF170B,0808D4ECBC4D77A0158FD800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A2138146A6A0E2E642053DFDAA9E16BC,74EF1291ADB07C95E02E3400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0DDFDD1D9A82399E61C371CEA1F9CE38,B58BFCFC24A6D75BCB169800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9FB3D625B946962B54E2C17B20C42D45,D7D987D5BF9DEDA710CA8400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1B55779FB61B29B8BD86771D0BF16D63,3686F4642D1F419AF0698C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,39E34A86EC6FFB7055E7FAC2E0C974C4,D271890EA753A7883B0D3800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B4A93386400F4CA3FDCA51846BE80335,457CB81CF3489FE6AE1BA400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A313371F69544947FCC573B943DD9183,9F548C3892CFF8A485074C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2012EFE25DC9AF67C46C0CBCF2EC7591,4E80A17F41BD8EA8C55F1000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,486BD31661FFCE74A65CE0D558850D9F,B613B59771B8352D12C27C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2186CAAECA47991101726EC2C6F6E67A,BEDAEA0EA1943FE1FF221400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4D42D4D87D70821014D0CD86BC38DA4D,A2D95578B1FA0E16B0C3B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4DA30AC34F7EE91EFBBDC6DF5555B76D,6937A53FF44D36E61351DC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,795C15464D421AF29BBA1907DD10F028,629184CDABC11E3CA0C56400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B3CE0E7908B649E875005DE7E7FD1FD0,24C51AEF24FF49A4A4747000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D957396D9E3C4AF53C89E45C54DC0A5B,17161024C0F22E10A7430400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,43B416FD921D9BA3CDA47EA123386302,9810CA384A6F6647E51EEC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0B03AE1B7D664FC5B96E2A914C633D89,DC3FCC1CB0099F7414302C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4A6BEE40BA96A40B7CE9E6844DFA73AC,E9EA864579D55C68B4B9D000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,70FDC71A73F676D9C4EA330754B901C7,049ECEC4A332DF7B51D98800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7E61A21AF6B328C26E1D7325DFA25B36,D7D91E06B4C43EDB795A8400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C666E78CA92F658FA60C350504BC09C6,4445B12A7082935CBEF71C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,55020E9C0982E00235CE187B034B8591,3B0A8F08523A46377BB9B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ED046D0B7B672F496ABF1C835EAA3C84,64C19D9FA085AE2270B45C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,175A73A9CA9507BC08465DFFA9B8A496,59D1449FB47C046EEF89A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,07C70D392BD451EE506780DFBD5DC83F,12B436EB13A2F5EE6F3B2400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F91C460017B34AA789A219167F5224B4,D4557BAA009D10CF37E70400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1DA7C45EA6A64514FFA689538ED5ED53,4C6C7DB8436151A89E15D000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E87DC47A13D472C5DB246BC2F8D2DB72,F587FF0ADB6832FBD5257800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FC7EA44A9CE859DD79E3D01FCFEDE8DF,7A8E075D75CEC76E2E79D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6A03EA7604753B3C008D3DB463A7442B,47CCC2AB07276A6A9BF0C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0017953B084C3B7424D0687D9C02A837,4A0117D0F840E0A91E2C6000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EEF338BCB48C15A37795C69AFC7C570E,EB007C6F30EFF093E499D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,53A6A76FED38FF6D62777B449E5B548C,F00CDCCCC10ECB062682A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5C70D559D6F8B8DD99751D56C418FA35,E3C5F362FF9ACACA40A3A400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9CA9F9FEFE37D57BF633754AF7B3634C,2534834DE550FC11D9268000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0C23013C1C3C0954638F9414DF49E0B8,A51D6BD87F72607CF707E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,76D341F1D8D1C65514A96277AA457C54,8900B6FD332891AFE38D5400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,647466BE40774C458ACE1760FB2CBFFC,EB85994EA6C7F2A785620000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DB0CC7E41AC5D78A9A09184DF4F90553,21A4C2684DA076CDC5F05C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,582C3F28F5E1380932C65D3544B7BDDA,2006B84F9FD789AB7EF88000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5F823650EEE1F31CEC3AADEFC452E429,80C0352D91A88126384F8400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7D0288577305FB4CF1979056E527E0B2,F37749459DF1409694865C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,45566B3BCE18DA92823C95C1D28BC600,63290D3ABC575FFE70B0E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BC54B44285B1123AF6D3EDA43A5446A7,0787BD187870B36B47403C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AFF23EA0922CAA395D87CE8F71E686FF,EA9A6507116E59F885954800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,81406FA900C3787CEFA83332ECE83ADD,B9A1B23CF74881FA72F13400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,17ED1CA689D152C83DDE7C21298B1621,B9DF2AD19F79E00D39BC7800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BD24FA43CF93AB8F615A2CB55205954E,8FB777F02793A4A9E96BF800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6A60A9D4076A4668ABDC434CFF4F4BF1,3874A5322A30FC61EE2EFC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B4A18EC0063E6B403DE774801F83366A,ED58E3518559614FF3CDC400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5B4F77B27473E3E9E9F37A270B85AF0C,3BE0FBEDB313262BC0BFD800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AB88BAA258597D5459F9B1279809543E,67E5E4AE690246C0D9E9A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A2951495A11BADAC3A91F653349FDB05,3E6FBD4969707A42327F8C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AF7D610513988261234BFDCA9174D852,7D1721E9A2057DBDFFC78000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ACC7B916F1D53C75BBB5EAC0C0C9155F,5B50D39BE87E86DB3FF7C000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,463C7145B20B01098C16142DCD76F3E8,9D4B45528F381B44F58C8000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0E5E028DDFA0A3C0C879FA07C9FE0B11,366B62B32BB6DBA922F9C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,131524709A0F2C72CF897E34BF7AD533,8B444B299DE38B10C9931C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B2BD87BFF31207E0584989577E010E25,58798AFBADD4F313CD4E4C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,878C4B8B7047A04F24B85A039CF1CA08,134AAC5663497B78E9ED6000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4372DFCE989ADEAF69B3CE5541F2D44B,5A4EAC4D5B411199843EC800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F4FFEF059262212C6A9440F4BBA67303,2147ADD96FADA5C3B0A82C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ECAF923AE9D823A2B5AA234584FA1956,A7345726E548029DE8C13800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BF988D41A1D5C109D0706B2068ADFE14,C3CF7CD2693FFD82DCDF1C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,449990955002E4111D56E0C20946C6BC,63437E1008262606709BAC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2EE20958580CE4F8A8BFEFFCB1D28D0A,C5F9DBA0853C4B619979B400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B6AB470BD27B24DFCE750B3EC08EE38F,2E4FF932B76DC48617597C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8AC1D7BB4F58B97A4973F3BE4AFDE91C,5E1785F6777316F60EA67400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,495AC23A71E47B790E70823F557301A7,97A06E87B1351A61E82E4000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F62F24627A42462892EF3AEDFBD86BCE,FAAB2671E38D5F25CC2DF000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,60372B92D3EE37510355F7CA2FF1AD89,248B52F48F946258FEBB3800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3A6CF14AE01A0CA1562D639A97AE49C7,31F7D19EAB76B3A4015E7800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D0FCAFC71D9A145F839F11688295A8C8,7772C8610A17D87744943400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,842DD75BB5B1E7E201E248663A83CF62,4C43DDCC9F35ECB2ECBAF800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,45AC11764243D64DFCDF35443F498A11,CA15ECFAD53D0E487DA51000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F0A05481CCC3E9759495DDF4D061F7FF,5284304B591163CF2AA35400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FCAB0F4F0DC5DDB74F6F45C3DE45A890,1430B0D94E51B3F0970B2800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,96705A918F3F7DC4779EEE959DF56DDC,11CD9894491DBACE093D5400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3324BCE404ABB6FFA1B815FCE3653504,10B5BF4FAD33E64AA1446400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,69C0EC0F65DB292E47D77861648220C1,BF307A6E2EAAC31C03ACD000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2CD92262C563D16CDA7076A3916B025D,C3DC96C3F8982607CB34F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3874E5B8CA16D381A0B33E8846EF7678,826BF9A7CE95C140A965CC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3368842420F16B5476313F02B16B3CDF,0C34B5F2BBD5B6D336B5A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,669E584D4DDB2E1EAE7E914C8541F311,B9246D668912C36EBCD2D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8D428F26986A1C746CCA262426C7C0B2,7BDB274C86342B7068033C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,02B97CAD06351C5C3CF86D396020026D,CB2BE5BC456ED73635071C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B4696D369884E4941D0E087B32E09D2B,2BB0A12257D862AAA55E0000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4EAB87DA796EC3488C342BDF311AEF22,23A0BCDE4B0DCC2C8D89F800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DCEBEE5F1C4C7A4E8A83B6D4CB236AEA,84C90910A45004A63EEA0C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,55855957EB252F0C59A6B1EC4506B9A3,9C693CF049FE93DD6D755400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FDE4AE6CCA16EF7FBC9AFE8067FD4E16,1E9A9CEDCA101D2193375000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3600B73545F54A51BFFF6ADF932AEBAE,7CC316918A7AFFCA973BEC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,202D67833ED8B105BDF66722B7E64F7F,540ECE8E6B38E99DF7FBB800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1868360A46FACED93D287EB937EED141,EBDFE734375B0DAEE15E8000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F65A50219A711BA33E5227885C5BD27C,E87D7741411E095602402400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FCD82A0A445AC9373D96BB9CE8AF6B61,517805CE2B3DD5310B6DE000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AC6AC085B92AB68E52620E350E1BB3F0,758E969F192A9B2B20B20C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E5A32C2C398BE819497923869A8D3316,97FC978698E4FF67A0F0DC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C7BC59BE121995B4AEB0332CED5A1198,B5CC69159E1C6047190D4400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,827E9C6B80997F053AE704994119544A,C073B256DE3FBBEB72696400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7702362250B6F5F6F323230C598F9051,1BE3AB6CA52CB8A98041E000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,00E5A0BD996A84C310238A05A5AE7495,A31D2FDB0196FA415CD15000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8D9CC3F666512DF3DB2056CBE7F62684,1026ED1657B49F41317A5C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BDFA3EF9D36448BAE383A49FD6A977E1,BB2F552110C1F5872EDD6800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,18D87AA310A0CB9B3CC8EF54439FFCC4,FC770426413A7100746FCC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,992813CB979D33BFBAC54D55CEB4B73C,D0148FCED4882E99B5550C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,833856A2E8B0EE42A5EFDFE505A2A98E,C21F2CA3950B24C3D5718000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D16805B6CFAC637A2A39924114036E5D,322B554745D2B0DC386FAC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2E627A1B378BDD2A3ACC7486963D69C1,00B201FDFC3C4AB6C6633000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,86F68E8A8FD9FE2457E9E1FE47D2523A,E4FC49F68FFEF7ECBF3F2400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F72BF5374387A730D780174FFFCF41EF,80396F47B01D853208C4E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,07A2299E7A5F5D56C0070412811B02BB,EC7B17DB8911F53B2F97BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EE2F57A690134B89D2E4B1C219CD7E03,F492970E081E648CD3366000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B2DB578B1D559A2778CBCB0216EE5144,B3253ED8496B17C05D7B7C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6C80254249001719EA40C51F2C4BCD4C,36E4172968201F89069D3400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D1C28E94E15762073DBEB812F5F1BEE2,DBED6738EA5E202530C78800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0F784B5CAF051C4FE137D2FE59A28C00,D6008F9F45FB8CC50677F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,39B71CEAC0104DBA6743E2EC285BF7DE,01FAF3FD3A79579778F7BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,261756F9DB55FDFC1116091B8D6BD58D,50FCAC1DD42FE079AF7BB000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,57E47170F70AB261FA998017D838199F,88EE9756212F1BDA4E12BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,02DE5B5F4AC153124891E768B5E3A1B6,B42185D92AAB6A18859F3400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,224E9EEF82B29CF691BC9463D9FFC129,DB92040E46BAB90675E89800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,524B0B8ED727F299E59957FA271A50B5,456AE3A856C6FF5A6BFDB000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,242A954E612E1F0194CE8493A4CD1C18,1F607A7E1DB1212B24A6E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A61AA82441B24BDD06AA6BB2CE77CF27,58FF4B1B3DFBB3E3726E6C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E01B82CC43D3D72D8CCD60C92C62EE09,C00041D883E5BAD0BF03AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3AE3E93F044396DFBB5A529A26DA2553,DB62C24686FE8014B399EC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,653A180F0F7C98B94130A5C40BD11EBE,9B9BCBC09E09E8D559ED1000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CB92A03B9A262683AA1A39CCF8866BA5,BA1BDB72BF6C2A94D92E4000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C79C8FC1619F413BDEDA3F9F8FB7AB19,2334AFE1C799B9815C7C4C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,71DC6F9B718457B72CBF948304DF4168,13230113D8C0A23FD5BA9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A11D3C634DAC5379B5970D401EB6C512,CF9956024F33B4A965371400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,05B7355A6962EEB393814579339EBC53,CA1CD0788A724158BC84D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D494D03F59245A4FD88A87C4E842F8B3,C8409116E5BA783DA8B89000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FA2235589D8182BF643DD68FBD3924B6,447CC4020F9F87565B5B5800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3537CDFE59252A2054E7EC1BC16C5061,9C8D45FB1480BE389112FC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D81AC6DF80593B62E4CC6D51EBF682BE,DF6CAEF62BC536826FCD7800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EF6FEDBC6F65EC72E71DD457D7F01E39,545AC9FC051774F6ECD97800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A926134D19884E0C5DFDFED7E5D43926,315E8EF19510592D48DE8C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D40A68048F84E3C89159DCDC2F6CA432,4E27DD965DCF6CEB40489000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E13A16536ABEDC92A087707F6962664B,F54DF012133B0CC1D91B8400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,443F8F2A487FE8CA0E4E36CB0394FA2F,FE5A205335BA7EB4F9EB1C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4CBDFF21AE06C34FBCF8BCE67A33ECE0,1A80583C4CDFF82675F59400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CE08B54548CDE074E31630B0C5F2545E,47904CB4205016ABEDD7FC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,16C810823D7C5BF3D5A7DF540BE927BE,BA3C7853EE826A7FC1E8CC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,493E34068D98BE1CB0DC488E4215EB44,A10DFDCC8CF7E13AA9F48000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2B16221E154B8E737705BFE4EF686EEA,FBE1504BD0786AB3CE8D5800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,40067BF44F7EBEEC97866D1D2B589C0E,76C385ABD7A3198D4B2D0800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AEE48475101B133756C5260C974C3CDC,B6B159E0B62A3E135CA3E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7C337455E7D04CDABF9100A0DCB518C0,7FEF52780DDF19198AEB3400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,34509DDB0528245C753B34D0A470161E,79608012018A272CFC4FBC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B82EC07003C40E7F69415CFB29090181,E31D8D19A1038B6F3D25BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,101CE5DD0A72E07C3759B23BD07CB713,13389C0B65706D05753F4C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E3508CC1421B61CC1829F1AC5000A3E4,F3A0E53D7E27598C99901400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BE906939FEB3D53EE5E3B3F09AF34392,44F8E711171F64FE3B7A9400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,58AE77015B346DC96112C7463CE44CF5,217BCBDD9A7A15E4E3B9AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,01EB1E42FA9BFECEA63771A8EC8FF608,1282EA813670CDA82B028800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0287CC4F44011FFB7BFC45C753A7E764,EDC47B4AD2F99E044B773000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FFAC099B033FA5CE08C1E10751436B0D,774EB65C534D5C554A4E8800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AB57C444B024972E3CF20C2F885D7211,8C6F76D58DB1181A94989400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,66B10CEDC32FC796527D98A4B9052E0E,48BDC529943EC0D5E66CF000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9D4FC614B4A5ECBF84C40E3F2B7B3B0D,BAD7D26D697368A3931A5C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7529E5C6269C75FCABA03362FFAF0BA8,4428CA3F074553B7CD193C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,290F258C9AE1A88D5DB89D1C963ED8CB,49A6A7D3EE75322C985B5800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4A635EEB118CF0401AA539B3E78D9BC2,E75EB6229EBFCD7651869C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A0A8CC5650A8C449C27358D13F5D149D,33D344289813FD1762951400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,00347953F59FEE734FA747B5BBB492EC,163D84E824A58AEA21C45400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9C1DB2550ABDC1D83D3C6234C1F7216D,DA743AF98691235ADBCB9400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,566FFA71F19564E0334521F522D247A9,E40AB25D08CEB2A2E2961000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2BE2558950270CE4EA4F041311B700B0,9D3B8DC6DF02EE2215575000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2C7CF5A9C76A34069A0086D67162CAE8,C7C27DFBC668647941BFB800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C6704C0DA2E47E546BF089365FBB7EEF,3EB8772558CE07528DAFBC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A4789958665D25892122D9B7838A7665,E3AEA74A7E4487671C72B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0359DF86F25D1EF335EDECE2F1300D89,62C3FD4450596914203B4400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,34BC3AAB3035AAAA631514F56C19D85C,9C6C6F2C7BF109B71BC24000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2AE6301993E8679EE9783099EE4B74C4,5827D1C48907242EC9693000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8E688E8C19E4842AC61D673A47502BD8,E937B1C11E000FE617CD9400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,635D70F76E8DD5A60C93E636555643A3,1FC3F91D21ABDB110560D000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CFBD75A9EF7C590502CB305E255B32B7,5AA46577C4A19E56222EAC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,52017B0F5A735EB4B4195B41C5ECDDAF,141288BAA0C78E43A21F8800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9B7F9780EE1DB174EB3DE3E0DCB4430B,67DFFA9CDC3456FD0ACB4400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CCB9CCC90211E81ED80A3663FE9520DD,B296494F844BE19BD0BA5800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,094C25D5E5F556578FEC3D09E1F18FBE,2ACF6FC60CE2E75D0A26C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8EC3B41444043CA925036ED10D358A08,5026E7548AE49E190927A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,90E7DF0DE887D9A66C7481D56918E113,4B84AF649EA0CD5F0840D000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,968EE15F77994D3215AD0B3B830C8022,353867C1CAEC7F26927B1800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1DB07381378EFF68414A6DF0B19CFA74,2DA3CF74819513D2FA280C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,905553A290DD902F230AB5334EC3463A,013A682A7041996B08865C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6C61792C1391DCBEA18157DA3A5383F7,61BE5406A149B4FF8B5E9400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8C1D6A7204459D8394710449B1D13BCE,CEFE606815A3CE4E25379400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,52A2764A059CED2CC46E04FACAE047AB,B9ED555DE46B9F27FA452800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,583303EC3575D11BF8340CC10F6FB42A,DA213F2F44A4DF52BFE50C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ABDB49A4B210381D9622AD5822150096,F3672C97FEFC462BDB03BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,711639E35CA2CA66EE65C4A45CDD6380,536057FBB46AA0D25803FC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0C8DB765506BFF298F89022E51C308B0,89EA94F579D333C66670CC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EE21F43BC9268966A546FDD100C3D841,D4593228E6274177E5750000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EA1A2B1A515C72C4F4097AA555C93705,E1DDDB18724B2B66A8D08400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0C9B8393D38BDF1065B162DF707D3009,B878385FDB1AFC41854B6C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EC0493D80DA33AF6FF5FC42D62870052,4ADA9E7AF7F353A9D9EC5800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E41565124D918E28E7ED57AE770D7633,C8C8CAEB0DF9667A2D34D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6F4B3D805D5DCB5672FA617066A4B2BB,346EA70E1588E99C88A47800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8A66D7FC4CF30B989EE579AA08872713,4BE79F8A52FF99AE1C710000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8C901D0043F0660C0A9BC60171ABDAB8,148A92FF28DBB5CB7012DC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B80E227C8575C331DA9AE0F4050D218D,4CE2F914C3EB439A03393C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9ECF1E38C9B9103C6185B00F3C4D93BD,CB38318E5B3CF543530D4C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BAC15FB21C609560794F4C965685E290,46B7D216BCBA27E2A4085000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C4AA0670C7B8A667728DA0D594069C99,12D51B9D3C7909002613F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D099EF68D7E3D1D47F23AD15AEE4B5F3,93622C70D1D635CCF84E7800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B33945EE00E962EEEBDE6A8FF4742E4B,509F4F51D9BEB63CF9B59000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0D473BB0B5BD4DEB25DEE36BB376C10E,3FD579DB817D8A510BAF6400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A14218445FC5A8E72C96B1F27D73AB52,A3BEF92801B0252DF5DB1C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,494C095A54E5CDB1A1F6095C42D760E4,15A68280AEB04086C3638400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8F90256E47BE4678036609D34B89544E,2396E34F472750F9D3524000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5C0927F8F0FD2307F2C6FECE9243F06A,87AFF076277855F812670400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C636207195E46AE24C49F4F9D0B786F0,86C6671A2A8BA28EC6918400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A7AD2E1B41F3761C59B29F0BFC5890F0,10574CB3947EDCD7E9595400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C9FE56B04AC1EDDB9E04E352E8199A3A,5C1ABFC9A21F5D95E1DA0C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C3974645E95D98E4F9274CF62B0D885E,ACCE97C26AA1DD1BB9340800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,82D095C92DF409D3473ECA08A5A81B65,30BA06CE2F1AD2A3EAE8D000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BFAA576B9A290496C47C3D498F8D4FE6,B43D9C5448CC3F754EA0A800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,040B228CF2C6002BE385DEFD7DB55725,62D7DDCF277DDBBB84289400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F1E565C0B52EB2B1E24C17AA3848652A,9F8A216B9C64DC5A03833400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2E8A846052A19AF00098BF1AE1FDA9EB,2C5BF370EA47936AE4802C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ACE3EC57D2DBFF11B9B8A81B4406FCB9,6689168B45E7E9084283D000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7414C5D415AB5574A82668573BB04166,AFA7009AE75A5B6E1C8AF000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A986D44920217FD956E7C4CBC3DE0527,F9BF62C6B3C722659717AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A08A9B02F531CB9D580DF8176A3A994E,D379914524C2ED1D1B693000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4C0F9E924F063A6243865DE1123CA045,D7F6FEB5091DA38F0D1F9800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D55B8C7718CF349F906071C76555C986,564E13F008AA9129A319E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0613FF46EFDEB6A663D915B86A9D6C0B,E75538CAD55D599EC6718C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B00D4FA90CD2DA85E1E220AEEFE2C5EC,ADE280F0C17C8BF566451C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FD380C653CBF3A927A769324D9E00324,3DD21D2EA170DDDFD5E16000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A9172812C62A2FB789825C71C23BE469,765EA88CBA94AA15DD3BB800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,59FA5C6FA03F2BD23E35CAFCAF58FD46,546D5EE3655E43632DD94C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F6A4D79BF1DA9F46E41241CD2CD723FF,74ADD5456F2CD3059AAF3400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,46077741E91463DCBB61F3BA88599848,24D72BDB319C6EA927B37400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,46E2AFDEF73C2242C2EDA4C690C91E1A,23B988C1308602402A4FC000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C240BF323A1B10CF791397D47340B26B,F13F3AD8A00FF908FC6BD800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,87839B0D4AB1B531495D00751BDCB435,ADAD6944C3392006CC85E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,635EFEF876F6318332BE61BE6B4B83E5,4E763956FC5B9B2C37C09C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4A4A4AAFB83EC7B93840A3DADA92F1E9,7032880172E7BCFB8C72E000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,915C8330D241274251BD71B7E6D37699,E2003179F7CB405965011000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E20E7DC2DF7BAE037EC7D880B3C26E90,992AADDC12E81003488C9800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EEB75A96C92304E40C2E0E806E918E9A,BCA49B63865490E279409800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BB1806312EF173FB73897A3D0BE6A053,F731BB08A833760754AE0800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,64E576287B81C3442F95345C078D22DB,B5DEBC603B1ECE7161C95000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DCB334F708467997480B458DB66356E3,CFBD2049D829D9C42D26E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,39EC58A24300E932FF9258EBA374ED62,A489F81E6E16D0F436977C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,52FE65AB5569A4DDC91DB5EB747AD872,712CF42C24F212EE4B80B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9401DD3CF0C494B91A3CB28A677A6F9F,6958E170DB5879834C90AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A7A70C832ACD99447B4DA063BB40A249,8C4337552EC9121D9C0CB000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F777485D595BCDAED8AA529E91A161F1,97DE44F1E89882C3A91A5000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8D25D899D7458F37D0F26C7D5FD3C0C9,1E9604A46215B01EE4C11400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2C75E9C40BBE9AC71B03E2B1A4FD8F77,BA6F3343BB0A50DFC9F54800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5E2FBA59B74C51357D5944849B5D5B01,D5312B4F4774EF49E9318400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B48D0294C24464CC796F7665C639A3A5,DEF4ECF98FBCE2A4DF450000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E1EE10EFABD753F49C13D0B6FFE567F1,C6CF830856A1AAB6750F1C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,328BE773AB9ACABE5EBDB67A72DAB593,1246E6852A5B78EFC63F1400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C6FA835395102B135A0DF22ED100A30C,F7EC6507B3D8882E9F86E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,42BA23DC01CE115FEE284E30D93C2EE1,85061F767191A64FB6DF3800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,590C81F77828CC50FAE2322F88F2FF72,4428F98DAC165B709AF39000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DD5A394D5AF47943FFFCEED795E55131,7BF03394EA112244A2E53000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E4B350364A61282E9C7E109C200BB59D,22A39338B073F44ACC3A7400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2EC97054F229452094AB6E95713E2B73,C7DC6BC4D8586FD0A278A400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9949BBF69464815BBADB53732B830972,221299171F63C0DF3AEF7800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AE7021704039DB7C25139864145D2BD2,1945B987B54CB70E5D718C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,31E7C45E5C16698DB87FDC4414521085,68EE8B49C4E327CD7EFF1000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2C9301A7630A80058E6016DF1E61F67C,22E69372E777618A21335800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8BA8B91866F892579CF8EB18BFBCD4E9,F3E2B573F70A66FB55C23C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,312372D0F65B447765758D9CAF2434D9,93609D380DFF429A870F0C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CA787B420C4399138BE2317A0DC94249,9D8372124BE92CCA0D06B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,27DAE7C8BAE72D001FC9002353E9B64E,4E15629B933D694F68733C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,26BAA1215821CA9F90E4CD0690C10F2E,58103A993006A0C200359000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A3D6F246FB435A7FF58E1E88B434AD71,685673795E247655A1B4A800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FF468FF55671C17282B41DD246D2792D,26BDB78D824FF8CDBA768800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,600272165F44AF4C0E537AB5D8E66736,3C79315DB6DAAEF9EA724400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,508F035C9376D9FCC41AAA7D1CC764A8,41FD7AF4639235E4A02C1800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F4D22B29DE342EEA7CD8E4E3F43A4943,CD833E80A4BF33DF3CFA2000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,58843C6F5856B56CA9198695EFE17CF0,5676CE8A0AE61F33C3ABF400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6E3C73DB4F5CC678DCC7BFDEE568E826,A247316EF8F882D42FCCBC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D37AE74AD0489776723124F6CE81D42D,78355AEA1296483A3CBE8800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F43BA41AD0DB41DA4F54FF5D6FEE4771,DC1D9BAFDDD5EE83C1B8AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B65FB4E50DD0050E79BAF78FD2F4B9BF,B0E291BAF797A8275AD71400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8DC778A2ADA661942266E0B86CD7DA91,B9D294C0291018F04A09E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B6E293ECCA4A56F1D93C8242D5E6756C,25ED1EE23A3C11FCE7D57000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4B5B86CCE2090F22424EDA2C512197C8,A365A6A867B02EE9343CD800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9F250AC936FDA89F1966C3D3812BFBF4,3A4AB781109515A1139EE800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,87CF86E273A7A8BE687D1802005D2A5E,8ED03BDAA9E92833A6727000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,98CA9A659D2C4DB1A235DD54BA36EB77,0AECFC5BE2B7C1DC6D0BC400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,53EA0452158CD2DF5F5AE2DCAAC86BFF,29A163B42A2D557A70974400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,74B58541693349756299EE79D408C415,56D28DFB499FDB30B87CA400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,16E25268D3F1498A42525626C2440505,39FCE1A8505CD73BA1DAB400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,737D32452FEC46C026B249E7D77B9A80,9D515ADFCD93432FE73A8800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ADD0543912E4A632AC4C0814294751F9,59D694C394485E65DADDBC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0F30160AF9D726612C00476DA06F2FD1,6EEBE451744257F9435F6400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0198B362FF964F890256674B225AE2EF,DAB5FF67D8925B0DADD74C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F99975FC4D5AD6FDA57655CF1DC35FB3,8396623C8B44C2904815EC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,762CD396934B5E7916EB48D07E3D8383,D597556F2C650DCDAB686800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ACFD433259569169FEE586564F056F31,CE6C1FC52C2CC10E886B6400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C0C3AA12D8EFA599621B8219AEDDB39D,F46A4FF31C0FB6ADA7FC3000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,88964441874790C6B1AD12C6BB907060,CC9729FDA25819796E90D000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BF65C58D03260691C1267E992128F675,BC7F343F69A3B6C09C114000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B13CE2B597DA27FB9DE4B9ADFF717495,87BE699F1748597BCFA46800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A2680E79DC76B80EE268FEE9C1EC945D,18E19922978E90020F353800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0B31B79AB27925553119466A3C845C49,041D5919C3CA2542AE91AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FFCA938AC5DDF97C526941838527CE9E,FF7CAB0E892D9AD987AE2800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,36D5B7252A7B57BE2D4E06879E6975C4,427F72A3254D4BA24C1E6000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E4FEB6B8FB01025D6C479852FEA000C1,825845CFF96383778D0ED800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E138E7CD58527B7022BD1553C4E493F4,71AE9F089F8B970EA986F400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F0FD7F38EC52265FC33D60DC9553A5D7,140CEF85332BCDDBC3B26400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4F0BDF1C4BE4A35C065EAAEFAE646A01,96E64BBB007D4761869BF000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,03706861BAEEF0DC1CEA996C3EA93D92,C2CF16AE93CB94D5A85FB400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B1259AB7C681443C290E0C1BF5224DC1,DAD36B50AA8455AD5D6F8C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E225F155FA5CA852E151B7AD3BC40394,9F480C67DF663A51B91C5000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,30098EF69DBBDD25FB9C87315D3020F1,51D39392BCF956FF0F34F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,749E0FD7B6ECD4B45E80C6E54A14BD0D,44F77EF7111682F411EA7800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5A5444F0E7E7C61CFB031F86E62EAFF6,2F65BCBA94C07D4346DF7C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C9C7A48094A579C8A989B28848DED187,CB88C47F30FAB985582B0800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,50449C950CE2DB6E0DF22609E005E175,64EB723CBF43873DD2320000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9DEEC383005D68C05C665CF484E371FE,F941903FC1062E6FB20AFC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FB3BB14E175FA9172A2741E04F139B5C,4E9CD42E9C244AB3690E8000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,24C01EBBC6B68F9BD4850D1BC360FEBF,BDFFBF809BA31A516463A400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FBF4841145F14A97DDE18D3ADE4CA794,5CF60E45C59FAEAC54A66C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B3AD72EE04894CB4B446CCB338E31656,39C9B3FA3B84200BD76B8C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3BFD7004020C1DE138BB6C78F2042D89,5BB03480D9B6CA5180F82000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,024D288A0F95E4633BD0B05CD5B700FA,39DFD93B2CBEBD1CBE42F400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1AEC0EE2B2E999AB36583974C7668FDC,68FA512A754892098A4F7800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6BE0D7EC8A11ED5969386ED31F2AC1A0,37161F968FC6655AFE02E000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FD087BD12284E354ADA00FBD47EA0B99,B1E477DCAE13CA518EEB7800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5E342819ED86F7026BF73B76227A8CEE,B002DE9D67667700161BEC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,75D09A20C3586EC73944AB4C216D44F5,8AD9F7E3FD40F9637AA0E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0E360DD744AA863F6E0341840510AA1B,478446EF066F048C82A65800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C048603EDA30D4858F8F6C48A9563B41,4D53E54088CA31C0AD9A6400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,759445A6138231604DF7C13CCAE4BDEB,5CB44BCCD6460A0950CAB000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A91CF2204877ED8903E7B1AC9348BB22,915775CBBD20EE58C44B0C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E21BA0D598D2FC61442E32449589112F,4A83081F3332DCC326F5DC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,04F3AA9BA2A1D95E8CDEA6FC34308017,D6082B73B98D161CBD653800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,594A2A8792456A71705BDC4E89E2521E,65929B7C79B8DEF106487000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,446D9EEB6FA4D645BB682CB86B8277B0,5AC860D938A44B12E17F0000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C951406FF94B294E9276024B879B0661,AC12B1BC46CBCB5AF2B0DC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4E542E0D75649646B084DE8FB0285C77,65BB3D00B13D1EF9E5173800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A1268CF4BAF41B5D12BB865F0E3D8B22,3BE83D7A359D3597AB3B2800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,36C3C451F2325C18FCDD73CC0FF21B18,4B6718390394E16180E97000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CE257F14B53AC96792EA2CE8B4A4E58E,A519EDEB91EFC18B1256F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3F14F22F59C516912AF25CA62FA8F053,F3BA543B2C728E3FA4D30800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3A4F5220261EAD5516E8C517BD2ED831,DD66EE67A690B8633E51E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,495C634F6667460C254D97B35445707A,C6F29CFA4B9CAED36E270800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,66EB5313B0C83A64FD376BE02E03166C,5E895656A03CE5C83A4DC000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,35889435A0DCD4B799BE7695320B69CD,ABFE28829FE27DE9E31B3000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,183B1F81EE25E31AB5DAAA33D015CBA6,E80C570BEF8028830FB68000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F92B33E2BF63BF05291C09F0213FF72D,9FC5D30A549A662B0278A400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E12E30F732A3A2A206467BB3F174FF2D,FBF985953AC4308EC8678000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E66C7ADE2AB71A9DDF8BA3A18F9C1302,E2F0A7D8972CA55D5873E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,59DF68C04CB938BB9CAFF8EBBB6C6CA5,A12CE62E9B86E592D647C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5688229644E7F91AD75B47BAAE320C27,445E1ACDA1CEB5490A7B5400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8B26CFC065CE0BC44A97E33A5A07345C,75F0660EB010AB40421F8C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,77BE38B4815AF6DFBE17DD48BA9AB1C9,A8F61F6D8CF832A592B4B800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5E73AD699EEE8DE8460F2C01C2077CD6,CA6BFA4A79B9104550A07C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1B546F3B274639D31AE0ECF3C378D180,5A668DBD6AE23D628EBD1800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,57887332BE21016DAA2200E69A5E850C,937CBB32F8A08E24E9550800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F11C61AB9A2DCF1D73D2566C07F2F90B,70BA40635D38BC070B1A0C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2DFE7BFB3589E3EB3E77895EDF0130C3,7C0BF94042656E559D11C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C7F9F26B1CB9C77524810B97C649137B,CC9F502181117A6015AEBC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E3579A2F7B2267295B14F6A2B2F09937,FE52FA20DF09C6BAD47E9800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7A2C147FF0868F41FF61BD482C7E21BA,81B79CC89B3DC23A023F5800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ED5E4033949163C5AB820CA16C51AFF6,A82B3722F3589AC19CFA9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,22AB205407EF30ECCDDA49B779ADA405,9B570A9C9E89BB11D1C04C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BD5EFBC04BE376CADFE0207ACD8AF095,AB4D42353B1DA514E410AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,785180CB3BC2FE8792E424C407F12437,03BF1E622929CAA732929000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7E1DBB304169F6DE4F12151FDEA7B4E4,F74A4BA566896445694F0000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6A1668D9BF09C4B9087F7640A835AE9C,C467FA442E9052B1A1944400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,096E3A437ED6A342BBA60D4484EB348C,AF5684AA97791DB5F4F04000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,218223AE24CF97E6BFFFFF1F646ED3AC,912BAC5C763BAFB866356800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,32E34FC02745575D7844046B5EC92297,753BA5F36C33B0A8FF19C800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,78193A44A12EF231CA47659678D49122,B812FB79D7769618FB98D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4C79BDF37D02F924936515FA9191535C,3A3EAA1A02A8770D634AC400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9149129D54A114706A17C92268E95788,4BAE933AF24AC862AA02C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,827123B8A0AFFF76512B9A4247F18263,4E176C62EF8993DC49BCC000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B7D362E0CAB7936076570676D3B00926,F7142C9528BDE0C3410DDC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C4835D3873B027F4A04E01E09F10B567,6CBDDC9F268BB2523D371400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2C71B6B495E2280B5ADA4882926012E6,6608CAED6ECE6BBF4E4C6C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4DFAD94ADEFED514A8336CF78DF291E3,BC9E3927D64A5AE83FD09400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,39E9E060A7A89B40951170834EB358D0,882709FBBF4C15A53ACAA800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,93FA22B2882936519A894C83FB71D7CA,10CBC4E60938FF6E02BF9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6D3054E4F523088E4672B051B4830D87,8C235DDFDC54AC96423FD000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9A9AD3AE912E007D1B7A773CE5995F5A,C7440E0B278459AAA5452000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5DE6E922819457D8A17A4495C432CC78,346894BB2BB3E5396360F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CA9D461DE4EFE173DD0F70B21F0EF2E4,897F09AACA45A443DF046400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6CC4A0AD6363AA75173ECDDEF1867500,5E1369255B428A3CAFE91000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A83B7718967EC4E71FF17A13E504979D,8ED613C3B5EFDCADB43EA800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,920D2CB5A77A118740641007D01050DA,5CA75E88F9DDE10943C2E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A25AA14FAC524225CF34796E3B48D4E1,486C8EC0519E9CF7BE04BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,079234F699B3D4F4E10E81A6F6CB4AF2,953C821F1B60AFA612147400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2C0FA3B7884EC1F49A1FD64D25D918CF,66B1BE35A97A35B552B54000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4809694A853437CEBC1430403F2C0AC1,8B52048DACE2B2426E64D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CA0B1E50C790195CF83E867BBBA1B11F,8A7B4F4A857CBB91D743FC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,750AD2FEB9321E8161BDCC3737F294CA,3AFA341A9F80B2C6DECFA400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EFA25251D61F8DED93F2DD2F5C773DF0,B05617847AAD49659499E000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4372D23AD9D5985A321D47FF3B94311E,2F0458EEAE7C37C9B0D28C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0E4D5FFBCC246C0B694D0BAD0C6E01AB,BB9E49591CDEA7B38E42C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C2AF47D04C203797F5EC2CC841A3CEC5,58EFB4158198FF36194F6400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5A7F52669369320D3CD5883FA4EA3F2B,6CCC2ECD466D42B7A4305400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C50ED70A1FCF421FE6D3E68BAF7D90A8,DB98CC5763982C52885DEC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,09483A1EED2CF60CCE0DF670E077459C,17C5625B1E4B638831C73800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9E8BE0C3DF99181656D71A665B2F6E78,8930954F0BEE0ECCE8FF7800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2E1A3948DF968DACB8B50C7BE01F7457,1AFC087AF7C3DFB650B5BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DC70952E477B9CC06C507BEE1B1D0DF5,2CD73DFE8A454951F8F5B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,20C31EC84115A792269692DD24318D5E,710E521A571C20908E4FDC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,57EDCE38D0847365DDC3E2476AC3E1F2,9C8BA4C70BFFA8D374427800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2FE14DF7EC89DCFC40F4532DA4CD8FBF,E929007EF952A2CBCAA1E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C891106C395E6FE27CB057717252D1D0,426667B142F5B2C6DA154800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E00C2B6A32C78A39943C5C47F2662676,DFF8A3A85B25DC99325CEC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FEC57F0F123AD7D36B4B6F35703F64F2,0E5DD6F7FDA06422FF0C2C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7EA718A0C7FA4D3C942BEDBFF0E8C27F,0494E06696570FBC07B20800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5202C1B460DF93CFC147F50DF0DB8AD2,909CBBBB1234814F20B94C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A5BEC24FA1715EE2A004AA64ADB891D8,79D301F620BB0AF9F434D800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FE558B21568C8EC4F96ED18492327DD4,20D3E5E9D2FD6C7B33644400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,72CD73EC048C026C289501852295D8AE,A7BE173CBA66C90364D90C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,68359A95139538BDFDA335F9766CB52F,D9153BE65D53CA5E79DFF800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FEED7553983FBBD1D9758393FDD5F1B7,B8CEF24E0140134FCA4E4000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0A07B08315437682AFB159924B0686BD,4CAFF446C895B91BE7A06800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3C326933EE7F409FF467A907DF0036F3,F80FA736C483EA30DF9A7000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,95F16978198BE120A3080E7339C4DC16,61B588B440110F1176018C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4FB88DCEA84BEC2D61D59D26AAB1E5B2,63B99FB3D4828DF14A632400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,99052813B4FE1DE320C9D18250D7EC0A,99B838E84079665C7AE32C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,58B92356701F585C5DEF34A1867E1164,6E4C4F64265374992B89F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D4730E1A155968372A2796C007DB61C2,F24DBC3188928DD7F6DEB400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,31F061BE095DD8F0CB9B12915F677513,432999E45E51F50F160E8000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A8FA54FD77B174B01419DEC483C3BCFD,960214386FC602BD0B634800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B69C34EB57A1E9BAFEA2FC8BDDDACB97,D31496A364D337F21BA43400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E3DEFCCF108C176B427C6D558DAB2257,FD4F744BDFF1BD5408F63400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,634413A6F936B67FB937E47831BA1744,3D2045CEBCE10C5E3945FC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,761FDBAD45F01BB614C990C5DE842A48,7A29FE2AAB86F4F7BC520800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EC9ED9B997C283EDFAD1D86D2B4A7164,058544343376B8BE3F87B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,81627CD5C956F5B71A9C0ED4E521420D,4E313907E89F9F9B55398000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3DCD0B3984C8247B8562EF6237E9545F,17BB7D71DC7324EE7C747400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6F27674CBCD953B4059F3B55F78E8795,14900F7EC6BC6B801CF05400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ABCE668D4532111068E0094A3A100E4D,A18AF444D2BDC12B60B2F400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,280E9EBF56FAAD5DB2C5CF73CE296DEB,20055334D6BD65B69284B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,84608515AAF1D869F9EE47B6EF48EF0C,78BA397766592C4E05A7E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1692DA2B7086E7AA8D3F3035C7107359,87194D864264ABD4CB900400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,07DE064FA13210922ED16199034CA965,2ADF1D09B4ADD164B76D1000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,75EF82FBCC14256470722FADF3776AE5,CD3DDE9AA2529DD7A8C8F800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B99DC3A08EB5341A32F8C62E4BFEE43D,F417AB93249FE05D579B5800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A4183E5E094E733AE11BDD7F6331043C,90655E9480FB2602C4D18000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,28D40D70C58A581E2BA5DB2D8312BA8B,535906A3650D559BBC8DB800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,13B5B284571B20D9EE9799C96BD40FC4,B71466E5B1F11AD6B8AFA400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0029C96AEEAE094A86983794BD582AF6,836AAE69B149DD54877A2000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DBF2EC98F31ABBA2722DBCF89EC6A44E,68343042FC4F198337C3BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,083D0ED162BF1A80574B5906CC1F4AAA,FA3ECFC6779C1AB430E4BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4E1D795034EC45B8F9821D6CA8203BDB,E816931A5308C59819AC2C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4EE4972E7F60987243A9799E63D662A3,B16939018348AF781859E000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,849CF4F89D3233F3E584CE0C129D4BFA,D9C2178D63D98597ADCA8C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,57EDC728BCC2B30DA9B7BED3393C3CF9,CE8A219B97FC38CF8EB29400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9A3E2A0D88F960CBB1808EE7211C6362,B4753C1822EE5832EAC3D800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,032219016ED376CF2D9CD997FB3ED6A6,9D99910AFB22549BFA59CC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6465F4B9C6DBA457BE7EB0580642E4EA,349CEB847C741124EECD5400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EC5EAFC6E0CA5C7811E4DBE5188454D0,FF3B8B9C7E49CAEB00BA7400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FC779DF1C75EC65275971CA4DDA54445,BDF0ACA83F71F5795FF91400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,97167B93AEAC688D65F5CCA5AA540628,A8934C20176826D8E9210C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D6FFDE9C6396DBADD186625777F0EB1C,25D38BAB18E0B1A4561EC400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BBABE99B5DA9F492794D1B33F2E69306,A2F9EB555D1860FCEB90D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,31336FEC645D23BFBDB2F17A7D051ACD,DFE297E427C7EBBB85125800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F4CEA98E6B5D08FFDDEF9E83F02EFF76,0793F349B87CE001C3D24000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,03C233F35A8980F0167C9C01FD381026,1B936C46CFBC6210208A5C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,83937A0CE40B24304B9FD30B2ADE62C8,81449A34B68F541FC0D26800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CF885925FF754C3D2E0F17FEAD4AB540,FB623A02D722E99948756C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B263174AD8926BDBDEEC5E9A9E92424E,4F2D03FD412BB66F8FB45000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3493E0EE360CCA3459BAD5F44C23E804,0C327FCEC6FCD5C9BD410400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1CBE4486B6754A05800548C706835524,A5B423FCA3BC87386CBCEC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,96057C428E150BBCBD9AAA18405BC04A,0683236771BBE74B4A2E1800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F2B319F79573629E56759FF5BAC67303,C89F69CBFCAFB5CB5F5BE400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1DB5E2D4F572F50D2DC637921E46D270,8B09544DFDD6BA2F6D8F8400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8EAB71FD2C9F62866607E317946B8515,7B0C4057E9CF10535B275800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9033CFBD34995F33F7530BF78C207EB2,34D5E0DF065F9CD9373DB800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,55B56528FE385672E006DF3E5F15593C,71D3C14D2EEA2465AD0FC400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,52A4AFF178091E85625C39583A935096,7EAA20BD18083FAE1FED9800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7F3F6A273A8A75EEEA484F270209DE61,695C9E3DE158905DB1B9B400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B607EFEFC1959FA8B32E0979F241CD7F,C3B8B80D01C9390D837DD000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B5D74887A6DC4F652ADC13A10D4CDD9B,19CC8430EDFC5FC268973800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A09F045DD589B5223037DFEE3519A376,E60E7F22341E66A3F3EE1C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F3E0ABB2B1E4F00647F2D5C236A1B26F,7A776258F5C5649E5E201800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C47160A0897A8422C528D7BFC8D20188,D1D32C73E3DCA58B70BB3400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F779D231ADDA823C8F9EE13C5CFF8327,F1D9065561B6D0591BDAF000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BAA3CD7BE838696F2F8F7161D6C8BC07,2F4835836D00DE30A77A2800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,29D8058453F0FB8777E0116247D1624B,D626DCE8C83BBCAEB1E60C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,962F26AB8CC6CFE7004EBB98219F6E46,3114E64D5E558C1CBBCC4400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B4C608BC11A358456FD44A38ABECD461,1EF6FD55FB22D74591F2DC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DBC87B90E5CC736D89A063C8E7EA12C0,69A047C836101660FA0EE800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E2FB2C67E647416BF2A58284677FACC5,6995DFA7822EFFB01826D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6D2FF6CD3C9A5038B9B33755C6DDB410,4A047503796C5AF0A83D7800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3D20566CFBFCBED16DF8D3A0284A85E6,835C3F58AE388B93F74E4400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,88F65F3F649962491D3E0FA045EE23EC,9612855A987ADDA693BCCC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C6ABB80355AD24C621F34965507BDBB3,98603A29610E403253C30C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C37280108B16E450EEED07BA7575FEEC,2B1FC2879493BEAB8BA75C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F637E1EFDD8A8F577040337DE4817A7D,00E125367FED4AAD7277E000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EDE6AF5E598389D9BA050D5527405DE6,904AA4D51E003AD87D170400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AAEAA56781A803C19F7FD64ED2ED2DB8,EFA8298CB630D5D9C3325000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1B7AC6EB7CFE9DB7CBD20B3AC8BE73F2,46DE7FDFC06B8AE2F3A4C000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E6A8681B7B066BA7CBC43139B22C3DDA,BA6F5DCB9895FFD2509C6C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A12237D9124C39E638D56647E6A86452,D25F86768AA3E961C69C0400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,07066B004388D3DFB3B04A64AD83B7F7,7650268E5DDA7BD220CF0800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,55A2BA46DE50A3C16EDF3100416146F0,5FA039D0354C912D161E5000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6109A82102094EA00C2DC1F5D1458EEF,8D84E7F5DF9FBA1102E23C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F37ED30ED80B697C3C71DC8CF41CB486,AFA2019028CBC07C803FF800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,80D00951B03829654F56D8577B2319DD,259B7737DEA5AC6586C9CC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4ADE88B1C1450BD2C04DB298627E794D,11F7BD5CD2F0457A912F5C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6B8CDA8600BB3D20418AC2D6AB2BE0FB,6CB450249C7A80539ECCC400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AF5D6A632867A1AED9B0591504B6C52A,8976868E00E168C2BFCBD000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D7881B90D486C90EFF4389A071039F0D,F652A1C5A41ABBF0AF09BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,31744AC378C3A56E2E599A052133E3A1,990811B8536A91B40FDF3000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DB828FC7E2BA095663B1CAC857B1C882,991E252E53D405FA7584CC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7789790E921E29D8CB10D2E90A6CFAD7,C0615EBDF99A548693DB0400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,631510BEDF7A2CF31DA6E69F9AC78378,6D875FDC78C70F8BCF206000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,41D1BA21CE28CB116716247CD1567A1D,94903B7659431E454569A800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0F98273C34B90E685BD7873383CB3BC7,0C738DC3E630AC6FDAE9C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,75BCBDB5B2E23F46B6366229281E5487,68E6EF3AD2A857847D050C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A2293CC54F3395A790F86AA56A95E357,B2FCBED0F540FB5ECEC9AC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6E92707ADB71C3808BA4E241DF3EDCF2,D45945189B01B92C0ECBCC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4B469FF0DD32AC00928D4770977EA271,C20B5CC8B92FF28E449FD400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A0FC84522EECA927DFA68F9D07C70B0C,451A254F4DEDE634F514D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FE996981505A918727D5BD232B0E157C,646E03E626B160D204C3B000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4F388F8FFBDA93A421167BD44F368647,5F6F12C381E2E75AE300A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0086CFA9CA7E7AFE28C4497FD19AE133,928AEEB2868BEDA13F275000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4359ACB771F941FB8F52513D01CED563,393130BF04B3D914DBCFF800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A19C58E94C9EF7C363A627A72EE4C57C,B1A84094A806E28A43B57800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C165902B95C0CB08ED5DC0FAF3580F2D,6CCB395862241FA3661D6800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,26BC352460FD073FE015A410A7CFA935,61E88484BB88EB782141E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4C3F3A58186A0DB703180BE8E4AD5387,EB8671AD139925539E964400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E9CC86936DF7AECCA2B86736689DF416,ED230A13BF046D8507B77C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2D90CEDC4BB18209C39572AC480BA46A,E011AF24697A323D4CD4D400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D769D834E79706D9D620286FD60B1882,35943940C4DA30A49167C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FDAD1FA63E891B0D3BF81D8629FCEF99,1C2DC750B9127257FC288800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,825721F453B177BFEF0BAE758AF1AD2A,77052E1CE9499B1B7867F400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2F3D588F1FE8E4CA01533D4A0DD9B4DA,141618BC5D4E72E62A9F7400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1B8A0B4D6916EF18BAD7CADCE39D2833,AEF6463AB65B838A5109A800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B32247244EC86BEEFC52D7C2491E9D74,116F80B9090964FD599D3800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3BE4339301C93DED261879486C6A8D90,8E23D0F627E7802D1A3CD800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C903F42E92B38595CE04FD53910EA6A1,7E92DE635B26DC6051E8F400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A24E8A73014E79FA74FBC2CF1CBAC711,0989A5AB4FA9CBD9AD0D3C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2F3F558FA77482A4CC9922AFFC7802BE,281A9A26975E9C768A7E1C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,82910421BEFAACBAEBB5EB41DC83208C,A2D604FA378CF8119DB73800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,53E78DBF2B389F15A2371DCCEE1E1DA2,8ACB8E30CDBF4AF3839FFC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,559E459FD9ECE082963014F6055E0E36,436F9DDD7F7E58599A5ABC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,719BF029031FC8D8650BC2699D5DDD18,A66649CF28F4FCEC3C4E4C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,723B50440DE3F461C4E5BB5BC9CCB5F0,B8686043FB0F1413AA938400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,17D769E91662028370034FEFD2876A25,635E411815C51636EF944400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0C102DA3F7A14B3A85B2A92A31CA4789,0EC89144BABEF0E7095C5C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AADB189813213B480EC68B9678281753,AB74FA76105F492560E98800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5919335F3F34EB77A0D13D8790E78DE2,8B1EAD6A1613CB2D66A6E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6822677E661136749615304BC7FE951A,5C94BA4C4C4C6A1A15AB8800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2CC83DC6F20C2A7CF26E091238784A90,9AFF51719A5284B6CD051400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5729A297D31DCA3C80C1AC10AC176B22,D00E1485CBA3A95D55FE6C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8C93C54A70BC96C53D439450A3FF9807,FEC899718D29F53351630400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,85CDF4DB93CC4C8317EB38D365154735,091A1DD86B98DC9CC88E7C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2ECDC19D1D190EE4E6AEF05FF23EEDE5,5CBE8BB86ADDEF96A8CCB000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0797E696BB2890ED056F4EB0528AE0CA,793D86F92A994DFB06BC1C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C066293DCF36C84E6B3CFE96F94F9C47,5F12425ECEF96D7AD1DE2800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BBF3B6F26357F2D1079ACE9DFDFC5ACF,1BBE191A388CC485ACC73C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B4A0038D686FBA95824576415B778DCC,AC22616B710B0813F74BF400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,23D7856727E8712982B14CF92F15BEA3,C5FBA55A7DE5D2383803B400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,56CA39A1C3CBFD2B69F2655FC24FE648,C483786DD70F46392ECFF400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,06EB61F4D242712C3DF57BEFD0CDEFE1,3F1ACAC22225021770211C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,36C3122A765088E042535DD98BC9ED18,4AFE30D76B2A655E030ECC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DF71036C59630C04C024ACD193606F4E,C69F93BC5CD672FA60646400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0A52F96B6B364526C5A69E166586D25A,CCB7F20B61EBFAF3A43D1400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C945FFBA58D767D0D10D9F782CEE0187,C9843051992470B4CE005400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FDFA26664DEC8A4924CB13A5FC20EE17,6CD559CDDB082F8DA4352800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,12A863FDE1C0EE9B6D16DE221CFCB61A,F78ABDD98DCA48EF73190C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3A5F7A1CB88C2C9956749106395F96F4,34BC4920C153F06D3B7E3400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,604BDD6E1FFA40CF8C6F96DFCDD62DA8,4984353CD8F24EEF1B6D7400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9587947D970D63EBFE4AD39D0E82F512,339EAC3E342374FC2A164C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1A6CD41A12ADD4531D8296CECDB12374,3790B825CC997DA4B7A7D000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D6E632FAEB35738451AD453137E9F3BE,5EE75C98E3F881BE277A8400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DD3FDEFF3C7F70BD04683F6DB3418F41,BD09AC2921A2DB430D86F000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,527D3DC8958F7E48800A4061F1B4C52D,616B6E288F8484D1C5FC3400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,49C88A8C09113B1B20755F2BD890084F,4939E9DE1A45020E61931C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F8028EC02D6E7E00021452C58B0B60C7,E5D8BED53BFDDC5D32820400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,205E4C48735F32916A0E1D3F3A0B6B21,4AD530D94EA8D498044B4800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B8690268A6F86560B3C5359C6BF6FA3A,6A92D7E521805F27C5E67000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5ACCD297E16C479EF11A21BFE70358D7,FCF7BD1B1EA29CF4373EA400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,74B8193542AE198C23C6031F08FF43CA,780DBFC6135777BDF9F79000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CA25D7D6F552A94C5481386366D1A0F5,99547CBD65A09C3CA2985C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DA5C2F5CF869E6D16ED05925CDD78840,4934C178087623626F070C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B1851E619BA5D0A36461F6267B6A9413,90A284CA11E5A5AC86EFC400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,994A371B63FE48129BCB11984A2604D9,5BDF08835EEF4D01CBE41800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0AE26AB49E6158225D94BF39FDB29EBA,83B81B517E7C54D578B5B400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E64B2E37042CB00C5C9624BA2EBB6DB1,F059311F4B7EE022146C8400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FCD24B6C00C5FBAAD39224D54B3FE4D2,C8B1E798A5299804F9F06C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FEA2A9307DBD872BEA7C42B4517FF856,FCDB2B1870F46D34F2BD9000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D6F8D8379339F4B9B6C6C7EA4941880D,B02031965E6AAD7912746800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6C7D5314CD07485E80A9CBC3536E8E3C,F625C41964CFF56A0CB92800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7B1B09636A93D1D6089F3E8C45939ECF,AB411511CC23630BB1068C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,60BC3B66ACCB2FD8FBA2D180C345DDD4,66ADE7E8DD5FD245FE67D000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,080B4DA8017F1C8076BEAEC2ED4C23D7,67219CE095584AB1F3CE0400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,36B9E1DD94CE2C7D5C50B4F4782666D7,837763889C34FFB59C0BF400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7426357487507712D1544AF62DDE03DD,E272A3916D75F94A9D18E800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E3EC5110BF23B8292F7440D236070240,F98D822F01565E3F776C1400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1E12B4E8AF0B527E971E7B0F1912E6AD,8092FAAEF7EF58F4B96CE400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FF63EF8928F00762BD9204F407F224A2,8954E660D5557692BF693400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E6063EE906F69B00C636C2FEAC781869,56EB75B9916E5DE0192A5C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,967DD337ADAE9A73E570CDCA4BDF866E,0A5806A9778A0B3E031BB800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,98D1CA43D4C21509F98120B0D6A1E7DF,4CC4B4C66ABD3A5DA0638400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1D97DF816F20FF7ADD1E41D01138B49F,625867D3F8DC80B255FB2000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E3946EEF6DAFEB57B967823218D603C2,9FBD7D5310778C193D99B400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8E81C1A706E1C873CF8AA371322021D3,C588BF56F7CC23581225BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,20DA7808951EF3A9429114C3462F5747,30DF8359FE34CE5C0374F800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B0E6E1CA7B8C4E025636F5A66D09D29E,D45B3366E8740A43367BC800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,60003F3AD24A644CD2D69EE03C01EF0E,DAE6302331046B94B6741800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A6E7C6E2615E1D7C356737AC52EE514F,FE3A2765EAEAFC0F938D2C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DB1A6886B5DC43DD77A0261E1AFF8B2F,B98EF65CC4BA464524735400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C0A0C0D4B354E14F2ABF41F602630AA1,18A96D6E34F8FD4798DA3800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,62BE756646EB90390EC21E6D21BB587D,10401EE31F4A5BF98EDA6000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7F2B44B17C9EC961778E1255E9B688A1,C272B7FD9ACF088ADA573800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C433B7A9DD753EDE601E7FCA21334158,3B189C41DDFE0D439C0F3400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5D998A557E253D40A32DA7CF97D5A29E,EEC272A2BAD8F690B5F4CC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8D1B821D9FCF2BE276D723A435F9954F,85004EEC5849D04F018DF800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C8A5404B37F54719B284515CFB9B4001,4AB5D01ABC448CF3105C6800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9D3B27999D28125B877274228906EE0D,DA8C14CD5CFD377A8376BC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E5F666FAE14359D65D2F6EB5EA45961E,94AA2EB983B386BCF21B2800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,06F16C45CE7ED4E6201646F0C750D419,1593E4E1BCCEC056923B9C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,82FABF6D7B45CE4C64EB0C6E9DC502E8,F6487E29D164CAC32D86D000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,541F1AC3AFD74DC7C70D6575555AD55D,86D04EC42CAE37F01ACF6000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C597908361D898A0413B7A66354A6065,14AD14240E876852E6226800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,200438F0979DB724420285D43B2179AD,F293B889DABDE43C1BE6B400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,739E0F37032ED78A0913D042A8C3FE4A,8DD8DBC7F999094FD4F0E400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,16A8AD7D1C5631CFE5F4CB9CB9ED9163,6D3F95B914469F0ABBB81800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AC81B47EAB5649E55A9F6E0B93B4BF49,2BA8B6AD2E1E2B4902330C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6B0267728AD6B956D254EAD2B770FB37,C03BE3BC733D51F61B1C7C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,10B1A550E224F719E76D98ACCC2C85D5,7FC28C88085940FB0C555400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E71F7EBF5F6D8804D1C223EB722BB46C,58742C27AD8676AF08918000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4C3F8FDDD8FCE4D20174F6286CDF71F1,7CFD68608E450A2D7D20A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,99F18874776E15A839C2A3E7C321EDB3,58B30EF52C693A4B40BB0000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8AC814B518EE9ED843E35E8EE9C25409,C0FDDF957DE317A4FF92FC00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,17768D68621DA8385EF1348B274F372C,44E1FDBBAB1D9EC1E358C000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AB55D41FCAA07EC8E8C94315F698D793,5A2F087594F2156AF1DE9800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6CB52163A5B7FFE27654B0310B572B6E,98AD77BE4612570C68FD3C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CAC07183EEB34F38E70334AB594FC570,5927855548220C340D283800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,71B14D3013D7651AB586A96FDB2C55C4,CC9BF8358407E57A3B448800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6081353FC28741F50C7B103D6B2907C6,AEB5F267FC32315C39DF4400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6AF53DD62899A5F9336C3A52898EB0CE,9BA4DB7E121A278A11EAD800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2B746DC2022342A402ED432B27F6B243,13305D58C682BFF823F3C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B16EAB6CF2E07F8A3AD61D2DAC6496C6,4B890108A901763F0ABB1000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,20E2CEA085C2CF1B0C4706E4F8CE9196,21EA802F554471BAE0D64400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D1CE68C2764772B1264B93A71E305D3A,297C675B8A2A277866A7F800
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,504B4718D7CD40663A3FA48E87AD07EC,ADAB984B10C1A3D0A14A1000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C964C6996F55F06AE5320FDE2406007E,A023AD3226510F472CCE6000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BB308E478D79556474FA4E4B53FC151D,5CD9540DC7AC8A3566D90C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,29EA1AC88BB26824AE83CF126990FD0A,69E85D6AD0832BEF5629A000
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,873D1B21E6080852EC53F65086D30974,0F17A7DC95E6E47C6ED51400
diff --git a/src/tests/comp128-3vectors b/src/tests/comp128-3vectors
new file mode 100644
index 0000000..e2c7112
--- /dev/null
+++ b/src/tests/comp128-3vectors
@@ -0,0 +1,1024 @@
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,00000000000000000000000000000000,34B4225BF16B96E118A85986
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,00102030405060708090A0B0C0D0E0F0,A892A8EFD6D33E3650372F78
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,F699F0BABA87114F0350BD8B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,000102030405060708090A0B0C0D0E0F,A5B4C7CA0514C4E1B25CBFED
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C2FD8EB45A807D375CEC72D3599C1E04,F123A4B0D526DA2AEB199FCC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,059BD4242157FEE159683C03B1530CF1,8CD58D5C05D24653C730F1B4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6A00543B7F4AA6F5938E3915F1A04F61,9CB62587973B8079FFDDAA68
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B022CB9F45B6F2E05986825EB60ED3A2,DD465462764C701D8E07230B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9C36DFE0444A53EA8D64666EEF095829,68DB77768CFA36F5D9584C2F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,65EB5166053C36EBE4149CA8E032F85B,8236AA79048BF9FD23F22CC4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FE7F96A0CA6743F4FB43725B65C98CEF,C3D19C91C0BE1A8A80866991
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CA93097AC1F7FAD6AB300291D7378320,47D77CC1AAAFF848FD02EBB6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7ECCE6BCB6A960BE9F347FA4BAFF9806,3F9CE85AFB1B11B747E41A1C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,537CECFE605EF84CEC6AACC26748D8DB,23AD4EFE3BBD5437ACD58FEC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,77DA6AAB1D63954F7EB121F69939F087,3782AA51C410DF352D5BA4C6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6E6989BE6CEE7154543770AE80B1EF0D,E3EA331104D11B0340EF41C5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FA3C9311948C43A88A81F9F151218628,DCAA5F8ABAE6FD1FE3D994A9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CBAB8FBD3E6268579024BBE465A085AB,7A08AD916948AC88F285F679
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D9E9545BBAB220B79C11B4268E4AF22E,76F8883C06A19A0F3AA0FD70
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1F0260A9AE9FEE9D4463888C7E2B58DB,08EE26FD13A86CD1AAD5B368
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9A3451F2C657E4BEDC052AC7E42A731E,A8279A6015E5935099372857
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F1B858F9D05C34D05BD5D0483B037071,F31AD0315CC5E86B76B97ECD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,67E29E6639A94DE8CF774F21D5FCD211,6ACFB2184DD373148D5D582C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,66287B28DA9E5A4A3674842A416580E3,0EAEDE610EB9744B2B95913A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,06CB6DE4CF812D00C494C2CC088D7FA8,D41C2FB8F696C84D882DCF6E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,06A7F31DE8BC76B51A3D43968930B60C,9CEA8DC24E04224753B9D218
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,29D36234576AD8395163D1CC9826167C,16D467073F5958EA424A09F5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0A41667DE9BA1AC860500C4ECB359EE3,22AC7CEFE26F3A4CB98E4B23
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,15999866101B177430FE614EB17E76D9,A1332C1FCE97402B4A743D8F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C02AB81126B1990F77F810B82B797475,3CA34AC67EAAB7048DEC15B3
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,47061308D6D2A8B4DAEE2AC7A44D6842,0282A3DF9C9A981A22572231
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8BB120BF99F7F51DBF66B7CBEF239874,3B915FC6905D571E0244A15B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2BA369767520B8C1E2CA69166546D1CF,D9FA973CBA46C79E08993ED8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E867952D2484B31C4DE74554B8CFD8DA,D2C4A14D3FF5550E776E783C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,51A70B24DB7DFC4DABC254C6CD9BA81D,FF770276E357757BF17B6A03
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D79459E2FE6E8E17510C4649E5EBBD43,DD587C63DA11D7A39B0E51AB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,992511517261548767BC3A5EA0269459,3123ADD2EBF5C4D7B3F4197C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,38112A87B58DB889B06F70A35B2BB039,EA4365FEED09E1A34304A763
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4DC4F985E69146A65C0A853EF5C307D6,57B9BE9FB46D951171CF1DBB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F08DFB812F799107F8A817C6A93504B5,5F67F38F8F729A1A34468A50
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2E7AD4225A84CAF9662B79D8AA4EC60B,9CE2261C86FEC23836F8C25C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,904503A6A4B61660F24330E74C49183C,8D434E4F0B2518ABAF9C92FF
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FC0B25783A74FE16F97AF6EEC84799C7,E4869E47D43D385ADD6FED7B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B351F09F9FC46188103A266FA50BC54F,DA4E687A478A4C3EB77EA5EA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,453DE3EDBBC72BC9C65335B47536EB38,2CC578A40472AD4C336D3880
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,250133698D0068AAD95A2E0FA356FE47,F7BDAD3677641DC625C40A3A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,482A117B4A39F9DD842E67F376E4AC8A,C7C280868DFE252421111382
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B7B435E33F800DEC7881A5AA6DC7795B,68ECFEF6FBCE86F0145B2710
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5528C93204329236879A8D0EB66EEDF6,5A3B116AD3A228DF0C148E35
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2BD00A4ABA3837E76379B9C0257CE95B,DBAE1F2F91E090C7F250A576
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,897FAEE5AD4A10D443E1E581E1FADD2A,5E0FAC279B3DB617F1D79E74
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,50C13D10436296F6511294F0659346B4,6774D9611D81D2B28EF62CB8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,42F5D66DDC05442E1B394FC346AFB794,AE323C87CC24BB8816555868
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,23978478FFFB8DFFA9160898AA04E67B,F9E28ACDC3FEF9C732506146
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0C736BC43FA36DDE8B66F9E5F5F38468,836E479A4C3F9060A5048129
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3D0F54C3DF060F76A908CE871B5A15CA,2C8672011E95771FB6A01EC8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F300DF8463DCCB4D8222A7021B005E92,19243A7269EB1F75402A01DC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7177B3EC6658F41C7245E773138A0F8E,3790D6633E9FCB095F1D823B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1A2FE0B12CB713888781DCE0E90B4776,CB65E9845237344F55D7AE1A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1BCB63AF37AC2DF53B077E413B588001,00DB17E0FE288237A333C4A9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F0A650FE37B236732D4B889CA6A6C835,7FC46A51EC2B7BE0C66E1A6F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,68B09A32A5459C6F6E5472109FC164E1,C8F1AF34F21E61D9CD2E6839
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1B57DD3CCEFE0F33E04EF164B25A124E,A8486968444DAFB51C552932
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3CA34C720BEB4E2FEFEA6D04BA4FD876,2B1C5B57D1249DACE613F90D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CAF61E2E3773AC8891E12638871595C9,4E03646AB273D7835417418A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B1433F3F651E0E478A12AD2163681E48,0204AEAE7AA03ED2B87985A6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,907DB9ADC521BEAA511AE36FE1A8084F,96E8BC8BFA3054AE764A8BC2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BBD2E95BE518C72529FC67918EF9AC4C,9461716327C1CA6ECD8E3DD5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5EFA59CB1257BCEBD7C52842485B1A7C,FA69B871271C6D3BBE9C2C0F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1B170AF028368F19F26CBEE3EA04B69C,9801411E3847223CF3F6F5DF
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8A03489DB2DA7957AC574307457C539F,18265685F3F9FDE17DF568E9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9DE46F89BE1AEEE7E08E6E6C96136B4B,948500624AA36F84F8A9144D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3502F3348EF421C546A22C60F73D82BB,3EB807C5B084B783A6ABEAA4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,266F0F71417652FDA262E209ABA9813A,1AD344CCB780727F29F539A2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F89ED91B773DB362E3B535ADE806309E,6154B2555DAA2536747C8C00
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,95898AC8F59AE30AE6C131F433609984,F4231FEE2955D325C6358E8E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FCF177BED3855914164948383A7BFCD3,B013BDAFD7B01F6D12635E71
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A41287144BE0462B64F3BF9FC79AFA28,11FF56DE220314FC01D2F427
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C37A6607EB0CC90AAE0E805987BA2BFF,96AA9F8F1B82C816479F6F30
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B30688EDF5883129A3EEE07EFBD716CF,2484F97D0E207448CFFCCABC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F49EC19D30C40E62ED346BB1E973B3C5,7F3DB7AD4F1E679D1C1BA290
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AA60565959700AFA4E24383A7852F98E,40CA139963F8B07D82F93631
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,72673BD9D3ECF3BC9948B6F96D44000B,A29BDBCBAA7C7DFB9ADF7DDB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F764D588E2AF04E183D9DC2008E54159,892163F30BD023B629FEEBFC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6225365E542C73635002C5D0F49D79E8,9ABE4CC8526046189A15E8DE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,578FCABE430EB1F154DB178035C87340,A08DCD277DAFEDDE2CF34B7F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,90D2277F8E73308C9FF488C22C64A371,BDC38FC4489A714E45D465AD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CCF9D01093489E9322A92CC5B45EDAF4,524D9D6323724755C3A58301
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DC7AA07884338A1CA74A149C920B7113,804DF3B4AF8F2EC1EFD23E30
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B49C7B29E5DC67EE200FC77742DDEE5E,5A6B21FD6EF3D215C82D7033
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,943410D203597F271EB914B045471F9A,243BAB53856F7401082F722B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A66F889E48C971C19BB1ACE8BA14C7D1,53BC19A4C99196271B6DDCDC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2C1987A822D55252A53F4C5165A9A6E0,695CA9F5FE4FB59944E42198
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6E2A72A29D3E40CF17720A2EB2729F54,277189F03E8DCE092840239B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5756340ED21CCF06BD9CAB1B1B6B1F83,F514DEB77B49396051473F97
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8F4D4E3BD46C79E859EA6C4B1707115A,FA8DFF24FDDC28232DC7A86F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6133F53D525646BAEE9303FD23A6A957,4A1797DFA2F0382CE2C8A691
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E4079E18FBB564363AD12F51E06CE783,367F320EA19755B931A57645
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7DE5A96E6249F511894B554941711A59,B9D48BA0539A31A9532462A8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AFD50CEBA82EC1FD6EE45FF2ABE23BBE,3A1327E46668F8A9C4F703B0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0106CFA6F81EAD4369327F7064B87D16,3F432307ED0F3C057C5F2712
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CE3DB3EC969DAA637C83C99083F1F8A0,7CED22DC3DC07E253C978C97
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C3392F85F96A21B777F33EFD11A832A9,247BF07ADD6941713F40A327
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5A815EC556143C56EA7CB787305F8EC8,BFF611BF4369FB38215FAF6A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,49ABB28EBD9106B5F204CC1AA2EEAE97,0A9E54DD9C485068CCFD153F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DEACE7DEE61E242671C0EF0E441C1B6B,B6FD390C7404DAD7D4359416
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9A327503C873F7679DBC3271382093C7,189B498F1D693458BD79489C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6546D5569FDAA7E85217D87DC34C7E0C,16FE55376BD68D78B6161E58
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0B695FEA192283985799C9CBD614C37A,8D71BFD1C8A1559770C77689
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,11E2A6E032B79DC02CCA50CDF257FEF1,C8E97ED1703AE1CA0CFD14B4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,15C788FB019580F2EAEC04B3F074D96F,F83FCEA965FE9D0A141EE058
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,24463A9F5279C2F684EB2F17B9C0B793,10E3C959293B21D9B4293BBC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A5C0D212FD189818BED2B980CD771710,21777B0F52239D696F7BEE2F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6B38A49118E0A121DF29B8781E71C89F,F922FE3B5C9A2826818B51E0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,100F07B6881EA8482EEAB29E695630D2,A70F52D10C35A99831518DD0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5478D20CB06C9D9EE3E5B159BAE6EFBC,CBA83BF92463F9B14A6882AA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2B1EAEED654AE3FDDC35BAA926D45B0F,B2AE2D718F554D6855936C83
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,827512AB9C1C73CCB8AE1A292233C563,0B512886B3CED2AF94A54153
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,312063D0754C824DA712B64D2A6B33A2,8C3C7D5EAE0CC1BC2141AEF1
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C41B044B098F5D6773229480C84D66EF,36A53378F71AA1014DDDE622
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,06CA7A392F7FB360BE218ABE86020D80,0D7C440E7D4D7C36077390E9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E8F4DAFA1CF388ED27662B6F234BF3B2,D755DE9D6A83D78A02EEBD2D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7451425964131EA8FDEEDC3D51ECCF9E,4FAB527C28B1F4BDB34158FC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BA511CCBC0A07FFC45233241E1224340,13683A5A03D2A1927F0ACDD8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8CB04EA4DB020D5D4EE3812B38AB84CB,26648E563AB2075E2B5C7281
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,04A2631D325386F77579CDEAA967C37A,C1DD35C987CA7918F4044197
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A5BE4CC4A516147408183FDEE9DA1D65,696A0827A2FFA3C73A10404C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,36C43B5BCE7BECE6FB2DE03F4BE709E7,FFD099DE6A2C089BED7D35D2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,35EA9D9C382CB460467F8B8D1145917F,40D9401342FE480A8A009E2E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E00D2EB361732C08E2C3125DB7D2E3F2,491710E39159DBC5ADE0DBAB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F587457CAEF8E5BAFBC129641BF8EC69,CE15ACA01CD0077FDAA76A87
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BFE148C7255EAADCE3753A7CB3E1FDB2,46BF49FEB914AB4ED667E4E0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A8452A0CF7A80F80C4D4EB5C7E7969DC,C04AB5C52A9B1C5964B0B86B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BF289490E673C0B56DB6E768B3CC0A48,856BD7044AF5997C29FC5138
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1DA9BF8EABE527222D4B7301F5F6E14A,3D61A9F9DB5C452DD7C26F95
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,036DBC1FC7537E77FA4CBDF33DFF927F,44CC56A1DC3A5408286C3EBF
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A26A8816BC2735427354CD88742487B6,BBCCC740C5571A4653805B58
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FB57CEE47FB84A4CF354FFB02BD7F27E,CC74DB56925160F24B998789
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6B7D14B36BEFA7AFD23BED5C89274817,7FE28DAB2C64E290FE8D8CD1
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,783B2B4B5D240551B0D1C5073C86EAE3,BF879E67B031772A214137FC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A5C5D0E238A3C63E36ABE8CBB8D3A755,C637492282DDADF2344C5F12
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F60AFBED2788482DCE9012B308F91018,A6D07EDAD84C22F55D578795
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FF29BAAE83F0D4BDAEDF57439B240E2E,C88A6075AE55B48C41413694
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BD2EE48803F736F741F7BC539143B6B7,6AF648295D9D5BB30994F0DF
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8759E7150969DE74EA60E6DC6B0605FE,DCECE445325ADB93295C208C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E8A3873E185A8130171809B1C670E466,A52DC25C7BA9A8B5D35A3447
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CFE9F56C387AF2EECB6C16BD73100E84,9F0AAF87BBE52A0D82B0F79C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,504D313E64F204B950E1FCBA766E6A10,2A6E346A487840A9FDC91C91
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BB973244B0AB827B73463C26EABEDFF4,CAA52660E1D03128283903BB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,099D40CC50BECB4943D5417116A02A43,184B3CD556245A75AC3B2E95
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6F936FB2AEF98A81E78E4D190DF9D876,591FF4491319AE943B7E3F5A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ED889EE0177981FB3054558A3D93F5D9,C3A3682BE186AC971EAE5B96
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D610FF2C9B5E26A58C3852B1CF968675,CAA5295895B7A4167F1FAE69
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,60608D5017ADBEC3DCEC4F36DC2CCADB,3850062836D6D9C342A5754F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1607142E96C20A6D55887D473E1C1ABA,57B3B068E892BCC25B81BAD0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0173C6DB9751FC3246158C2F1082842F,D24D9DE4DFF90F1862A202AB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,15A4DFEECD687B26B2398404FE651E26,124DB27D43E78C9E1F22329B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8C04FCE7248101CA8434190F8F3F18E7,DEDA9836E027014878678B27
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A1F308C74C95344CDA356907739190DC,34E78710601E8D992F9AF14F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6CB50D780B92072B37F757ED6E63B0C6,A2AA5D1A550999067BB69BA3
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5E2BFEC3FEE9ACE4CBF6B5441346F5DA,18617E4E20754E060463F495
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B28D0F02AFCB8CC20235898B355CF3A1,E1A687503828B8AF3C8EC33A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E7A8045111133E9BD905248B3E3D9E8A,98B3E202A3A5A3FA76358768
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CD33375D0823C4C5B6CEB0F6B4E39F8D,92FB4EB73E56C32476604CDB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4D8318B9642BAE26C791643DF0F53080,BE8371F5FB7CEEA806AA292C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9DABE054F9D33BCE9795748681EE640C,CDEBCCD1A2CA52EE3907F9AB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,16AE48F67D14618AD12F93B7D5E3A74B,29D03897577684C1CC2C1A60
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1D544B9110BAE6EBB3FB9BF625DC725D,4309E6F048FF37D9D21E7EDC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,563E947E114D9A470A52F05745B59B58,9EAE363902AD89A2EA744C7B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0804E1F159B228C2B549965B0628B600,7874B39866D29960E6A4EA9E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AED48B489D0D2C9E81AC8D5E6A15084A,45FAA83D1F868D1E1509A444
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FACBAF33A3409556CD177817673BBE7E,396965C351C7668CD26E734C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A9919EFE9BEEC2374E74FCA514442152,9116234214E63C5C488B384B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,870EDD564454CB858A51E67D360D217E,6A159AF76162DABBD6D0DF63
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E66FBF9271A50B84A5D7AB53E7373176,30ACE630BED634AF6D1DD338
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1B561ED7F3EEC916D48BE068611A49FB,47AE71282F48B4F5892F49F7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,400472731C0366CF0BE540DBFA745BD3,12EFAB1FA8A873483C53F0CA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DAE6DFAD532BABC40BB959C970D8814B,40ECC9C908605D2AD7C04044
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,61F1FDE5DA854053A07508662FD5198B,56DE93DEBF9302DA18649DE2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BFF8B0D12F533A3E3C9C46BB8B73D4E3,8DB85E1EC6AF6F2720F44165
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,172A3E21168D80ECFAD1E4E122C0B1A9,60CD03545AA93CF3EE8B9A5E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F304D41493FEC89AECE06C355269D24F,BDA5728AF5174BFE8A1DE7A6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7223C3B61097610DB9D86BE0E5C7425A,EF3B366F60DB819EA4CE3CC8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,73D79A145D0F20AACCFCE879398A14D9,6B90D4161C02C37583E93EC4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D2A3297B864EA9FABB396211FB8D7DC5,43BC011FDDB44128F061841D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FBF84C03307C92DBF77E819748CA693A,562EFBF4DB6078A949AFF824
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,70D8437A9120D5F1EECC400CB8F009F7,A3585AC211D316CA9BAF8349
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AD70A2CFA9289909520AC3E442066E95,4DE9364411A3D22E18E05EA9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,200DF7FF1CA3C90B9CE7BA3E1622C243,87C54F689FAF75861A864E36
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3558987AA0E60869CCD2AAB41000171D,F88E5005DDA6A32A86B46323
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,76C03334BEECF8488345EFE09AFEBA8F,7A8B21E14808D8FDE12DEC82
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6C34C7E0F992FFD107CB3C21AFD8FCF2,7223C34459BE33199E397C97
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F567B76CC9075EADFD5B1F5BB995DAD8,3A4D7CAE9189475274EB3A26
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FE553460124B7240AD5927F199968901,66628D72D37687E345B5B80A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,20A361BE827F77F8752324D0DF0191B7,7BF2B29BD955EE9DF8A0A462
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,89E668A5719753E733E483449D26FC35,59E64E6FFA910A75BA6BBA60
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EC23E756AD6C5A8EB93FE43FD1743407,296AD8FCB4BE13EB61403D22
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4C78D75FAC41A829171137A924C008FD,B5B12D7F28E1C3CF0BBA182B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1582B13A55A441793F24364F4E79D70A,4FD6AA11A62B3DAEE7AD9B01
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F83F6CF2845E6049268A98937165CF46,90C14A957BDC4877443E00E8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,89A39A6F6D0EFC34FB44B9D94E1FF998,B42B60ABBBC80A57030893E0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,506069010FBBD8FB9B621E1E2499CFDB,E143B413238339E5F1691BA7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F05CDF287428D1F8A82F8D2B90AD2D45,97F6CA366943F4B6CE9FF71B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,250C4E224C7EEED75B5C47E9B281F038,22468D1F2D1F8BF1D39859FA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D8C1F4CEDF1F26B67CE6AED5CF27346B,652F8F8149F1819CA8C863CA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,821DCF113144A74A0696107A511BDA50,3D8BB5E6B9C0F05E46D96D75
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,21BB340C501240AC021FFAE616ADA466,876909CD1586AC2DF5FC7AE6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F877A1C9FD4DA1F3E375314677BD07CD,F373090EBB599E4E9EF9F429
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,26845DAB23924FD243CE4BA118AFC18F,039F20C260E2A343D36182FF
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0C74932F7693F4D4F7E0418E41879F5B,B7C468AD4CD5728DC6565CFA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9AF335A2CC31464188BD38CF75937FA2,1D1A99E64812A75CEF4596A9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A9F5E25ECAA22CC54A2637CB8DC01FFF,A756214C3895B523FB63A378
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,42FB25801DC657140C86C1C7F18F2F01,590D9B13514090C19B0000BB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CFA06688034DC8F1A474C5CB230FF52B,5937F4916B9D698E3FABE3DE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C711B7789802A2E87BD9C892ADF37666,B885298A5DBA44735E11AC17
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0754A2EB8464B56CD205F6956C88869D,CE314473BF4B1E1B6C7BF179
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4EF0E8BE04D929C3AF6BF71852A72A30,E14518550A295F9866046EFE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E845124E430E41B9F7AAD910F93DC6C8,B478A5A1128ABBE224610BC3
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7FB474511BD214140C90B521798743A9,E8BE27301A6EF0E481A68DDC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C727D9EFB0A70769934515C95015E3FD,C33D3FBA7B6B71F7F5B1E18E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,64ED08124194B3590CAF7263A77E688E,58FB6C2A5678A5FE49C6DF04
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3384D1E460CDBA2E9306042D35C2DAF8,53249E60E0137005D6C0582F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4E4E2DAA21826A569BEB3E89C3F75954,0B51B269D2EF17CC2AC97F8C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7783A5B5BBCCF29C2AD09070882EDDAA,F0365CC0D5656AB7C9EEC1D0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,362BC8026D00F70FAC9406ED186655D4,261C2E9B772917B953719AFC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DA0E8368EEE2F2946AB1D284CFB58D75,2AFF64A0A7796A645F8F6569
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1C3F461143F2E728E463F6AA04835328,98F8FB6C03A8F04E5DA6BD80
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1BFD760F78B82698E764E2F009E83A10,ADB4E618176A154B4077D28B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BD4833A790171E25A3FBB40DFCA805CD,BCA4151338D53087CE12FEA2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,012135788F5DE698BA7865313D838D1F,B6E7203A49F125E0EF781021
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CE3713587AD50A75828DBD3B27C4227E,F1E1EC13C4DD8F300801DFA0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8CA1F5693F9485DA47665289F9B7994C,820A5683642DC787C4E86A46
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,412A89798D2ACDFFE18E5C1E4D7316A7,D93057561BBC893843682759
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,00CD5CE9424EC374FC60FB76B70EECBF,88A74D19F682E1A255932C53
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,038994C83ADEB2F74D8C8839F53437F0,A3F15A3800700FBF4036A9B6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5378C9DF92AAF716A3E3479FC93F9B6B,A1FA256354C249210CE3F4A6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3201667073288CAACF46F3BEBAE0367A,345D24DCF43872214332792E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7C88491C3A4AF5934C112299476F96F2,DDA8299BAFAE19127326C0E4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A09EA9002485129676578DBC2D29970E,19E2856AA1822D8C382BFCE6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0249D8409D94C899CB3211101F44E706,34F666DF84C3F4861F031B70
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,393735BBB4F295F78875118F2282FEFD,1172696A8F3AD17FF4CDC32D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,880A34A01DB11B02983984409F474598,78E946DE142861000EA71378
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8D2D885D4E8D0FEDF57329B9CA684481,5C839ED7586932AF9267D4EE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D9A319465292440E4EC40AD4311412BD,34C4833EAB737BB515DC3E29
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,03928DA8EE9E6EF58F8BDD25383BD746,FBD0B65369592420F4AC13E7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,871B76BCCE398DA305C05583328F3D0B,10070DBD7C735D468E99FEFC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2D8329DFF082425E4865ED82BEB77D2B,6891E87151FABC787888D502
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1AD6B061F1E920431D52B7C4D7A6814A,1ABA71E6777298078DC34E9A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6A3113748345C8D03408EC03C67AA757,C073219B699FF4EFE6BD7F20
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E78DEA79071AE8EBB147A5BA7530F6C2,7F90BBB9710C26C21B7B306D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,10A9BD1DF9BC3D57CE9878D0AFA3171C,8BA97E65B5C543E81A0968E9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,691DA2346C6D0FDB814C07F9A243DF31,14331E4947AD140A7C1A6ECB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CE6961F7424C872568BB6FFA0D7C00EA,05543EA7314FB4A0012243D9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BEF85F199B780AEC2D7E75FECD14F54D,DBED42B3388D1DCB8FCCE224
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B08AF472657A3742598FB1BC3E3DC702,776AE1440D3ECC1771317076
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1F585700F880CE42F2C10AA2B2D9F743,CB2B3444973841C5300DC71B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,25E7C4BF79991617715EA342FDD16E9F,A4360B1247A7112B4525966F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EE3964F72F1F85062AF2AE995B43A511,672886691D327820EA6639D6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0F4DA216B41A2ED1777CBA97B4674B1B,B4A8EA97BED0613AF1C51AB8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,66A36F7EC04C45F3948403E381E3D5AA,9782454E51F13175CE23C4C6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E88D31F0EB83D24F7D26371129763FBC,18DB2B166C64F39868EE8D97
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8C13DE138071D1F51FCB8D6FE388E038,662291FFC942AF9D67AECE01
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F05DCF03504184FDA2616467DF5795A0,BD52678F949A9FB2B389E5E2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4F4F788BEC2EE583ACF059B0ADE014CC,747EAF9274A438F5AC993B1A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C37A8A7C38765AC31CDA16786D732F1E,D3411AE9A8133D3577EB52E0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E4B2DC6D7322CC108231C199330860B1,0B8183CDB4774373E772B3AB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,642BA8B13B3A34684C6D526A5FD2D752,902123CAFC2DCF346D63151B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EDB994505213703314C42A325D55A40C,83D68942220B5A33CD61FB1F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4D1399046DA2D653405F1F1940E875C8,4750475855BE7402E5D1C5C1
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5C435B4BC5E2F9651ECE8EE13D6E25A2,D8876ABEFACB10BC3AC52E34
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8D9A5A7C9CBA7A07DCFAEA0E4DAB2725,C58EB014A932C3116E65DE5D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,09A2B5AECF349CB2D56B7A39872E4163,C1FC34A7A8D16531914737B4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,28EC2B7795CD5FD29D0D88A33D0E107C,C4254A6DC983AC85BFDCC948
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3BD04EB44E9025675CEEA1EE80A4EFD6,CD3F1B6EA3FDD0DE0EC87DC2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B8FE9194D8EDD327E9D5EC30E09CE619,C2D257CA005B12DAB1C57E99
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9FF0A2CE81526677BECE7093E03F40B7,F639DA208C14F5D5E9B9155B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D8523C03A2DE75F6421D0BC91F02359C,4E3BD1349917A5406A619CF1
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,860466FE29D625755A732EF72620BF12,DB53D349D3D90BFD6C7B5097
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,96DCD22792D5381A5B33392650D8FE83,64C9D371D87E92B9BAFF2BFD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4482BCC96F8D8E5FD6EE2A3A302316C2,15D4F790CE455848506F6917
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A53C321889AD33DE74846729AD0482B5,6819059E37B8BCB2CD3BB056
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CB6FBE7ACE4F1F336B7535F43CE3FCE3,36FEB6CC91753307137675E5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,32323174AE66387A960E21E3E48A36C2,7941EB9CFAAF2FCF2D9D4F9D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A2F01BB704E0A56C51D985BC170C2516,2DBFDD6D224B73B8E3BA0F93
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B2FD8D3C4135A5F53C9E9CB2E857C3B0,783971383699EAAB8A969788
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FBFE997F54C815A9769C576DC5557918,A6EFB070C41375FA9077AD0B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A04DE27D214F2BAE0AED7427D2D57228,74A3F90EF7E7E5F9535ACB23
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6E47EF035F98F3D57BC9C1CB574D6799,FCF439F7297B000610E5BC95
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9F7C7310B4AE3FBEA22077FE53884CD4,D2D20CA60E5D657DF4AC1A57
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2B5456E3EDE3A798519D70DA24FDC14E,8786A30EB8DFDE39EF411441
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3EB8F29B8FFCE34CFE924A4C8B412BEB,FBF694C68239868881135AB5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9112F7C42E8F86ADC612FFEF62BCF031,A9A0EC605E0B7D959D94F3C9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3B9E8C729F6BD2A647103C87B50084FF,C7C6B0CA4CF9067D70D50450
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,721720697921EEF72389D70412DC9BD2,2E3A7C1632E8038E1DF4710D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F4B192A4752141D528FDFE57403D1D29,7EC20B5FFD6D4F380EED6885
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,96ABB2AD544F3B40B2302317A00AE6C9,3AACD18243723B98834DE29E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,475BC853DB0993F15E4DDA017849EAA3,E356B15F48DE8C2A841CF3DF
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DB6E877FDF36CD13D291111A2A52706A,20685B9CA28B27D1B267808B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AF844AFF605D6CA75D32C8D76911FA5F,05FAD3B3B7DF158D73F68D46
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B7BD6D23A35ADAA7A24698768989CCBB,2D4B6CE203755ED6DE54D1BD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F22358724D39699DC5F3CE252E5CB388,1362F31DED7766967CDF75FD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8DEED8937A8635D058E1D3B2937C1F14,FED61D4167F6721F3D42A96A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C8F471A85DF24F8694B66679B8F3AA05,0C53F26A0AA68A554F02BFDF
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FBFFD9900A7DEFA9FFAAAC445CEB9FC8,3A77E657D27C1AB33CC527EF
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,57D09B46F380628DC5529A22E6CBB191,FF53F69D116A083581928053
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2EC3BC236DABD021D37F1777AADD9678,AC7EB38011974A02ABEAB3F9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B79834352EA64EB93D000257045EE658,F9EAA0D8DC11FF29D6B516E9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,655B9375AC8864762424A4A0D47B7BD3,4646C2ED5F21B2EE15379E4D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6E022D6981D611DB9C6E1D71BC5F3829,E9421D6DA7103E98C42BC348
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E82BC59288987CC4900809B3F93A258C,765C26D3449490E464D3A68E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D88E96E65B3E75EBEC71A167FC745022,9CE53769A506228A186ADB5D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DE5DEBB9E0A8676C9AFD507CC4584B13,4011F610990EEA93D7CE122A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4285D4CFB1769D1A24E50EA2E22E9897,981AF6169C7B6C2F9D35EBBD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,662AA01C92C099B9F1E6202E196893FC,A18AC40821D530216226AF89
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,02AC385F2414C82B3471B647689AB849,E94F1A9A2178C9FF103B0844
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0F51172514E1CD57F69EF867599C3DC8,3982621C739ED323AAAA7C37
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F3525F3F535F4E6DF13BECC1D8FB1455,F62AFDBE83135B71413D79D3
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F92792BC56E1E01FF0E02D970FAFFC4E,7BA8768FC9DE99379058D8F7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2B52F1DCB4966196D138C68DD54E5919,E461FD2030A5BF5D6EE94D1D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2A185CE97AFEBF1571FE5D75D3BD575C,97C86A5C1573579F9C21F2A3
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B41B160321225CE25C4E33E040056747,F571B0467D59F5B9CCF118D6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F670778BBA33B3995EABDF9CE23E1F2F,7EC8E7AEB2F5F754DBF3E2C8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9F01509B4208F00FE9AC5F302E913FD0,57EC36681223C3B6D4926342
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FA147D70166C6DDE3DF84175679D182B,10BBA85D4FEAD1DD02D2A997
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CC0F7F90199AA8DC130452DD284E1472,1A02ED61BFF438D4C5CA0141
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,76ED3C6965EDE96C8D4E1E8ECD9F56DC,6F3A549C817F60E6DAA3EDC9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4B337D2FDC0E5FE89A8E552DC45CD4C5,ECED242B4B6A1D78DE53A425
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C2E1E0773A8030D18A18B9D272425875,BA7CB02898761CDC4873BFD3
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A0B7D58F803E71297F445D449F7D692C,7D106558576E6F817F16B949
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F9E967729FA158B2BBADB67F03F24077,6909F02053F635627288732B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E11EE60D5050CE4C9D0B03410C5ECAE6,71C3F54E1E05B0F7DAD5C400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,02F33A269A8DC9F28CA3C370A15E8844,F1947CDB239461CA161485F5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3EAB022CAE7D0FF5171CCF46470D2B1A,EC68B69C5D6B56DB8009520A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D8D4A183A00EE3AE6342CD895CC722FE,1AA84577A5A893B64CE59077
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8C4AFE74025F7228BEC7F0F338143284,7D0F91326ED356A09110688B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0A42C179A055958B374DDB41CCB29EEE,0D2943530809487742DFA541
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D7ED82A3EE21516F5B5E9AC6F7C461A5,6366910A49AAAEA42C9030D7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,86D87077BC84F98E3937097EEAB02ED5,2A9ABE810CF32B7A58CA3F3C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,880C564BBCEFC7442CE047715FF7D734,0CFCFAC0132393C12C1604E3
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DC31B4989AD996F310386615015ACE5E,5CC175EAB000EA51A1FFE224
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,392104223345E1D18364CBEA4BC3BFD3,E264A4072A07CD9B6680274E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5F7D8CD69434C38C21342F0AA1BA95B2,A6054BD785E94219C6B13676
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0583E23F330357A0ED6C19F4B1170687,D4420333B9B73E98C9CDBEBD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BEBA72E43E8B849A8AB7DE5E384F4FFF,5C8E69DDEDACC0A2E2C53C49
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E924217A8D6DC3349F1227E479B75ED3,EE4F62ED04137D5789593131
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D816518AD87D4EFCE7596BF5CB44F4E7,05064CA57A799CC2BE53F4DA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,915D72694C6A437D7B0F426F09E052D6,D424688D264C9C6C4DF27328
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A670F35E061E9939F3BCDC59FE3713BD,6D01F2C7E14F078D905D0CC5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5C4F891E7FA9033123808EA080057854,CC073D07C363B207B01E7A92
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,18179F59C553186751D81E19B525844B,5A85AB9293AA28FF5FAB717E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C2B28E2A5F646D7C1D8CD149FC5CFCBC,C92B159FBE27D67E60BC4979
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B4359F02926FB5CFE5CA5A8D1CA4DD9A,3F3BE16228D0E16613464E71
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DEE387A6D3DE2596552C22CC63DE4C51,0C00FAC805524991C3279C9D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D51C0E2C76902EF329A51E1F0D7BCCD2,2B22C1C141C8048A35731F8F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D5E486BEEB5F24C1DA0E8845A9B7A814,EFC378D93A3973CC122E8D4B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5854C608946E292D53C10BB4217FFCFE,1C9F8CC7D50860B4B3A0D942
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B7AF5AC0E19E193F7D8E6F39A574735B,548B1F53379D01046A6A56CC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,34E91F46730F97BB9FB5EB234FD1F8EA,63AA742D508678B048EA9C39
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,341BA1C17F4B3404873D4FAA11360CFE,72FB0649FA0BF6BB586BD4D8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9E3FB44A03A77C0CE5A61A6A1401D171,6CC44D57F3A12BF4EFD1A8EE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6F736586A0EEF3449DF0B60D94FDF437,9CD22D8E908B8604E50AEB38
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3DD7BBAA9219A684937E01ED3C4BB765,979F4315ADAEEFEB2FA425A8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,14DA6A52901E8307A0A670AE479F86CA,2BCB1F00A750C44FD8796227
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0CCFC8E22D343F1F742109B298C70B6D,8A2774F0FCA16945E112A963
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6EFFA300E7B42409B6C0AB2F7702DE10,03775D043B0E74A09DD3C682
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6E32DD9C06363D3861E0CA8757F6F1E6,7A93227B2D9D42E1E9BC575D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EA9C1AE547A7A703FB9927071741BBDE,E21A228CBE7A6CB571B048A3
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9C02C6007B9520A2C91AD461F0D036B0,1152183D03228FA0824CB02B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D05F647A9D52588A8080FABC23C8FBE2,081E7E132E7FA762FEC9615A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C69B4F862714E7DE0F164AFB86FA749A,A6EA4793326C53FCDD068BFE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8AAA18EA7C2BF9AA2CF2FEBC67A2D237,A844FDC8F08B88FA76203076
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,99EB982A955878D476D0DCF26F4F2549,4CEFC03D8877AE5A4948132D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E67E1D6202D1579CB6CE1019C356365C,359F17EFCEBBAF1871BB23B0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,51E24C6EAE298E24032801B915284529,A4D4A479C4A563C887CDAB23
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A680BBE906B789E8EABF3A2105F729BD,003D877DB7245BD9494FAA4F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B8C0B91B3E4D721C64AB693D3661F0A8,E6BD97DF792D7247ED7470A6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C209970974C5FB03F13CC37DC5AB14D9,CE35FF0D37FA85538CE400E0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AA776ED1EDA8F6365E7B6C309C2D14C0,44363E12F52D707C3A603CFC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,63B00CC8C764D6349953A48FA7C9257A,926B6CD0BA38598834ED10B0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DCADDE2690C1A93C5E2726A62AAE2C7D,FBE1015B5DA87469EFC57492
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,37EAF2D85AFD72C0262AA7DCD7429557,33EB97675FC486E25CA4015D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AD2B37FCBFF34E2A3E298C4D0E889335,F1FB1A310DE2FDF67C14D823
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,21E5FC1E51A73A330400ACE4C4620506,D5C7DEF1981B1E4777A03D0A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5715725A11F18EAF719F33AD86259A07,51A55915E4606D4BB5293F18
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C3F809C472F506B2B1E857DC01069423,05206E64B7887F0DC6496ECE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,93F2E9C05B6A1C6BDC847F30C173335F,59A69CA2FF24C63F4081368D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BE45C95A8747E9AB6CA17343FCBE1EA9,2584335E68BAE8EA10F0218E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,680BA0B4E836CB3878E5D0DEF1C2E896,E62B658EEA7A6D7A43D59686
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2D3653DC920D22B620131542E79336C0,F95FC2F730472BB634C51FE0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C5519276EB1657216B1EC10FB4A93F56,3C425689A01D4D8029631CBD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,20BCB9E98C1415A8EDF358D37539315B,BE8FC33FE00171B5785F850D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,444B9544EE15034F9B8C15E25488BBDC,192A693DB42C3C1A80AE4D59
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,74838C600CAB9BED85163A7DEA529D7C,45BE629E6266098B2637BABB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BA67F869BE79821954A4C23546F5A620,8B1D92C591984DF390B34396
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BAE1A926B0C4C0F224D15BF6480BBB57,B47B558C9333834B06901440
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3C5A480FBEC0C7DB36FFE0A4D0714EFE,6324B2C4EC3557EFBB6F0A3A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,79D99D9DAD7E91564058816E40216884,AE065CF21B8617F8206DF134
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,652F5B2A342B02A44BF35CE9BD99B1F1,27530B5DFE9656356422E110
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,298878C7092089346E99538E4B530732,1E99C17F976097BF59727225
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FCE036213AA9AE0890D6B4C4BBE1B819,2F406CE178B194BA4223F014
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7CFB133F107F8FAAF1105550EE7153B2,E9219A1A70AF922F7F6335C0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2E5CAE7355EC3D5082C3AC9D2D4C1D74,111F2E0C0435C381F5F0443D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A432251736F441174D9BF11302FC8A0B,6D85327CBC7D05E5B498061D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,864C3D7719506623594FD357343FAB3A,99E9C7CE8A73118896FDFA73
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8060C76EA57A22E835FC003E36FBE134,5237739EF805E1A8B0175638
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,39B499359679F3DF5F20EC816087037F,EED3A9367C317D2C93F7350F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3B125A61F19898D982A68D4747F3AB27,E4565B4CD803A7E706F7648C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,59E0CA0FC910ED47CE9541E983256AB5,86D49D08A5CF0A80AD3D5471
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1509F82B32C4D1CBD190289F2ABE2A4F,181C407D8EB4F6228F3829D2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A5A1461145641E786BEBD6A7216DE8DC,94431F516F754C875F978737
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,059B3DA9442AB954CAF99206B5BE89AD,651C10359109860AF1718475
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AA9D00B5EE743F509F23AFB30D2DC459,FDB81785C720C703A71500C9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,55D1E3F5EA6945E329135BE531AE5AA2,EE17C77D4FFFCC83060A77A6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6874E0DAA246D283EEBA23D56647A2D1,E7018B195236562B25504B68
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B97E607938AB98651325079D9873FC2F,7264AB0E73B77C3BCD9C59E2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,97242D0FFC2DDC9D58DCB99F47FDB54D,FFA3866BD5962ED89FEA8F43
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7105A4274D2BB5F3964D306E0B84BC00,362122F98251B36C8D265AE7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,85A013070F50C8F318EA9D2C297B32B8,2DC9C0B8598E482E3419CADF
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7F9D1A3A528BF1503E0659C4599BAF4D,426C7085EA709094FB9E9737
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E40D0BF9B1BDB25BB8CEF5F2C6E42EEE,0A24F4C8561666FACEB3FA68
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,99F0E31E63033B10C013EBBEB5A38F25,D04FDC9CE5590DAEEC9D2071
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ACED4722C9A93AA8653984D1F748AAF3,F24EA9777C8BA77D01531F9E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C24AD9258F77CD859AAD2E9929D0F86B,BCEDF6F871E3EA3157A11783
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C3F2BFEAD419E0677BCCC0BBBA0F0526,B5677CC99B8ECE1CC3E6A8E5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,754FECEB3814DD16FCCB048240C569F8,DEFDCB968FE746EF38F62121
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,858D87A91898E0E017AE65A8E96E009B,13239CEC1BBA2B7080B21A45
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5420B3F002F35796CD870C9EF308E305,2AD04D7F4DEB18EBFC6380E2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,75B4876D6E2469EEAD26C4CBF4AA3A74,BC66DF1BFE59D3EA4B51056E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,68BAC8C22D18BBAFA5746D8BAF3919D9,E125668BA81A618FAD0A626E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,61A5CB8A7FE9124A50581DE1F88B4CE3,4713490084F1061AC0D01E61
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,48A114454ECCCD8F73B798AC77397379,D69DC6F8710A5DDC1C097681
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7295FAE9C0FB3EF6DCCD703EA74BFA6C,0EAC8CD8DC71849E3C8D11D7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,41EFD59DFD1E7A03F6608E0E7BD475C6,9276BF8FD0EFE2CEC453E0CD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C5D1CB28DEAFD2DE2D83F9670C193867,F8C00545BEA262A59508A28F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F1341091B0AAD50914EC51A0DA1E8DB6,0C1CCCA9988F7891593B5B3D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,15B47603920EDDE3B34B7842547DDF58,75E90A950B489CF1B3FF84E0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AE9B3B9176219A4A8AC1AFEC7C152267,54212EDF5C06DD53A5360B30
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,640A58C1432588AA65E3B88E8A66326E,1864A1790FA618FC9AB896C0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BBDB0C7B620366A7E9F3BB5AD634CA0F,A059B19B2641F578227C1F99
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FF31F65075488B3C9C4271D31087052A,B81CD905D39B5FDA2E4A454E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,33D44783AD83F85CEC49E9710B8230E7,68085BE1594AF4350BB72575
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DB70BDF5382D01D9F38D868298BBB2D4,E67ABBB646268CA0DD275928
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1ABA1A66B8F22CF69576CE375FAB4EA0,182F2FB770A7F636B2B97AAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2CCC1B002E74532B12BFCE0F75F080EF,1F298C218193E0F3ED2959D6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A2E879EB6F21E729463FF02A5367088F,CCEA2AB95BC3214EAC9E627E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5325A7BD8CD28B4C8009AFF45E545987,5BF39891DE27F5C1D971D6A6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E422ACCAFEBBA23C330372593E0EDCE5,EFBD096CA871AE04B041A3FA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AF4F883267B43FB627A6CED4D39D3297,3F3DE74071C3A880C23A2B12
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FD49F0326311DC823D757F635197572E,907E8D9EA29C29D0DD4FAB21
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FCE21FB08EF39CBE431CBE3023C4DB27,73ACE1A839774DEA18A34375
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A0BAE1DF873FE7A7D9048D635F1F14EF,F3074BBA4F277FBF5A7F91D9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B37C38BB80328F3C97A977F2ED254174,AC6039F5FC06E302415DDC31
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,665CE0C255C84288F7A75A5F81926F6A,55234AC615BB3356CD0E41A1
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8E7AA9F32253F42EF7F2D68EFBEB21AC,04762BF970D1F2DECF22252D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A1BE05726059C3F312787A379F585DE2,094D1F7842C129B549BCEE85
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,58B1B3DC7636E88130647FD67B68B08E,8486978922E8D4D23A075885
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,01DF6BC1C5CEE8C177C59CFFB4370F8E,B71D112215E3D942CC8B156B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E21294A89D9132C2DAA27BE40B719293,00DBB154E82102ECAAE060A4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,35B192D8BF541EB6EE622972AF7A640C,4137A9C7D970F4A79B0C0826
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,457C3690BCC9955B543D2147A98B8BEF,1E6860FC647A0AAB87131B5B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7CBCAFB6658EDF3F504659B024251EFF,D0BE0503E3D82D3545C0FF71
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,247DA9A5A1C41600098D805C941BD8EF,0B620D456A380440F69C42B0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,96C7ADF6EB63AE953A9AD69CFDEC2ECD,7B0035A1982AEECC937A9463
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4998CF82AA9184C7AAC721A14EDE612A,8B953D21C3805FCBDB26DC03
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6D6C2397F816501C1C2A54026A87A891,5E4E4DA6DF8439447F40AB24
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9296C69A474A588CAA17E769F09E650E,F2454421E40AF0469806ED3F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B5E72F91519E8C1676DE816B80E8ADCD,A3A9B91A7FC134B9D01551BD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7BCBBFBE158DC5930C71CA5752DB2993,20AEED525F372D089FA1A1C8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9F5DAE81ABAFEB880B768EC43240DECC,AD83E8DD85824241E62C971F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9A0E44698E7CFA4A85C50490D45B5D52,B10B5377EDB51812B6F29C8B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,57248389353A263554EB4D9980CCC158,D1881105398CD380A6B43608
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,276F117BDD45E2A3B9E8A034CCBFAD36,31C05360EC269EDAEF5B60CF
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,58B44C471348805E3472F3B3CFCDF804,C6C8C30D56B10D1D9BB0C187
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7B6B3A243E90EE58132A0C4E648BA8D9,741851B256F0927411A5E180
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,48B2B680CDD3420DA07D8043834FFA25,8A4DE29E3AD6AFFCA0B4D62D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9AB7967D54AA825C370EB756206BD293,3F9C0A891963C81E9C98532E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,05415E6C0388DBD47BA5B90075C80AD1,FDFBB592A74BB2B7DC06605D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A16149E3963E97DE8616C5B2D5C69BCD,28A88DB95E7F29561D90AD81
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B440410CE552432A8CD1C8CFB44D4E17,9A8DC4CD1584016C16C71170
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,282F5FC1F1DB7045E95C4D2CEF993331,278540F1D12DA5AACCCCA5F2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,69CB46C66FEC0563177C54A92389B55C,266B6C7FEB0A70ED61325A02
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8A996783B3ED992261F42900F14EEDE0,E09AC5B6CB1C5D8919A65CF1
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A0D7319B6F633463D5D2678D9535DE6A,96CF081192B489B4942226CC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A9163934E4B6F027E9D1B897089E5B8E,7C33CF678EAC9D79A4B8DF71
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7FA59B5C3098D6A4EA607ACE8EBDFCF6,09E20383EF90CD41BCFB9E3C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,545266A2AC95D9DC7523D9CC26811772,76E1DD1B438A449674311F7F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AD404E88DD3ABACDC4B15C02F41F9DE9,2EFC6B2B406E29D1E947AF1C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,92CED35256204E31EC3549F58B15C7B0,1E212698E114E761E5B663C5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EB1F247E0D56A25EF5718ED36380EEBD,01BBF677F47C754FF502EBAC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,32083C4DBFF7FE1FA500529BEC94F689,587DE353144F1B3BA54447E0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3C6742A6E91A429CC202BB838BB717B8,CA7694D92FD84F2F45AA6613
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4C6A5D02313B182340FEECDB0A9CB3B2,416B5C878D90B6481EF1DDBE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,04C814633D2B0BF3B03D3B2F5CFB9C43,A9E3C68FD77D1B30D415180C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E6908C7AEFB2819798F070BF57354A8C,EEAE98FAF4225799B577D6B5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D8B8B932852D90A70F245B3E01E0623E,E978D99AE21FA544E0FF7750
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B2B1C9AA587FFAA004D2DE4743EB7DB6,BEA2D1F69952283D80F4CBCC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C891C1DDE6D0688136A82B79CB8DD541,E6E6CBF549BAC1BCC230DC82
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E5619F847A4005AE32AA9751EB407CF8,F9AA6BB21CE75E21372A5ABB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E6FE23F42B4B5FE8B333A1428E7EC789,A84BC66F58C540B8BDA0CC71
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0B784416AEABB2135791774CA7FF04FD,9AB68C5F642C64C82567E299
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,42AFC9A45375E548CE1EF6E49C907FF3,A756D8B5D20EFF34C4C5D18E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,61334E0DB09793458FF1E7874BD5BB56,FF1EB9D76FF1B2E49564419D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,471A3B7B06D76797970D836EF664519D,C51C2ADB60D331AE5AA94282
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,26D42F7E2DCB2C1FEB76511033C7D986,A28858C9C4E7FA2CC6F99576
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8FA6CCDCD58890BCBEDBC29EE019A002,B394C74C41F3FCA60AD2ED04
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FE6B666AA3EF9CBA6094007E3F1B4F24,9246AE183103570DBB6E1AE3
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7A18F941E8B893C7D27D63F5F4E14985,345E27AC263C7C0585F25D06
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C3E4A515BF1C087D1F3AEE498D3C9F4A,B3A6BBEAB3459BD607310A3F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DE2024BDD217B94AA602EB502C9FAF4A,9104CB600FEDEFA6365F34D2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,61636EA7E19138D4BBEA213AD61543AE,90992C68472147762B9FEEE8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FAB3BFF88D340A5423E32C9596BC8B57,7FA6B3679298BB21B282A350
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4D37D80ED1C7F445F73A66F59C11FCF0,110889228DE97F9AFE93A1BE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A6D10F7561451F50B85255AD5BD912D3,668622414C3922D25727F3C7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E6A2AC428310CAF276CB6035CB8EB9BD,8F3E6B6B0E0485B69F011B6D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,68204E1B48BDB4F5D89F8A970B2CCACD,D404A42390D66EB423F42554
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,98CFEE98EDA627B7680EF7EDBB4CE14C,A3B4D01986D64F0AD563F7EB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D06325445759A1D117B09B2E8F4C94C2,F5F33FCC60A8E84325C3CF5E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,92A8B5DC09AC530E8A5CF2F38AFF22F9,2DDC1FB79A084C64EA4BE75A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B5521DC9BCD55EC9CFB2158A120BAE06,D30F832834176C330CC43514
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C14B9437622D990C05C3FFCB734DA52A,D0CA95116EBF7A6261CCF2EB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,75E4718349BE6677212F218679338FD8,837DC56CE8A0EB5D01903DB0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,937F3F6779B2D4EBD5B7A35E3DC89D57,A86B1F588435C042D6C7642C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,352D597B542F025B4D18A42C9B50E426,AF86E90EB92C60A7B0B942E5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,195505E0551B87975CCE1A27F0B1DE1A,7960D8F66F1D52C8623223DF
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,23AD7E24D5C114B17F629337C20B2267,430761488833363E43CCF38D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,258C233904FE9E5DF8E21E6F5AE217F7,5A03D16A770893B19751D022
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CB9F5D69C3189F895C6D541588F6F6B1,AD173ABFA217584CF3E33648
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1D41DB0E40598D7AFD4500E3B3B1934A,884D68E5EE36AA82FC277648
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A825B23D799E59BF6AC02ECEC986D308,16731DAC8BC5B9FFE165452F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5A02958E967EFB560ADFAA471FBE545E,CD455E83E810D06DAC2DA40A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E0BA11F212EFD783B1B2D7C1D94CFB02,BB3D1F7CF901443C564AF018
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6D2B6B980D7A84ABC255FD36D78F73C9,9A9EB3E89127EC8C59F2F219
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,29F7F3DD366FD417FB27FF070C6C64D9,90A88C2971EF2519A9286ED5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,398096054AAE11F1466EC25C31EB488B,FE8C1282B3EC0BCC245DC0D4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4727E5FA42CC306C94BF5BC60BA5D04B,0979930740A16964D4E8DC9D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,00FA2643930D4CAB0B437D4538A9A5CC,D7199ABE41A62FA9A21C940D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4BA3FC019B73B6D50671AA008136D42F,632173C95CFD3E845734F21A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DCF66A88B4E29275FEAD88B7FA30432E,CAFCCD143CAE517ECED695CE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,65945975D5C9C172830BA2C9E82FBCEE,C124C3DB68DAC92C77D9D77B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,88F1F56B2114A44495DD4758A27DEE2D,8AE73C7FE5A4D3AFBA04735C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,98D15F929E3C088915CF014704953670,D40859F7D1B9B0BE218DF3A0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2A5CE1BDCB03D4A716F94FA058AA4A8D,3C52E2C038B420B1148FFE5A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,06E140F81908655D7EB5CAB5BA500EAB,EB33656525860992973EC912
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CA5F889F82FF6D1BC8A37EE7F4CBCD8C,5A05C0D894B353837EA89DCB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,18EF57BF7CE6A833096851B002085B85,836B27AE1ACCFB7E70705D78
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,03C91A567AA4F70F8FAE96090B1735EB,6A9AFF2DEF414785C740FA96
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,87A6F4595E81766503F3A69ED488CF30,1CEDAC452A7E2A8F0C6F0898
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A6D2C603B63B9C2DB78C7603FBBA2E56,4D87573C92C742AD679C5F47
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,38F20E83B0B6B0ADECCD8AEEBFA0D699,174B64864AB6D9ACB49E9974
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FAC8FECBB568C411B8A026E8101EAFCB,DD1C6136805B948F8A752F88
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,444530537C31B55D744B3ECDAE7351F8,79452F3E1F9D9CAD82B87F8C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,857CC0770DCA36D9022EDAB273E28663,83392240C1AF37AE62A628EF
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,57E9FAD98FEDEE806AF218277C3C5D41,7CCCC317E62BA74A9C76D519
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B5824EEB55DCD5A494FF1A90587A823B,286597939FE17FB2632AC770
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2B279D4E24EB13A17401CACFEE936E04,5218F1BB472334ED7E1638FA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1EC44F30AD61EFB4E0DAF670C3B2AB70,9996814A92FCDB239CE26937
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4FCF2FCBA29AB5869EA97742E1B84F36,4C4AEBEFFF5F38B19E115879
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,12690AD6A685FC5E12F9F2C8BC3B0EBC,4E637991268BBECEB730DCB7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D9F82CD33E3B58A7A12CCD5CE0263795,60B29E63DDB9E1662CAB28AA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9338545BE3BC0BEBB1147A60A901D050,87A06D1628D7F632AA0B09FE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,430A9DDFFBFDCDB35CF25C2FBD5FE048,3CFDE615E8709673BBF5F81D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9142B3536CBC753D11927BE8106E06BF,7AA594617CF61D637A708F6C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3035ECECE2B602952946B25C768FFF52,790963DE796E2AD0F5609414
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,238844A8C5D4EE87E1E3DBB9730AF9E1,EBADA4E0EB87ACAE64160331
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2EE2ECCD9C8353F0DDDB3DE1FB564845,9639CD9CB0B49815D3CEBDA5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4CCF70032015B8E5DC13EE6DF5D9766C,4199007AFF50F079DD974CC9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,940EC0E6CC164B97CE7A620569BAF28D,8F4FDA8480B3DB9879680E17
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E9AE188BDDE66B3F5854C3EF4B039A52,E2C4FA22AE53E117546C6A9D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,77933DBD0A5BEC33112F9984918AE86B,3BFF6F18700B52ECCE2C84C7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,18B1D51FAEAD1B22B013DEACB696F70C,A60CA3558AF36179BB73000A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AC8C43FFCA7B3BE74B75C4FE2161DDD2,0A94334EA63F06450687F5A2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A1F89ECC18A10A50802BDA9425C04646,04D28F46E4CB41BF9A42A595
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E1EF29848E8859D78699F0BC084FFBBF,FAAD7FD942FA72B031577F66
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1A6E390B309FFE210B5F08E60C9A38C6,AEB7315860F4D0720BB9F041
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B574C2AA7D8F662B40A8B32502003397,1DF4B893D380E8976DD338E3
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,92D42585A0375621E0CE7624BC8A9EB0,A5806E604AF5D548D7680484
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EDF884875394558E62621E8017BC4F2A,4716D1CBC44A8B3EFEAF67A9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B7E1B515FE3CDC08B58C5A9954DEAC7C,4D639CF494DDFD648BB5AD8E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7B7E1FD35769F032A5253E103D39C9F1,ECE54620684332950785D8BE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0679BCD97C8EEA6B4E3A6D9A6DE33835,CF055324EBBAB90814F42FBF
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2DC3110C86D5C7E1D4D3D545EE24FC2C,07938CAEBE1712F5AE1E3ACD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AE1B1164BDBBEAAB83ABE0C419DDE545,9A5C9BA8D8F26A7CF64AF101
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B0754B5A1DD4B70D6668EC8EB4B39C10,52FB1BF1CE5FC6A231F0F05E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EC45EBD7DD6E36E070B10B353CCF9D5C,B729AF7EA1195B39374B58E7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F61666A78A70D5FB48828AF5EF5AA2E7,D27EC7914E016A7FCFF71C54
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,50BD44366D78AF6A69F20093BBD5E0A3,17A8D6BF04FD4962D8E160BE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,29C0FA93802FAB90949F502AF5E3F2FF,A8061D733AE08D36228A60C6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CDF57CEB499D1531687C8E6BEC30F2AD,2FE50295265CB2EE759C723D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CBE08B4E5F61E801F929658192748795,6616AA22DB8D0F9B383553F7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AF1E33D1EB2364CC5EE33ACB28D96BF1,7A57FA9E969374C8E3C47BB3
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A836E1719AE7E54F8A98ECAF902F587C,80938563139ED354B2C3FFA8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,39BEEBBC77D4CC9FB568B738E7D02F71,34087FA85A9AAC1586847BE1
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EC546393DD23CF2F2862384E8C3B146B,BB153B83A3908B4E64712AD1
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,96BD017521C07684145B6F8C6A001308,854845992A4DF09576663A15
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F60B146739D623E691FFE695283958A2,D4E66CF06941040DCACA85CF
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,881FF49FE99FF0FBA2ADB26C9E27276D,0ACAF775BB38337A1E344200
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3714AF1C4056F4F1A0334428E70EAE31,675C9D5C711980E4568EF851
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,76ADE089A96DC337F6DF5AA2AD9B9379,A4BB7572C86755C40F67668C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FC2E67BF2FDB79D53509A1B712C3338F,A08FDFE14B6377B9593425DC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9B1BF9D0A2191C33F6D3119A057E9ECF,A40A99883EC4D2F5E66F135F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8D2D20EA10C7B5E377E110CC2C471298,D771870E3342A158CF5BC565
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,20DBA5BA5C7364DAF94894FA2E3133EB,D5ED6537C0B3343D2E3DB575
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5B045A56105D5FB8E568F5A18107F339,DB2DB2118ED8A75AEC9F17EE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6A6EC373C993C65460F60D5C2F1E2C42,AFC9C8DBB8D37070636E8A83
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,78D42F93ACCE494C7C48F8B3FEB833BC,56C4A07071992807A24873FD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,877087DBDE0778182AEDF1835188A17A,A5AEBA67FD0C0053573562D1
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C5D36C7B00BC21E37EE7C8BD2344A2B1,9B87A0E5CD0F142730217AAB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2848480D8E779C0190C1FAE7572E7A4A,8F1512191748CB0A91FDB205
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C3863CD1CBCB481366B8B1C1F6FF8530,0766720770BE1247317F7B64
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BB71FAB97A20ED16A391A519A9EC193F,486EC0A9E17EE343578837C0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,052E5A2C31CEF3E3695A1397853D45D5,936CA82F9FE6A435C4EE4DE2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7B6306FDF353E730770E66604F8466CF,4121273261A8C8394BCE6FD5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9A6FC4AFFD8C84F8818AF4AF49FDD53D,5763599BEEC27160A6AF64BD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D61D855CEBE64691EB99EB73E3758FA7,E5D9845F2515E2BD741E912C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6982C1E0C933006920542445974B0459,AD5EBEEDACF13A80488D11FB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8D36A5A66DD0059C76B2C0510EC41E25,4AF22EF5C1DB27880E8F1B1F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CD58C379E73A4923555FF88714E16CF4,D30AE0937D8729472D0FC3C0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E04F522692CBEE4B7F0315AD9C51D72D,D96F380954E06B17783E0391
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BBE2C80970B92F6ADC1AE1525A197DAF,9FCEBC453C0C2F496D2B7B0B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D8CC3E92B9C35D22B2FBB94706521276,BBF9E042F4E5EEDDC95EBA85
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,30B4398C6A943E1FE0C3C512BD4BC788,8503AC4FEB9FDF7CAB207F62
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,42176E40D3F1446A15D9AF3BAEAA97D1,FBB1A09D0DC843BAA4151147
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2D9269B04D899888C4DFD9D18A14EC62,9319967214C5E69AF7CC83A5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,577A7773EDAF8B1A958F7786EAEF9777,F32957EF68CF821FE3C4C844
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,62BD892681623A3893599E5EDC068BF7,52B4A34186CA802DC7650385
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A8FF2DED49942BCF15D92184DB625AEC,2B7ECBDD13D65F645A1D71BC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2A375F080809C959288ECBE28177B8FE,A15C38E02FF392874C2477F3
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F24A400691C85CFA11801DB8358EDB28,AED4274B44DA2CE47C646927
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2A605DD2D6CD2532034962B7B341C92F,90C1FE26F8FE8BFAA6CCDBD4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4A0EE729A82478AB404AF83758EABB77,9FB249FB258CC32EA8C2CE1A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,77E87DCD362B3011042A2D50DA174118,50FEB8FE43E76FBD9FA6DB41
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,05099369CBEF98698783BA40403A34A2,D6F60A72FB0E5B8B693A2191
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,33817E9D63C8FE1DDE0025FDE7DB2FEC,2CB474A91467A18E2A9CC760
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D237DCCFF06B1DB84E8D2BE3554FCAE9,8501DE0D5ACF9E38D7363AFB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,377F98A0C49FB4150128986DCE4E7359,25D8CAAA3EFEE5D7102D6039
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B00DBA3CC58C9C7ABF27447431B16300,9221FB5259BE9C96B161459F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,20E5A41ACB1B3853D6A31C52DB5DD12E,67928229F5693E291B98E732
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DF9CCAEC4EAEA10523ACF122D4224B0F,D6912B17D1ED650EDBFA5DA6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,030C9B40FF53EDDB9097EACF7DE5030A,931B4F1E46FD35AB1E5B0F58
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,977A5F4C3C961719DB5EB52D0B38B1FB,F8E13B7190E28876833BF22E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D726AFD1E009A57B50C7751EBCF50A3A,455B65855ACAB653892103D3
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0066B204988D12934052DD78451B3C38,7A14D3B1095D6E7778043C48
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,310344BE7D063F69CE10EEE11B3D3325,D9D916E2A9E0EB1D884F8817
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BE1E399FDAC7463EC2AE1BE7CC281C15,761BD212E485B0C0FB45CD19
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3BCA508E2BBE57BC3F0373052B3BC3D9,8EFE95BCDA4BD9EDB5DF843C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F2FDDB7EC0126349130702C354D9FF5C,212B7A28EBB7D9F43B25FF93
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,476582F5421F58306A3C57C8F6CDED9A,1A591D59BCEA8D9B7D10A2B8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AC58FDECC86B561D6A148B4074872BE5,4BA6D4EFC468467C570168BE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CFE2C743B197342B2C6E33058DCEADF5,D683B57875B63428A0523913
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D0921D159B743A3E644DD3787FC767F6,5729DB51AA3905FCF6D11CF5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,22A292888CA8C313B785F970B490C261,7AB4DC3F26954848629108E5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,996E7453208FCE8F62601B741B187880,129FA10FA3D67ECE7A49E8E2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B3915B7F245EE8CA2613C5DBFA40B36B,AC6CF8265CA6021E6D28F477
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4865C31CE54C3BB99E53591AC8979198,2650D862DA70A85B9FB4503A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FC16BFBF1FADDB8A3DED51D1ADD8C391,F3CD6C256753896320EC4D9A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1D27C082749B362A9E079528CE462144,49BBBBF8AB5CFF59005105E9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,19308C8E873FE8D050BDDD797A029CCD,476AF62908F1421B62E0E334
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1426F94934BB10E6B4EBAD64D2A54A2B,02C6B814B58120E5F96B4107
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AF1E5C627A5533D575A8CECA5B1117BB,9304DD11B97DB619F303A9C8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,58547645FA672D474940279877918D62,BA40A424762D75DD60C52373
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3A56DCC21C13902FC625643FB25C987B,57E685340A4A237414207C48
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B851E274F9E1E7545ADDEDE0DBA5D2BD,AE14288C0BD0877319194E8A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7627A495FF15D16F5DFC3BA784DCC243,5772AAE27CAB453D3F6EC374
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,34CCAF8288808B8281740C29BE6E71B7,C8CC08C9299FC6A61C92B17A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,17795498DFB7074869664325E892F88E,E0319808163DA661053AEE0D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7A4338E4C6CAED253D81CA943F266933,12FD13AE57F3492999AC0020
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9C70DA6910BEDF88184ED2CC8A630A64,D13FA04CA1CEE854310D2469
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,51C87035E8757BE513382458A614D592,6643B6F05BD94565D7292AE0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,890B1AB6B918B6FB910A874F1774097E,5E8292F81CFC8D81DEC5C71E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,093495E9E1A906FF8A02BE13F27EBCAC,E4700F7002C7311ADFDD0AD3
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,18534EA254A6E58AD279F2E75ECD1A2A,F7CA1F8D9D942211356F15AD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E1B25EA19D9FE6E6C075DD10D7178DA9,DAF3C83EE49AA9C795967BA3
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3919CCE828CAA532B0D12734DE55C183,F9FCA32B5411A7A5802D8953
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,056CA833057E0B16285F753AD6FC4925,D99B2F3AEDD8CB9229BBEC0D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4E900241483908832374039E44B365DD,CFCD7BF4E9C80C302555C5E6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,434E2A4081A6AAA495F6324A27A2BEE6,546C2AE028F7324B31EAD660
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,542BB3F650D3FD2197D651F2AA3B4CB8,779CCC12B9A81203BF6E64E0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,85C002B4D3C22F62CA29B5A200016AEF,DBFE7A507BF3581FDBC223B3
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A0D62D296D3DE757A2B97313CDFDBF28,F6A883DBB001D5A06FA56A18
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,758F8139CC9EDB9E1046D5D71AC6ACFB,A11C1322F33B0C7147ACF210
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9854BB439AC95310546DDCD6AF8C9EE4,75DFC039FC4A5AC6541750D7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,162B05A3D90E4F691CF4FDA964F9D08D,BD38EC87D8FBA954372B5C2C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3B870DC087FFF1CFE1FF0E7A3E5809FF,D8926C4CE7F8A1B73803DEB8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,74DC9116A986A4C5623CCFFE99151940,E384D58528BA6593998B47F8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4F0AE2A1169E2C0CA32D76222F82AFDA,D0D1CF2A17E32F33E0BC5D85
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6E401FA58FAFE8C370CDD180384503D4,410C2B70E0AF9ED19AD1A655
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B600D795E20D5072AAEC3F12FED9831C,8B195E4EDDFFBEA4FD2078CD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,84F03AD81AC842234924AEACBFD9E007,C4841B6926907A9ACE38F692
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A9BB6DD52A6131A6324B2200A94A4C37,6135DD345339D952D055778D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8C76285968EE5CE3C5BC053389320472,483B0678643EC1B079587398
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,85CE181363376F133C27395F546810A6,9942C4C95A9803060EF4E9C5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0EF7F03976FD4F35877D81E51773F788,E07E11CCD12DECB4ECFD52F4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9E051CE8CD789364F3D4514664637125,37016F051186B84AB6C0017C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3B969219C864C6EBC0E28E998E723EFD,06FDA82FD788237C82EFC648
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,941D4CA3583D84AD49E8B36043A26B3B,711A06F246EFD1CB875555EC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,42452AB4BBA8816B642A7A2B2CB32746,4963261C47864C816D91D757
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EFDC72ECFC87BAF583836B28CD402AEB,8FFA4E3012A7358F04C87DDC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6A4A1F77C1F84217AD574B06BADA8D8D,7C615CB1B0C19C1B5108B2D0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,612B7A4023CE381E2DA777DB5670EF19,0F3C0B32C0CFA19A1E371569
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F2FE60B316249CFC37FC844738B6776D,094BD3DFB26EE7787C67A768
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F16427D34518986545D6C34CE3DE0F25,E61CAA8E90A56C33E64B314E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8CC551123C8D0E5F06CE4CCC4C5CD2D6,4A12AC7EC803DE9CCE4F5BD2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0CAF4FCF0116F09688F70DB5BAC4FCF7,7BB52ED93C9753BCD663B3CB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,570A449DE9CF697AD0F857BA3B4ED10A,F4D5A26EB7CFE1DB99F235B9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E7492BB6C9641FDC442007FADEE378C2,47BB8A378D5FD4618BAAD3FC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4BF5A78BEB85E6EE45587980AA8997D1,856007831C77BAEED3FB74B7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C467D3AA1E425853300115E2852A2C22,79D0A87198628A4DD0715A81
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,166E20A0937416E40585C4725A2639A9,E60A8BF1DE5F8FEF98FCAD74
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FF60302DDA32C9B15B810FDFF066C3A8,066BACD0B62EE1F2DFF67E5C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0CF0CC0B334C5FD676AA5ABD29DB391C,A8C1FF13A05A3C2AFADF07B7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3C6F0B6C1F4145802A9B4DAFF7595919,B21DEA3B36A10ADE95DFDA5E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,59E49926C0291B47EBAF1E08B8FC2E6A,29CB0AAFC26985131F99553C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,24A9969D2A435B8E8B8F425AAA089862,6358ED4DD9679D461F39870B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CEBF9A54B65EE526C4F5962B744BC8E9,CFBD78D18CE385CD80AE77D4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,766D7612E2649493E42E8729651E3915,B543D6DF887B827AFE7A49C4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8D9C226BDE5412DD4CDC3B82143A831D,18C01DD500E50DC6BF028E93
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AC89833635DA629FB52A15D4603AED87,5865D89B38AC00FC8277D3AF
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,75C5B6B1886B874B64A935443CB167A4,B71CEE0596FBA77B740403C9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A154255A2AFF8779A6E0396E3FAE226D,CCD287D9A032F1D350D3EDEC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3493696275EBF6764767DD177EBA8F68,9D0F44BEFE7C57E7A342E055
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,48B4168389CC95EF4EA289767AA4CBDF,77DC39A48A7C1944B4AFD3AD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9F1EE8FC7A3886E78B1A21338B64837D,419BC21C840CA23E21C29EC9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B1EA444B1E3E7A548FDDA7FF64D2E602,4977A7DE73FD33B1D97D58FE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,68F8AE28016004B285403B23E021BB2B,CA330C40333234E36F460166
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7F4F1775A34BE9711260B1F977E152EC,7BC572ED3D38FFBA5488732D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3901121148BCE143BD22B87CB212EF88,5D3212E5298C99705B7AC932
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DC61F4169C4BC0B3C54437F622977834,D4506840401DF6554635D857
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5A051696E25C5B4852D48331780441CB,BA534B6CA3EBAF8CF044826F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,841B5804EAB0F86CDE97139A2C6B180F,278FBF615BF11F7FE79EAF23
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F1488D2026FE51161531EB1966D59BAC,A3CDC43DD0FAC00FD463B36E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,13913340133F4E555B5BFFADDB325BF2,6E07FFB5F8E5DFE6FF97BC2E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9111742825025FF594EFF2F058048BF2,BD0896C75C8E80FD28C0C583
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BD1CB3D3F4E34057AF30029403CA3E54,3EDF25FC2A8068B3EB1DB74C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C054E12BAFDF2452238738A499CC283D,B7212228FAD9A97F935AB498
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F52B105E60C0CB09793BC8CD7AEEE955,323153E1E96C18F34AC12A74
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2FD896ADBD204A8EC457DD6AE4446DCA,78DA31F53A33D4BEA3F7A1E6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,03C67D7FB9F90F3B8416249FEDE60208,3C625218B4B96297314882EC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FD0C8286A0EC5A245BBCCED275905A72,FAD0C4B702D988EF237D72DB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A93176818C64D31AE11DB44936738D59,665AE6D0A8E0E0FCF7FE4204
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7801F9B77C8EA23586FF132942D674A9,C6E394BDAEEB9F1797D9A6EA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4FA3B9E855330723B00CE8E3B7FD0F2D,6C615F2A5E6E5BB3E3F4E496
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F3AAEFD08C1A3711672606593FBB55DC,916E07B07A2BECC7D73C5C7B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A8A2B83CC57E49E38B3116FB032AB7C2,A4264C771ABF08AB7B9BF457
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B169A6F3D14D65AB435A411580C72BE0,91EC0B1EBCB8F77237ECE573
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0D43F218A959414940569C4F80B5F9F1,A2FCC9A9F23685617EA963CE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AD5A7D67CBA2D953A064FA3F0C7B426E,9D2F2AF67DF7016C4078365B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A1F412EC7A762D4D86B524DB41A03CC5,B46893F2D972D64E0C56595D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4FEE01CAB67DB39FE9E67E62D1EAF9FC,B84477FBD1FD9217E64D21F5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2C557D29B854DC9F895D95B3D9A71C83,5FC57C5E4DDD05E6D4125643
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,78BAA554D65A8E13CC4AE5F244FDFA65,05C6EC3E2A5CB9469981D932
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8026E9D238C5ABB9F3453D505358379B,0A1D29C53AC98815E7A834A5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3960A9455AAB630A81F8379CB8254B81,3ED3FD6E9716E4CD21F791D9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E9D5E86088C4798AAE8EC68FF93B8B6A,1BCD6B153FBB8C98DCFE2BB8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E27A3FAEE5ED88DB4184F48A312A955A,C0A83F05F7F5D8718FE8A810
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3ABF186BF4A9EE6DE4BDD164A9EB3CEA,0B85C14D123EB7FBEB22BC64
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2B4FEDF3F46178F97BBCB8DC4B6A7AEF,9B47E11E0BA56D68AE00ABD6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DF9DB131DD1927D7523C996A8CCC7956,AFA7F27615E6AABD484B3750
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3151C7838F64ACE68776C88E23DC26F1,7A18AA3641D5750C64165993
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AFACB079853517002305BA6CB7D7876F,09851BB26A04A16B792B284C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,620666076D2D088AD2F61F4B4DEC00DB,63D6377AE4D5077130A3D5FD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,81181C3B04A2FB3E90A2ECB3A09DF44F,CB46C3E3ADC28DA7A9F12CBE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9B485FC8D34D39B84D619B0FA0B91DF1,0366966D53CB7BDE4BF394C1
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DE43D7FFAF046211613FAA0672781DDE,653E1665CF9679361F428A57
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,78C2DFA82F4520991FD3507A142AABD2,4FBE0E4158E403B8D8AA3269
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6E11061572E6C5755F897773043DB7D4,7E654E600CEAFE38C6337807
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3FB2EF05BD30839A1F11EEA2BC4C3ACC,CAF07B3BBA6A5CD6757F0C5E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6D2557CB6B0ABDAC48E074C3CF3EEA55,909EEF3E8014ABD0C3141328
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ED451E3A8E2618FAE17CF8C9C544EA12,D43D8B75C4370BD8DA08289E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1A7EF4A0F0C6F89DFA9020D69425B363,BFA698344E2EC5C260D3A3CA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,39FF53E64F2B4B8E445981EC8B04DE8F,926623ACF4217A7D4DE94C15
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7FB4E8BCE634F763D8FE2CD361057705,3001951E5CDE9C320AABCBA6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,744DE4EF2893C68A63D340FE40E2C1CE,420982D52145AB5A54B5EADE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C8ECBED4C9D3639D3F1E5F4F34F682A1,24865C4F6792107A3365B515
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D00ED1D1D9CF74C256AB08C8621FA824,665B7FA2B71AEB7C2DFF7724
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,83745C78C893B3F786C728DEF6A48A57,6872F7917E0FEF0F22D5EA61
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7FBB66885417679E5970AE6B72C5BE4A,84E39E1931E4CA690551F64D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AF9D46D06E926C66835517B0155C8ECD,150A4C90CF275DA03AA0A342
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,236722E40A7FE9AF48008DA60028B057,28C9E79D9B3B9433BA2C8AE8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,374143C31BF20F54B0300181515ECCEF,028C56AE7D60DB723E0A4983
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A84BEB57AB7EFA419243F145D7B81623,4E00F03F82C080039BD00EF5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,078CFA828AAC69931B30F86E3451B81E,AE382636F9F94C763B8070A8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1F9476007304E4C4A3C1F14A27CB2EEA,476B9D26577B59CB3E1C3C16
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9DBAF388DAE2EB27B76759C2CCACBE8B,263C08E1450EAEDF987EDCE7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9C2BF5F3EFC3DEA34B4EC3F633280B03,41B8D9727A5E8469EDB661F0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AC7D476A7CD104A4C4B152FE82899E39,A3B40003A9FD7894C517C8E3
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,178B204DF182A0560778E5B90D1EE170,E77BF4BCA640C82BD2665C9B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9AEF8B5694B926BC4A55DED5259163A2,55A731EF32A6CA3E833F50B4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6B8D7482E2E1A96F6B776DBE6F7E5661,CC7E378D8C7AF20E8FCFA0A2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2AC44A80A5FE1003DD24AA4C6864BC61,EF7AAE0833F9C3888473EC56
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C260EAC6C71D7B7D35C5BE1CCB34212A,716DC830699958E175632ABD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5946CA2D7232076E2DBEC266E794D1A4,338283CB5CD91817A465438B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,210EAA6C850967ACCF3325487329849F,29D05D8C1655A641764CEAF0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,393C4E5602EF6301890D599C7766CA53,85EF9BF7A469BF0005D5AB2D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5ADE7491694A75A5CE0959996473031A,78A42538B39E12BED126DD63
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,670A72D60EC6606E4E798EBB1D4AEA45,3EB374EDE44B276A0B05F01D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,23916D367C0E00CF13B2BA5849293281,3BCEC47F6FF94E05EB2756D7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3D37E626165E8CFDA7F3503EE96D78AF,7DB25F0039486AA1C17DEEBC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,730EC7F1A1334D7A9FA161ECB8FE839D,4DFCF9EDFE4056DAFBFDEDCC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BF7A806A9945108833CB3C136032A33D,68CB935008CA401B29CDFE2C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,148D1ADC6281ED1ECEEB4C61A5D5974B,6611E49991CA4CCC35CA4BBC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5D941E9A26889E243CC7BA63356B8769,B2F69A7288ED0794C8D8F3BB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,52176D9C830733245657C8866CB3FD34,07D624B781E45E1AB2885D43
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B6677C3BD296A6AF1E3D08CB803D9F29,6DA6E7C25EEFC908BC9CF2ED
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,35BC2C889362E17CC46F1DC4C69DE5AE,9B632BFDC79828C2EAE40ACD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A8FE6732AC535DB718039CBB7FA0B0A1,CED6008EBFC492271022666F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9CCB764C102F715FA8F6A77B9C681A29,E63D04D6553D5A1272EC93FF
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F430C9CCD6AC2C5448DFC20AEAC26B78,A268C88D3C4AAC5C3C40B971
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CD2A3D23F134BCD78AD204776884DF1D,09C2DF4686D239A55FECF7C2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C6CA21CE9B44435BB2DA88DD15B1E1A6,23E6DAB765FA303816298AC7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7F7A993FDE676BE6063A83BCCA2F4CA7,A0C1BD80DBE3CDDF9157ED84
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,84D99115B8B2820C52546580B5A71517,EF30D73FC9C7712BC1F4E834
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8F104901412EE55AD48EC8A70C0D920A,F9758C4357FCDDCFF5D7878C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B6E970122768A8B60D6ECF3590256587,449F36D3222CBF4262F7052F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9456329E3E51725FF4306C1BF1FC84C5,47E7AF27620A89B4F646FB8D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A320F5AB902AB2DA39A19819EC3DAE64,F6BBE92D2F19DEE5593071F4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,ACF41B8B0345BB7C0611BAA2F78CE719,1FBC5EAEE93BD973D6AA6F6F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,511765C125FBEE4D4E5BA1B339214FEA,6B1389AE53C01A07793BE30D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,960398E8F44A9F26F51694BDE61DE6E8,1EBE854FECE2AB72356FF067
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,43172468FF0E5D538B047E89D9FAF128,968204B1B443C19E2E861632
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EA690D56A40B0E4810CAA7C3F48ACD9B,2B46CF65F8EE650856000B67
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CB2728D1C06FA1653325838B6786ED19,C757A09F686434C6D2AF8FBB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4ED8EFD706EC13D2D8ADB73E922BDB96,F188C54E0B4FADFEEEDA9E98
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DE4E12D86637EC29DAF8366BE5A17078,927929B7DC0C251D1B2BB0B7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2E241E86614830FC4AFBC8A9B994BC11,37D46D82906800EA6197658F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FE24429FB354D1BE7F27D1220D7801EF,A3D69BDD8EB8C24A6A19BC09
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C1C835697AF1C95B77CB5D09ECBF402F,684525124857136F39ADE1DD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AE8AD487E7DD8B1D8CABB82C4B3183CB,EDED085BBF5084415D4E0205
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,70BD0DB40F3D976E4907DCBA3E40FEE1,EFE6A6F67370348AE283ECCF
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5B1072BA60B98806208700F6BBCB338F,4C374E86BEBA04BAF4A2E11C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,536C7B7A6D773299B839416916D78B6D,1F09C90AD9654A66EDEC86C2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,024FF49B1CA2AC1056801B2B9FF0BDF1,3CB058ECA6914AD9AB4E58F9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3F73F9C6FD5F80388DE7CD0D447E34C0,9D4EEE0478937DC2D7EFAC6E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,56C45BC9B76D6D98B674258314ABD03F,9307A97F0128F4A8A062C418
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6EEDC53430538D16AEE578433AC82C87,6BFE03DA1A89F83253866536
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,15526C4A50256C554663A59AFA31E799,33F71C34F358D46AD9618212
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,31FFA6A17A8A7537CCF03A58E7F32A28,52F193B5D4238BCBA227B4C7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C7EEE147EE90896BD4375E96B11D467D,712F540C52D2B47177F8087D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,218825B240C7C411F7A140834E3C6439,7659E16CBE00DBEE2C89CA18
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8A61111A6B336543BF5ECCEB68F7D15C,2E895DC6ED3A9C291F169FA4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,05582B24643BF38B4F0B95044533098F,484D01822E1106E27D0DF3C4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A209067823F038C99E35FBBCA3BDE95A,0DCD65DF9F21B41F6A1E5C8E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,62EE981575D6A3250E3BA64DC029F078,C7A87C7FA9F0372FE89BF1C4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,062C73ABD56F61A3A42DAEE0F8EFD09D,F5660C6FFCDA6350574A887B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2F4F96A5A4062B572F7BBE311ECF6492,3F67F5A619A34CE49DC41D7E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B360977080E47CD9B3ED66ED22091F4C,23822C6017E8BA385838AD0A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E6E7FBD7603CD52AC2EC8CE72041DD0A,F76BC82C5FF94A2F0B411FD0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F53314B138E7CCAFB2F7131185918AEA,7788F96D0ACEA0A2645D52A7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D6B452B402B2851C2012C30029645FBA,311CA2C5A76C27E8C7A58685
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AFDAF1BA6EF954C620B12F16762B35AE,189701B5967B2D96457EC247
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,642D3D046F4F1EBD0C775B06592CDB27,D054138B51038F23BD0171D8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CDB86368F91E71CE0FC34A70F6FD65C2,74D76011A1912FA279AD1CC8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9FEDAF38BA24A6C82BBA68DA95B71176,7FE4FCFBF42536F899ED7360
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FEF1D522E84CB983A5090B4151780376,1489794A18B7CBD81B3E6E57
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,861435ACCB03FB2D1BFCA69782DC469D,4B4D585395AD649C89FE6139
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,995AD2985AD4E5A1F8802B90B7F8ECA2,993C1F391FBAEDF577E144E0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,742263AF7482CE41D4EF0B03A3AD9B7E,ADB826CAEF2799A7A88DC866
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,479337DF35E98C67741FAD81CF3365B9,FCD508F6FC1ABBE522E0B691
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C7B92DE42D76D646284A2C2039F584B7,C541B2C7568E4817DFF95FAB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D0C3DDC48EC35DA67B369A9DE0CCFFCC,8AA975041C6BD0DA7586DF38
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,599B31C8C3852C1C23DD19A7ACD55C11,07D62DF63575B19B89DA071F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0F269A0EA3E8EFEF5C3C52D43D002225,A34DB9124623C3271061BAA6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6AD44C3FDCF70DD91323CC62CFB02D4E,D17293017E5E46B0F054C9F7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BFE112DD9EBF7E917C561F72750B8912,8825188DA2B08941B8089631
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B87A63D3DCAB1BAA9A185650D52F98BA,781E9ED97865B4EAEB24F256
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,15F374A04F54435C4986C3CEADE49078,3803413B6A6B7D342431E19B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9597CEB0C4C1DE570C408C732C4AAEFF,FFFCC0E7778720989F5D207B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9EA551BB7EEAB54B2CB0B7D5A4B46984,12670590F5569DF36255182B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E0A14DD9D10CA02AAB400CDD6D4CC8DF,3B8D453873F43514FBFD6E61
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6D7D889972B4415B2C4BB94147A8B0C3,9AB50F45E012C1C13DC0C123
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BD143BC0B969AB78A4C77AEF75DF577B,46BA7B2AACE2BE1EAE041BAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AFA3644F3A1B2E3C949409CD1A9FD9EE,4784E15B8459FC8C31BC5A09
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F68A2E4BC14FF93B6E8AED6603681E44,08C81D5D7ACCE6421A3A83C8
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8958835A3177994FD9E234A64E8DD402,4AB52CC30160C00D0CB8149E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FC5A744BCC4C3A64731A56568F1824CD,7691B4A850CC3F520A27CAED
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A0BC0414793FD7CD91C4901ED37CEFEE,50387A5169C4BEF0DFABA169
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CECD899ECEEA771F48B27ADAE869A0FA,AC47989CED4D7ECC858B3316
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,227AE04F7CD994EC2C5028C28EB91C60,2CF79C8535D09CBE08C99BC4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E1EFAED76F88CB13D36D6FA9341E6F72,D0939986C168DF7A71F8692B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0BB39C124E6009D6F192BE076CE0B912,07ECF9210CD0BA6CB7D0A331
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,66B94A56E5ED1CA6D8D13B716C826EFD,AB586CD5C53D9B44AC9CF827
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,74BA0547304F65181E09067ADE3944A8,1BC03458AF15F80379307560
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E5BDCE39E73DED6127D19DC0D2B07509,2AA15E6321BC75606E161299
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5FA9FB4F33B5931DFD0B608D3673CD8B,2EF8B7A870C0B16E2A2E0771
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,591C4B52914FD9906794F9B437297932,57D657E1F9EE91A75BAA281C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,88A97F10BDFC5900CE69B02CD4927D5F,49B141185B37C4BFE45770A6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B699392799957BDD5F7578F616FE052D,E9FAD536A1195A43049FD4FB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CFDE65C7EEAB7B468E5EB53B59560E56,EBDD36D040A23090DD2B7C8C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7DD1288548E9EE0F59C7AB71ED93F458,570BEE5B5B720E1C71CC4188
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F8A953657955164A202FD05D61ADDF18,ADB41A9171F3B9EE64CDA252
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8496897F262DB3DA5B1492BD910EE533,A116D7BB8E39511500CBB3C9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3143866A1BFF58CB64A6AAD00A8B98A0,88A823BB6C87C33D326F89E5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BF2FA6BFFB949B5EF935D4A70A1AD000,059EB7C4093E12A3D67AF43B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,22D61E9979690AE0F70A2097CF75617A,4CEA847615CAAB386441D6D3
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8BA2EEFC875953E279A0BBFF3E40D8EF,EEB30C7A21BF5CFF8C016E76
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0054786656570CAF86CDA3E2658CBC4A,A8D77AF4C0D2968BF86F7EBC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CD789413163D67BEB5A8B4B2041AC8C3,1E8EFC2C49634EF3D3A76663
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D5C2E3294F8E3F7EDDB41D8038E8AE4F,DFB267CA183A97071445EBCD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4547BAA42EC0A2AAA5E4A51A40F508C7,D726295FDD1F03CC38C0D510
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,30675DADA51DD427E74150DAA3D08E47,D98EC07B336FF2636664CEDA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E2049EFBF5A6B4DBFC1D827F7B6F4F79,8A5E61E9C25DABB569047DBB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4DAEAD6EFAFC7B2D5B7599CB572D966A,3C7F91652C4F50ECAE96E31C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,421C75DAE44E53BF661EC64716C3315F,B7C0ECAAD15C95E5A230B0BC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3DB086094870B8992C429827A41388CE,932A80FADA930997A72E765B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1AED87BACBA9546CEFD7577149B343CF,37496BAE7BAB169805B8A404
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1D4C0838314C7B2E969CA01BE86628E8,FDA25570F91EE000DF4A462D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E2CFEBC9DF035EED50D61010B97114DF,507A0EE2F0E2377E9617D79A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B7C41BDBCB3DC0D686FB4F2A246F1EBE,9A8B1932BE12002117B66C60
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7F3C6DDFAAB07F2655835AD684C1EAF8,84F746F4206E7B9D70DCE400
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4BE5B436297DD7EFE78F43B50E9D83AE,6A7351376DEBD0C0045287F5
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,810A6D4C4BCC9B442FCC2D17302ABD52,BE8DFEFD0040A9171DFD1028
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,70BCCBE54FA56D6C760B2A30F12DFE3C,D066D8EAE15177FB53E82557
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,685C7FF8585763703654DA98CA2ADD46,E3AA8A4EEFF90AE719F7C9A1
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,70EF0027B67540B854A5678C2FFDC575,58B0CE51BE4916D04D84D042
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AA3ED4404B99F8C48BC481239F470693,C7184C0EC24D703FE74FE294
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9E5EF17C6BEAA0E6B65199ED365DDAB3,9EE43A1ED3DBFD931A317464
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2E9403A2E3000BA5C4E0D27C2EC64E4C,813E15EBE8D0B17B5823E4BE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0173781A0EB21F505712948BE75B8458,1E3CA90D5A9E8A6464C8E36D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3C088C10489234D6328F501EDBC98C00,9D7C6B66C8630DC1F3F5312D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A6EE02A9C039B2D0FC48FD8A345F1892,0A822A5118F13C0B8734AF15
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BE8C4CBEAF4F1F1A9A3695894F9F2A99,CE0FCD1BC48933F43895E002
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,128432DC7C3146F692965FB0D0A3308D,770FB50A91CC47B0382B00E0
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C88C48F3C4D3EC720F01BF9C6A65265C,8F6DF2A2261D41952A2C382A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B971116F61EB90F27D34457F37AD39D9,2D33847A8C32844FDD307D51
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,227551D0863419A949DD59F36CC83C5D,829E83017A42C31FB195868D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,03234F66F921406777377B0DF204A6DE,C43127089A8D4F7250387C9D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D88DF421C6DE2284789ED5D54F45F03D,3495E7918C60F295548DB637
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C3FA2BC662694EEBEE4781F73CBB18C4,F691B5C42013705DE1C5AE33
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2DDA9DD56CF3E1FA5BC6B9118623A733,469BDF4A8704D356A9A14B2F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C6A403AE0667AF03479DA7D96BC52127,4996261F8E580F85B515239D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9B2F270E808E89FE9FBF7910D371A99A,7904A08879D7368E8D28AB78
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,346CF33E3A1CB2ECCBB0293B22718FE0,4A381CFA638346B0AF50A973
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9DBDF0D1632047D2E033CD99585D4EF4,8D52FC66020546B7F4C0AA7D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C0C92335F55CAFA75C8F32ECC8A8382E,AAFF1231A4FF3657E2A2F050
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F98D69B9C10B8180D8CCEB6F8512EFC8,ECBAA8308702D56AB4B8042E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D18711BD38803D6528A0ACC4A2D13F44,FE40B2A809ED1B288A23EFED
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,B45EB08D49D283FE8D5CD8736CFBB0BA,2DC3543A5FC96E5D77C6A8C6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E5A19F4E5A95B41A6B467A4BB61FAF6D,FDDE48FF9ABA9E1772A997F1
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DEC185B0F7E9E915A245BCDFAA61B9EE,B4E8B8317BB9B202C1216D39
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7CF79DD706BB5431EA0505C5A9FD02DD,B25B155F0F33758DA3E34314
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D7FB84CABA49F0DCDA6CF9BD8DC7706E,47350A6046056A36028A17EB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1552B8D34DA464D3306574792B48A3D9,E941C62F7B37C94DA75700C7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BC7D021BCD887C891AD03F35E0A15BAF,BE126713B56707925500E8FB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9134777CBFF4D521A3BFA344F7A0B437,FDBDA66AC1D9ADAD8755DFE7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7C5F8F8A6BCE5887333A3CAD627717E6,31F970EEF6CD0EB0A2CF4916
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DF8CB343C509A1AD4F2EA7115FCCDB06,693FC7E51AEE34C25011590B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A91315BD7A33231FD527B607D84FC46E,1830B3DC10BDF6965B2C9228
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AD2E8EE1869FF0D47F58E63BC83318BE,5FC704AB4A24BB4EE7D4E116
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6DEAB545F792A82A34768E828C52895D,2C2573C04319579657238F81
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,002EE13DDD62BB00513824409FCC1E3C,714637C12FBC3464601C51B9
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,1B7F26BDA08083628375BEF7D154A39D,70863DE4A86FFCBEB170F92D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,50260BE0CBC61779A3974F29E8F759AA,0977C5842CB846C0F9B7C801
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7CBE72435D57253A7F17B38DBDC91592,FE586E3BB775847DD4D53896
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AD828B0B2057DC6664ED791C5D07B0F0,2DA0521A402DB87C23EAA90F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4D15F0C7057F79CA95112266A0EC2372,C8E87F6A99CD77F1E6C52BDB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2603498B980BB24F3CE98CDCE7132DEC,3D120C7D2A0626188F107796
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7D3F0B786427ACCC65036320D6A87E0D,90EB68AFCE491FF4FC1B935C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CEF85C041E9B497E104280592B4E20A5,D7088EDD39E9441B42FA5425
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,28423C88529A282FEB63168012C7C9CA,8541314D155ED02F6180F8C4
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3D00F94DF50F19EB4FABAEABCFD6D2D0,CBDC0CBBA58873A08254EBBB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2770701DD269B0577098A27093E4CBEA,D433D01C27279F7871B8CFBE
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A6196F4218B78A98B4744669E89272A4,6D9355226838299D764B92FD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C2746977B2BDFC92BFBD7B108E31888F,13349E628F46C2871403E8BD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,EEC4692F450CC83E13E2B71D59B3A3A6,8373F07A23AA5A770F09475E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C6016D020BC4C0FFB135065862E023E1,C65DD883032F002242741716
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7EC7083310F0065F3B96E8472A3B13D1,1023CB8704E3F094C4254B40
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CF0F35022BD195F113924F7FE3170685,F91BEF9CEBB0E62E17D189F1
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,13B6AB833AA9181A0D0589C321E1D641,B2393292651E5C5FAC10EEA2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,78E0922DFDD9664537CA30DE31608273,2BB017C76BEC45864CE8B5DA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,92D6133ADB98116A1A8D5233C0D82798,A638E5FB37FD99BDAE373B67
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E63DD02079DC3B5E6EB39366B31C321B,6F90B73C6489D8259C852982
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,18A08A6C828005707749B55A9B372486,D5EB1D8073248AD37B8F239D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D27A6AFDA367D22533F4A41A08E728D6,C0C1517C33F09E91EAC4779A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9DB473E798478DA64AF4ED702F4BC9F2,3022D423A1C7629E82A5A24E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D9B45909B8FB54224223494EEA6E2E30,032151B6AB3213BB7DDFD85D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,8031C87FC0B6482EE41FDAE9452ECB50,B6944CB7B74EAB9C478F1C52
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D75BDA282D511A8FDA9D2A668C9C8DAF,2C95EC0DD014289FFB35213F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CEF23CA86FACB24BC87C7A6F8E3B1C88,18A4524E271A78D2DE5E7499
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FF647B9CC719F05B6FC23549E39B1357,0C865AB29E48C3C797B021D6
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,27F8DA6420EAD9D9577BB23FBC8505DF,C3C5015754C0AFA87182B618
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FF8354FB4DF2F4DCAD9810E6D6BE2701,82C123077C870F46B60D29BD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,036E56648887DBAA96FFA30E83D8AD82,B8DCA4880CAD8DB0E51FD51C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,86E00865EFAA86388A9005B52F005717,9241332752219C7BB5A5D3CD
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,C9629A55E7894963AD2EEC2468FE7DBB,E1136ACDFFD9517A555B0F27
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E09B731993F83B3F5B3870A764951ABE,9BF47B15A4384C738487A24B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2378E55D7C30DFC2977F4B1DC4F9F5E8,48EB70F03A3CCB87CD82A158
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6184EA341945118E0646CBA5D58384B4,77675FA63C5BD4A41AC05381
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3BFDF98A86FD921FB72992EC14F7BB45,F538CEDE6978E01EAACBB45B
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,4456A5A28E7312B13115462951C92AFC,72A818CF0DC16D1CF61AAB95
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FDD3E2786D4F2CFD9480D66573BCFC9C,2841008AEF343BB28D103C8D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,6DD6D61B2289A9410CFD9714CCECD4C8,F59AB2D1B59D311242978F3F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F167C9441706277D09DDCDD7F7E52A64,126858DE4A4F5819448B7834
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,980FF11DA5B60F457D8AD89A85974578,379707AA8D96C5D76FA14C03
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,70AAC833D245454AE89349A7446EE506,5D79BA0B6FFD9631AF1CB4C7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,927181350C74438DE3B9E8098A617E9B,F763FBD8F7F514F54B18A736
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,CE2FC9FE401D5EEDE241E70202A918BB,6D93D8C321F8333F0AEA5D7D
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D0052E34EB985536E784846E9328E485,4AE323A62EEF67882838712A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F0258B4E9282FCC224A3D6CD3B03CBD9,91BE1348792DB54DB4CE8DED
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,D14CDA342B363D1D643CA37F69459559,9B5263789B687DA01AD2408F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,3D1B125A5450D9985C1FEB85BBC60A50,36BEC97C697AA64958AC0150
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,89F95D62593A08D9C22E451C7FB8C5BA,D57A8B45E9EAA8AB3A514325
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,5B1FD080E4A66D7057CFADEF6FED9146,1BCDA51E01D6A6BD34B4447F
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AFA91C8D534BF172752D8A5A1E5ABF18,1171B4FE506D4A96CDDEC1A3
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,928BCB824199B826E519EADA61A4C251,EB2F19E5D73C846D8C714D3E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F53692F3FB3982697190167751544A29,7AD742D7360FA6C3D379B2B7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,35E981691DFC03F3594134BBB7666048,E80AAB73B59F47BF4EE3ACA2
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E1C591DED46E4151A2758DEE40739154,46FE19CD4CFFB3A2A20C2DED
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FDDF6A5BB92A46D2E62A4D892D62DA3E,91A0067C6F79DCDD23291EB7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2D57BDF8192CA18115B7D8475314A217,9714FD544700667211D17C04
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9B362EB227A8F692CBCAAE2A35126D4A,6A9616C6A43C428F826A47F7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,AFA7288D0D58EADB8C9786402A6E64D8,997C242B49CB8E562D648454
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DC52408D8CA5204B565EDC5A0FC1F62A,EE9CB66B77A507D22BF44138
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,FCF61DE9035595B243208EC41671C65A,2FF077600EA3FA5A0D767078
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,9A583D3FDD3982A15876623AA3030000,19A047A8A76A867653D4911C
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2DCFD0800243D3D0D713A0FE4B1C41C9,C64FC0E63E24B5990B10208E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,72213EB6319FADAE5FFE5DBD4FD3F03E,8BB15C1A20487D9F61DCBB2A
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,2895AC8B667126F52244CEC0831D2C11,179EC0708E768B387560A03E
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7091C9ABA945FF32B3CD9818DE84AEE5,2107211D6BC2DC36418D81EC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,219370CB0713962417F979DED2342075,BF57FBAF865A8BADEA9AABE7
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,F836740255B8ECB99BA7AEE2CE8C53BB,A3A5D83BA6B66D2EC39B4035
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,E31DC4205A93AC332F96CA5E7BC9FCA7,73483A4971376226A7D00F88
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,A5128C271F394B9FF303E9D169451808,E189B4B86326531262853FCB
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,7F8AB312C58E36A6FF9B80D9FA784D4F,77BEDAC4AF6EBD00DDCA8663
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,DD6FAC2574A4358D54BF123B5B2E4D47,29D903403B472B3968A0D511
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,85CAE759E11DCD58B02C500C539C1FD2,EA65F4805AED4E612A71FB27
diff --git a/src/tests/config/test.conf b/src/tests/config/test.conf
new file mode 100644
index 0000000..832b125
--- /dev/null
+++ b/src/tests/config/test.conf
@@ -0,0 +1,114 @@
+# -*- text -*-
+##
+## test.conf -- Virtual server configuration for testing radiusd.
+##
+## $Id$
+##
+
+test_port = 10000
+
+correct_escapes = true
+
+# Only for testing!
+# Setting this on a production system is a BAD IDEA.
+security {
+ allow_vulnerable_openssl = yes
+}
+
+modules {
+ $INCLUDE ${maindir}/mods-enabled/
+ $INCLUDE ${testdir}/config/eap-test
+}
+
+realm test.example.com {
+ authhost = 127.0.0.1:${test_port}
+ secret = testing123
+}
+
+policy {
+ files.authorize {
+ if (User-Name == "bob") {
+ update control {
+ &Cleartext-Password := "bob"
+ }
+ }
+ }
+
+ $INCLUDE ${maindir}/policy.d/
+}
+
+
+#
+# This virtual server is chosen for processing requests when using:
+#
+# radiusd -Xd src/tests/ -i 127.0.0.1 -p 12340 -n test
+#
+server test {
+ listen {
+ ipaddr = 127.0.0.1
+ port = ${test_port}
+ type = auth
+ }
+
+authorize {
+ update reply {
+ &Test-Server-Port = "%{Packet-Dst-Port}"
+ }
+
+ if (User-Name == "bob") {
+ #
+ # Digest-* tests have a password of "zanzibar"
+ # Or, a hashed version thereof.
+ #
+ if (Digest-Response) {
+ if (&Test-Number == "1") {
+ update control {
+ &Cleartext-Password := "zanzibar"
+ }
+ }
+ elsif (Test-Number == "2") {
+ update control {
+ &Digest-HA1 := 12af60467a33e8518da5c68bbff12b11
+ }
+ }
+ }
+ else {
+ update control {
+ &Cleartext-Password := "bob"
+ }
+ }
+ }
+
+ if (User-Name =~ /^(.*)@test\.example\.com$/) {
+ update request {
+ &Stripped-User-Name := "%{1}"
+ }
+ update control {
+ &Proxy-To-Realm := test.example.com
+ }
+ }
+
+ chap
+ mschap
+ digest
+ eap-test
+ pap
+}
+
+authenticate {
+ pap
+ chap
+ mschap
+ digest
+ eap-test
+}
+
+accounting {
+ if (Packet-Src-IP-Address != 255.255.255.255) {
+ detail
+ }
+
+ ok
+}
+
+}
diff --git a/src/tests/dictionary.test b/src/tests/dictionary.test
new file mode 100644
index 0000000..1b3130f
--- /dev/null
+++ b/src/tests/dictionary.test
@@ -0,0 +1,11 @@
+#
+# Used for internal testing
+#
+VENDOR TEST 32000
+
+BEGIN-VENDOR TEST
+ATTRIBUTE Test-Name 1 string
+ATTRIBUTE Test-Number 2 integer
+ATTRIBUTE Test-Server-Port 3 integer
+ATTRIBUTE Test-Signed 4 signed
+END-VENDOR TEST
diff --git a/src/tests/digest-01/digest-auth-MD5 b/src/tests/digest-01/digest-auth-MD5
new file mode 100644
index 0000000..cdac8f7
--- /dev/null
+++ b/src/tests/digest-01/digest-auth-MD5
@@ -0,0 +1,28 @@
+#
+# http://ftp6.us.freebsd.org/pub/rfc/internet-drafts/draft-smith-sipping-auth-examples-01.txt
+#
+# Section 3.3
+#
+#
+# In the "users" file:
+#
+# bob Cleartext-Password := "zanzibar"
+# Or bob Digest-HA1 := "12af60467a33e8518da5c68bbff12b11"
+#
+#
+#
+# How many tests we have for this input file
+#
+# TESTS 1 2
+#
+User-Name = "bob",
+Digest-Response = "89eb0059246c02b2f6ee02c7961d5ea3",
+Digest-Realm = "biloxi.com",
+Digest-Nonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093",
+Digest-Method = "INVITE",
+Digest-URI = "sip:bob@biloxi.com",
+Digest-User-Name = "bob",
+Digest-QOP = "auth",
+Digest-Algorithm = "MD5",
+Digest-Nonce-Count = "00000001",
+Digest-CNonce = "0a4f113b",
diff --git a/src/tests/digest-01/digest-auth-MD5_Sess b/src/tests/digest-01/digest-auth-MD5_Sess
new file mode 100644
index 0000000..1c8d0ac
--- /dev/null
+++ b/src/tests/digest-01/digest-auth-MD5_Sess
@@ -0,0 +1,23 @@
+#
+# http://ftp6.us.freebsd.org/pub/rfc/internet-drafts/draft-smith-sipping-auth-examples-01.txt
+##
+# 3.4
+#
+#
+# In the "users" file:
+# bob User-Password := "zanzibar"
+# Or bob Digest-HA1 := "12af60467a33e8518da5c68bbff12b11"
+#
+# TESTS 1 2
+#
+User-Name = "bob",
+Digest-Response = "e4e4ea61d186d07a92c9e1f6919902e9",
+Digest-Realm = "biloxi.com",
+Digest-Nonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093",
+Digest-Method = "INVITE",
+Digest-URI = "sip:bob@biloxi.com",
+Digest-User-Name = "bob",
+Digest-QOP = "auth",
+Digest-Algorithm = "MD5-Sess",
+Digest-Nonce-Count = "00000001",
+Digest-CNonce = "0a4f113b",
diff --git a/src/tests/digest-01/digest-auth-int b/src/tests/digest-01/digest-auth-int
new file mode 100644
index 0000000..f3ea45e
--- /dev/null
+++ b/src/tests/digest-01/digest-auth-int
@@ -0,0 +1,23 @@
+#
+# http://ftp6.us.freebsd.org/pub/rfc/internet-drafts/draft-smith-sipping-auth-examples-01.txt
+#
+# 3.5.2
+#
+#
+# In the "users" file: bob Cleartext-Password := "zanzibar"
+#
+# TESTS 1
+#
+User-Name = "bob",
+Digest-Response = "bdbeebb2da6adb6bca02599c2239e192"
+Digest-Realm = "biloxi.com",
+Digest-Nonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093",
+Digest-Method = "INVITE",
+Digest-URI = "sip:bob@biloxi.com",
+Digest-Algorithm = "MD5",
+Digest-User-Name = "bob",
+Digest-QOP = "auth-int",
+Digest-Nonce-Count = "00000001",
+Digest-CNonce = "0a4f113b",
+Digest-Body-Digest = "c1ed018b8ec4a3b170c0921f5b564e48",
+Message-Authenticator = ""
diff --git a/src/tests/digest-01/digest-auth-noalgo b/src/tests/digest-01/digest-auth-noalgo
new file mode 100644
index 0000000..1ab6ba6
--- /dev/null
+++ b/src/tests/digest-01/digest-auth-noalgo
@@ -0,0 +1,21 @@
+#
+# http://ftp6.us.freebsd.org/pub/rfc/internet-drafts/draft-smith-sipping-auth-examples-01.txt
+#
+# 3.2
+#
+# In the "users" file:
+# bob User-Password := "zanzibar"
+# Or bob Digest-HA1 := "12af60467a33e8518da5c68bbff12b11"
+#
+# TESTS 1
+#
+User-Name = "bob",
+Digest-Response = "89eb0059246c02b2f6ee02c7961d5ea3",
+Digest-Realm = "biloxi.com",
+Digest-Nonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093",
+Digest-Method = "INVITE",
+Digest-URI = "sip:bob@biloxi.com",
+Digest-User-Name = "bob",
+Digest-QOP = "auth",
+Digest-Nonce-Count = "00000001",
+Digest-CNonce = "0a4f113b",
diff --git a/src/tests/digest-01/digest-auth_int-MD5 b/src/tests/digest-01/digest-auth_int-MD5
new file mode 100644
index 0000000..30e1c48
--- /dev/null
+++ b/src/tests/digest-01/digest-auth_int-MD5
@@ -0,0 +1,23 @@
+#
+# http://ftp6.us.freebsd.org/pub/rfc/internet-drafts/draft-smith-sipping-auth-examples-01.txt
+#
+# 3.5.2
+#
+# In the "users" file:
+# bob User-Password := "zanzibar"
+# Or bob Digest-HA1 := "12af60467a33e8518da5c68bbff12b11"
+#
+# TESTS 1 2
+#
+User-Name = "bob",
+Digest-Response = "bdbeebb2da6adb6bca02599c2239e192"
+Digest-Realm = "biloxi.com",
+Digest-Nonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093",
+Digest-Method = "INVITE",
+Digest-URI = "sip:bob@biloxi.com",
+Digest-Algorithm = "MD5",
+Digest-User-Name = "bob",
+Digest-QOP = "auth-int",
+Digest-Nonce-Count = "00000001",
+Digest-CNonce = "0a4f113b",
+Digest-Body-Digest = "c1ed018b8ec4a3b170c0921f5b564e48",
diff --git a/src/tests/digest-01/digest-auth_int-MD5_Sess b/src/tests/digest-01/digest-auth_int-MD5_Sess
new file mode 100644
index 0000000..7665bc0
--- /dev/null
+++ b/src/tests/digest-01/digest-auth_int-MD5_Sess
@@ -0,0 +1,24 @@
+#
+# http://ftp6.us.freebsd.org/pub/rfc/internet-drafts/draft-smith-sipping-auth-examples-01.txt
+##
+# 3.6
+#
+# In the "users" file:
+# bob User-Password := "zanzibar"
+# Or bob Digest-HA1 := "12af60467a33e8518da5c68bbff12b11"
+#
+#
+# TESTS 1 2
+#
+User-Name = "bob",
+Digest-Response = "91984da2d8663716e91554859c22ca70",
+Digest-Realm = "biloxi.com",
+Digest-Nonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093",
+Digest-Method = "INVITE",
+Digest-URI = "sip:bob@biloxi.com",
+Digest-User-Name = "bob",
+Digest-QOP = "auth-int",
+Digest-Algorithm = "MD5-Sess",
+Digest-Nonce-Count = "00000001",
+Digest-CNonce = "0a4f113b",
+Digest-Body-Digest = "c1ed018b8ec4a3b170c0921f5b564e48",
diff --git a/src/tests/digest-01/digest-auth_int-noalgo b/src/tests/digest-01/digest-auth_int-noalgo
new file mode 100644
index 0000000..83675d8
--- /dev/null
+++ b/src/tests/digest-01/digest-auth_int-noalgo
@@ -0,0 +1,22 @@
+#
+# http://ftp6.us.freebsd.org/pub/rfc/internet-drafts/draft-smith-sipping-auth-examples-01.txt
+#
+# 3.5.2
+#
+# In the "users" file:
+# bob User-Password := "zanzibar"
+# Or bob Digest-HA1 := "12af60467a33e8518da5c68bbff12b11"
+#
+# TESTS 1 2
+#
+User-Name = "bob",
+Digest-Response = "bdbeebb2da6adb6bca02599c2239e192"
+Digest-Realm = "biloxi.com",
+Digest-Nonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093",
+Digest-Method = "INVITE",
+Digest-URI = "sip:bob@biloxi.com",
+Digest-User-Name = "bob",
+Digest-QOP = "auth-int",
+Digest-Nonce-Count = "00000001",
+Digest-CNonce = "0a4f113b",
+Digest-Body-Digest = "c1ed018b8ec4a3b170c0921f5b564e48",
diff --git a/src/tests/digest-01/digest-md5-sess b/src/tests/digest-01/digest-md5-sess
new file mode 100644
index 0000000..a77cbab
--- /dev/null
+++ b/src/tests/digest-01/digest-md5-sess
@@ -0,0 +1,21 @@
+#
+# http://ftp6.us.freebsd.org/pub/rfc/internet-drafts/draft-smith-sipping-auth-examples-01.txt
+#
+# ??
+#
+#
+# In the "users" file: bob Cleartext-Password := "zanzibar"
+#
+# TESTS 1
+#
+User-name = "bob",
+Digest-Response = "e4e4ea61d186d07a92c9e1f6919902e9",
+Digest-Realm = "biloxi.com",
+Digest-Nonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093",
+Digest-Method = "INVITE",
+Digest-URI = "sip:bob@biloxi.com",
+Digest-Algorithm = "MD5-sess",
+Digest-User-Name = "bob",
+Digest-QOP = "auth",
+Digest-Nonce-Count = "00000001",
+Digest-CNonce = "0a4f113b"
diff --git a/src/tests/eap-fast.conf b/src/tests/eap-fast.conf
new file mode 100644
index 0000000..b80f270
--- /dev/null
+++ b/src/tests/eap-fast.conf
@@ -0,0 +1,15 @@
+network={
+ key_mgmt=IEEE8021X
+ eap=FAST
+
+ anonymous_identity="anonymous"
+
+ identity="bob"
+ password="bob"
+
+ phase1="tls_disable_session_ticket=0 fast_provisioning=2"
+
+ pac_file="blob://eap-fast-pac"
+
+ ca_cert="../../raddb/certs/ca.pem"
+}
diff --git a/src/tests/eap-md5.conf b/src/tests/eap-md5.conf
new file mode 100644
index 0000000..9e2eca6
--- /dev/null
+++ b/src/tests/eap-md5.conf
@@ -0,0 +1,10 @@
+#
+# eapol_test -c eap-md5.conf -s testing123 -n
+#
+network={
+ key_mgmt=NONE
+ eap=MD5
+
+ identity="bob"
+ password="bob"
+}
diff --git a/src/tests/eap-mschapv2.conf b/src/tests/eap-mschapv2.conf
new file mode 100644
index 0000000..4afcfd0
--- /dev/null
+++ b/src/tests/eap-mschapv2.conf
@@ -0,0 +1,10 @@
+#
+# eapol_test -c eap-mschapv2.conf -s testing123
+#
+network={
+ key_mgmt=IEEE8021X
+ eap=MSCHAPV2
+
+ identity="bob"
+ password="bob"
+}
diff --git a/src/tests/eap-pwd.conf b/src/tests/eap-pwd.conf
new file mode 100644
index 0000000..a756cc1
--- /dev/null
+++ b/src/tests/eap-pwd.conf
@@ -0,0 +1,11 @@
+network={
+ key_mgmt=IEEE8021X
+ eap=PWD
+
+ identity="bob"
+ password="bob"
+
+ pairwise=CCMP
+ group=CCMP
+ priority=1
+}
diff --git a/src/tests/eap-tls.conf b/src/tests/eap-tls.conf
new file mode 100644
index 0000000..c88a559
--- /dev/null
+++ b/src/tests/eap-tls.conf
@@ -0,0 +1,19 @@
+#
+# eapol_test -c eap-tls.conf -s testing123
+#
+# Set also "nostrip" in raddb/proxy.conf, realm "example.com"
+# And make it a LOCAL realm.
+#
+network={
+ key_mgmt=IEEE8021X
+ eap=TLS
+
+ identity="user@example.org"
+
+ phase1=""
+
+ ca_cert="../../raddb/certs/ca.pem"
+ client_cert="../../raddb/certs/client.crt"
+ private_key="../../raddb/certs/client.key"
+ private_key_passwd="whatever"
+}
diff --git a/src/tests/eap-ttls-eap-mschapv2.conf b/src/tests/eap-ttls-eap-mschapv2.conf
new file mode 100644
index 0000000..abe5d33
--- /dev/null
+++ b/src/tests/eap-ttls-eap-mschapv2.conf
@@ -0,0 +1,17 @@
+#
+# eapol_test -c eap-ttls-eap-mschapv2.conf -s testing123
+#
+network={
+ key_mgmt=IEEE8021X
+ eap=TTLS
+
+ anonymous_identity="anonymous"
+
+ identity="bob"
+ password="bob"
+
+ phase1=""
+ phase2="autheap=MSCHAPV2"
+
+ ca_cert="../../raddb/certs/ca.pem"
+}
diff --git a/src/tests/eap-ttls-eap-tls.conf b/src/tests/eap-ttls-eap-tls.conf
new file mode 100644
index 0000000..0937473
--- /dev/null
+++ b/src/tests/eap-ttls-eap-tls.conf
@@ -0,0 +1,15 @@
+network={
+ key_mgmt=IEEE8021X
+ eap=TTLS
+ identity="user@example.org"
+
+ phase1=""
+ phase2="autheap=TLS"
+
+ ca_cert="../../raddb/certs/ca.pem"
+
+ ca_cert2="../../raddb/certs/ca.pem"
+ client_cert2="../../raddb/certs/client.crt"
+ private_key2="../../raddb/certs/client.key"
+ private_key2_passwd="whatever"
+}
diff --git a/src/tests/eap-ttls-mschapv2.conf b/src/tests/eap-ttls-mschapv2.conf
new file mode 100644
index 0000000..7901ac8
--- /dev/null
+++ b/src/tests/eap-ttls-mschapv2.conf
@@ -0,0 +1,12 @@
+#
+# eapol_test -c eap-ttls-mschapv2.conf -s testing123
+#
+network={
+ key_mgmt=IEEE8021X
+ eap=TTLS
+ anonymous_identity="anonymous"
+ identity="bob"
+ password="bob"
+ phase1=""
+ phase2="auth=MSCHAPV2"
+}
diff --git a/src/tests/eap-ttls-pap.conf b/src/tests/eap-ttls-pap.conf
new file mode 100644
index 0000000..19fd752
--- /dev/null
+++ b/src/tests/eap-ttls-pap.conf
@@ -0,0 +1,12 @@
+#
+# eapol_test -c eap-ttls-pap.conf -s testing123
+#
+network={
+ key_mgmt=IEEE8021X
+ eap=TTLS
+ anonymous_identity="anonymous"
+ identity="bob"
+ password="bob"
+ phase1=""
+ phase2="auth=PAP"
+}
diff --git a/src/tests/eapcrypto-01/eapcrypto-out.txt b/src/tests/eapcrypto-01/eapcrypto-out.txt
new file mode 100644
index 0000000..3b12512
--- /dev/null
+++ b/src/tests/eapcrypto-01/eapcrypto-out.txt
@@ -0,0 +1,63 @@
+SHA1buffer was: 65617073_696d0011_22334455_66771021_32435465_
+ 76873041_52637485_96a74d6c_40de483a_dd995090_
+ 2c4024ce_765e0002_00010001
+Input was:
+ identity: (len=6)65617073696d
+ nonce_mt: 4d6c40de483add9950902c4024ce765e
+ rand0: 89abcdef89abcdef89abcdef89abcdef
+ rand1: 9abcdef89abcdef89abcdef89abcdef8
+ rand2: abcdef89abcdef89abcdef89abcdef89
+ sres0: 1234abcd
+ sres1: 1234abcd
+ sres2: 234abcd1
+ Kc0: 0011223344556677
+ Kc1: 1021324354657687
+ Kc2: 30415263748596a7
+ versionlist[4]: 00020001
+ select 00 01
+
+
+Output
+mk: d1cdd6d3_574ef82e_c1e83879_559e89f8_de8e6e90
+K_aut: 54323970_481b5159_48d00a34_422bbe3c
+K_encr: 72469fd8_bb6c2a4a_93ac42e5_b4668acb
+msk: 0a572a3f_2baeea10_640598c9_41901995_f842097a
+ cbb13272_bc949b66_8fb4f5a3_deefed09_3947fe64
+ c88f7df8_dadcab5f_8d003913_8e9bcff7_1a810316
+ 11eeb959
+emsk: 207256b3_9ada49ef_c1850c12_30461921_d700f9f7
+ 83a0f5a9_cff155bd_6c8f1aa5_072b7530_af44f515
+ 3cb983b0_8343effa_2eb161e2_7d3543bc_5bd6db22
+ 993af27b
+SHA1buffer was: 31323434_30373031_30303030_30303031_40656170_
+ 73696d2e_666f6fa0_a1a2a3a4_a5a6a7b0_b1b2b3b4_
+ b5b6b7c0_c1c2c3c4_c5c6c701_23456789_abcdeffe_
+ dcba9876_54321000_010001
+Input was:
+ identity: (len=27)313234343037303130303030303030314065617073696d2e666f6f
+ nonce_mt: 0123456789abcdeffedcba9876543210
+ rand0: 101112131415161718191a1b1c1d1e1f
+ rand1: 202122232425262728292a2b2c2d2e2f
+ rand2: 303132333435363738393a3b3c3d3e3f
+ sres0: d1d2d3d4
+ sres1: e1e2e3e4
+ sres2: f1f2f3f4
+ Kc0: a0a1a2a3a4a5a6a7
+ Kc1: b0b1b2b3b4b5b6b7
+ Kc2: c0c1c2c3c4c5c6c7
+ versionlist[2]: 0001
+ select 00 01
+
+
+Output
+mk: e576d5ca_332e9930_018bf1ba_ee2763c7_95b3c712
+K_aut: 25af1942_efcbf4bc_72b39434_21f2a974
+K_encr: 536e5ebc_4465582a_a6a8ec99_86ebb620
+msk: 39d45aea_f4e30601_983e972b_6cfd46d1_c3637733
+ 65690d09_cd44976b_525f47d3_a60a985e_955c53b0
+ 90b2e4b7_3719196a_40254296_8fd14a88_8f46b9a7
+ 886e4488
+emsk: 5949eab0_fff69d52_315c6c63_4fd14a7f_0d52023d
+ 56f79698_fa6596ab_eed4f93f_bb48eb53_4d985414
+ ceed0d9a_8ed33c38_7c9dfdab_92ffbdf2_40fcecf6
+ 5a2c93b9
diff --git a/src/tests/eapmd5-01/client.gdb b/src/tests/eapmd5-01/client.gdb
new file mode 100644
index 0000000..b77a269
--- /dev/null
+++ b/src/tests/eapmd5-01/client.gdb
@@ -0,0 +1,5 @@
+file ../../main/radeapclient
+set args -s -x localhost auth testing123 <req.txt
+
+
+
diff --git a/src/tests/eapmd5-01/client.sh b/src/tests/eapmd5-01/client.sh
new file mode 100644
index 0000000..53f84b1
--- /dev/null
+++ b/src/tests/eapmd5-01/client.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+#( echo 'User-Name = "eapmd5"';
+# echo 'Cleartext-Password = "md5md5"';
+# echo 'NAS-IP-Address = marajade.sandelman.ottawa.on.ca';
+# echo 'EAP-Code = Response';
+# echo 'EAP-Id = 210';
+# echo 'EAP-Type-Identity = "eapsim';
+# echo 'Message-Authenticator = 0';
+# echo 'NAS-Port = 0' ) >req.txt
+
+../../modules/rlm_eap/radeapclient -s -x localhost auth testing123 <req.txt
+
+
diff --git a/src/tests/eapmd5-01/req.txt b/src/tests/eapmd5-01/req.txt
new file mode 100644
index 0000000..d0de5e1
--- /dev/null
+++ b/src/tests/eapmd5-01/req.txt
@@ -0,0 +1,8 @@
+User-Name = "eapmd5"
+Cleartext-Password = "md5md5"
+NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+EAP-Code = Response
+EAP-Id = 210
+EAP-Type-Identity = "eapsim"
+Message-Authenticator = 0
+NAS-Port = 0
diff --git a/src/tests/eapsim-02/check.gdb b/src/tests/eapsim-02/check.gdb
new file mode 100644
index 0000000..72da283
--- /dev/null
+++ b/src/tests/eapsim-02/check.gdb
@@ -0,0 +1,3 @@
+# $Id$
+file ./eapsimlibtest
+set args <eapsim-in.txt
diff --git a/src/tests/eapsim-02/client.sh b/src/tests/eapsim-02/client.sh
new file mode 100644
index 0000000..32c5aff
--- /dev/null
+++ b/src/tests/eapsim-02/client.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+( echo 'User-Name = "eapsim"';
+ echo 'Cleartext-Password = "md5md5"';
+ echo 'NAS-IP-Address = marajade.sandelman.ottawa.on.ca';
+ echo 'EAP-Code = Response';
+ echo 'EAP-Id = 210';
+ echo 'EAP-Type-Identify = "eapsim';
+ echo 'Message-Authenticator = 0';
+ echo 'NAS-Port = 0' ) >req.txt
+
+../../main/radeapclient -x localhost auth testing123 <req.txt
+
+
diff --git a/src/tests/eapsim-02/eapsim-in.txt b/src/tests/eapsim-02/eapsim-in.txt
new file mode 100644
index 0000000..b138c64
--- /dev/null
+++ b/src/tests/eapsim-02/eapsim-in.txt
@@ -0,0 +1,59 @@
+User-Name = "eapsim"
+NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+EAP-Code = Response
+EAP-Id = 210
+EAP-Type-Identity = "eapmd5"
+Message-Authenticator = 0
+NAS-Port = 0
+
+User-Name = "eapsim"
+NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+EAP-Code = Request
+EAP-Id = 211
+EAP-Sim-Subtype = Start
+EAP-Sim-ANY_ID_REQ = 0x0000
+EAP-Sim-VERSION_LIST = 0x000100010000
+Message-Authenticator = 0
+NAS-Port = 0
+
+User-Name = "eapsim"
+NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+EAP-Code = Response
+EAP-Id = 211
+EAP-Sim-Subtype = Start
+EAP-Sim-NONCE_MT = 0x00004d6c40de483add9950902c4024ce765e
+EAP-Sim-IDENTITY = 0x000c456170536572766572477579
+EAP-Sim-SELECTED_VERSION = 0x0001
+Message-Authenticator = 0
+NAS-Port = 0
+
+User-Name = "eapsim"
+NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+EAP-Code = Request
+EAP-Id = 212
+EAP-Sim-Subtype = Challenge
+EAP-Sim-RAND = 0x00000123456789abcdef123456789abcdeff23456789abcdefff
+EAP-Sim-MAC = 0x4d6c40de483add9950902c4024ce765e
+EAP-Sim-KEY = 0x0123456789abcdef0123456789abcdef
+EAP-Sim-EXTRA = 0x4d6c40de483add9950902c4024ce765e
+Message-Authenticator = 0
+NAS-Port = 0
+
+NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+EAP-Code = Request
+EAP-Id = 212
+EAP-Sim-Subtype = Challenge
+EAP-Sim-Identity = 0x001a323134343730313030303030303031306540706169732e6d6f66
+EAP-Sim-Rand1 = 0x101112131415161718191a1b1c1d1e1f
+EAP-Sim-SRES1 = 0xd1d2d3d4
+EAP-Sim-KC1 = 0xa0a1a2a3a4a5a6a7
+EAP-Sim-Rand2 = 0x202122232425262728292a2b2c2d2e2f
+EAP-Sim-SRES2 = 0xe1e2e3e4
+EAP-Sim-KC2 = 0xb0b1b2b3b4b5b6b7
+EAP-Sim-Rand3 = 0x303132333435363738393a3b3c3d3e3f
+EAP-Sim-SRES3 = 0xf1f2f3f4
+EAP-Sim-KC3 = 0xc0c1c2c3c4c5c6c7
+
+
+
+
diff --git a/src/tests/eapsim-02/eapsim-out.txt b/src/tests/eapsim-02/eapsim-out.txt
new file mode 100644
index 0000000..c582c51
--- /dev/null
+++ b/src/tests/eapsim-02/eapsim-out.txt
@@ -0,0 +1,161 @@
+
+Read:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ EAP-Id = 210
+ EAP-Type-Identity = "eapmd5"
+ Message-Authenticator = 0x30
+ NAS-Port = 0
+Mapped to:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ EAP-Id = 210
+ EAP-Type-Identity = "eapmd5"
+ Message-Authenticator = 0x30
+ NAS-Port = 0
+ EAP-Message = 0x02d2000b016561706d6435
+Unmapped to:
+ EAP-Message = 0x02d2000b016561706d6435
+ EAP-Id = 210
+ EAP-Code = Response
+ EAP-Type-Identity = "eapmd5"
+
+Read:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Request
+ EAP-Id = 211
+ EAP-Sim-Subtype = Start
+ EAP-Sim-ANY_ID_REQ = 0x0000
+ EAP-Sim-VERSION_LIST = 0x000100010000
+ Message-Authenticator = 0x30
+ NAS-Port = 0
+Mapped to:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Request
+ EAP-Id = 211
+ EAP-Sim-Subtype = Start
+ EAP-Sim-ANY_ID_REQ = 0x0000
+ EAP-Sim-VERSION_LIST = 0x000100010000
+ Message-Authenticator = 0x30
+ NAS-Port = 0
+ EAP-Message = 0x01d30014120a00000d0100000f02000100010000
+Unmapped to:
+ EAP-Message = 0x01d30014120a00000d0100000f02000100010000
+ EAP-Id = 211
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0a00000d0100000f02000100010000
+ EAP-Sim-Subtype = Start
+ EAP-Sim-ANY_ID_REQ = 0x0000
+ EAP-Sim-VERSION_LIST = 0x000100010000
+
+Read:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ EAP-Id = 211
+ EAP-Sim-Subtype = Start
+ EAP-Sim-NONCE_MT = 0x00004d6c40de483add9950902c4024ce765e
+ EAP-Sim-IDENTITY = 0x000c456170536572766572477579
+ EAP-Sim-SELECTED_VERSION = 0x0001
+ Message-Authenticator = 0x30
+ NAS-Port = 0
+Mapped to:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ EAP-Id = 211
+ EAP-Sim-Subtype = Start
+ EAP-Sim-NONCE_MT = 0x00004d6c40de483add9950902c4024ce765e
+ EAP-Sim-IDENTITY = 0x000c456170536572766572477579
+ EAP-Sim-SELECTED_VERSION = 0x0001
+ Message-Authenticator = 0x30
+ NAS-Port = 0
+ EAP-Message = 0x02d30030120a0000070500004d6c40de483add9950902c4024ce765e0e04000c45617053657276657247757910010001
+Unmapped to:
+ EAP-Message = 0x02d30030120a0000070500004d6c40de483add9950902c4024ce765e0e04000c45617053657276657247757910010001
+ EAP-Id = 211
+ EAP-Code = Response
+ EAP-Type-SIM = 0x0a0000070500004d6c40de483add9950902c4024ce765e0e04000c45617053657276657247757910010001
+ EAP-Sim-Subtype = Start
+ EAP-Sim-NONCE_MT = 0x00004d6c40de483add9950902c4024ce765e
+ EAP-Sim-IDENTITY = 0x000c456170536572766572477579
+ EAP-Sim-SELECTED_VERSION = 0x0001
+
+Read:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Request
+ EAP-Id = 212
+ EAP-Sim-Subtype = Challenge
+ EAP-Sim-RAND = 0x00000123456789abcdef123456789abcdeff23456789abcdefff
+ EAP-Sim-MAC = 0x4d6c40de483add9950902c4024ce765e
+ EAP-Sim-KEY = 0x0123456789abcdef0123456789abcdef
+ EAP-Sim-EXTRA = 0x4d6c40de483add9950902c4024ce765e
+ Message-Authenticator = 0x30
+ NAS-Port = 0
+Mapped to:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Request
+ EAP-Id = 212
+ EAP-Sim-Subtype = Challenge
+ EAP-Sim-RAND = 0x00000123456789abcdef123456789abcdeff23456789abcdefff
+ EAP-Sim-MAC = 0x4d6c40de483add9950902c4024ce765e
+ EAP-Sim-KEY = 0x0123456789abcdef0123456789abcdef
+ EAP-Sim-EXTRA = 0x4d6c40de483add9950902c4024ce765e
+ Message-Authenticator = 0x30
+ NAS-Port = 0
+ EAP-Message = 0x01d40038120b0000010700000123456789abcdef123456789abcdeff23456789abcdefff0b05000013ff4927cdce9e996d7dd44d860802e8
+Unmapped to:
+ EAP-Message = 0x01d40038120b0000010700000123456789abcdef123456789abcdeff23456789abcdefff0b05000013ff4927cdce9e996d7dd44d860802e8
+ EAP-Id = 212
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010700000123456789abcdef123456789abcdeff23456789abcdefff0b05000013ff4927cdce9e996d7dd44d860802e8
+ EAP-Sim-Subtype = Challenge
+ EAP-Sim-RAND = 0x00000123456789abcdef123456789abcdeff23456789abcdefff
+ EAP-Sim-MAC = 0x000013ff4927cdce9e996d7dd44d860802e8
+Confirming MAC...succeed
+
+Read:
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Request
+ EAP-Id = 212
+ EAP-Sim-Subtype = Challenge
+ EAP-Sim-IDENTITY = 0x001a323134343730313030303030303031306540706169732e6d6f66
+ EAP-Sim-Rand1 = 0x101112131415161718191a1b1c1d1e1f
+ EAP-Sim-SRES1 = 0xd1d2d3d4
+ EAP-Sim-KC1 = 0xa0a1a2a3a4a5a6a7
+ EAP-Sim-Rand2 = 0x202122232425262728292a2b2c2d2e2f
+ EAP-Sim-SRES2 = 0xe1e2e3e4
+ EAP-Sim-KC2 = 0xb0b1b2b3b4b5b6b7
+ EAP-Sim-Rand3 = 0x303132333435363738393a3b3c3d3e3f
+ EAP-Sim-SRES3 = 0xf1f2f3f4
+ EAP-Sim-KC3 = 0xc0c1c2c3c4c5c6c7
+Mapped to:
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Request
+ EAP-Id = 212
+ EAP-Sim-Subtype = Challenge
+ EAP-Sim-IDENTITY = 0x001a323134343730313030303030303031306540706169732e6d6f66
+ EAP-Sim-Rand1 = 0x101112131415161718191a1b1c1d1e1f
+ EAP-Sim-SRES1 = 0xd1d2d3d4
+ EAP-Sim-KC1 = 0xa0a1a2a3a4a5a6a7
+ EAP-Sim-Rand2 = 0x202122232425262728292a2b2c2d2e2f
+ EAP-Sim-SRES2 = 0xe1e2e3e4
+ EAP-Sim-KC2 = 0xb0b1b2b3b4b5b6b7
+ EAP-Sim-Rand3 = 0x303132333435363738393a3b3c3d3e3f
+ EAP-Sim-SRES3 = 0xf1f2f3f4
+ EAP-Sim-KC3 = 0xc0c1c2c3c4c5c6c7
+ EAP-Message = 0x01d40028120b00000e08001a323134343730313030303030303031306540706169732e6d6f660000
+ Message-Authenticator = 0x00000000000000000000000000000000
+Unmapped to:
+ EAP-Message = 0x01d40028120b00000e08001a323134343730313030303030303031306540706169732e6d6f660000
+ EAP-Id = 212
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b00000e08001a323134343730313030303030303031306540706169732e6d6f660000
+ EAP-Sim-Subtype = Challenge
+ EAP-Sim-IDENTITY = 0x001a323134343730313030303030303031306540706169732e6d6f660000
diff --git a/src/tests/eapsim-02/req.txt b/src/tests/eapsim-02/req.txt
new file mode 100644
index 0000000..28226e7
--- /dev/null
+++ b/src/tests/eapsim-02/req.txt
@@ -0,0 +1,8 @@
+User-Name = "eapsim"
+Cleartext-Password = "md5md5"
+NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+EAP-Code = Response
+EAP-Id = 210
+EAP-Type-Identity = "eapsim"
+Message-Authenticator = 0
+NAS-Port = 0
diff --git a/src/tests/eapsim-03/check.gdb b/src/tests/eapsim-03/check.gdb
new file mode 100644
index 0000000..8418d96
--- /dev/null
+++ b/src/tests/eapsim-03/check.gdb
@@ -0,0 +1,2 @@
+file ../../main/radeapclient
+set args -x localhost auth testing123 <eapsim-in.txt \ No newline at end of file
diff --git a/src/tests/eapsim-03/client.sh b/src/tests/eapsim-03/client.sh
new file mode 100644
index 0000000..2ae1747
--- /dev/null
+++ b/src/tests/eapsim-03/client.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+../../main/radeapclient -x localhost auth testing123 <eapsim-in.txt
+
+
+
diff --git a/src/tests/eapsim-03/eapsim-cooked.txt b/src/tests/eapsim-03/eapsim-cooked.txt
new file mode 100644
index 0000000..caed4d8
--- /dev/null
+++ b/src/tests/eapsim-03/eapsim-cooked.txt
@@ -0,0 +1,169 @@
+
++++> About to send encoded packet:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ EAP-Type-Identity = "eapsim"
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0xabcd1234abcd1234abcd1234abcd1234
+ EAP-Sim-Rand2 = 0xbcd1234abcd1234abcd1234abcd1234a
+ EAP-Sim-Rand3 = 0xcd1234abcd1234abcd1234abcd1234ab
+ EAP-Sim-SRES1 = 0x1234abcd
+ EAP-Sim-SRES2 = 0x234abcd1
+ EAP-Sim-SRES3 = 0x34abcd12
+ EAP-Sim-KC1 = 0x0011223344556677
+ EAP-Sim-KC2 = 0x1021324354657687
+ EAP-Sim-KC3 = 0x30415263748596a7
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Message = 0x02XX
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=999, length=78
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+<+++ EAP decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0a00000f0200020001000011010100
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0a00000f0200020001000011010100
+ EAP-Sim-Subtype = Start
+ EAP-Sim-VERSION_LIST = 0x000200010000
+ EAP-Sim-FULLAUTH_ID_REQ = 0x0100
+
++++> About to send encoded packet:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0xabcd1234abcd1234abcd1234abcd1234
+ EAP-Sim-Rand2 = 0xbcd1234abcd1234abcd1234abcd1234a
+ EAP-Sim-Rand3 = 0xcd1234abcd1234abcd1234abcd1234ab
+ EAP-Sim-SRES1 = 0x1234abcd
+ EAP-Sim-SRES2 = 0x234abcd1
+ EAP-Sim-SRES3 = 0x34abcd12
+ EAP-Sim-KC1 = 0x0011223344556677
+ EAP-Sim-KC2 = 0x1021324354657687
+ EAP-Sim-KC3 = 0x30415263748596a7
+ EAP-Sim-State = 1
+ EAP-Sim-Subtype = Start
+ EAP-Sim-SELECTED_VERSION = 0x0001
+ EAP-Sim-NONCE_MT = 0x000000a3f6b4e832cf46b4d3e0d090623e22
+ EAP-Sim-IDENTITY = 0x000665617073696d
+ EAP-Id = YY
+ EAP-Message = 0x02XX
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Message = 0x02XX
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=999, length=138
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+<+++ EAP decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b050000XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b050000XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ EAP-Sim-Subtype = Challenge
+ EAP-Sim-RAND = 0x0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab
+ EAP-Sim-MAC = 0xYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
+Input was:
+ identity: (len=6)65617073696d
+ nonce_mt: 00a3f6b4e832cf46b4d3e0d090623e22
+ rand0: 00000000000000000000000000000000
+ rand1: 00000000000000000000000000000000
+ rand2: 00000000000000000000000000000000
+ sres0: 1234abcd
+ sres1: 234abcd1
+ sres2: 34abcd12
+ Kc0: 0011223344556677
+ Kc1: 1021324354657687
+ Kc2: 30415263748596a7
+ versionlist[2]: 0001
+ select 00 01
+
+
+Output
+mk: 85153a7d_7dfe0a4f_f3bf72f3_3521ff76_b048dbb2
+K_aut: 72cd7e8c_f2086e24_a98c1780_bc3d745b
+K_encr: be789668_329769c3_73c0b64b_beffd665
+msk: 9be9fbc9_5415fa9e_f9d52563_bddd9758_65a3fadb
+ 47a5815a_7310cf3f_10123d4e_ccaf9d4b_30b13c80
+ 4dd130e5_117f35ae_a0e50b43_9a08b80d_dd15922c
+ f7fd9956
+emsk: 5dd5a779_65415b21_69aa1300_09dc6ba4_96433d1e
+ 72065983_cbe8bc1d_6d744c99_dc76f16f_24324709
+ cb731af2_fbe69c6a_dd302662_a083d7e2_7c05c7cd
+ 279c3f66
+MAC check succeed
+
++++> About to send encoded packet:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0xabcd1234abcd1234abcd1234abcd1234
+ EAP-Sim-Rand2 = 0xbcd1234abcd1234abcd1234abcd1234a
+ EAP-Sim-Rand3 = 0xcd1234abcd1234abcd1234abcd1234ab
+ EAP-Sim-SRES1 = 0x1234abcd
+ EAP-Sim-SRES2 = 0x234abcd1
+ EAP-Sim-SRES3 = 0x34abcd12
+ EAP-Sim-KC1 = 0x0011223344556677
+ EAP-Sim-KC2 = 0x1021324354657687
+ EAP-Sim-KC3 = 0x30415263748596a7
+ EAP-Sim-State = 1
+ EAP-Sim-Subtype = Challenge
+ EAP-Id = YY
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Sim-MAC = 0x1234abcd234abcd134abcd12
+ EAP-Sim-KEY = 0x72cd7e8cf2086e24a98c1780bc3d745b
+ EAP-Message = 0x02XX
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Message = 0x02XX
+rad_recv: Access-Accept packet from host 127.0.0.1:1812, id=999, length=168
+ MS-MPPE-Recv-Key = 0x9be9fbc95415fa9ef9d52563bddd975865a3fadb47a5815a7310cf3f10123d4e
+ MS-MPPE-Send-Key = 0xccaf9d4b30b13c804dd130e5117f35aea0e50b439a08b80ddd15922cf7fd9956
+ EAP-Message = 0x03XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ User-Name = "eapsim"
+<+++ EAP decoded packet:
+ MS-MPPE-Recv-Key = 0x9be9fbc95415fa9ef9d52563bddd975865a3fadb47a5815a7310cf3f10123d4e
+ MS-MPPE-Send-Key = 0xccaf9d4b30b13c804dd130e5117f35aea0e50b439a08b80ddd15922cf7fd9956
+ EAP-Message = 0x03XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ User-Name = "eapsim"
+ EAP-Id = YY
+ EAP-Code = Success
diff --git a/src/tests/eapsim-03/eapsim-in.txt b/src/tests/eapsim-03/eapsim-in.txt
new file mode 100644
index 0000000..ffc3f84
--- /dev/null
+++ b/src/tests/eapsim-03/eapsim-in.txt
@@ -0,0 +1,17 @@
+User-Name = "eapsim"
+NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+EAP-Code = Response
+EAP-Type-Identity = "eapsim"
+Message-Authenticator = 0
+NAS-Port = 0
+EAP-Sim-Rand1 = 0xabcd1234abcd1234abcd1234abcd1234
+EAP-Sim-Rand2 = 0xbcd1234abcd1234abcd1234abcd1234a
+EAP-Sim-Rand3 = 0xcd1234abcd1234abcd1234abcd1234ab
+EAP-Sim-Sres1 = 0x1234abcd
+EAP-Sim-Sres2 = 0x234abcd1
+EAP-Sim-Sres3 = 0x34abcd12
+EAP-Sim-KC1 = 0x0011223344556677
+EAP-Sim-KC2 = 0x1021324354657687
+EAP-Sim-KC3 = 0x30415263748596a7
+
+
diff --git a/src/tests/eapsim-03/eapsim-out.txt b/src/tests/eapsim-03/eapsim-out.txt
new file mode 100644
index 0000000..caed4d8
--- /dev/null
+++ b/src/tests/eapsim-03/eapsim-out.txt
@@ -0,0 +1,169 @@
+
++++> About to send encoded packet:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ EAP-Type-Identity = "eapsim"
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0xabcd1234abcd1234abcd1234abcd1234
+ EAP-Sim-Rand2 = 0xbcd1234abcd1234abcd1234abcd1234a
+ EAP-Sim-Rand3 = 0xcd1234abcd1234abcd1234abcd1234ab
+ EAP-Sim-SRES1 = 0x1234abcd
+ EAP-Sim-SRES2 = 0x234abcd1
+ EAP-Sim-SRES3 = 0x34abcd12
+ EAP-Sim-KC1 = 0x0011223344556677
+ EAP-Sim-KC2 = 0x1021324354657687
+ EAP-Sim-KC3 = 0x30415263748596a7
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Message = 0x02XX
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=999, length=78
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+<+++ EAP decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0a00000f0200020001000011010100
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0a00000f0200020001000011010100
+ EAP-Sim-Subtype = Start
+ EAP-Sim-VERSION_LIST = 0x000200010000
+ EAP-Sim-FULLAUTH_ID_REQ = 0x0100
+
++++> About to send encoded packet:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0xabcd1234abcd1234abcd1234abcd1234
+ EAP-Sim-Rand2 = 0xbcd1234abcd1234abcd1234abcd1234a
+ EAP-Sim-Rand3 = 0xcd1234abcd1234abcd1234abcd1234ab
+ EAP-Sim-SRES1 = 0x1234abcd
+ EAP-Sim-SRES2 = 0x234abcd1
+ EAP-Sim-SRES3 = 0x34abcd12
+ EAP-Sim-KC1 = 0x0011223344556677
+ EAP-Sim-KC2 = 0x1021324354657687
+ EAP-Sim-KC3 = 0x30415263748596a7
+ EAP-Sim-State = 1
+ EAP-Sim-Subtype = Start
+ EAP-Sim-SELECTED_VERSION = 0x0001
+ EAP-Sim-NONCE_MT = 0x000000a3f6b4e832cf46b4d3e0d090623e22
+ EAP-Sim-IDENTITY = 0x000665617073696d
+ EAP-Id = YY
+ EAP-Message = 0x02XX
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Message = 0x02XX
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=999, length=138
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+<+++ EAP decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b050000XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b050000XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ EAP-Sim-Subtype = Challenge
+ EAP-Sim-RAND = 0x0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab
+ EAP-Sim-MAC = 0xYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
+Input was:
+ identity: (len=6)65617073696d
+ nonce_mt: 00a3f6b4e832cf46b4d3e0d090623e22
+ rand0: 00000000000000000000000000000000
+ rand1: 00000000000000000000000000000000
+ rand2: 00000000000000000000000000000000
+ sres0: 1234abcd
+ sres1: 234abcd1
+ sres2: 34abcd12
+ Kc0: 0011223344556677
+ Kc1: 1021324354657687
+ Kc2: 30415263748596a7
+ versionlist[2]: 0001
+ select 00 01
+
+
+Output
+mk: 85153a7d_7dfe0a4f_f3bf72f3_3521ff76_b048dbb2
+K_aut: 72cd7e8c_f2086e24_a98c1780_bc3d745b
+K_encr: be789668_329769c3_73c0b64b_beffd665
+msk: 9be9fbc9_5415fa9e_f9d52563_bddd9758_65a3fadb
+ 47a5815a_7310cf3f_10123d4e_ccaf9d4b_30b13c80
+ 4dd130e5_117f35ae_a0e50b43_9a08b80d_dd15922c
+ f7fd9956
+emsk: 5dd5a779_65415b21_69aa1300_09dc6ba4_96433d1e
+ 72065983_cbe8bc1d_6d744c99_dc76f16f_24324709
+ cb731af2_fbe69c6a_dd302662_a083d7e2_7c05c7cd
+ 279c3f66
+MAC check succeed
+
++++> About to send encoded packet:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0xabcd1234abcd1234abcd1234abcd1234
+ EAP-Sim-Rand2 = 0xbcd1234abcd1234abcd1234abcd1234a
+ EAP-Sim-Rand3 = 0xcd1234abcd1234abcd1234abcd1234ab
+ EAP-Sim-SRES1 = 0x1234abcd
+ EAP-Sim-SRES2 = 0x234abcd1
+ EAP-Sim-SRES3 = 0x34abcd12
+ EAP-Sim-KC1 = 0x0011223344556677
+ EAP-Sim-KC2 = 0x1021324354657687
+ EAP-Sim-KC3 = 0x30415263748596a7
+ EAP-Sim-State = 1
+ EAP-Sim-Subtype = Challenge
+ EAP-Id = YY
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Sim-MAC = 0x1234abcd234abcd134abcd12
+ EAP-Sim-KEY = 0x72cd7e8cf2086e24a98c1780bc3d745b
+ EAP-Message = 0x02XX
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Message = 0x02XX
+rad_recv: Access-Accept packet from host 127.0.0.1:1812, id=999, length=168
+ MS-MPPE-Recv-Key = 0x9be9fbc95415fa9ef9d52563bddd975865a3fadb47a5815a7310cf3f10123d4e
+ MS-MPPE-Send-Key = 0xccaf9d4b30b13c804dd130e5117f35aea0e50b439a08b80ddd15922cf7fd9956
+ EAP-Message = 0x03XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ User-Name = "eapsim"
+<+++ EAP decoded packet:
+ MS-MPPE-Recv-Key = 0x9be9fbc95415fa9ef9d52563bddd975865a3fadb47a5815a7310cf3f10123d4e
+ MS-MPPE-Send-Key = 0xccaf9d4b30b13c804dd130e5117f35aea0e50b439a08b80ddd15922cf7fd9956
+ EAP-Message = 0x03XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ User-Name = "eapsim"
+ EAP-Id = YY
+ EAP-Code = Success
diff --git a/src/tests/eapsim-03/eapsim-sanitize.sed b/src/tests/eapsim-03/eapsim-sanitize.sed
new file mode 100644
index 0000000..1cde2b6
--- /dev/null
+++ b/src/tests/eapsim-03/eapsim-sanitize.sed
@@ -0,0 +1,21 @@
+s/\(Sending Access-Request of id\).*\(to 127.0.0.1:1812\)/\1 999 \2/
+s/\(Message-Authenticator = 0x\).*/\1ABCDABCDABCDABCDABCDABCDABCDABCD/
+s/\(State = 0x\).*/\1ABCDABCDABCDABCDABCDABCDABCDABCD/
+s/\(rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id\)=.*,\( length=.*\)/\1=999,\2/
+s/\(rad_recv: Access-Accept packet from host 127.0.0.1:1812, id\)=.*,\( length=.*\)/\1=999,\2/
+s/\(EAP-Message = 0x..\)\(.*\)/\1XX/
+s/\(EAP-Id = \).*/\1YY/
+s/\(EAP-Type-MD5 = \).*/\1MD5/
+s/\(EAP-Type-SIM = 0x0b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b050000\)................................/\1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/
+s/\(EAP-Type-SIM = 0x0b0000010d0000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f0b050000\)................................/\1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/
+s/\(EAP-Type-SIM = 0x0b0000010d00003000000000000000000000000000000031000000000000000000000000000000320000000000000000000000000000000b050000\)................................/\1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/
+
+s/\(EAP-Sim-MAC = 0x\)..................................../\1YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY/
+s/DATA: (96) 01../DATA: (96) 01YY/
+s/DATA: (40) 02../DATA: (40) 02YY/
+s/hmac-sha1 mac(20): ........_........_........_........_......../hmac-sha1 mac(20): XXXXXXXX_XXXXXXXX_XXXXXXXX_XXXXXXXX_XXXXXXXX/
+
+
+
+
+
diff --git a/src/tests/eapsim-03/radiusd-example.txt b/src/tests/eapsim-03/radiusd-example.txt
new file mode 100644
index 0000000..c261cbe
--- /dev/null
+++ b/src/tests/eapsim-03/radiusd-example.txt
@@ -0,0 +1,1506 @@
+##
+## radiusd.conf -- FreeRADIUS server configuration file.
+##
+## http://www.freeradius.org/
+## $Id$
+##
+
+# This is the radiusd.conf file used for testing EAP-SIM stuff.
+#
+#
+
+# The location of other config files and
+# logfiles are declared in this file
+#
+# Also general configuration for modules can be done
+# in this file, it is exported through the API to
+# modules that ask for it.
+#
+# The configuration variables defined here are of the form ${foo}
+# They are local to this file, and do not change from request to
+# request.
+#
+# The per-request variables are of the form %{Attribute-Name}, and
+# are taken from the values of the attribute in the incoming
+# request. See 'doc/configuration/variables.rst' for more information.
+
+prefix = /elros/mcr/root
+exec_prefix = ${prefix}
+sysconfdir = ${prefix}/etc
+localstatedir = ${prefix}/var
+sbindir = ${exec_prefix}/sbin
+logdir = ${localstatedir}/log/radius
+raddbdir = ${sysconfdir}/raddb
+radacctdir = ${logdir}/radacct
+
+# Location of config and logfiles.
+confdir = ${raddbdir}
+run_dir = ${localstatedir}/run/radiusd
+
+#
+# The logging messages for the server are appended to the
+# tail of this file.
+#
+log_file = ${logdir}/radius.log
+
+#
+# libdir: Where to find the rlm_* modules.
+#
+# This should be automatically set at configuration time.
+#
+# If the server builds and installs, but fails at execution time
+# with an 'undefined symbol' error, then you can use the libdir
+# directive to work around the problem.
+#
+# The cause is usually that a library has been installed on your
+# system in a place where the dynamic linker CANNOT find it. When
+# executing as root (or another user), your personal environment MAY
+# be set up to allow the dynamic linker to find the library. When
+# executing as a daemon, FreeRADIUS MAY NOT have the same
+# personalized configuration.
+#
+# To work around the problem, find out which library contains that symbol,
+# and add the directory containing that library to the end of 'libdir',
+# with a colon separating the directory names. NO spaces are allowed.
+#
+# e.g. libdir = /usr/local/lib:/opt/package/lib
+#
+# You can also try setting the LD_LIBRARY_PATH environment variable
+# in a script which starts the server.
+#
+# If that does not work, then you can re-configure and re-build the
+# server to NOT use shared libraries, via:
+#
+# ./configure --disable-shared
+# make
+# make install
+#
+libdir = ${exec_prefix}/lib
+
+# pidfile: Where to place the PID of the RADIUS server.
+#
+# The server may be signalled while it's running by using this
+# file.
+#
+# This file is written when ONLY running in daemon mode.
+#
+# e.g.: kill -HUP `cat /var/run/radiusd/radiusd.pid`
+#
+pidfile = ${run_dir}/radiusd.pid
+
+
+# user/group: The name (or #number) of the user/group to run radiusd as.
+#
+# If these are commented out, the server will run as the user/group
+# that started it. In order to change to a different user/group, you
+# MUST be root ( or have root privleges ) to start the server.
+#
+# We STRONGLY recommend that you run the server with as few permissions
+# as possible. That is, if you're not using shadow passwords, the
+# user and group items below should be set to 'nobody'.
+#
+# On SCO (ODT 3) use "user = nouser" and "group = nogroup".
+#
+# NOTE that some kernels refuse to setgid(group) when the value of
+# (unsigned)group is above 60000; don't use group nobody on these systems!
+#
+# On systems with shadow passwords, you might have to set 'group = shadow'
+# for the server to be able to read the shadow password file. If you can
+# authenticate users while in debug mode, but not in daemon mode, it may be
+# that the debugging mode server is running as a user that can read the
+# shadow info, and the user listed below can not.
+#
+#user = nobody
+#group = nobody
+
+# max_request_time: The maximum time (in seconds) to handle a request.
+#
+# Requests which take more time than this to process may be killed, and
+# a REJECT message is returned.
+#
+# WARNING: If you notice that requests take a long time to be handled,
+# then this MAY INDICATE a bug in the server, in one of the modules
+# used to handle a request, OR in your local configuration.
+#
+# This problem is most often seen when using an SQL database. If it takes
+# more than a second or two to receive an answer from the SQL database,
+# then it probably means that you haven't indexed the database. See your
+# SQL server documentation for more information.
+#
+# Useful range of values: 5 to 120
+#
+max_request_time = 30
+
+# cleanup_delay: The time to wait (in seconds) before cleaning up
+# a reply which was sent to the NAS.
+#
+# The RADIUS request is normally cached internally for a short period
+# of time, after the reply is sent to the NAS. The reply packet may be
+# lost in the network, and the NAS will not see it. The NAS will then
+# re-send the request, and the server will respond quickly with the
+# cached reply.
+#
+# If this value is set too low, then duplicate requests from the NAS
+# MAY NOT be detected, and will instead be handled as separate requests.
+#
+# If this value is set too high, then the server will cache too many
+# requests, and some new requests may get blocked. (See 'max_requests'.)
+#
+# Useful range of values: 2 to 10
+#
+cleanup_delay = 5
+
+# max_requests: The maximum number of requests which the server keeps
+# track of. This should be 256 multiplied by the number of clients.
+# e.g. With 4 clients, this number should be 1024.
+#
+# If this number is too low, then when the server becomes busy,
+# it will not respond to any new requests, until the 'cleanup_delay'
+# time has passed, and it has removed the old requests.
+#
+# If this number is set too high, then the server will use a bit more
+# memory for no real benefit.
+#
+# If you aren't sure what it should be set to, it's better to set it
+# too high than too low. Setting it to 1000 per client is probably
+# the highest it should be.
+#
+# Useful range of values: 256 to infinity
+#
+max_requests = 1024
+
+# bind_address: Make the server listen on a particular IP address, and
+# send replies out from that address. This directive is most useful
+# for machines with multiple IP addresses on one interface.
+#
+# It can either contain "*", or an IP address, or a fully qualified
+# Internet domain name. The default is "*"
+#
+bind_address = *
+
+# port: Allows you to bind FreeRADIUS to a specific port.
+#
+# The default port that most NAS boxes use is 1645, which is historical.
+# RFC 2138 defines 1812 to be the new port. Many new servers and
+# NAS boxes use 1812, which can create interoperability problems.
+#
+# The port is defined here to be 0 so that the server will pick up
+# the machine's local configuration for the radius port, as defined
+# in /etc/services.
+#
+# If you want to use the default RADIUS port as defined on your server,
+# (usually through 'grep radius /etc/services') set this to 0 (zero).
+#
+# A port given on the command-line via '-p' over-rides this one.
+#
+port = 0
+
+# hostname_lookups: Log the names of clients or just their IP addresses
+# e.g., www.freeradius.org (on) or 206.47.27.232 (off).
+#
+# The default is 'off' because it would be overall better for the net
+# if people had to knowingly turn this feature on, since enabling it
+# means that each client request will result in AT LEAST one lookup
+# request to the nameserver. Enabling hostname_lookups will also
+# mean that your server may stop randomly for 30 seconds from time
+# to time, if the DNS requests take too long.
+#
+# Turning hostname lookups off also means that the server won't block
+# for 30 seconds, if it sees an IP address which has no name associated
+# with it.
+#
+# allowed values: {no, yes}
+#
+hostname_lookups = no
+
+# Core dumps are a bad thing. This should only be set to 'yes'
+# if you're debugging a problem with the server.
+#
+# allowed values: {no, yes}
+#
+allow_core_dumps = yes
+
+# Log the full User-Name attribute, as it was found in the request.
+#
+# allowed values: {no, yes}
+#
+log_stripped_names = no
+
+# Log authentication requests to the log file.
+#
+# allowed values: {no, yes}
+#
+log_auth = no
+
+# Log passwords with the authentication requests.
+# log_auth_badpass - logs password if it's rejected
+# log_auth_goodpass - logs password if it's correct
+#
+# allowed values: {no, yes}
+#
+log_auth_badpass = no
+log_auth_goodpass = no
+
+# usercollide: Turn "username collision" code on and off. See the
+# "doc/duplicate-users" file
+#
+usercollide = no
+
+# lower_user / lower_pass:
+# Lower case the username/password "before" or "after"
+# attempting to authenticate.
+#
+# If "before", the server will first modify the request and then try
+# to auth the user. If "after", the server will first auth using the
+# values provided by the user. If that fails it will reprocess the
+# request after modifying it as you specify below.
+#
+# This is as close as we can get to case insensitivity. It is the
+# admin's job to ensure that the username on the auth db side is
+# *also* lowercase to make this work
+#
+# Default is 'no' (don't lowercase values)
+# Valid values = "before" / "after" / "no"
+#
+lower_user = no
+lower_pass = no
+
+# nospace_user / nospace_pass:
+#
+# Some users like to enter spaces in their username or password
+# incorrectly. To save yourself the tech support call, you can
+# eliminate those spaces here:
+#
+# Default is 'no' (don't remove spaces)
+# Valid values = "before" / "after" / "no" (explanation above)
+#
+nospace_user = no
+nospace_pass = no
+
+# The program to execute to do concurrency checks.
+checkrad = ${sbindir}/checkrad
+
+# SECURITY CONFIGURATION
+#
+# There may be multiple methods of attacking on the server. This
+# section holds the configuration items which minimize the impact
+# of those attacks
+#
+security {
+ #
+ # max_attributes: The maximum number of attributes
+ # permitted in a RADIUS packet. Packets which have MORE
+ # than this number of attributes in them will be dropped.
+ #
+ # If this number is set too low, then no RADIUS packets
+ # will be accepted.
+ #
+ # If this number is set too high, then an attacker may be
+ # able to send a small number of packets which will cause
+ # the server to use all available memory on the machine.
+ #
+ # Setting this number to 0 means "allow any number of attributes"
+ max_attributes = 200
+
+ #
+ # reject_delay: When sending an Access-Reject, it can be
+ # delayed for a few seconds. This may help slow down a DoS
+ # attack. It also helps to slow down people trying to brute-force
+ # crack a users password.
+ #
+ # Setting this number to 0 means "send rejects immediately"
+ #
+ # If this number is set higher than 'cleanup_delay', then the
+ # rejects will be sent at 'cleanup_delay' time, when the request
+ # is deleted from the internal cache of requests.
+ #
+ # Useful ranges: 1 to 5
+ reject_delay = 1
+
+ #
+ # status_server: Whether or not the server will respond
+ # to Status-Server requests.
+ #
+ # Normally this should be set to "no", because they're useless.
+ # See: http://www.freeradius.org/rfc/rfc2865.html#Keep-Alives
+ #
+ # However, certain NAS boxes may require them.
+ #
+ # When sent a Status-Server message, the server responds with
+ # and Access-Accept packet, containing a Reply-Message attribute,
+ # which is a string describing how long the server has been
+ # running.
+ #
+ status_server = no
+}
+
+# PROXY CONFIGURATION
+#
+# proxy_requests: Turns proxying of RADIUS requests on or off.
+#
+# The server has proxying turned on by default. If your system is NOT
+# set up to proxy requests to another server, then you can turn proxying
+# off here. This will save a small amount of resources on the server.
+#
+# If you have proxying turned off, and your configuration files say
+# to proxy a request, then an error message will be logged.
+#
+# To disable proxying, change the "yes" to "no", and comment the
+# $INCLUDE line.
+#
+# allowed values: {no, yes}
+#
+proxy_requests = yes
+$INCLUDE ${confdir}/proxy.conf
+
+
+# CLIENTS CONFIGURATION
+#
+# Client configuration is defined in "clients.conf".
+#
+
+# The 'clients.conf' file contains all of the information from the old
+# 'clients' and 'naslist' configuration files. We recommend that you
+# do NOT use 'client's or 'naslist', although they are still
+# supported.
+#
+# Anything listed in 'clients.conf' will take precedence over the
+# information from the old-style configuration files.
+#
+$INCLUDE ${confdir}/clients.conf
+
+
+# SNMP CONFIGURATION
+#
+# Snmp configuration is only valid if SNMP support was enabled
+# at compile time.
+#
+# To enable SNMP querying of the server, set the value of the
+# 'snmp' attribute to 'yes'
+#
+snmp = no
+$INCLUDE ${confdir}/snmp.conf
+
+
+# THREAD POOL CONFIGURATION
+#
+# The thread pool is a long-lived group of threads which
+# take turns (round-robin) handling any incoming requests.
+#
+# You probably want to have a few spare threads around,
+# so that high-load situations can be handled immediately. If you
+# don't have any spare threads, then the request handling will
+# be delayed while a new thread is created, and added to the pool.
+#
+# You probably don't want too many spare threads around,
+# otherwise they'll be sitting there taking up resources, and
+# not doing anything productive.
+#
+# The numbers given below should be adequate for most situations.
+#
+thread pool {
+ # Number of servers to start initially --- should be a reasonable
+ # ballpark figure.
+ start_servers = 5
+
+ # Limit on the total number of servers running.
+ #
+ # If this limit is ever reached, clients will be LOCKED OUT, so it
+ # should NOT BE SET TOO LOW. It is intended mainly as a brake to
+ # keep a runaway server from taking the system with it as it spirals
+ # down...
+ #
+ # You may find that the server is regularly reaching the
+ # 'max_servers' number of threads, and that increasing
+ # 'max_servers' doesn't seem to make much difference.
+ #
+ # If this is the case, then the problem is MOST LIKELY that
+ # your back-end databases are taking too long to respond, and
+ # are preventing the server from responding in a timely manner.
+ #
+ # The solution is NOT do keep increasing the 'max_servers'
+ # value, but instead to fix the underlying cause of the
+ # problem: slow database, or 'hostname_lookups=yes'.
+ #
+ # For more information, see 'max_request_time', above.
+ #
+ max_servers = 32
+
+ # Server-pool size regulation. Rather than making you guess
+ # how many servers you need, FreeRADIUS dynamically adapts to
+ # the load it sees, that is, it tries to maintain enough
+ # servers to handle the current load, plus a few spare
+ # servers to handle transient load spikes.
+ #
+ # It does this by periodically checking how many servers are
+ # waiting for a request. If there are fewer than
+ # min_spare_servers, it creates a new spare. If there are
+ # more than max_spare_servers, some of the spares die off.
+ # The default values are probably OK for most sites.
+ #
+ min_spare_servers = 3
+ max_spare_servers = 10
+
+ # There may be memory leaks or resource allocation problems with
+ # the server. If so, set this value to 300 or so, so that the
+ # resources will be cleaned up periodically.
+ #
+ # This should only be necessary if there are serious bugs in the
+ # server which have not yet been fixed.
+ #
+ # '0' is a special value meaning 'infinity', or 'the servers never
+ # exit'
+ max_requests_per_server = 0
+}
+
+# MODULE CONFIGURATION
+#
+# The names and configuration of each module is located in this section.
+#
+# After the modules are defined here, they may be referred to by name,
+# in other sections of this configuration file.
+#
+modules {
+ #
+ # Each module has a configuration as follows:
+ #
+ # name [ instance ] {
+ # config_item = value
+ # ...
+ # }
+ #
+ # The 'name' is used to load the 'rlm_name' library
+ # which implements the functionality of the module.
+ #
+ # The 'instance' is optional. To have two different instances
+ # of a module, it first must be referred to by 'name'.
+ # The different copies of the module are then created by
+ # inventing two 'instance' names, e.g. 'instance1' and 'instance2'
+ #
+ # The instance names can then be used in later configuration
+ # INSTEAD of the original 'name'. See the 'radutmp' configuration
+ # below for an example.
+ #
+
+ # PAP module to authenticate users based on their stored password
+ #
+ # Supports multiple encryption schemes
+ # clear: Clear text
+ # crypt: Unix crypt
+ # md5: MD5 ecnryption
+ # sha1: SHA1 encryption.
+ # DEFAULT: crypt
+ pap {
+ encryption_scheme = crypt
+ }
+
+ # CHAP module
+ #
+ # To authenticate requests containing a CHAP-Password attribute.
+ #
+ chap {
+ authtype = CHAP
+ }
+
+ # Pluggable Authentication Modules
+ #
+ # For Linux, see:
+ # http://www.kernel.org/pub/linux/libs/pam/index.html
+ #
+ pam {
+ #
+ # The name to use for PAM authentication.
+ # PAM looks in /etc/pam.d/${pam_auth_name}
+ # for it's configuration. See 'redhat/radiusd-pam'
+ # for a sample PAM configuration file.
+ #
+ # Note that any Pam-Auth attribute set in the 'authorize'
+ # section will over-ride this one.
+ #
+ pam_auth = radiusd
+ }
+
+ # Unix /etc/passwd style authentication
+ #
+ unix {
+ #
+ # Cache /etc/passwd, /etc/shadow, and /etc/group
+ #
+ # The default is to NOT cache them.
+ #
+ # For FreeBSD, you do NOT want to enable the cache,
+ # as it's password lookups are done via a database, so
+ # set this value to 'no'.
+ #
+ # Some systems (e.g. RedHat Linux with pam_pwbd) can
+ # take *seconds* to check a password, from a passwd
+ # file containing 1000's of entries. For those systems,
+ # you should set the cache value to 'yes', and set
+ # the locations of the 'passwd', 'shadow', and 'group'
+ # files, below.
+ #
+ # allowed values: {no, yes}
+ cache = no
+
+ # Reload the cache every 600 seconds (10mins). 0 to disable.
+ cache_reload = 600
+
+ #
+ # Define the locations of the normal passwd, shadow, and
+ # group files.
+ #
+ # 'shadow' is commented out by default, because not all
+ # systems have shadow passwords.
+ #
+ # To force the module to use the system password functions,
+ # instead of reading the files, leave the following entries
+ # commented out.
+ #
+ # This is required for some systems, like FreeBSD,
+ # and Mac OSX.
+ #
+ # passwd = /etc/passwd
+ # shadow = /etc/shadow
+ # group = /etc/group
+
+
+ #
+ # Where the 'wtmp' file is located.
+ # This should be moved to it's own module soon.
+ #
+ # The only use for 'radlast'. If you don't use
+ # 'radlast', then you can comment out this item.
+ #
+ radwtmp = ${logdir}/radwtmp
+ }
+
+ # Extensible Authentication Protocol
+ #
+ # For all EAP related authentications
+ eap {
+ # Invoke the default supported EAP type when
+ # EAP-Identity response is received.
+ #
+ # The incoming EAP messages MAY NOT specify which EAP
+ # type they will be using, so it MUST be set here.
+ #
+ # For now, only one default EAP type may be used at a time.
+ #
+ default_eap_type = md5
+
+ # Default expiry time to clean the EAP list,
+ # It is maintained to correlate the
+ # EAP-response for each EAP-request sent.
+ timer_expire = 60
+
+ # Supported EAP-types
+ md5 {
+ }
+
+ sim {
+ }
+
+ # Cisco LEAP
+ #
+ # Cisco LEAP uses the MS-CHAP algorithm (but not
+ # the MS-CHAP attributes) to perform it's authentication.
+ #
+ # As a result, LEAP *requires* access to the plain-text
+ # User-Password, or the NT-Password attributes.
+ # 'System' authentication is impossible with LEAP.
+ #
+ leap {
+ }
+
+ ## EAP-TLS is highly experimental EAP-Type at the moment.
+ # Please give feedback on the mailing list.
+ #tls {
+ # private_key_password = password
+ # private_key_file = /path/filename
+
+ # If Private key & Certificate are located in the
+ # same file, then private_key_file & certificate_file
+ # must contain the same file name.
+ # certificate_file = /path/filename
+
+ # Trusted Root CA list
+ #ca_file = /path/filename
+
+ # dh_file = /path/filename
+ #random_file = /path/filename
+ #
+ # This can never exceed MAX_RADIUS_LEN (4096)
+ # preferably half the MAX_RADIUS_LEN, to
+ # accomodate other attributes in RADIUS packet.
+ # On most APs the MAX packet length is configured
+ # between 1500 - 1600. In these cases, fragment
+ # size should be <= 1024.
+ #
+ # fragment_size = 1024
+
+ # include_length is a flag which is by default set to yes
+ # If set to yes, Total Length of the message is included
+ # in EVERY packet we send.
+ # If set to no, Total Length of the message is included
+ # ONLY in the First packet of a fragment series.
+ #
+ # include_length = yes
+ #}
+ }
+
+ # Microsoft CHAP authentication
+ #
+ # This module supports MS-CHAP and MS-CHAPv2 authentication.
+ # It also enforces the SMB-Account-Ctrl attribute.
+ #
+ mschap {
+ #
+ # As of 0.9, the mschap module does NOT support
+ # reading from /etc/smbpasswd.
+ #
+ # If you are using /etc/smbpasswd, see the 'passwd'
+ # module for an example of how to use /etc/smbpasswd
+
+ # authtype value, if present, will be used
+ # to overwrite (or add) Auth-Type during
+ # authorization. Normally should be MS-CHAP
+ authtype = MS-CHAP
+
+ # if use_mppe is not set to no mschap will
+ # add MS-CHAP-MPPE-Keys for MS-CHAPv1 and
+ # MS-MPPE-Recv-Key/MS-MPPE-Send-Key for MS-CHAPv2
+ # use_mppe = no
+
+ # if mppe is enabled require_encryption makes
+ # encryption moderate
+ # require_encryption = yes
+
+ # require_strong always requires 128 bit key
+ # encryption
+ # require_strong = yes
+ }
+
+ # Lightweight Directory Access Protocol (LDAP)
+ #
+ # This module definition allows you to use LDAP for
+ # authorization and authentication (Auth-Type := LDAP)
+ #
+ # See doc/rlm_ldap for description of configuration options
+ # and sample authorize{} and authenticate{} blocks
+ ldap {
+ server = "ldap.your.domain"
+ # identity = "cn=admin,o=My Org,c=UA"
+ # password = mypass
+ basedn = "o=My Org,c=UA"
+ filter = "(uid=%{%{Stripped-User-Name}:-%{User-Name}})"
+
+ # set this to 'yes' to use TLS encrypted connections
+ # to the LDAP database by using the StartTLS extended
+ # operation.
+ # The StartTLS operation is supposed to be used with normal
+ # ldap connections instead of using ldaps (port 636) connections
+ start_tls = no
+
+ # default_profile = "cn=radprofile,ou=dialup,o=My Org,c=UA"
+ # profile_attribute = "radiusProfileDn"
+ access_attr = "dialupAccess"
+
+ # Mapping of RADIUS dictionary attributes to LDAP
+ # directory attributes.
+ dictionary_mapping = ${raddbdir}/ldap.attrmap
+
+ ldap_connections_number = 5
+ # password_header = "{clear}"
+ # password_attribute = userPassword
+ # groupname_attribute = cn
+ # groupmembership_filter = "(|(&(objectClass=GroupOfNames)(member=%{Ldap-UserDn}))(&(objectClass=GroupOfUniqueNames)(uniquemember=%{Ldap-UserDn})))"
+ # groupmembership_attribute = radiusGroupName
+ timeout = 4
+ timelimit = 3
+ net_timeout = 1
+ # compare_check_items = yes
+ # access_attr_used_for_allow = yes
+ }
+
+ # passwd module allows to do authorization via any passwd-like
+ # file and to extract any attributes from these modules
+ #
+ # parameters are:
+ # filename - path to filename
+ # format - format for filename record. This parameters
+ # correlates record in the passwd file and RADIUS
+ # attributes.
+ #
+ # Field marked as '*' is key field. That is, the parameter
+ # with this name from the request is used to search for
+ # the record from passwd file
+ # Attribute marked as '=' is added to reply_items instead
+ # of default configure_items
+ # Attribute marked as '~' is added to request_items
+ #
+ # Field marked as ',' may contain a comma separated list
+ # of attributes.
+ # authtype - if record found this Auth-Type is used to authenticate
+ # user
+ # hash_size - hashtable size. If 0 or not specified records are not
+ # stored in memory and file is red on every request.
+ # allow_multiple_keys - if few records for every key are allowed
+ # ignore_nislike - ignore NIS-related records
+ # delimiter - symbol to use as a field separator in passwd file,
+ # for format ':' symbol is always used. '\0', '\n' are
+ # not allowed
+ #
+
+ # An example configuration for using /etc/smbpasswd.
+ #
+ #passwd etc_smbpasswd {
+ # filename = /etc/smbpasswd
+ # format = "*User-Name::LM-Password:NT-Password:SMB-Account-CTRL-TEXT::"
+ # authtype = MS-CHAP
+ # hash_size = 100
+ # ignore_nislike = no
+ # allow_multiple_keys = no
+ #}
+
+ # Similar configuration, for the /etc/group file. Adds a Group-Name
+ # attribute for every group that the user is member of.
+ #
+ #passwd etc_group {
+ # filename = /etc/group
+ # format = "=Group-Name:::*,User-Name"
+ # hash_size = 50
+ # ignore_nislike = yes
+ # allow_multiple_keys = yes
+ # delimiter = ":"
+ #}
+
+ # Realm module, for proxying.
+ #
+ # You can have multiple instances of the realm module to
+ # support multiple realm syntaxs at the same time. The
+ # search order is defined the order in the authorize and
+ # preacct blocks after the module config block.
+ #
+ # Two config options:
+ # format - must be 'prefix' or 'suffix'
+ # delimiter - must be a single character
+
+ # 'realm/username'
+ #
+ # Using this entry, IPASS users have their realm set to "IPASS".
+ realm realmslash {
+ format = prefix
+ delimiter = "/"
+ }
+
+ # 'username@realm'
+ #
+ realm suffix {
+ format = suffix
+ delimiter = "@"
+ }
+
+ # 'username%realm'
+ #
+ realm realmpercent {
+ format = suffix
+ delimiter = "%"
+ }
+
+ # Preprocess the incoming RADIUS request, before handing it off
+ # to other modules.
+ #
+ # This module processes the 'huntgroups' and 'hints' files.
+ # In addition, it re-writes some weird attributes created
+ # by some NASes, and converts the attributes into a form which
+ # is a little more standard.
+ #
+ preprocess {
+ huntgroups = ${confdir}/huntgroups
+ hints = ${confdir}/hints
+
+ # This hack changes Ascend's wierd port numberings
+ # to standard 0-??? port numbers so that the "+" works
+ # for IP address assignments.
+ with_ascend_hack = no
+ ascend_channels_per_line = 23
+
+ # Windows NT machines often authenticate themselves as
+ # NT_DOMAIN\username
+ #
+ # If this is set to 'yes', then the NT_DOMAIN portion
+ # of the user-name is silently discarded.
+ with_ntdomain_hack = no
+
+ # Specialix Jetstream 8500 24 port access server.
+ #
+ # If the user name is 10 characters or longer, a "/"
+ # and the excess characters after the 10th are
+ # appended to the user name.
+ #
+ # If you're not running that NAS, you don't need
+ # this hack.
+ with_specialix_jetstream_hack = no
+
+ # Cisco sends it's VSA attributes with the attribute
+ # name *again* in the string, like:
+ #
+ # H323-Attribute = "h323-attribute=value".
+ #
+ # If this configuration item is set to 'yes', then
+ # the redundant data in the the attribute text is stripped
+ # out. The result is:
+ #
+ # H323-Attribute = "value"
+ #
+ # If you're not running a Cisco NAS, you don't need
+ # this hack.
+ with_cisco_vsa_hack = no
+ }
+
+ # Livingston-style 'users' file
+ #
+ files {
+ usersfile = ${confdir}/users
+ acctusersfile = ${confdir}/acct_users
+
+ # If you want to use the old Cistron 'users' file
+ # with FreeRADIUS, you should change the next line
+ # to 'compat = cistron'. You can the copy your 'users'
+ # file from Cistron.
+ compat = no
+ }
+
+ # Write a detailed log of all accounting records received.
+ #
+ detail {
+ # Note that we do NOT use NAS-IP-Address here, as
+ # that attribute MAY BE from the originating NAS, and
+ # NOT from the proxy which actually sent us the
+ # request. The Client-IP-Address attribute is ALWAYS
+ # the address of the client which sent us the
+ # request.
+ #
+ # The following line creates a new detail file for
+ # every radius client (by IP address or hostname).
+ # In addition, a new detail file is created every
+ # day, so that the detail file doesn't have to go
+ # through a 'log rotation'
+ #
+ # If your detail files are large, you may also want
+ # to add a ':%H' (see doc/configuration/variables.rst) to the end
+ # of it, to create a new detail file every hour, e.g.:
+ #
+ # ..../detail-%Y%m%d:%H
+ #
+ # This will create a new detail file for every hour.
+ #
+ detailfile = ${radacctdir}/%{Client-IP-Address}/detail-%Y%m%d
+
+ #
+ # The Unix-style permissions on the 'detail' file.
+ #
+ # The detail file often contains secret or private
+ # information about users. So by keeping the file
+ # permissions restrictive, we can prevent unwanted
+ # people from seeing that information.
+ detailperm = 0600
+ }
+
+ # Include another file that has the SQL-related configuration.
+ # This is another file only because it tends to be big.
+ #
+ # The following configuration file is for use with MySQL.
+ #
+ # For Postgresql, use: ${confdir}/postgresql.conf
+ # For MS-SQL, use: ${confdir}/mssql.conf
+ # For Oracle, use: ${confdir}/oraclesql.conf
+ #
+ $INCLUDE ${confdir}/sql.conf
+
+ # Write a 'utmp' style file, of which users are currently
+ # logged in, and where they've logged in from.
+ #
+ # This file is used mainly for Simultaneous-Use checking,
+ # and also 'radwho', to see who's currently logged in.
+ #
+ radutmp {
+ # Where the file is stored. It's not a log file,
+ # so it doesn't need rotating.
+ #
+ filename = ${logdir}/radutmp
+
+ # The field in the packet to key on for the
+ # 'user' name, If you have other fields which you want
+ # to use to key on to control Simultaneous-Use,
+ # then you can use them here.
+ #
+ # Note, however, that the size of the field in the
+ # 'utmp' data structure is small, around 32
+ # characters, so that will limit the possible choices
+ # of keys.
+ #
+ username = %{User-Name}
+
+ # Whether or not we want to treat "user" the same
+ # as "USER", or "User". Some systems have problems
+ # with case sensitivity, so this should be set to
+ # 'no' to enable the comparisons of the key attribute
+ # to be case insensitive.
+ #
+ case_sensitive = yes
+
+ # Accounting information may be lost, so the user MAY
+ # have logged off of the NAS, but we haven't noticed.
+ # If so, we can verify this information with the NAS,
+ #
+ # If we want to believe the 'utmp' file, then this
+ # configuration entry can be set to 'no'.
+ #
+ check_with_nas = yes
+
+ # Set the file permissions, as the contents of this file
+ # are usually private.
+ perm = 0600
+
+ caller_id = "yes"
+ }
+
+ # "Safe" radutmp - does not contain caller ID, so it can be
+ # world-readable, and radwho can work for normal users, without
+ # exposing any information that isn't already exposed by who(1).
+ #
+ # This is another 'instance' of the radutmp module, but it is given
+ # then name "sradutmp" to identify it later in the "accounting"
+ # section.
+ radutmp sradutmp {
+ filename = ${logdir}/sradutmp
+ perm = 0644
+ caller_id = "no"
+ }
+
+ # attr_filter - filters the attributes received in replies from
+ # proxied servers, to make sure we send back to our RADIUS client
+ # only allowed attributes.
+ attr_filter {
+ attrsfile = ${confdir}/attrs
+ }
+
+ # counter module:
+ # This module takes an attribute (count_attribute).
+ # It also takes a key, and creates a counter for each unique
+ # key. The count is incremented when accounting packets are
+ # received by the server. The value of the increment depends
+ # on the attribute type.
+ # If the attribute is Acct-Session-Time or of an integer type we add the
+ # value of the attribute. If it is anything else we increase the
+ # counter by one.
+ #
+ # The 'reset' parameter defines when the counters are all reset to
+ # zero. It can be hourly, daily, weekly, monthly or never.
+ #
+ # hourly: Reset on 00:00 of every hour
+ # daily: Reset on 00:00:00 every day
+ # weekly: Reset on 00:00:00 on sunday
+ # monthly: Reset on 00:00:00 of the first day of each month
+ #
+ # It can also be user defined. It should be of the form:
+ # num[hdwm] where:
+ # h: hours, d: days, w: weeks, m: months
+ # If the letter is ommited days will be assumed. In example:
+ # reset = 10h (reset every 10 hours)
+ # reset = 12 (reset every 12 days)
+ #
+ #
+ # The check_name attribute defines an attribute which will be
+ # registered by the counter module and can be used to set the
+ # maximum allowed value for the counter after which the user
+ # is rejected.
+ # Something like:
+ #
+ # DEFAULT Max-Daily-Session := 36000
+ # Fall-Through = 1
+ #
+ # You should add the counter module in the instantiate
+ # section so that it registers check_name before the files
+ # module reads the users file.
+ #
+ # If check_name is set and the user is to be rejected then we
+ # send back a Reply-Message and we log a Failure-Message in
+ # the radius.log
+ # If the count attribute is Acct-Session-Time then on each login
+ # we send back the remaining online time as a Session-Timeout attribute
+ #
+ # The counter-name can also be used instead of using the check_name
+ # like below:
+ #
+ # DEFAULT Daily-Session-Time > 3600, Auth-Type = Reject
+ # Reply-Message = "You've used up more than one hour today"
+ #
+ # The allowed-servicetype attribute can be used to only take
+ # into account specific sessions. For example if a user first
+ # logs in through a login menu and then selects ppp there will
+ # be two sessions. One for Login-User and one for Framed-User
+ # service type. We only need to take into account the second one.
+ #
+ # The module should be added in the instantiate, authorize and
+ # accounting sections. Make sure that in the authorize
+ # section it comes after any module which sets the
+ # 'check_name' attribute.
+ #
+ counter daily {
+ filename = ${raddbdir}/db.daily
+ key = User-Name
+ count_attribute = Acct-Session-Time
+ reset = daily
+ counter_name = Daily-Session-Time
+ check_name = Max-Daily-Session
+ allowed_service_type = Framed-User
+ cache_size = 5000
+ }
+
+ # The "always" module is here for debugging purposes. Each
+ # instance simply returns the same result, always, without
+ # doing anything.
+ always fail {
+ rcode = fail
+ }
+ always reject {
+ rcode = reject
+ }
+ always ok {
+ rcode = ok
+ simulcount = 0
+ mpp = no
+ }
+
+ #
+ # The 'expression' module currently has no configuration.
+ expr {
+ }
+
+ #
+ # The 'digest' module currently has no configuration.
+ #
+ # "Digest" authentication against a Cisco SIP server.
+ # See 'doc/rfc/draft-sterman-aaa-sip-00.txt' for details
+ # on performing digest authentication for Cisco SIP servers.
+ #
+ digest {
+ }
+
+ #
+ # Execute external programs
+ #
+ # The first example is useful only for 'xlat'. To use it,
+ # put 'exec' into the 'instantiate' section. You can then
+ # do dynamic translation of attributes like:
+ #
+ # Attribute-Name = `{%exec:/path/to/program args}`
+ #
+ # The value of the attribute will be replaced with the output
+ # of the program which is executed. Due to RADIUS protocol
+ # limitations, any output over 253 bytes will be ignored.
+ #
+ # The RADIUS attributes from the user request will be placed
+ # into environment variables of the executed program, as
+ # described in 'doc/configuration/variables.rst'
+ #
+ exec {
+ wait = yes
+ input_pairs = request
+ }
+
+ #
+ # This is a more general example of the execute module.
+ #
+ # If you wish to execute an external program in more than
+ # one section (e.g. 'authorize', 'pre_proxy', etc), then it
+ # is probably best to define a different instance of the
+ # 'exec' module for every section.
+ #
+ exec echo {
+ #
+ # Wait for the program to finish.
+ #
+ # If we do NOT wait, then the program is "fire and
+ # forget", and any output attributes from it are ignored.
+ #
+ # If we are looking for the program to output
+ # attributes, and want to add those attributes to the
+ # request, then we MUST wait for the program to
+ # finish, and therefore set 'wait=yes'
+ #
+ # allowed values: {no, yes}
+ wait = yes
+
+ #
+ # The name of the program to execute, and it's
+ # arguments. Dynamic translation is done on this
+ # field, so things like the following example will
+ # work.
+ #
+ program = "/bin/echo %{User-Name}"
+
+ #
+ # The attributes which are placed into the
+ # environment variables for the program.
+ #
+ # Allowed values are:
+ #
+ # request attributes from the request
+ # reply attributes from the reply
+ # proxy-request attributes from the proxy request
+ # proxy-reply attributes from the proxy reply
+ #
+ # Note that some attributes may not exist at some
+ # stages. e.g. There may be no proxy-reply
+ # attributes if this module is used in the
+ # 'authorize' section.
+ #
+ input_pairs = request
+
+ #
+ # Where to place the output attributes (if any) from
+ # the executed program. The values allowed, and the
+ # restrictions as to availability, are the same as
+ # for the input_pairs.
+ #
+ output_pairs = reply
+
+ #
+ # When to execute the program. If the packet
+ # type does NOT match what's listed here, then
+ # the module does NOT execute the program.
+ #
+ # For a list of allowed packet types, see
+ # the 'dictionary' file, and look for VALUEs
+ # of the Packet-Type attribute.
+ #
+ # By default, the module executes on ANY packet.
+ # Un-comment out the following line to tell the
+ # module to execute only if an Access-Accept is
+ # being sent to the NAS.
+ #
+ #packet_type = Access-Accept
+ }
+
+ # Do server side ip pool management. Should be added in post-auth and
+ # accounting sections.
+ #
+ # The module also requires the existence of the Pool-Name
+ # attribute. That way the administrator can add the Pool-Name
+ # attribute in the user profiles and use different pools
+ # for different users. The Pool-Name attribute is a *check* item not
+ # a reply item.
+ #
+ # Example:
+ # radiusd.conf: ippool students { [...] }
+ # users file : DEFAULT Group == students, Pool-Name := "students"
+ #
+ # ********* IF YOU CHANGE THE RANGE PARAMETERS YOU MUST THEN ERASE THE DB FILES *******
+ #
+ ippool main_pool {
+
+ # range-start,range-stop: The start and end ip
+ # addresses for the ip pool
+ range-start = 192.0.2.1
+ range-stop = 192.0.2.254
+
+ # netmask: The network mask used for the ip's
+ netmask = 255.255.255.0
+
+ # cache_size: The gdbm cache size for the db
+ # files. Should be equal to the number of ip's
+ # available in the ip pool
+ cache_size = 800
+
+ # session-db: The main db file used to allocate ip's to clients
+ session-db = ${raddbdir}/db.ippool
+
+ # ip-index: Helper db index file used in multilink
+ ip-index = ${raddbdir}/db.ipindex
+
+ # override: Will this ippool override a Framed-IP-Address already set
+ override = no
+ }
+
+ # ANSI X9.9 token support. Not included by default.
+ # $INCLUDE ${confdir}/x99.conf
+
+}
+
+# Instantiation
+#
+# This section orders the loading of the modules. Modules
+# listed here will get loaded BEFORE the later sections like
+# authorize, authenticate, etc. get examined.
+#
+# This section is not strictly needed. When a section like
+# authorize refers to a module, it's automatically loaded and
+# initialized. However, some modules may not be listed in any
+# of the following sections, so they can be listed here.
+#
+# Also, listing modules here ensures that you have control over
+# the order in which they are initalized. If one module needs
+# something defined by another module, you can list them in order
+# here, and ensure that the configuration will be OK.
+#
+instantiate {
+ #
+ # The expression module doesn't do authorization,
+ # authentication, or accounting. It only does dynamic
+ # translation, of the form:
+ #
+ # Session-Timeout = `%{expr:2 + 3}`
+ #
+ # So the module needs to be instantiated, but CANNOT be
+ # listed in any other section. See 'doc/rlm_expr' for
+ # more information.
+ #
+ expr
+
+ #
+ # We add the counter module here so that it registers
+ # the check_name attribute before any module which sets
+ # it
+# daily
+}
+
+# Authorization. First preprocess (hints and huntgroups files),
+# then realms, and finally look in the "users" file.
+#
+# The order of the realm modules will determine the order that
+# we try to find a matching realm.
+#
+# Make *sure* that 'preprocess' comes before any realm if you
+# need to setup hints for the remote radius server
+authorize {
+ #
+ # The preprocess module takes care of sanitizing some bizarre
+ # attributes in the request, and turning them into attributes
+ # which are more standard.
+ #
+ # It takes care of processing the 'raddb/hints' and the
+ # 'raddb/huntgroups' files.
+ #
+ # It also adds a Client-IP-Address attribute to the request.
+ preprocess
+
+ #
+ # The chap module will set 'Auth-Type := CHAP' if we are
+ # handling a CHAP request and Auth-Type has not already been set
+ chap
+
+# attr_filter
+
+ #
+ # This module takes care of EAP-MD5, EAP-TLS, and EAP-LEAP
+ # authentication.
+ eap
+
+ #
+ # If you have a Cisco SIP server authenticating against
+ # FreeRADIUS, uncomment the following line.
+ # digest
+
+ #
+ # Look for IPASS style 'realm/', and if not found, look for
+ # '@realm', and decide whether or not to proxy, based on
+ # that.
+# realmslash
+ suffix
+
+ #
+ # Read the 'users' file
+ files
+
+ #
+ # If you are using /etc/smbpasswd, and are also doing
+ # mschap authentication, the un-comment this line, and
+ # configure the 'etc_smbpasswd' module, above.
+# etc_smbpasswd
+
+ #
+ # If the users are logging in with an MS-CHAP-Challenge
+ # attribute for authentication, the mschap module will find
+ # the MS-CHAP-Challenge attribute, and add 'Auth-Type := MS-CHAP'
+ # to the request, which will cause the server to then use
+ # the mschap module for authentication.
+ mschap
+
+
+# The ldap module will set Auth-Type to LDAP if it has not already been set
+# ldap
+# daily
+}
+
+
+# Authentication.
+#
+# This section lists which modules are available for authentication.
+# Note that it does NOT mean 'try each module in order'. It means
+# that you have to have a module from the 'authorize' section add
+# a configuration attribute 'Auth-Type := FOO'. That authentication type
+# is then used to pick the appropriate module from the list below.
+#
+# The default Auth-Type is Local. That is, whatever is not included inside
+# an authtype section will be called only if Auth-Type is set to Local.
+#
+# So you should do the following:
+# - Set Auth-Type to an appropriate value in the authorize modules above.
+# For example, the chap module will set Auth-Type to CHAP, ldap to LDAP, etc.
+# - After that create corresponding authtype sections in the
+# authenticate section below and call the appropriate modules.
+authenticate {
+ #
+ # PAP authentication, when a back-end database listed
+ # in the 'authorize' section supplies a password. The
+ # password can be clear-text, or encrypted.
+ Auth-Type PAP {
+ pap
+ }
+
+ #
+ # Most people want CHAP authentication
+ # A back-end database listed in the 'authorize' section
+ # MUST supply a CLEAR TEXT password. Encrypted passwords
+ # won't work.
+ Auth-Type CHAP {
+ chap
+ }
+
+ #
+ # MSCHAP authentication.
+ Auth-Type MS-CHAP {
+ mschap
+ }
+
+ #
+ # If you have a Cisco SIP server authenticating against
+ # FreeRADIUS, uncomment the following line.
+ # digest
+
+ #
+ # Pluggable Authentication Modules.
+# pam
+
+ #
+ # See 'man getpwent' for information on how the 'unix'
+ # module checks the users password. Note that packets
+ # containing CHAP-Password attributes CANNOT be authenticated
+ # against /etc/passwd! See the FAQ for details.
+ #
+ unix
+
+ # Uncomment it if you want to use ldap for authentication
+# Auth-Type LDAP {
+# ldap
+# }
+
+
+ #
+ # Allow EAP authentication.
+ eap
+}
+
+
+#
+# Pre-accounting. Decide which accounting type to use.
+#
+preacct {
+ preprocess
+
+ #
+ # Look for IPASS-style 'realm/', and if not found, look for
+ # '@realm', and decide whether or not to proxy, based on
+ # that.
+ #
+ # Accounting requests are generally proxied to the same
+ # home server as authentication requests.
+# realmslash
+ suffix
+
+ #
+ # Read the 'acct_users' file
+ files
+}
+
+#
+# Accounting. Log the accounting data.
+#
+accounting {
+ #
+ # Ensure that we have a semi-unique identifier for every
+ # request, and many NAS boxes are broken.
+ acct_unique
+
+ #
+ # Create a 'detail'ed log of the packets.
+ # Note that accounting requests which are proxied
+ # are also logged in the detail file.
+ detail
+# daily
+
+ unix # wtmp file
+
+ #
+ # For Simultaneous-Use tracking.
+ #
+ # Due to packet losses in the network, the data here
+ # may be incorrect. There's little we can do about it.
+ radutmp
+# sradutmp
+
+ # Return an address to the IP Pool when we see a stop record.
+# main_pool
+}
+
+
+# Session database, used for checking Simultaneous-Use. Either the radutmp
+# or rlm_sql module can handle this.
+# The rlm_sql module is *much* faster
+session {
+ radutmp
+# sql
+}
+
+
+# Post-Authentication
+# Once we KNOW that the user has been authenticated, there are
+# additional steps we can take.
+post-auth {
+ # Get an address from the IP Pool.
+# main_pool
+}
+
+#
+# When the server decides to proxy a request to a home server,
+# the proxied request is first passed through the pre-proxy
+# stage. This stage can re-write the request, or decide to
+# cancel the proxy.
+#
+# Only a few modules currently have this method.
+#
+pre-proxy {
+}
+
+#
+# When the server receives a reply to a request it proxied
+# to a home server, the request may be massaged here, in the
+# post-proxy stage.
+#
+post-proxy {
+ #
+ # If you are proxing LEAP, you MUST configure the EAP
+ # module, and you MUST list it here, in the post-proxy
+ # stage.
+ #
+ # You MUST also use the 'nostrip' option in the 'realm'
+ # configuration. Otherwise, the User-Name attribute
+ # in the proxied request will not match the user name
+ # hidden inside of the EAP packet, and the end server will
+ # reject the EAP request.
+ #
+ eap
+}
diff --git a/src/tests/eapsim-03/users-example.txt b/src/tests/eapsim-03/users-example.txt
new file mode 100644
index 0000000..7bcfc89
--- /dev/null
+++ b/src/tests/eapsim-03/users-example.txt
@@ -0,0 +1,34 @@
+1244070100000001@eapsim.foo Auth-Type := EAP, Autz-Type:= EAP, EAP-Type := SIM
+ EAP-Sim-Rand1 = 0x101112131415161718191a1b1c1d1e1f,
+ EAP-Sim-SRES1 = 0xd1d2d3d4,
+ EAP-Sim-Rand2 = 0x202122232425262728292a2b2c2d2e2f,
+ EAP-Sim-SRES2 = 0xe1e2e3e4,
+ EAP-Sim-Rand3 = 0x303132333435363738393a3b3c3d3e3f,
+ EAP-Sim-SRES3 = 0xf1f2f3f4,
+ EAP-Sim-KC1 = 0xa0a1a2a3a4a5a6a7,
+ EAP-Sim-KC2 = 0xb0b1b2b3b4b5b6b7,
+ EAP-Sim-KC3 = 0xc0c1c2c3c4c5c6c7,
+
+1232420100000015 Auth-Type := EAP, Autz-Type:=EAP, EAP-Type := SIM
+ EAP-Sim-Rand1 = 0x30000000000000000000000000000000,
+ EAP-Sim-SRES1 = 0x30112233,
+ EAP-Sim-KC1 = 0x445566778899AABB,
+ EAP-Sim-Rand2 = 0x31000000000000000000000000000000,
+ EAP-Sim-SRES2 = 0x31112233,
+ EAP-Sim-KC2 = 0x445566778899AABB,
+ EAP-Sim-Rand3 = 0x32000000000000000000000000000000,
+ EAP-Sim-SRES3 = 0x32112233,
+ EAP-Sim-KC3 = 0x445566778899AABB,
+
+eapsim Auth-Type := EAP, Autz-Type:=EAP, EAP-Type := SIM
+ EAP-Sim-Rand1 = 0xabcd1234abcd1234abcd1234abcd1234,
+ EAP-Sim-SRES1 = 0x1234abcd,
+ EAP-Sim-KC1 = 0x0011223344556677,
+ EAP-Sim-Rand2 = 0xbcd1234abcd1234abcd1234abcd1234a,
+ EAP-Sim-SRES2 = 0x234abcd1,
+ EAP-Sim-KC2 = 0x1021324354657687,
+ EAP-Sim-Rand3 = 0xcd1234abcd1234abcd1234abcd1234ab,
+ EAP-Sim-SRES3 = 0x34abcd12,
+ EAP-Sim-KC3 = 0x30415263748596a7
+
+
diff --git a/src/tests/eapsim-04/client.sh b/src/tests/eapsim-04/client.sh
new file mode 100644
index 0000000..2ae1747
--- /dev/null
+++ b/src/tests/eapsim-04/client.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+../../main/radeapclient -x localhost auth testing123 <eapsim-in.txt
+
+
+
diff --git a/src/tests/eapsim-04/eapsim-cooked.txt b/src/tests/eapsim-04/eapsim-cooked.txt
new file mode 100644
index 0000000..7c6d97f
--- /dev/null
+++ b/src/tests/eapsim-04/eapsim-cooked.txt
@@ -0,0 +1,169 @@
+
++++> About to send encoded packet:
+ User-Name = "1244070100000001@eapsim.foo"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ EAP-Type-Identity = "1244070100000001@eapsim.foo"
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x101112131415161718191a1b1c1d1e1f
+ EAP-Sim-SRES1 = 0xd1d2d3d4
+ EAP-Sim-Rand2 = 0x202122232425262728292a2b2c2d2e2f
+ EAP-Sim-SRES2 = 0xe1e2e3e4
+ EAP-Sim-Rand3 = 0x303132333435363738393a3b3c3d3e3f
+ EAP-Sim-SRES3 = 0xf1f2f3f4
+ EAP-Sim-KC1 = 0xa0a1a2a3a4a5a6a7
+ EAP-Sim-KC2 = 0xb0b1b2b3b4b5b6b7
+ EAP-Sim-KC3 = 0xc0c1c2c3c4c5c6c7
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "1244070100000001@eapsim.foo"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Message = 0x02XX
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=999, length=78
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+<+++ EAP decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0a00000f0200020001000011010100
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0a00000f0200020001000011010100
+ EAP-Sim-Subtype = Start
+ EAP-Sim-VERSION_LIST = 0x000200010000
+ EAP-Sim-FULLAUTH_ID_REQ = 0x0100
+
++++> About to send encoded packet:
+ User-Name = "1244070100000001@eapsim.foo"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x101112131415161718191a1b1c1d1e1f
+ EAP-Sim-SRES1 = 0xd1d2d3d4
+ EAP-Sim-Rand2 = 0x202122232425262728292a2b2c2d2e2f
+ EAP-Sim-SRES2 = 0xe1e2e3e4
+ EAP-Sim-Rand3 = 0x303132333435363738393a3b3c3d3e3f
+ EAP-Sim-SRES3 = 0xf1f2f3f4
+ EAP-Sim-KC1 = 0xa0a1a2a3a4a5a6a7
+ EAP-Sim-KC2 = 0xb0b1b2b3b4b5b6b7
+ EAP-Sim-KC3 = 0xc0c1c2c3c4c5c6c7
+ EAP-Sim-State = 1
+ EAP-Sim-Subtype = Start
+ EAP-Sim-SELECTED_VERSION = 0x0001
+ EAP-Sim-NONCE_MT = 0x000000a3f6b4e832cf46b4d3e0d090623e22
+ EAP-Sim-IDENTITY = 0x001b313234343037303130303030303030314065617073696d2e666f6f
+ EAP-Id = YY
+ EAP-Message = 0x02XX
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "1244070100000001@eapsim.foo"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Message = 0x02XX
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=999, length=138
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+<+++ EAP decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d0000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f0b050000XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d0000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f0b050000XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ EAP-Sim-Subtype = Challenge
+ EAP-Sim-RAND = 0x0000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f
+ EAP-Sim-MAC = 0xYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
+Input was:
+ identity: (len=27)313234343037303130303030303030314065617073696d2e666f6f
+ nonce_mt: 00a3f6b4e832cf46b4d3e0d090623e22
+ rand0: 00000000000000000000000000000000
+ rand1: 00000000000000000000000000000000
+ rand2: 00000000000000000000000000000000
+ sres0: d1d2d3d4
+ sres1: e1e2e3e4
+ sres2: f1f2f3f4
+ Kc0: a0a1a2a3a4a5a6a7
+ Kc1: b0b1b2b3b4b5b6b7
+ Kc2: c0c1c2c3c4c5c6c7
+ versionlist[2]: 0001
+ select 00 01
+
+
+Output
+mk: 2a56fd95_adac4bf7_645c2e60_7296a8af_9e1214a1
+K_aut: 2853a70a_4ca089cc_0cf8a24a_45ecec93
+K_encr: 77987afb_1cfd251d_749d2f16_0611338e
+msk: e8adff17_1d82d5e6_9a78d526_1e86ee56_93cbe646
+ 59332585_1f1f58f0_598c3a0c_1640339b_c3407fb4
+ 56a14ada_a4791445_e8a3cf40_49b4628f_8e9f597a
+ 7891e9d2
+emsk: b33c4a19_c1df9108_17196271_7c4b7f98_e53a64ba
+ a67d4e23_5ff142cb_6e427434_8a71358a_3c2b1313
+ 4cec6be3_a99e60c8_ae543fdd_52ecd7b3_0542e1df
+ 5d10c5f7
+MAC check succeed
+
++++> About to send encoded packet:
+ User-Name = "1244070100000001@eapsim.foo"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x101112131415161718191a1b1c1d1e1f
+ EAP-Sim-SRES1 = 0xd1d2d3d4
+ EAP-Sim-Rand2 = 0x202122232425262728292a2b2c2d2e2f
+ EAP-Sim-SRES2 = 0xe1e2e3e4
+ EAP-Sim-Rand3 = 0x303132333435363738393a3b3c3d3e3f
+ EAP-Sim-SRES3 = 0xf1f2f3f4
+ EAP-Sim-KC1 = 0xa0a1a2a3a4a5a6a7
+ EAP-Sim-KC2 = 0xb0b1b2b3b4b5b6b7
+ EAP-Sim-KC3 = 0xc0c1c2c3c4c5c6c7
+ EAP-Sim-State = 1
+ EAP-Sim-Subtype = Challenge
+ EAP-Id = YY
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Sim-MAC = 0xd1d2d3d4e1e2e3e4f1f2f3f4
+ EAP-Sim-KEY = 0x2853a70a4ca089cc0cf8a24a45ecec93
+ EAP-Message = 0x02XX
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "1244070100000001@eapsim.foo"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Message = 0x02XX
+rad_recv: Access-Accept packet from host 127.0.0.1:1812, id=999, length=189
+ MS-MPPE-Recv-Key = 0xe8adff171d82d5e69a78d5261e86ee5693cbe646593325851f1f58f0598c3a0c
+ MS-MPPE-Send-Key = 0x1640339bc3407fb456a14adaa4791445e8a3cf4049b4628f8e9f597a7891e9d2
+ EAP-Message = 0x03XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ User-Name = "1244070100000001@eapsim.foo"
+<+++ EAP decoded packet:
+ MS-MPPE-Recv-Key = 0xe8adff171d82d5e69a78d5261e86ee5693cbe646593325851f1f58f0598c3a0c
+ MS-MPPE-Send-Key = 0x1640339bc3407fb456a14adaa4791445e8a3cf4049b4628f8e9f597a7891e9d2
+ EAP-Message = 0x03XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ User-Name = "1244070100000001@eapsim.foo"
+ EAP-Id = YY
+ EAP-Code = Success
diff --git a/src/tests/eapsim-04/eapsim-in.txt b/src/tests/eapsim-04/eapsim-in.txt
new file mode 100644
index 0000000..eadd58f
--- /dev/null
+++ b/src/tests/eapsim-04/eapsim-in.txt
@@ -0,0 +1,17 @@
+User-Name = "1244070100000001@eapsim.foo"
+NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+EAP-Code = Response
+EAP-Type-Identity = "1244070100000001@eapsim.foo"
+Message-Authenticator = 0
+NAS-Port = 0
+EAP-Sim-Rand1 = 0x101112131415161718191a1b1c1d1e1f
+EAP-Sim-SRES1 = 0xd1d2d3d4
+EAP-Sim-Rand2 = 0x202122232425262728292a2b2c2d2e2f
+EAP-Sim-SRES2 = 0xe1e2e3e4
+EAP-Sim-Rand3 = 0x303132333435363738393a3b3c3d3e3f
+EAP-Sim-SRES3 = 0xf1f2f3f4
+EAP-Sim-KC1 = 0xa0a1a2a3a4a5a6a7
+EAP-Sim-KC2 = 0xb0b1b2b3b4b5b6b7
+EAP-Sim-KC3 = 0xc0c1c2c3c4c5c6c7
+
+
diff --git a/src/tests/eapsim-04/myvectors.txt b/src/tests/eapsim-04/myvectors.txt
new file mode 100644
index 0000000..d1cfadd
--- /dev/null
+++ b/src/tests/eapsim-04/myvectors.txt
@@ -0,0 +1,136 @@
++Input was:
++ identity: (len=27)313234343037303130303030303030314065617073696d2e666f6f
++ nonce_mt: 0123456789abcdeffedcba9876543201
++ chal0: 101112131415161718191a1b1c1d1e1f
++ chal1: 202122232425262728292a2b2c2d2e2f
++ chal2: 303132333435363738393a3b3c3d3e3f
++ sres0: d1d2d3d4
++ sres1: e1e2e3e4
++ sres2: f1f2f3f4
++ Kc0: a0a1a2a3a4a5a6a7
++ Kc1: b0b1b2b3b4b5b6b7
++ Kc2: c0c1c2c3c4c5c6c7
++ versionlist[4]: 00020001
++ select 00 01
++
++
++Output
++mk: c21b4e4f_4e43619b_891e711f_f84f5e37_5e296d1a
++K_aut: cdd4e489_a7ae78da_67b593bd_8c231102
++K_encr: c322b087_a282de07_b0079dab_8e65d96f
++msk: eeea3a75_8d74d4f4_a7a77b98_5733806e_8093c8f9
++ 6a733668_70fcfb7e_4b0d7ab3_e8657531_25de1aa1
++ 318e21c4_57346f82_ba047e35_16ea4648_22f8039e
++ aa6cc9be
++emsk: fdc00fa1_d159910d_99251485_5d057d84_a4b1bd00
++ 4d34bef5_f7070000_cb380340_e8f00240_fcf9ffbf
++ c0970040_fdb20408_00000000_58e50640_003b3731
++ fcf9ffbf
+
++SHA1buffer was: 31323434_30373031_30303030_30303031_40656170_
++ 73696d2e_666f6fa0_a1a2a3a4_a5a6a7b0_b1b2b3b4_
++ b5b6b7c0_c1c2c3c4_c5c6c701_23456789_abcdeffe_
++ dcba9876_54320100_010001
+
+VERSION is 1 byte:
+
++SHA1buffer was: 31323434_30373031_30303030_30303031_40656170_
++ 73696d2e_666f6fa0_a1a2a3a4_a5a6a7b0_b1b2b3b4_
++ b5b6b7c0_c1c2c3c4_c5c6c701_23456789_abcdeffe_
++ dcba9876_54320100_0101
++Input was:
++ identity: (len=27)313234343037303130303030303030314065617073696d2e666f6f
++ nonce_mt: 0123456789abcdeffedcba9876543201
++ rand0: 101112131415161718191a1b1c1d1e1f
++ rand1: 202122232425262728292a2b2c2d2e2f
++ rand2: 303132333435363738393a3b3c3d3e3f
++ sres0: d1d2d3d4
++ sres1: e1e2e3e4
++ sres2: f1f2f3f4
++ Kc0: a0a1a2a3a4a5a6a7
++ Kc1: b0b1b2b3b4b5b6b7
++ Kc2: c0c1c2c3c4c5c6c7
++ versionlist[2]: 0001
++ select 00 01
++
++
++Output
++mk: cfe4d5bc_fb87bcab_4d83ebea_90c179df_3cfee43c
++K_aut: 32aa4046_770c30ed_bce21212_d7d9393c
++K_encr: e3810875_f8c40f7f_cb2544ed_d0d873c3
++msk: 3a8dd0fb_411d15e1_4d485c8b_bd94ab23_a8ea3e5a
++ d888521c_d1a3fa7d_1fabd7e2_afd062f6_75c3de8b
++ 5adda978_91d78a3d_2efcb988_265ceee3_fa924279
++ 43fa0125
++emsk: 5296957b_61bc72f8_5c2acbd5_501299d1_b7e2b04f
++ 39127a69_003b0140_f8200340_003b0140_03000000
++ 98bb0240_003b0140_e8f9ffbf_a4810408_bc373731
++ fcf9ffbf
+
+1-byte:
++SHA1buffer was: 31323434_30373031_30303030_30303031_40656170_
++ 73696d2e_666f6fa0_a1a2a3a4_a5a6a7b0_b1b2b3b4_
++ b5b6b7c0_c1c2c3c4_c5c6c701_23456789_abcdeffe_
++ dcba9876_54321000_0101
++Input was:
++ identity: (len=27)313234343037303130303030303030314065617073696d2e666f6f
++ nonce_mt: 0123456789abcdeffedcba9876543210
++ rand0: 101112131415161718191a1b1c1d1e1f
++ rand1: 202122232425262728292a2b2c2d2e2f
++ rand2: 303132333435363738393a3b3c3d3e3f
++ sres0: d1d2d3d4
++ sres1: e1e2e3e4
++ sres2: f1f2f3f4
++ Kc0: a0a1a2a3a4a5a6a7
++ Kc1: b0b1b2b3b4b5b6b7
++ Kc2: c0c1c2c3c4c5c6c7
++ versionlist[2]: 0001
++ select 00 01
++
++
++Output
++mk: d328f534_d9292b67_0e73c798_591e1e09_04c0c8cc
++K_aut: aa19e454_833aa2ea_ccc116db_9312b543
++K_encr: 51fc0641_e4d9fa43_23f9516d_15b9f618
++msk: ced8e588_7d883785_ee2d2e41_f1aeb82d_1cfca277
++ 7309b411_30047c52_130807c0_bdf0e56e_205433e0
++ 58b2f48e_2337809d_e1b2681c_e30932d9_2a62cbe8
++ 40bfb568
++emsk: a273b6f5_47d12da7_c1d0dff4_746e0ded_70e74a83
++ 520b22a8_003b0140_f8200340_003b0140_03000000
++ 98bb0240_003b0140_e8f9ffbf_a4810408_bc373731
++ fcf9ffbf
+
+2-bytes:
++SHA1buffer was: 31323434_30373031_30303030_30303031_40656170_
++ 73696d2e_666f6fa0_a1a2a3a4_a5a6a7b0_b1b2b3b4_
++ b5b6b7c0_c1c2c3c4_c5c6c701_23456789_abcdeffe_
++ dcba9876_54321000_010001
++Input was:
++ identity: (len=27)313234343037303130303030303030314065617073696d2e666f6f
++ nonce_mt: 0123456789abcdeffedcba9876543210
++ rand0: 101112131415161718191a1b1c1d1e1f
++ rand1: 202122232425262728292a2b2c2d2e2f
++ rand2: 303132333435363738393a3b3c3d3e3f
++ sres0: d1d2d3d4
++ sres1: e1e2e3e4
++ sres2: f1f2f3f4
++ Kc0: a0a1a2a3a4a5a6a7
++ Kc1: b0b1b2b3b4b5b6b7
++ Kc2: c0c1c2c3c4c5c6c7
++ versionlist[2]: 0001
++ select 00 01
++
++
++Output
++mk: e576d5ca_332e9930_018bf1ba_ee2763c7_95b3c712
++K_aut: 536e5ebc_4465582a_a6a8ec99_86ebb620
++K_encr: 25af1942_efcbf4bc_72b39434_21f2a974
++msk: 39d45aea_f4e30601_983e972b_6cfd46d1_c3637733
++ 65690d09_cd44976b_525f47d3_a60a985e_955c53b0
++ 90b2e4b7_3719196a_40254296_8fd14a88_8f46b9a7
++ 886e4488
++emsk: 5949eab0_fff69d52_315c6c63_4fd14a7f_0d52023d
++ 56f79698_003b0140_f8200340_003b0140_03000000
++ 98bb0240_003b0140_e8f9ffbf_a4810408_bc373731
++ fcf9ffbf
diff --git a/src/tests/eapsim-04/users.txt b/src/tests/eapsim-04/users.txt
new file mode 100644
index 0000000..af2e006
--- /dev/null
+++ b/src/tests/eapsim-04/users.txt
@@ -0,0 +1,17 @@
+1244070100000001@eapsim.foo Auth-Type := EAP, EAP-Type := SIM
+ EAP-Sim-Chal1 = 0x101112131415161718191a1b1c1d1e1f,
+ EAP-Sim-SRES1 = 0xd1d2d3d4,
+ EAP-Sim-Chal2 = 0x202122232425262728292a2b2c2d2e2f,
+ EAP-Sim-SRES2 = 0xe1e2e3e4,
+ EAP-Sim-Chal3 = 0x303132333435363738393a3b3c3d3e3f,
+ EAP-Sim-SRES3 = 0xf1f2f3f4,
+ EAP-Sim-KC1 = 0xa0a1a2a3a4a5a6a7,
+ EAP-Sim-KC2 = 0xb0b1b2b3b4b5b6b7,
+ EAP-Sim-KC3 = 0xc0c1c2c3c4c5c6c7,
+ Service-Type = Framed-User,
+ Framed-Protocol = PPP,
+ Framed-IP-Address = 172.16.3.34,
+ Framed-IP-Netmask = 255.255.255.0,
+ Framed-Routing = Broadcast-Listen,
+ Framed-MTU = 1234
+
diff --git a/src/tests/eapsim-05/check.gdb b/src/tests/eapsim-05/check.gdb
new file mode 100644
index 0000000..8418d96
--- /dev/null
+++ b/src/tests/eapsim-05/check.gdb
@@ -0,0 +1,2 @@
+file ../../main/radeapclient
+set args -x localhost auth testing123 <eapsim-in.txt \ No newline at end of file
diff --git a/src/tests/eapsim-05/client.sh b/src/tests/eapsim-05/client.sh
new file mode 100644
index 0000000..2ae1747
--- /dev/null
+++ b/src/tests/eapsim-05/client.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+../../main/radeapclient -x localhost auth testing123 <eapsim-in.txt
+
+
+
diff --git a/src/tests/eapsim-05/description.txt b/src/tests/eapsim-05/description.txt
new file mode 100644
index 0000000..a490df4
--- /dev/null
+++ b/src/tests/eapsim-05/description.txt
@@ -0,0 +1,2 @@
+This test is identical to eapsim-03, except that the RAND is purposely
+wrong.
diff --git a/src/tests/eapsim-05/eapsim-cooked.txt b/src/tests/eapsim-05/eapsim-cooked.txt
new file mode 100644
index 0000000..8b8c4a5
--- /dev/null
+++ b/src/tests/eapsim-05/eapsim-cooked.txt
@@ -0,0 +1,148 @@
+
++++> About to send encoded packet:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ EAP-Type-Identity = "eapsim"
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x89abcbeef9abcdef89abcdef89abcdef
+ EAP-Sim-Rand2 = 0x9abcdef89abcdef89abcdef89abcdef8
+ EAP-Sim-Rand3 = 0xabcdef89abcdef89abcdef89abcdef89
+ EAP-Sim-SRES1 = 0x1234abcd
+ EAP-Sim-SRES2 = 0x234abcd1
+ EAP-Sim-SRES3 = 0x34abcd12
+ EAP-Sim-KC1 = 0x0011223344556677
+ EAP-Sim-KC2 = 0x1021324354657687
+ EAP-Sim-KC3 = 0x30415263748596a7
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Message = 0x02XX
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=999, length=78
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+<+++ EAP decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0a00000f0200020001000011010100
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0a00000f0200020001000011010100
+ EAP-Sim-Subtype = Start
+ EAP-Sim-VERSION_LIST = 0x000200010000
+ EAP-Sim-FULLAUTH_ID_REQ = 0x0100
+
++++> About to send encoded packet:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x89abcbeef9abcdef89abcdef89abcdef
+ EAP-Sim-Rand2 = 0x9abcdef89abcdef89abcdef89abcdef8
+ EAP-Sim-Rand3 = 0xabcdef89abcdef89abcdef89abcdef89
+ EAP-Sim-SRES1 = 0x1234abcd
+ EAP-Sim-SRES2 = 0x234abcd1
+ EAP-Sim-SRES3 = 0x34abcd12
+ EAP-Sim-KC1 = 0x0011223344556677
+ EAP-Sim-KC2 = 0x1021324354657687
+ EAP-Sim-KC3 = 0x30415263748596a7
+ EAP-Sim-State = 1
+ EAP-Sim-Subtype = Start
+ EAP-Sim-SELECTED_VERSION = 0x0001
+ EAP-Sim-NONCE_MT = 0x000000a3f6b4e832cf46b4d3e0d090623e22
+ EAP-Sim-IDENTITY = 0x000665617073696d
+ EAP-Id = YY
+ EAP-Message = 0x02XX
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Message = 0x02XX
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=999, length=138
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+<+++ EAP decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b050000XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b050000XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ EAP-Sim-Subtype = Challenge
+ EAP-Sim-RAND = 0x0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab
+ EAP-Sim-MAC = 0xYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
+__________________
++++> About to send encoded packet:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x89abcbeef9abcdef89abcdef89abcdef
+ EAP-Sim-Rand2 = 0x9abcdef89abcdef89abcdef89abcdef8
+ EAP-Sim-Rand3 = 0xabcdef89abcdef89abcdef89abcdef89
+ EAP-Sim-SRES1 = 0x1234abcd
+ EAP-Sim-SRES2 = 0x234abcd1
+ EAP-Sim-SRES3 = 0x34abcd12
+ EAP-Sim-KC1 = 0x0011223344556677
+ EAP-Sim-KC2 = 0x1021324354657687
+ EAP-Sim-KC3 = 0x30415263748596a7
+ EAP-Sim-State = 0
+ EAP-Sim-Subtype = Start
+ EAP-Sim-SELECTED_VERSION = 0x0001
+ EAP-Sim-NONCE_MT = 0x000000a3f6b4e832cf46b4d3e0d090623e22
+ EAP-Sim-IDENTITY = 0x000665617073696d
+ EAP-Id = YY
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Message = 0x02XX
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Message = 0x02XX
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=999, length=138
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+<+++ EAP decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b050000XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b050000XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ EAP-Sim-Subtype = Challenge
+ EAP-Sim-RAND = 0x0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab
+ EAP-Sim-MAC = 0xYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
diff --git a/src/tests/eapsim-05/eapsim-in.txt b/src/tests/eapsim-05/eapsim-in.txt
new file mode 100644
index 0000000..a48fdd9
--- /dev/null
+++ b/src/tests/eapsim-05/eapsim-in.txt
@@ -0,0 +1,15 @@
+User-Name = "eapsim"
+NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+EAP-Code = Response
+EAP-Type-Identity = "eapsim"
+Message-Authenticator = 0
+NAS-Port = 0
+EAP-Sim-Rand1 = 0x89abcBEEF9abcdef89abcdef89abcdef
+EAP-Sim-Rand2 = 0x9abcdef89abcdef89abcdef89abcdef8
+EAP-Sim-Rand3 = 0xabcdef89abcdef89abcdef89abcdef89
+EAP-Sim-Sres1 = 0x1234abcd
+EAP-Sim-Sres2 = 0x234abcd1
+EAP-Sim-Sres3 = 0x34abcd12
+EAP-Sim-KC1 = 0x0011223344556677
+EAP-Sim-KC2 = 0x1021324354657687
+EAP-Sim-KC3 = 0x30415263748596a7
diff --git a/src/tests/eapsim-05/eapsim-out.txt b/src/tests/eapsim-05/eapsim-out.txt
new file mode 100644
index 0000000..8b8c4a5
--- /dev/null
+++ b/src/tests/eapsim-05/eapsim-out.txt
@@ -0,0 +1,148 @@
+
++++> About to send encoded packet:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ EAP-Type-Identity = "eapsim"
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x89abcbeef9abcdef89abcdef89abcdef
+ EAP-Sim-Rand2 = 0x9abcdef89abcdef89abcdef89abcdef8
+ EAP-Sim-Rand3 = 0xabcdef89abcdef89abcdef89abcdef89
+ EAP-Sim-SRES1 = 0x1234abcd
+ EAP-Sim-SRES2 = 0x234abcd1
+ EAP-Sim-SRES3 = 0x34abcd12
+ EAP-Sim-KC1 = 0x0011223344556677
+ EAP-Sim-KC2 = 0x1021324354657687
+ EAP-Sim-KC3 = 0x30415263748596a7
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Message = 0x02XX
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=999, length=78
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+<+++ EAP decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0a00000f0200020001000011010100
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0a00000f0200020001000011010100
+ EAP-Sim-Subtype = Start
+ EAP-Sim-VERSION_LIST = 0x000200010000
+ EAP-Sim-FULLAUTH_ID_REQ = 0x0100
+
++++> About to send encoded packet:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x89abcbeef9abcdef89abcdef89abcdef
+ EAP-Sim-Rand2 = 0x9abcdef89abcdef89abcdef89abcdef8
+ EAP-Sim-Rand3 = 0xabcdef89abcdef89abcdef89abcdef89
+ EAP-Sim-SRES1 = 0x1234abcd
+ EAP-Sim-SRES2 = 0x234abcd1
+ EAP-Sim-SRES3 = 0x34abcd12
+ EAP-Sim-KC1 = 0x0011223344556677
+ EAP-Sim-KC2 = 0x1021324354657687
+ EAP-Sim-KC3 = 0x30415263748596a7
+ EAP-Sim-State = 1
+ EAP-Sim-Subtype = Start
+ EAP-Sim-SELECTED_VERSION = 0x0001
+ EAP-Sim-NONCE_MT = 0x000000a3f6b4e832cf46b4d3e0d090623e22
+ EAP-Sim-IDENTITY = 0x000665617073696d
+ EAP-Id = YY
+ EAP-Message = 0x02XX
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Message = 0x02XX
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=999, length=138
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+<+++ EAP decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b050000XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b050000XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ EAP-Sim-Subtype = Challenge
+ EAP-Sim-RAND = 0x0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab
+ EAP-Sim-MAC = 0xYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
+__________________
++++> About to send encoded packet:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x89abcbeef9abcdef89abcdef89abcdef
+ EAP-Sim-Rand2 = 0x9abcdef89abcdef89abcdef89abcdef8
+ EAP-Sim-Rand3 = 0xabcdef89abcdef89abcdef89abcdef89
+ EAP-Sim-SRES1 = 0x1234abcd
+ EAP-Sim-SRES2 = 0x234abcd1
+ EAP-Sim-SRES3 = 0x34abcd12
+ EAP-Sim-KC1 = 0x0011223344556677
+ EAP-Sim-KC2 = 0x1021324354657687
+ EAP-Sim-KC3 = 0x30415263748596a7
+ EAP-Sim-State = 0
+ EAP-Sim-Subtype = Start
+ EAP-Sim-SELECTED_VERSION = 0x0001
+ EAP-Sim-NONCE_MT = 0x000000a3f6b4e832cf46b4d3e0d090623e22
+ EAP-Sim-IDENTITY = 0x000665617073696d
+ EAP-Id = YY
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Message = 0x02XX
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Message = 0x02XX
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=999, length=138
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+<+++ EAP decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b050000XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b050000XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ EAP-Sim-Subtype = Challenge
+ EAP-Sim-RAND = 0x0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab
+ EAP-Sim-MAC = 0xYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
diff --git a/src/tests/eapsim-05/eapsim-raw.txt b/src/tests/eapsim-05/eapsim-raw.txt
new file mode 100644
index 0000000..ef7d7c6
--- /dev/null
+++ b/src/tests/eapsim-05/eapsim-raw.txt
@@ -0,0 +1,148 @@
+
++++> About to send encoded packet:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ EAP-Type-Identity = "eapsim"
+ Message-Authenticator = 0x30
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x89abcbeef9abcdef89abcdef89abcdef
+ EAP-Sim-Rand2 = 0x9abcdef89abcdef89abcdef89abcdef8
+ EAP-Sim-Rand3 = 0xabcdef89abcdef89abcdef89abcdef89
+ EAP-Sim-SRES1 = 0x1234abcd
+ EAP-Sim-SRES2 = 0x234abcd1
+ EAP-Sim-SRES3 = 0x34abcd12
+ EAP-Sim-KC1 = 0x0011223344556677
+ EAP-Sim-KC2 = 0x1021324354657687
+ EAP-Sim-KC3 = 0x30415263748596a7
+Sending Access-Request of id 204 to 127.0.0.1:1812
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0x00000000000000000000000000000000
+ NAS-Port = 0
+ EAP-Message = 0x02cb000b0165617073696d
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=204, length=78
+ EAP-Message = 0x01a70014120a00000f0200020001000011010100
+ Message-Authenticator = 0xce4f705db940a4a61c0896712e259ecb
+ State = 0xc313c2aa36347b14bc248a365651d25f
+<+++ EAP decoded packet:
+ EAP-Message = 0x01a70014120a00000f0200020001000011010100
+ Message-Authenticator = 0xce4f705db940a4a61c0896712e259ecb
+ State = 0xc313c2aa36347b14bc248a365651d25f
+ EAP-Id = 167
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0a00000f0200020001000011010100
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01a70014120a00000f0200020001000011010100
+ Message-Authenticator = 0xce4f705db940a4a61c0896712e259ecb
+ State = 0xc313c2aa36347b14bc248a365651d25f
+ EAP-Id = 167
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0a00000f0200020001000011010100
+ EAP-Sim-Subtype = Start
+ EAP-Sim-VERSION_LIST = 0x000200010000
+ EAP-Sim-FULLAUTH_ID_REQ = 0x0100
+
++++> About to send encoded packet:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ Message-Authenticator = 0x00000000000000000000000000000000
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x89abcbeef9abcdef89abcdef89abcdef
+ EAP-Sim-Rand2 = 0x9abcdef89abcdef89abcdef89abcdef8
+ EAP-Sim-Rand3 = 0xabcdef89abcdef89abcdef89abcdef89
+ EAP-Sim-SRES1 = 0x1234abcd
+ EAP-Sim-SRES2 = 0x234abcd1
+ EAP-Sim-SRES3 = 0x34abcd12
+ EAP-Sim-KC1 = 0x0011223344556677
+ EAP-Sim-KC2 = 0x1021324354657687
+ EAP-Sim-KC3 = 0x30415263748596a7
+ EAP-Sim-State = 1
+ EAP-Sim-Subtype = Start
+ EAP-Sim-SELECTED_VERSION = 0x0001
+ EAP-Sim-NONCE_MT = 0x000000a3f6b4e832cf46b4d3e0d090623e22
+ EAP-Sim-IDENTITY = 0x000665617073696d
+ EAP-Id = 167
+ EAP-Message = 0x02a7002c120a0000100100010705000000a3f6b4e832cf46b4d3e0d090623e220e03000665617073696d0000
+ State = 0xc313c2aa36347b14bc248a365651d25f
+Sending Access-Request of id 205 to 127.0.0.1:1812
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0x00000000000000000000000000000000
+ NAS-Port = 0
+ EAP-Message = 0x02a7002c120a0000100100010705000000a3f6b4e832cf46b4d3e0d090623e220e03000665617073696d0000
+ State = 0xc313c2aa36347b14bc248a365651d25f
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=205, length=138
+ EAP-Message = 0x01a80050120b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b0500004295110227f46b83717e6d2f64ecd5f5
+ Message-Authenticator = 0xe76cfc6fb346d107b4b1d313faac212e
+ State = 0xf94335155e78fbfc00ee2fab6d277167
+<+++ EAP decoded packet:
+ EAP-Message = 0x01a80050120b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b0500004295110227f46b83717e6d2f64ecd5f5
+ Message-Authenticator = 0xe76cfc6fb346d107b4b1d313faac212e
+ State = 0xf94335155e78fbfc00ee2fab6d277167
+ EAP-Id = 168
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b0500004295110227f46b83717e6d2f64ecd5f5
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01a80050120b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b0500004295110227f46b83717e6d2f64ecd5f5
+ Message-Authenticator = 0xe76cfc6fb346d107b4b1d313faac212e
+ State = 0xf94335155e78fbfc00ee2fab6d277167
+ EAP-Id = 168
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b0500004295110227f46b83717e6d2f64ecd5f5
+ EAP-Sim-Subtype = Challenge
+ EAP-Sim-RAND = 0x0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab
+ EAP-Sim-MAC = 0x00004295110227f46b83717e6d2f64ecd5f5
+__________________
++++> About to send encoded packet:
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ Message-Authenticator = 0x00000000000000000000000000000000
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x89abcbeef9abcdef89abcdef89abcdef
+ EAP-Sim-Rand2 = 0x9abcdef89abcdef89abcdef89abcdef8
+ EAP-Sim-Rand3 = 0xabcdef89abcdef89abcdef89abcdef89
+ EAP-Sim-SRES1 = 0x1234abcd
+ EAP-Sim-SRES2 = 0x234abcd1
+ EAP-Sim-SRES3 = 0x34abcd12
+ EAP-Sim-KC1 = 0x0011223344556677
+ EAP-Sim-KC2 = 0x1021324354657687
+ EAP-Sim-KC3 = 0x30415263748596a7
+ EAP-Sim-State = 0
+ EAP-Sim-Subtype = Start
+ EAP-Sim-SELECTED_VERSION = 0x0001
+ EAP-Sim-NONCE_MT = 0x000000a3f6b4e832cf46b4d3e0d090623e22
+ EAP-Sim-IDENTITY = 0x000665617073696d
+ EAP-Id = 168
+ State = 0xf94335155e78fbfc00ee2fab6d277167
+ EAP-Message = 0x02a8002c120a0000100100010705000000a3f6b4e832cf46b4d3e0d090623e220e03000665617073696d0000
+Sending Access-Request of id 206 to 127.0.0.1:1812
+ User-Name = "eapsim"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0x00000000000000000000000000000000
+ NAS-Port = 0
+ State = 0xf94335155e78fbfc00ee2fab6d277167
+ EAP-Message = 0x02a8002c120a0000100100010705000000a3f6b4e832cf46b4d3e0d090623e220e03000665617073696d0000
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=206, length=138
+ EAP-Message = 0x01a90050120b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b050000d7c75f997bf27cd7a46f3b34f8a8f5b3
+ Message-Authenticator = 0x16fc76638c72e04b8b43ab48edcf5088
+ State = 0xb9449aca40be007b0a641b9fa5b1f761
+<+++ EAP decoded packet:
+ EAP-Message = 0x01a90050120b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b050000d7c75f997bf27cd7a46f3b34f8a8f5b3
+ Message-Authenticator = 0x16fc76638c72e04b8b43ab48edcf5088
+ State = 0xb9449aca40be007b0a641b9fa5b1f761
+ EAP-Id = 169
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b050000d7c75f997bf27cd7a46f3b34f8a8f5b3
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01a90050120b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b050000d7c75f997bf27cd7a46f3b34f8a8f5b3
+ Message-Authenticator = 0x16fc76638c72e04b8b43ab48edcf5088
+ State = 0xb9449aca40be007b0a641b9fa5b1f761
+ EAP-Id = 169
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab0b050000d7c75f997bf27cd7a46f3b34f8a8f5b3
+ EAP-Sim-Subtype = Challenge
+ EAP-Sim-RAND = 0x0000abcd1234abcd1234abcd1234abcd1234bcd1234abcd1234abcd1234abcd1234acd1234abcd1234abcd1234abcd1234ab
+ EAP-Sim-MAC = 0x0000d7c75f997bf27cd7a46f3b34f8a8f5b3
diff --git a/src/tests/eapsim-05/eapsim-sanitize.sed b/src/tests/eapsim-05/eapsim-sanitize.sed
new file mode 100644
index 0000000..84410ca
--- /dev/null
+++ b/src/tests/eapsim-05/eapsim-sanitize.sed
@@ -0,0 +1,10 @@
+s/\(Sending Access-Request of id\).*\(to 127.0.0.1:1812\)/\1 999 \2/
+s/\(Message-Authenticator = 0x\).*/\1ABCDABCDABCDABCDABCDABCDABCDABCD/
+s/\(State = 0x\).*/\1ABCDABCDABCDABCDABCDABCDABCDABCD/
+s/\(rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id\)=.*,\( length=.*\)/\1=999,\2/
+s/\(rad_recv: Access-Accept packet from host 127.0.0.1:1812, id\)=.*,\( length=.*\)/\1=999,\2/
+s/\(EAP-Message = 0x..\)\(.*\)/\1XX/
+s/\(EAP-Id = \).*/\1YY/
+s/\(EAP-Type-MD5 = \).*/\1MD5/
+
+
diff --git a/src/tests/eapsim-06/check.gdb b/src/tests/eapsim-06/check.gdb
new file mode 100644
index 0000000..a3bb6be
--- /dev/null
+++ b/src/tests/eapsim-06/check.gdb
@@ -0,0 +1,2 @@
+file ../../modules/rlm_eap/radeapclient
+set args -x localhost auth testing123 <eapsim-in.txt \ No newline at end of file
diff --git a/src/tests/eapsim-06/client.sh b/src/tests/eapsim-06/client.sh
new file mode 100644
index 0000000..2ae1747
--- /dev/null
+++ b/src/tests/eapsim-06/client.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+../../main/radeapclient -x localhost auth testing123 <eapsim-in.txt
+
+
+
diff --git a/src/tests/eapsim-06/description.txt b/src/tests/eapsim-06/description.txt
new file mode 100644
index 0000000..dacee1a
--- /dev/null
+++ b/src/tests/eapsim-06/description.txt
@@ -0,0 +1,24 @@
+This test is identical to eapsim-05, but uses triplets that come from
+the file simtriplets.dat.
+
+To configure this test, add the following to radiusd.conf:
+
+In modules {},
+
+ sim_files {
+ simtriplets = "/some/file"
+ }
+
+ (The default is to use ${raddbdir}/simtriplets.dat )
+
+In authorized {}, add:
+
+ sim_files
+
+before eap.
+
+Of course, you'll already have "sim" listed in the eap{} section of
+modules.
+
+
+
diff --git a/src/tests/eapsim-06/eapsim-cooked.txt b/src/tests/eapsim-06/eapsim-cooked.txt
new file mode 100644
index 0000000..6597b00
--- /dev/null
+++ b/src/tests/eapsim-06/eapsim-cooked.txt
@@ -0,0 +1,184 @@
+
++++> About to send encoded packet:
+ User-Name = "232420100000015"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ EAP-Type-Identity = "232420100000015"
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x30000000000000000000000000000000
+ EAP-Sim-Rand2 = 0x31000000000000000000000000000000
+ EAP-Sim-Rand3 = 0x32000000000000000000000000000000
+ EAP-Sim-SRES1 = 0x30112233
+ EAP-Sim-SRES2 = 0x31112233
+ EAP-Sim-SRES3 = 0x32112233
+ EAP-Sim-KC1 = 0x445566778899aabb
+ EAP-Sim-KC2 = 0x445566778899aabb
+ EAP-Sim-KC3 = 0x445566778899aabb
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "232420100000015"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Message = 0x02XX
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=999, length=78
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+<+++ EAP decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0a00000f0200020001000011010100
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0a00000f0200020001000011010100
+ EAP-Sim-Subtype = Start
+ EAP-Sim-VERSION_LIST = 0x000200010000
+ EAP-Sim-FULLAUTH_ID_REQ = 0x0100
+
++++> About to send encoded packet:
+ User-Name = "232420100000015"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x30000000000000000000000000000000
+ EAP-Sim-Rand2 = 0x31000000000000000000000000000000
+ EAP-Sim-Rand3 = 0x32000000000000000000000000000000
+ EAP-Sim-SRES1 = 0x30112233
+ EAP-Sim-SRES2 = 0x31112233
+ EAP-Sim-SRES3 = 0x32112233
+ EAP-Sim-KC1 = 0x445566778899aabb
+ EAP-Sim-KC2 = 0x445566778899aabb
+ EAP-Sim-KC3 = 0x445566778899aabb
+ EAP-Sim-State = 1
+ EAP-Sim-Subtype = Start
+ EAP-Sim-SELECTED_VERSION = 0x0001
+ EAP-Sim-NONCE_MT = 0x00001b764ea5668faa4b0e7dd876d25753f8
+ EAP-Sim-IDENTITY = 0x000f323332343230313030303030303135
+ EAP-Id = YY
+ EAP-Message = 0x02XX
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "232420100000015"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Message = 0x02XX
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=999, length=138
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+<+++ EAP decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d00003000000000000000000000000000000031000000000000000000000000000000320000000000000000000000000000000b050000XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d00003000000000000000000000000000000031000000000000000000000000000000320000000000000000000000000000000b050000XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ EAP-Sim-Subtype = Challenge
+ EAP-Sim-RAND = 0x0000300000000000000000000000000000003100000000000000000000000000000032000000000000000000000000000000
+ EAP-Sim-MAC = 0xYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
+Input was:
+ identity: (len=15)323332343230313030303030303135
+ nonce_mt: 1b764ea5668faa4b0e7dd876d25753f8
+ rand0: 00000000000000000000000000000000
+ rand1: 00000000000000000000000000000000
+ rand2: 00000000000000000000000000000000
+ sres0: 30112233
+ sres1: 31112233
+ sres2: 32112233
+ Kc0: 445566778899aabb
+ Kc1: 445566778899aabb
+ Kc2: 445566778899aabb
+ versionlist[2]: 0001
+ select 00 01
+
+
+Output
+mk: a444d7cc_dd514568_da171dd4_229ed4d1_a088c470
+K_aut: a4c96a3c_1b4e1932_acc3878d_ecb5d9c6
+K_encr: f544a796_43c4d95f_90aaa5b7_74267742
+msk: 8000f5e4_ed05a9bf_17b9ec6a_27f92d9d_f104966b
+ 03689665_de45db49_82ecdcc4_85c26910_e886de4f
+ bdfa4218_b4ef2f64_319c9b41_b77b3c90_69d616f9
+ 0781438a
+emsk: 3c87c92f_44193e35_dd18e906_3d7cff8f_cb6d6002
+ bf233300_5df66776_70086929_f0d27970_3e59c480
+ 675d6b45_0dc6a79a_51dc34b0_7091a5ff_8ca145ce
+ 98accef2
+
+hmac-sha1 key(16): a4c96a3c_1b4e1932_acc3878d_ecb5d9c6
+DATA: (96) 01YY0050_120b0000_010d0000_30000000_00000000
+ 00000000_00000000_31000000_00000000_00000000
+ 00000000_32000000_00000000_00000000_00000000
+ 0b050000_00000000_00000000_00000000_00000000
+ 1b764ea5_668faa4b_0e7dd876_d25753f8
+
+hmac-sha1 mac(20): XXXXXXXX_XXXXXXXX_XXXXXXXX_XXXXXXXX_XXXXXXXX
+MAC check succeed
+
+hmac-sha1 key(16): a4c96a3c_1b4e1932_acc3878d_ecb5d9c6
+DATA: (40) 02YY001c_120b0000_0b050000_00000000_00000000
+ 00000000_00000000_30112233_31112233_32112233
+
+hmac-sha1 mac(20): XXXXXXXX_XXXXXXXX_XXXXXXXX_XXXXXXXX_XXXXXXXX
+
++++> About to send encoded packet:
+ User-Name = "232420100000015"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x30000000000000000000000000000000
+ EAP-Sim-Rand2 = 0x31000000000000000000000000000000
+ EAP-Sim-Rand3 = 0x32000000000000000000000000000000
+ EAP-Sim-SRES1 = 0x30112233
+ EAP-Sim-SRES2 = 0x31112233
+ EAP-Sim-SRES3 = 0x32112233
+ EAP-Sim-KC1 = 0x445566778899aabb
+ EAP-Sim-KC2 = 0x445566778899aabb
+ EAP-Sim-KC3 = 0x445566778899aabb
+ EAP-Sim-State = 1
+ EAP-Sim-Subtype = Challenge
+ EAP-Id = YY
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Sim-MAC = 0x301122333111223332112233
+ EAP-Sim-KEY = 0xa4c96a3c1b4e1932acc3878decb5d9c6
+ EAP-Message = 0x02XX
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "232420100000015"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Message = 0x02XX
+rad_recv: Access-Accept packet from host 127.0.0.1:1812, id=999, length=177
+ MS-MPPE-Recv-Key = 0x8000f5e4ed05a9bf17b9ec6a27f92d9df104966b03689665de45db4982ecdcc4
+ MS-MPPE-Send-Key = 0x85c26910e886de4fbdfa4218b4ef2f64319c9b41b77b3c9069d616f90781438a
+ EAP-Message = 0x03XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ User-Name = "232420100000015"
+<+++ EAP decoded packet:
+ MS-MPPE-Recv-Key = 0x8000f5e4ed05a9bf17b9ec6a27f92d9df104966b03689665de45db4982ecdcc4
+ MS-MPPE-Send-Key = 0x85c26910e886de4fbdfa4218b4ef2f64319c9b41b77b3c9069d616f90781438a
+ EAP-Message = 0x03XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ User-Name = "232420100000015"
+ EAP-Id = YY
+ EAP-Code = Success
diff --git a/src/tests/eapsim-06/eapsim-in.txt b/src/tests/eapsim-06/eapsim-in.txt
new file mode 100644
index 0000000..5f875cc
--- /dev/null
+++ b/src/tests/eapsim-06/eapsim-in.txt
@@ -0,0 +1,15 @@
+User-Name = "232420100000015"
+NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+EAP-Code = Response
+EAP-Type-Identity = "232420100000015"
+Message-Authenticator = 0
+NAS-Port = 0
+EAP-Sim-Rand1 = 0x30000000000000000000000000000000
+EAP-Sim-Rand2 = 0x31000000000000000000000000000000
+EAP-Sim-Rand3 = 0x32000000000000000000000000000000
+EAP-Sim-Sres1 = 0x30112233
+EAP-Sim-Sres2 = 0x31112233
+EAP-Sim-Sres3 = 0x32112233
+EAP-Sim-KC1 = 0x445566778899AABB
+EAP-Sim-KC2 = 0x445566778899AABB
+EAP-Sim-KC3 = 0x445566778899AABB
diff --git a/src/tests/eapsim-06/eapsim-out.txt b/src/tests/eapsim-06/eapsim-out.txt
new file mode 100644
index 0000000..6597b00
--- /dev/null
+++ b/src/tests/eapsim-06/eapsim-out.txt
@@ -0,0 +1,184 @@
+
++++> About to send encoded packet:
+ User-Name = "232420100000015"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ EAP-Type-Identity = "232420100000015"
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x30000000000000000000000000000000
+ EAP-Sim-Rand2 = 0x31000000000000000000000000000000
+ EAP-Sim-Rand3 = 0x32000000000000000000000000000000
+ EAP-Sim-SRES1 = 0x30112233
+ EAP-Sim-SRES2 = 0x31112233
+ EAP-Sim-SRES3 = 0x32112233
+ EAP-Sim-KC1 = 0x445566778899aabb
+ EAP-Sim-KC2 = 0x445566778899aabb
+ EAP-Sim-KC3 = 0x445566778899aabb
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "232420100000015"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Message = 0x02XX
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=999, length=78
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+<+++ EAP decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0a00000f0200020001000011010100
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0a00000f0200020001000011010100
+ EAP-Sim-Subtype = Start
+ EAP-Sim-VERSION_LIST = 0x000200010000
+ EAP-Sim-FULLAUTH_ID_REQ = 0x0100
+
++++> About to send encoded packet:
+ User-Name = "232420100000015"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x30000000000000000000000000000000
+ EAP-Sim-Rand2 = 0x31000000000000000000000000000000
+ EAP-Sim-Rand3 = 0x32000000000000000000000000000000
+ EAP-Sim-SRES1 = 0x30112233
+ EAP-Sim-SRES2 = 0x31112233
+ EAP-Sim-SRES3 = 0x32112233
+ EAP-Sim-KC1 = 0x445566778899aabb
+ EAP-Sim-KC2 = 0x445566778899aabb
+ EAP-Sim-KC3 = 0x445566778899aabb
+ EAP-Sim-State = 1
+ EAP-Sim-Subtype = Start
+ EAP-Sim-SELECTED_VERSION = 0x0001
+ EAP-Sim-NONCE_MT = 0x00001b764ea5668faa4b0e7dd876d25753f8
+ EAP-Sim-IDENTITY = 0x000f323332343230313030303030303135
+ EAP-Id = YY
+ EAP-Message = 0x02XX
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "232420100000015"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Message = 0x02XX
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=999, length=138
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+<+++ EAP decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d00003000000000000000000000000000000031000000000000000000000000000000320000000000000000000000000000000b050000XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Id = YY
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d00003000000000000000000000000000000031000000000000000000000000000000320000000000000000000000000000000b050000XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ EAP-Sim-Subtype = Challenge
+ EAP-Sim-RAND = 0x0000300000000000000000000000000000003100000000000000000000000000000032000000000000000000000000000000
+ EAP-Sim-MAC = 0xYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
+Input was:
+ identity: (len=15)323332343230313030303030303135
+ nonce_mt: 1b764ea5668faa4b0e7dd876d25753f8
+ rand0: 00000000000000000000000000000000
+ rand1: 00000000000000000000000000000000
+ rand2: 00000000000000000000000000000000
+ sres0: 30112233
+ sres1: 31112233
+ sres2: 32112233
+ Kc0: 445566778899aabb
+ Kc1: 445566778899aabb
+ Kc2: 445566778899aabb
+ versionlist[2]: 0001
+ select 00 01
+
+
+Output
+mk: a444d7cc_dd514568_da171dd4_229ed4d1_a088c470
+K_aut: a4c96a3c_1b4e1932_acc3878d_ecb5d9c6
+K_encr: f544a796_43c4d95f_90aaa5b7_74267742
+msk: 8000f5e4_ed05a9bf_17b9ec6a_27f92d9d_f104966b
+ 03689665_de45db49_82ecdcc4_85c26910_e886de4f
+ bdfa4218_b4ef2f64_319c9b41_b77b3c90_69d616f9
+ 0781438a
+emsk: 3c87c92f_44193e35_dd18e906_3d7cff8f_cb6d6002
+ bf233300_5df66776_70086929_f0d27970_3e59c480
+ 675d6b45_0dc6a79a_51dc34b0_7091a5ff_8ca145ce
+ 98accef2
+
+hmac-sha1 key(16): a4c96a3c_1b4e1932_acc3878d_ecb5d9c6
+DATA: (96) 01YY0050_120b0000_010d0000_30000000_00000000
+ 00000000_00000000_31000000_00000000_00000000
+ 00000000_32000000_00000000_00000000_00000000
+ 0b050000_00000000_00000000_00000000_00000000
+ 1b764ea5_668faa4b_0e7dd876_d25753f8
+
+hmac-sha1 mac(20): XXXXXXXX_XXXXXXXX_XXXXXXXX_XXXXXXXX_XXXXXXXX
+MAC check succeed
+
+hmac-sha1 key(16): a4c96a3c_1b4e1932_acc3878d_ecb5d9c6
+DATA: (40) 02YY001c_120b0000_0b050000_00000000_00000000
+ 00000000_00000000_30112233_31112233_32112233
+
+hmac-sha1 mac(20): XXXXXXXX_XXXXXXXX_XXXXXXXX_XXXXXXXX_XXXXXXXX
+
++++> About to send encoded packet:
+ User-Name = "232420100000015"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x30000000000000000000000000000000
+ EAP-Sim-Rand2 = 0x31000000000000000000000000000000
+ EAP-Sim-Rand3 = 0x32000000000000000000000000000000
+ EAP-Sim-SRES1 = 0x30112233
+ EAP-Sim-SRES2 = 0x31112233
+ EAP-Sim-SRES3 = 0x32112233
+ EAP-Sim-KC1 = 0x445566778899aabb
+ EAP-Sim-KC2 = 0x445566778899aabb
+ EAP-Sim-KC3 = 0x445566778899aabb
+ EAP-Sim-State = 1
+ EAP-Sim-Subtype = Challenge
+ EAP-Id = YY
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Sim-MAC = 0x301122333111223332112233
+ EAP-Sim-KEY = 0xa4c96a3c1b4e1932acc3878decb5d9c6
+ EAP-Message = 0x02XX
+Sending Access-Request of id 999 to 127.0.0.1:1812
+ User-Name = "232420100000015"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ NAS-Port = 0
+ State = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ EAP-Message = 0x02XX
+rad_recv: Access-Accept packet from host 127.0.0.1:1812, id=999, length=177
+ MS-MPPE-Recv-Key = 0x8000f5e4ed05a9bf17b9ec6a27f92d9df104966b03689665de45db4982ecdcc4
+ MS-MPPE-Send-Key = 0x85c26910e886de4fbdfa4218b4ef2f64319c9b41b77b3c9069d616f90781438a
+ EAP-Message = 0x03XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ User-Name = "232420100000015"
+<+++ EAP decoded packet:
+ MS-MPPE-Recv-Key = 0x8000f5e4ed05a9bf17b9ec6a27f92d9df104966b03689665de45db4982ecdcc4
+ MS-MPPE-Send-Key = 0x85c26910e886de4fbdfa4218b4ef2f64319c9b41b77b3c9069d616f90781438a
+ EAP-Message = 0x03XX
+ Message-Authenticator = 0xABCDABCDABCDABCDABCDABCDABCDABCD
+ User-Name = "232420100000015"
+ EAP-Id = YY
+ EAP-Code = Success
diff --git a/src/tests/eapsim-06/eapsim-raw.txt b/src/tests/eapsim-06/eapsim-raw.txt
new file mode 100644
index 0000000..a2b0836
--- /dev/null
+++ b/src/tests/eapsim-06/eapsim-raw.txt
@@ -0,0 +1,184 @@
+
++++> About to send encoded packet:
+ User-Name = "232420100000015"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ EAP-Type-Identity = "232420100000015"
+ Message-Authenticator = 0x30
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x30000000000000000000000000000000
+ EAP-Sim-Rand2 = 0x31000000000000000000000000000000
+ EAP-Sim-Rand3 = 0x32000000000000000000000000000000
+ EAP-Sim-SRES1 = 0x30112233
+ EAP-Sim-SRES2 = 0x31112233
+ EAP-Sim-SRES3 = 0x32112233
+ EAP-Sim-KC1 = 0x445566778899aabb
+ EAP-Sim-KC2 = 0x445566778899aabb
+ EAP-Sim-KC3 = 0x445566778899aabb
+Sending Access-Request of id 22 to 127.0.0.1:1812
+ User-Name = "232420100000015"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0x00000000000000000000000000000000
+ NAS-Port = 0
+ EAP-Message = 0x0215001401323332343230313030303030303135
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=22, length=78
+ EAP-Message = 0x01270014120a00000f0200020001000011010100
+ Message-Authenticator = 0x77ea02a9c89f5e87f5ce65c10877232f
+ State = 0xf0524fef7731860cc1d28b0dc573017c
+<+++ EAP decoded packet:
+ EAP-Message = 0x01270014120a00000f0200020001000011010100
+ Message-Authenticator = 0x77ea02a9c89f5e87f5ce65c10877232f
+ State = 0xf0524fef7731860cc1d28b0dc573017c
+ EAP-Id = 39
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0a00000f0200020001000011010100
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01270014120a00000f0200020001000011010100
+ Message-Authenticator = 0x77ea02a9c89f5e87f5ce65c10877232f
+ State = 0xf0524fef7731860cc1d28b0dc573017c
+ EAP-Id = 39
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0a00000f0200020001000011010100
+ EAP-Sim-Subtype = Start
+ EAP-Sim-VERSION_LIST = 0x000200010000
+ EAP-Sim-FULLAUTH_ID_REQ = 0x0100
+
++++> About to send encoded packet:
+ User-Name = "232420100000015"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ Message-Authenticator = 0x00000000000000000000000000000000
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x30000000000000000000000000000000
+ EAP-Sim-Rand2 = 0x31000000000000000000000000000000
+ EAP-Sim-Rand3 = 0x32000000000000000000000000000000
+ EAP-Sim-SRES1 = 0x30112233
+ EAP-Sim-SRES2 = 0x31112233
+ EAP-Sim-SRES3 = 0x32112233
+ EAP-Sim-KC1 = 0x445566778899aabb
+ EAP-Sim-KC2 = 0x445566778899aabb
+ EAP-Sim-KC3 = 0x445566778899aabb
+ EAP-Sim-State = 1
+ EAP-Sim-Subtype = Start
+ EAP-Sim-SELECTED_VERSION = 0x0001
+ EAP-Sim-NONCE_MT = 0x00001b764ea5668faa4b0e7dd876d25753f8
+ EAP-Sim-IDENTITY = 0x000f323332343230313030303030303135
+ EAP-Id = 39
+ EAP-Message = 0x02270034120a000010010001070500001b764ea5668faa4b0e7dd876d25753f80e05000f32333234323031303030303030313500
+ State = 0xf0524fef7731860cc1d28b0dc573017c
+Sending Access-Request of id 23 to 127.0.0.1:1812
+ User-Name = "232420100000015"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0x00000000000000000000000000000000
+ NAS-Port = 0
+ EAP-Message = 0x02270034120a000010010001070500001b764ea5668faa4b0e7dd876d25753f80e05000f32333234323031303030303030313500
+ State = 0xf0524fef7731860cc1d28b0dc573017c
+rad_recv: Access-Challenge packet from host 127.0.0.1:1812, id=23, length=138
+ EAP-Message = 0x01280050120b0000010d00003000000000000000000000000000000031000000000000000000000000000000320000000000000000000000000000000b050000a91362adf370809ac998c123ebcb32bd
+ Message-Authenticator = 0x2a36d73274543865af44e142fcce7723
+ State = 0x73765e7615012c333beac9182696279c
+<+++ EAP decoded packet:
+ EAP-Message = 0x01280050120b0000010d00003000000000000000000000000000000031000000000000000000000000000000320000000000000000000000000000000b050000a91362adf370809ac998c123ebcb32bd
+ Message-Authenticator = 0x2a36d73274543865af44e142fcce7723
+ State = 0x73765e7615012c333beac9182696279c
+ EAP-Id = 40
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d00003000000000000000000000000000000031000000000000000000000000000000320000000000000000000000000000000b050000a91362adf370809ac998c123ebcb32bd
+<+++ EAP-sim decoded packet:
+ EAP-Message = 0x01280050120b0000010d00003000000000000000000000000000000031000000000000000000000000000000320000000000000000000000000000000b050000a91362adf370809ac998c123ebcb32bd
+ Message-Authenticator = 0x2a36d73274543865af44e142fcce7723
+ State = 0x73765e7615012c333beac9182696279c
+ EAP-Id = 40
+ EAP-Code = Request
+ EAP-Type-SIM = 0x0b0000010d00003000000000000000000000000000000031000000000000000000000000000000320000000000000000000000000000000b050000a91362adf370809ac998c123ebcb32bd
+ EAP-Sim-Subtype = Challenge
+ EAP-Sim-RAND = 0x0000300000000000000000000000000000003100000000000000000000000000000032000000000000000000000000000000
+ EAP-Sim-MAC = 0x0000a91362adf370809ac998c123ebcb32bd
+Input was:
+ identity: (len=15)323332343230313030303030303135
+ nonce_mt: 1b764ea5668faa4b0e7dd876d25753f8
+ rand0: 00000000000000000000000000000000
+ rand1: 00000000000000000000000000000000
+ rand2: 00000000000000000000000000000000
+ sres0: 30112233
+ sres1: 31112233
+ sres2: 32112233
+ Kc0: 445566778899aabb
+ Kc1: 445566778899aabb
+ Kc2: 445566778899aabb
+ versionlist[2]: 0001
+ select 00 01
+
+
+Output
+mk: a444d7cc_dd514568_da171dd4_229ed4d1_a088c470
+K_aut: a4c96a3c_1b4e1932_acc3878d_ecb5d9c6
+K_encr: f544a796_43c4d95f_90aaa5b7_74267742
+msk: 8000f5e4_ed05a9bf_17b9ec6a_27f92d9d_f104966b
+ 03689665_de45db49_82ecdcc4_85c26910_e886de4f
+ bdfa4218_b4ef2f64_319c9b41_b77b3c90_69d616f9
+ 0781438a
+emsk: 3c87c92f_44193e35_dd18e906_3d7cff8f_cb6d6002
+ bf233300_5df66776_70086929_f0d27970_3e59c480
+ 675d6b45_0dc6a79a_51dc34b0_7091a5ff_8ca145ce
+ 98accef2
+
+hmac-sha1 key(16): a4c96a3c_1b4e1932_acc3878d_ecb5d9c6
+DATA: (96) 01280050_120b0000_010d0000_30000000_00000000
+ 00000000_00000000_31000000_00000000_00000000
+ 00000000_32000000_00000000_00000000_00000000
+ 0b050000_00000000_00000000_00000000_00000000
+ 1b764ea5_668faa4b_0e7dd876_d25753f8
+
+hmac-sha1 mac(20): a91362ad_f370809a_c998c123_ebcb32bd_6a2915c2
+MAC check succeed
+
+hmac-sha1 key(16): a4c96a3c_1b4e1932_acc3878d_ecb5d9c6
+DATA: (40) 0228001c_120b0000_0b050000_00000000_00000000
+ 00000000_00000000_30112233_31112233_32112233
+
+hmac-sha1 mac(20): 7a3818ad_17959b80_99cd84eb_64e45346_d63098e9
+
++++> About to send encoded packet:
+ User-Name = "232420100000015"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ EAP-Code = Response
+ Message-Authenticator = 0x00000000000000000000000000000000
+ NAS-Port = 0
+ EAP-Sim-Rand1 = 0x30000000000000000000000000000000
+ EAP-Sim-Rand2 = 0x31000000000000000000000000000000
+ EAP-Sim-Rand3 = 0x32000000000000000000000000000000
+ EAP-Sim-SRES1 = 0x30112233
+ EAP-Sim-SRES2 = 0x31112233
+ EAP-Sim-SRES3 = 0x32112233
+ EAP-Sim-KC1 = 0x445566778899aabb
+ EAP-Sim-KC2 = 0x445566778899aabb
+ EAP-Sim-KC3 = 0x445566778899aabb
+ EAP-Sim-State = 1
+ EAP-Sim-Subtype = Challenge
+ EAP-Id = 40
+ State = 0x73765e7615012c333beac9182696279c
+ EAP-Sim-MAC = 0x301122333111223332112233
+ EAP-Sim-KEY = 0xa4c96a3c1b4e1932acc3878decb5d9c6
+ EAP-Message = 0x0228001c120b00000b0500007a3818ad17959b8099cd84eb64e45346
+Sending Access-Request of id 24 to 127.0.0.1:1812
+ User-Name = "232420100000015"
+ NAS-IP-Address = marajade.sandelman.ottawa.on.ca
+ Message-Authenticator = 0x00000000000000000000000000000000
+ NAS-Port = 0
+ State = 0x73765e7615012c333beac9182696279c
+ EAP-Message = 0x0228001c120b00000b0500007a3818ad17959b8099cd84eb64e45346
+rad_recv: Access-Accept packet from host 127.0.0.1:1812, id=24, length=177
+ MS-MPPE-Recv-Key = 0x8000f5e4ed05a9bf17b9ec6a27f92d9df104966b03689665de45db4982ecdcc4
+ MS-MPPE-Send-Key = 0x85c26910e886de4fbdfa4218b4ef2f64319c9b41b77b3c9069d616f90781438a
+ EAP-Message = 0x03000004
+ Message-Authenticator = 0xc34c14d1a9c794cbc3f7c5c274831277
+ User-Name = "232420100000015"
+<+++ EAP decoded packet:
+ MS-MPPE-Recv-Key = 0x8000f5e4ed05a9bf17b9ec6a27f92d9df104966b03689665de45db4982ecdcc4
+ MS-MPPE-Send-Key = 0x85c26910e886de4fbdfa4218b4ef2f64319c9b41b77b3c9069d616f90781438a
+ EAP-Message = 0x03000004
+ Message-Authenticator = 0xc34c14d1a9c794cbc3f7c5c274831277
+ User-Name = "232420100000015"
+ EAP-Id = 0
+ EAP-Code = Success
diff --git a/src/tests/eapsim-06/simtriplets.dat b/src/tests/eapsim-06/simtriplets.dat
new file mode 100644
index 0000000..3a64447
--- /dev/null
+++ b/src/tests/eapsim-06/simtriplets.dat
@@ -0,0 +1,5 @@
+232420100000015,30000000000000000000000000000000,30112233,445566778899AABB
+232420100000015,31000000000000000000000000000000,31112233,445566778899AABB
+232420100000015,32000000000000000000000000000000,32112233,445566778899AABB
+232420100000015,33000000000000000000000000000000,33112233,445566778899AABB
+232420100000015,34000000000000000000000000000000,34112233,445566778899AABB
diff --git a/src/tests/example.com b/src/tests/example.com
new file mode 100644
index 0000000..6ad3296
--- /dev/null
+++ b/src/tests/example.com
@@ -0,0 +1,5 @@
+#
+# TESTS 1
+#
+User-Name = "bob@example.com"
+User-Password = "bob"
diff --git a/src/tests/fips186-02/description.txt b/src/tests/fips186-02/description.txt
new file mode 100644
index 0000000..5c6859c
--- /dev/null
+++ b/src/tests/fips186-02/description.txt
@@ -0,0 +1,5 @@
+Test vectors were from
+http://csrc.nist.gov/CryptoToolkit/dss/Examples-1024bit.pdf
+
+
+
diff --git a/src/tests/fips186-02/fips186-2.txt b/src/tests/fips186-02/fips186-2.txt
new file mode 100644
index 0000000..d93053f
--- /dev/null
+++ b/src/tests/fips186-02/fips186-2.txt
@@ -0,0 +1,9 @@
+Input was: |bd029bbe_7f51960b_cf9edb2b_61f06f0f_eb5a38b6|
+Output was: 2070b322_3dba372f_de1c0ffc_7b2e3b49_8b260614
+ 3c6c18ba_cb0f6c55_babb1378_8e20d737_a3275116
+ c9ec5c2f_3261cba3_98384ecf_9189707c_20dbe3b6
+ 8d6fc9d2_37313854_7338c3f5_7cf68f38_683aea5b
+ f9e60c0d_73b177bc_69edde1b_eb3f596a_9555fee9
+ 0d570204_a3044bb5_a67f6509_25f14c1d_0446b252
+ 78360140_28faffbf_49840408_ccb30408_00b40408
+ 38faffbf_18ef0440_48ae1340_c0970040_48faffbf
diff --git a/src/tests/hmac-md5-01/digest1.txt b/src/tests/hmac-md5-01/digest1.txt
new file mode 100644
index 0000000..d3b63ee
--- /dev/null
+++ b/src/tests/hmac-md5-01/digest1.txt
@@ -0,0 +1 @@
+750c783e6ab0b503eaa86e310a5db738
diff --git a/src/tests/hmac-sha1-01/digest1.txt b/src/tests/hmac-sha1-01/digest1.txt
new file mode 100644
index 0000000..eb8f81e
--- /dev/null
+++ b/src/tests/hmac-sha1-01/digest1.txt
@@ -0,0 +1 @@
+effcdf6ae5eb2fa2d27416d5f184df9c259a7c79
diff --git a/src/tests/keywords/3gpp b/src/tests/keywords/3gpp
new file mode 100644
index 0000000..05e3fb2
--- /dev/null
+++ b/src/tests/keywords/3gpp
@@ -0,0 +1,19 @@
+#
+# PRE: update
+#
+update request {
+ 3GPP-IMSI := "hello"
+}
+
+#
+# "request:[0-9]" should be parsed as a list followed
+# by an attribute.
+#
+update control {
+ Tmp-String-0 := "%{3GPP-IMSI}"
+ Tmp-String-1 := "%{request:3GPP-IMSI}"
+}
+
+update reply {
+ Filter-Id := "filter"
+} \ No newline at end of file
diff --git a/src/tests/keywords/README.md b/src/tests/keywords/README.md
new file mode 100644
index 0000000..68ce136
--- /dev/null
+++ b/src/tests/keywords/README.md
@@ -0,0 +1,43 @@
+# The Keyword test Framework
+
+See `update` and `default-input.attrs` for examples.
+
+In short, the test framework assumes Access-Request with PAP
+authentication. The password is hard-coded into the configuration,
+and can't be changed.
+
+The entire test suite consists of two files:
+
+* foo
+
+ Contains a short piece of "unlang". The shorter the better. The
+ goal is to do something useful in unlang, and modify the input
+ packet and/or the reply.
+
+ If the test depends on another one, it should name the other test
+ at the top of the file. For example, the `if-else` test depends
+ on the `if` test. This dependency is given by the following lines
+ at the top of the `if-else` file:
+
+ `# PRE: if`
+
+* foo.attrs
+
+ Contains the input packet and the filter for the reply. There
+ always has to be attributes in the input, and filter attributes in the
+ reply.
+
+ If `foo` doesn't exist, then the `default-input.attrs` file is used.
+ This allows many tests to be simplified, as all they need is a
+ little bit of "unlang".
+
+
+## How it works
+
+The input packet is passed into the unit test framework, through the
+unlang snippet in `foo`, and filtered through the reply filter in
+`foo.attrs`. If everything matches, then the test case passes.
+
+To add a test, just put `foo` and (optionally) `foo.attrs` into this
+directory. The build framework will pick them up and automatically
+run them.
diff --git a/src/tests/keywords/all.mk b/src/tests/keywords/all.mk
new file mode 100644
index 0000000..739b738
--- /dev/null
+++ b/src/tests/keywords/all.mk
@@ -0,0 +1,123 @@
+#
+# Unit tests for unlang keywords
+#
+
+#
+# The test files are files without extensions.
+# The list is unordered. The order is added in the next step by looking
+# at precursors.
+#
+KEYWORD_FILES := $(filter-out %.conf %.md %.attrs %.mk %~ %.rej,$(subst $(DIR)/,,$(wildcard $(DIR)/*)))
+
+ifeq "$(OPENSSL_LIBS)" ""
+KEYWORD_FILES := $(filter-out pap-ssha2,$(KEYWORD_FILES))
+endif
+
+#
+# Create the output directory
+#
+.PHONY: $(BUILD_DIR)/tests/keywords
+$(BUILD_DIR)/tests/keywords:
+ @mkdir -p $@
+
+#
+# Find which input files are needed by the tests
+# strip out the ones which exist
+# move the filenames to the build directory.
+#
+BOOTSTRAP_EXISTS := $(addprefix $(DIR)/,$(addsuffix .attrs,$(KEYWORD_FILES)))
+BOOTSTRAP_NEEDS := $(filter-out $(wildcard $(BOOTSTRAP_EXISTS)),$(BOOTSTRAP_EXISTS))
+BOOTSTRAP := $(subst $(DIR),$(BUILD_DIR)/tests/keywords,$(BOOTSTRAP_NEEDS))
+
+#
+# For each file, look for precursor test.
+# Ensure that each test depends on its precursors.
+#
+-include $(BUILD_DIR)/tests/keywords/depends.mk
+
+export OPENSSL_LIBS
+
+$(BUILD_DIR)/tests/keywords/depends.mk: $(addprefix $(DIR)/,$(KEYWORD_FILES)) | $(BUILD_DIR)/tests/keywords
+ @rm -f $@
+ @for x in $^; do \
+ y=`grep 'PRE: ' $$x | sed 's/.*://;s/ / /g;s, , $(BUILD_DIR)/tests/keywords/,g'`; \
+ if [ "$$y" != "" ]; then \
+ z=`echo $$x | sed 's,src/,$(BUILD_DIR)/',`; \
+ echo "$$z: $$y" >> $@; \
+ echo "" >> $@; \
+ fi \
+ done
+
+#
+# These ones get copied over from the default input
+#
+$(BOOTSTRAP): $(DIR)/default-input.attrs | $(BUILD_DIR)/tests/keywords
+ @cp $< $@
+
+#
+# These ones get copied over from their original files
+#
+$(BUILD_DIR)/tests/keywords/%.attrs: $(DIR)/%.attrs | $(BUILD_DIR)/tests/keywords
+ @cp $< $@
+
+#
+# Don't auto-remove the files copied by the rule just above.
+# It's unnecessary, and it clutters the output with crap.
+#
+.PRECIOUS: $(BUILD_DIR)/tests/keywords/%.attrs
+
+KEYWORD_MODULES := $(shell grep -- mods-enabled src/tests/keywords/radiusd.conf | sed 's,.*/,,')
+KEYWORD_RADDB := $(addprefix raddb/mods-enabled/,$(KEYWORD_MODULES))
+KEYWORD_LIBS := $(addsuffix .la,$(addprefix rlm_,$(KEYWORD_MODULES))) rlm_example.la rlm_cache.la
+
+#
+# Files in the output dir depend on the unit tests
+#
+# src/tests/keywords/FOO unlang for the test
+# src/tests/keywords/FOO.attrs input RADIUS and output filter
+# build/tests/keywords/FOO updated if the test succeeds
+# build/tests/keywords/FOO.log debug output for the test
+#
+# Auto-depend on modules via $(shell grep INCLUDE $(DIR)/radiusd.conf | grep mods-enabled | sed 's/.*}/raddb/'))
+#
+# If the test fails, then look for ERROR in the input. No error
+# means it's unexpected, so we die.
+#
+# Otherwise, check the log file for a parse error which matches the
+# ERROR line in the input.
+#
+$(BUILD_DIR)/tests/keywords/%: ${DIR}/% $(BUILD_DIR)/tests/keywords/%.attrs $(TESTBINDIR)/unittest | $(BUILD_DIR)/tests/keywords $(KEYWORD_RADDB) $(KEYWORD_LIBS) build.raddb rlm_cache_rbtree.la rlm_test.la rlm_unix.la
+ @echo UNIT-TEST $(notdir $@)
+ @if ! KEYWORD=$(notdir $@) $(TESTBIN)/unittest -D share -d src/tests/keywords/ -i $@.attrs -f $@.attrs -xx > $@.log 2>&1; then \
+ if ! grep ERROR $< 2>&1 > /dev/null; then \
+ cat $@.log; \
+ echo "# $@.log"; \
+ echo KEYWORD=$(notdir $@) $(TESTBIN)/unittest -D share -d src/tests/keywords/ -i $@.attrs -f $@.attrs -xx; \
+ exit 1; \
+ fi; \
+ FOUND=$$(grep ^$< $@.log | head -1 | sed 's/:.*//;s/.*\[//;s/\].*//'); \
+ EXPECTED=$$(grep -n ERROR $< | sed 's/:.*//'); \
+ if [ "$$EXPECTED" != "$$FOUND" ]; then \
+ cat $@.log; \
+ echo "# $@.log"; \
+ echo KEYWORD=$(notdir $@) $(TESTBIN)/unittest -D share -d src/tests/keywords/ -i $@.attrs -f $@.attrs -xx; \
+ exit 1; \
+ fi \
+ fi
+ @touch $@
+
+#
+# Get all of the unit test output files
+#
+TESTS.KEYWORDS_FILES := $(addprefix $(BUILD_DIR)/tests/keywords/,$(KEYWORD_FILES))
+
+#
+# Depend on the output files, and create the directory first.
+#
+tests.keywords: $(TESTS.KEYWORDS_FILES)
+
+$(TESTS.KEYWORDS_FILES): $(TESTS.XLAT_FILES) $(TESTS.MAP_FILES)
+
+.PHONY: clean.tests.keywords
+clean.tests.keywords:
+ @rm -rf $(BUILD_DIR)/tests/keywords/
diff --git a/src/tests/keywords/array b/src/tests/keywords/array
new file mode 100644
index 0000000..a901a2b
--- /dev/null
+++ b/src/tests/keywords/array
@@ -0,0 +1,53 @@
+#
+# PRE: if
+#
+# Tests for dereferencing the Nth attribute
+#
+update reply {
+ Filter-Id := "filter"
+}
+
+update request {
+ Class := 0x01020304
+ Class += 0x05060708
+ Class += 0x090a0b0c
+}
+
+if (&Class != 0x01020304) {
+ update reply {
+ Filter-Id := "fail 0"
+ }
+}
+
+# Must be the same as above
+if (&Class[0] != 0x01020304) {
+ update reply {
+ Filter-Id += "fail 0a"
+ }
+}
+
+if (&Class[1] != 0x05060708) {
+ update reply {
+ Filter-Id += "fail 1"
+ }
+}
+
+if (&Class[2] != 0x090a0b0c) {
+ update reply {
+ Filter-Id += "fail 2"
+ }
+}
+
+# must not exist
+if (&Class[3]) {
+ update reply {
+ Filter-Id += "fail 3"
+ }
+}
+
+# Last element of the array
+if (&Class[n] != 0x090a0b0c) {
+ update reply {
+ Filter-Id += "fail 4"
+ }
+}
diff --git a/src/tests/keywords/base64 b/src/tests/keywords/base64
new file mode 100644
index 0000000..9661252
--- /dev/null
+++ b/src/tests/keywords/base64
@@ -0,0 +1,141 @@
+#
+# PRE: hex
+#
+update reply {
+ Filter-Id := "filter"
+}
+
+update request {
+ Tmp-String-0 := '9870'
+ Tmp-Octets-0 := 0x39383731
+ Tmp-IP-Address-0 := 57.56.55.50
+ Tmp-Date-0 := 959985459
+ Tmp-Integer-0 := 959985460
+ Tmp-Cast-Abinary := 'ip out forward srcip 57.56.55.53/32 udp dstport = 1812'
+ Tmp-Cast-IfId := '0000:0000:3938:3737'
+ Tmp-Cast-IPv6Addr := '::3938:3738'
+ Tmp-Cast-IPv6Prefix := '::3938:3739/128'
+ Tmp-Cast-Byte := 58
+ Tmp-Cast-Short := 14139
+ Tmp-Cast-Ethernet := 00:00:39:38:37:3c
+ Tmp-Cast-Integer64 := 1152921505566832445
+ Tmp-Cast-IPv4Prefix := 57.56.55.62/32
+}
+
+update request {
+ Tmp-String-0 := "%{base64:&Tmp-String-0}"
+ Tmp-String-1 := "%{base64:&Tmp-Octets-0}"
+ Tmp-String-2 := "%{base64:&Tmp-IP-Address-0}"
+ Tmp-String-3 := "%{base64:&Tmp-Date-0}"
+ Tmp-String-4 := "%{base64:&Tmp-Integer-0}"
+ Tmp-String-5 := "%{base64:&Tmp-Cast-Abinary}"
+ Tmp-String-6 := "%{base64:&Tmp-Cast-Ifid}"
+ Tmp-String-7 := "%{base64:&Tmp-Cast-IPv6Addr}"
+ Tmp-String-8 := "%{base64:&Tmp-Cast-IPv6Prefix}"
+ Tmp-String-9 := "%{base64:&Tmp-Cast-Byte}"
+}
+
+# String - bin 0x39383730
+if (Tmp-String-0 != 'OTg3MA==') {
+ update reply {
+ Filter-Id += 'fail 0'
+ }
+}
+
+# Octets - bin 0x39383731
+if (Tmp-String-1 != 'OTg3MQ==') {
+ update reply {
+ Filter-Id += 'fail 1'
+ }
+}
+
+# IP Address - bin 0x39383732
+if (Tmp-String-2 != 'OTg3Mg==') {
+ update reply {
+ Filter-Id += 'fail 2'
+ }
+}
+
+# Date - bin 0x39383733
+if (Tmp-String-3 != 'OTg3Mw==') {
+ update reply {
+ Filter-Id += 'fail 3'
+ }
+}
+
+# Integer - bin 0x39383734
+if (Tmp-String-4 != 'OTg3NA==') {
+ update reply {
+ Filter-Id += 'fail 4'
+ }
+}
+
+# Abinary - bin 0x0101000039383735000000002000110000000714000200000000000000000000
+if (Tmp-String-5 != 'AQEAADk4NzUAAAAAIAARAAAABxQAAgAAAAAAAAAAAAA=') {
+ update reply {
+ Filter-Id += 'fail 5'
+ }
+}
+
+# ifid - bin 0x0000000039383737
+if (Tmp-String-6 != 'AAAAADk4Nzc=') {
+ update reply {
+ Filter-Id += 'fail 6'
+ }
+}
+
+# ipv6addr - bin 0x00000000000000000000000039383738
+if (Tmp-String-7 != 'AAAAAAAAAAAAAAAAOTg3OA==') {
+ update reply {
+ Filter-ID += 'fail 7'
+ }
+}
+
+# ipv6addrprefix - bin 0x008000000000000000000000000039383739
+if (Tmp-String-8 != 'AIAAAAAAAAAAAAAAAAA5ODc5') {
+ update reply {
+ Filter-ID += 'fail 8'
+ }
+}
+
+# byte - bin 0x3a
+if (Tmp-String-9 != 'Og==') {
+ update reply {
+ Filter-ID += 'fail 9'
+ }
+}
+
+update request {
+ Tmp-String-0 := "%{base64:&Tmp-Cast-Short}"
+ Tmp-String-1 := "%{base64:&Tmp-Cast-Ethernet}"
+ Tmp-String-2 := "%{base64:&Tmp-Cast-Integer64}"
+ Tmp-String-3 := "%{base64:&Tmp-Cast-IPv4Prefix}"
+}
+
+# short - bin 0x373b
+if (Tmp-String-0 != 'Nzs=') {
+ update reply {
+ Filter-ID += 'fail 9'
+ }
+}
+
+# ethernet - bin 0x00003938373c
+if (Tmp-String-1 != 'AAA5ODc8') {
+ update reply {
+ Filter-Id += 'fail 10'
+ }
+}
+
+# integer64 - bin 0x100000003938373d
+if (Tmp-String-2 != 'EAAAADk4Nz0=') {
+ update reply {
+ Filter-Id += 'fail 11'
+ }
+}
+
+# ipv4prefix - bin 0x00203938373e
+if (Tmp-String-3 != 'ACA5ODc+') {
+ update reply {
+ Filter-Id += 'fail 12'
+ }
+}
diff --git a/src/tests/keywords/break-error b/src/tests/keywords/break-error
new file mode 100644
index 0000000..fff20f0
--- /dev/null
+++ b/src/tests/keywords/break-error
@@ -0,0 +1,11 @@
+update control {
+ Cleartext-Password := 'hello'
+}
+
+update reply {
+ Filter-Id := "filter"
+}
+
+if (User-Name == "bob") {
+ break # ERROR
+}
diff --git a/src/tests/keywords/cache b/src/tests/keywords/cache
new file mode 100644
index 0000000..58ce207
--- /dev/null
+++ b/src/tests/keywords/cache
@@ -0,0 +1,229 @@
+#
+# PRE: update if
+#
+update {
+ &control:Cleartext-Password := 'hello'
+ &request:Tmp-String-0 := 'testkey'
+ &reply:Filter-Id := 'filter'
+}
+
+
+#
+# Basic store and retrieve
+#
+update control {
+ &control:Tmp-String-1 := 'cache me'
+}
+
+cache
+if (!updated) {
+ update reply {
+ Filter-Id := 'fail 0'
+ }
+ reject
+}
+
+# Check status-only works correctly (should return ok and consume attribute)
+update control {
+ Cache-Status-Only := 'yes'
+}
+cache
+if (!ok) {
+ update reply {
+ Filter-Id := 'fail 2a'
+ }
+ reject
+}
+
+if (&control:Cache-Status-Only) {
+ update reply {
+ Filter-Id := 'fail 2b'
+ }
+ reject
+}
+
+# Retrieve the entry (should be copied to request list)
+cache
+if (!ok) {
+ update reply {
+ Filter-Id := 'fail 3a'
+ }
+ reject
+}
+
+if (&request:Tmp-String-1 != &control:Tmp-String-1) {
+ update reply {
+ Filter-Id := 'fail 3b'
+ }
+}
+
+# Retrieving the entry should not expire it
+update request {
+ Tmp-String-1 !* ANY
+}
+
+cache
+if (!ok) {
+ update reply {
+ Filter-Id := 'fail 4a'
+ }
+ reject
+}
+
+if (&request:Tmp-String-1 != &control:Tmp-String-1) {
+ update reply {
+ Filter-Id := 'fail 4b'
+ }
+ reject
+}
+
+# Force expiry of the entry
+update control {
+ Cache-TTL := 0
+}
+cache
+if (!ok) {
+ update reply {
+ Filter-Id := 'fail 5'
+ }
+ reject
+}
+
+# Check status-only works correctly (should return notfound and consume attribute)
+update control {
+ Cache-Status-Only := 'yes'
+}
+cache
+if (!notfound) {
+ update reply {
+ Filter-Id := 'fail 6a'
+ }
+ reject
+}
+
+if (&control:Cache-Status-Only) {
+ update reply {
+ Filter-Id := 'fail 6b'
+ }
+ reject
+}
+
+# Check read-only works correctly (should return notfound and consume attribute)
+update control {
+ Cache-Read-Only := 'yes'
+}
+cache
+if (!notfound) {
+ update reply {
+ Filter-Id := 'fail 7a'
+ }
+ reject
+}
+
+if (&control:Cache-Read-Only) {
+ update reply {
+ Filter-Id := 'fail 7b'
+ }
+ reject
+}
+
+# ...and check the entry wasn't recreated
+update control {
+ Cache-Status-Only := 'yes'
+}
+cache
+if (!notfound) {
+ update reply {
+ Filter-Id := 'fail 7c'
+ }
+ reject
+}
+
+# This should still allow the creation of a new entry
+update control {
+ Cache-TTL := -1
+}
+cache
+if (!updated) {
+ update reply {
+ Filter-Id := 'fail 8a'
+ }
+ reject
+}
+
+cache
+if (!ok) {
+ update reply {
+ Filter-Id := 'fail 8b'
+ }
+ reject
+}
+
+if (&Cache-TTL) {
+ update reply {
+ Filter-Id := 'fail 8c'
+ }
+ reject
+}
+
+if (&request:Tmp-String-1 != &control:Tmp-String-1) {
+ update reply {
+ Filter-Id := 'fail 8d'
+ }
+ reject
+}
+
+update control {
+ Tmp-String-1 := 'cache me2'
+}
+
+# Updating the Cache-TTL shouldn't make things go boom (we can't really check if it works)
+update control {
+ Cache-TTL := 30
+}
+cache
+if (!ok) {
+ update reply {
+ Filter-Id := 'fail 9a'
+ }
+ reject
+}
+
+# Request Tmp-String-1 shouldn't have been updated yet
+if (&request:Tmp-String-1 == &control:Tmp-String-1) {
+ update reply {
+ Filter-Id := 'fail 9b'
+ }
+ reject
+}
+
+# Check that a new entry is created
+update control {
+ Cache-TTL := -1
+}
+cache
+if (!updated) {
+ update reply {
+ Filter-Id := 'fail 10a'
+ }
+ reject
+}
+
+# Check Cache-Entry-Hits is updated as we expect
+if (&request:Cache-Entry-Hits != 0) {
+ update reply {
+ Filter-Id := 'fail 12a'
+ }
+ reject
+}
+
+cache
+
+if (&request:Cache-Entry-Hits != 1) {
+ update reply {
+ Filter-Id := 'fail 12b'
+ }
+ reject
+}
+
+
diff --git a/src/tests/keywords/case-attr-error b/src/tests/keywords/case-attr-error
new file mode 100644
index 0000000..e9516cc
--- /dev/null
+++ b/src/tests/keywords/case-attr-error
@@ -0,0 +1,20 @@
+# PRE: case-empty
+#
+update reply {
+ Filter-Id := "fail"
+}
+
+switch &reply:Filter-Id {
+ # deliberately empty
+ case "filter" {
+ }
+
+ case &Not-Dynamically-Allocated { # ERROR
+ update reply {
+ Filter-Id := "fail"
+ }
+ }
+
+ case {
+ }
+}
diff --git a/src/tests/keywords/case-empty b/src/tests/keywords/case-empty
new file mode 100644
index 0000000..46ea054
--- /dev/null
+++ b/src/tests/keywords/case-empty
@@ -0,0 +1,23 @@
+# PRE: switch
+#
+update reply {
+ Filter-Id := "filter"
+}
+
+switch &reply:Filter-Id {
+ # deliberately empty
+ case "filter" {
+ }
+
+ case "fail" {
+ update reply {
+ Filter-Id := "fail"
+ }
+ }
+
+ case {
+ update reply {
+ Filter-Id := "default"
+ }
+ }
+}
diff --git a/src/tests/keywords/case-empty-string b/src/tests/keywords/case-empty-string
new file mode 100644
index 0000000..4b8e7cd
--- /dev/null
+++ b/src/tests/keywords/case-empty-string
@@ -0,0 +1,25 @@
+# PRE: switch
+#
+update request {
+ Filter-Id := ""
+}
+
+switch &Filter-Id {
+ case "" {
+ update reply {
+ Filter-Id := "filter"
+ }
+ }
+
+ case "doug" {
+ update reply {
+ Filter-Id := "doug"
+ }
+ }
+
+ case {
+ update reply {
+ Filter-Id := "default"
+ }
+ }
+}
diff --git a/src/tests/keywords/case-list b/src/tests/keywords/case-list
new file mode 100644
index 0000000..e4a0290
--- /dev/null
+++ b/src/tests/keywords/case-list
@@ -0,0 +1,19 @@
+switch &User-Name {
+ case "bob" {
+ update reply {
+ Filter-Id := "filter"
+ }
+ }
+
+ case &reply: { # ERROR
+ update reply {
+ Filter-Id := "doug"
+ }
+ }
+
+ case {
+ update reply {
+ Filter-Id := "default"
+ }
+ }
+}
diff --git a/src/tests/keywords/cast-byte b/src/tests/keywords/cast-byte
new file mode 100644
index 0000000..4663d95
--- /dev/null
+++ b/src/tests/keywords/cast-byte
@@ -0,0 +1,25 @@
+#
+# PRE: update if
+#
+update {
+ control:Cleartext-Password := 'hello'
+ request:Class := 0xad
+}
+
+if (<byte>Class == 173) {
+ update reply {
+ Filter-Id := "filter"
+ }
+}
+
+if (<byte>Class < 173) {
+ update reply {
+ Filter-Id += "wrong"
+ }
+}
+
+if (<byte>Class > 173) {
+ update reply {
+ Filter-Id += "wrong"
+ }
+}
diff --git a/src/tests/keywords/cast-integer b/src/tests/keywords/cast-integer
new file mode 100644
index 0000000..4972ee7
--- /dev/null
+++ b/src/tests/keywords/cast-integer
@@ -0,0 +1,25 @@
+#
+# PRE: update if
+#
+update {
+ control:Cleartext-Password := 'hello'
+ request:Class := 0x00000101
+}
+
+if (<integer>Class == 257) {
+ update reply {
+ Filter-Id := "filter"
+ }
+}
+
+if (<integer>Class < 256) {
+ update reply {
+ Filter-Id += "wrong"
+ }
+}
+
+if (<integer>Class > 257) {
+ update reply {
+ Filter-Id += "wrong"
+ }
+}
diff --git a/src/tests/keywords/cast-ipaddr b/src/tests/keywords/cast-ipaddr
new file mode 100644
index 0000000..f0dd356
--- /dev/null
+++ b/src/tests/keywords/cast-ipaddr
@@ -0,0 +1,442 @@
+#
+# PRE: update if
+#
+update {
+ control:Cleartext-Password := 'hello'
+ request:NAS-IP-Address := 127.0.0.1
+ request:Tmp-Integer-0 := 2130706433
+ reply:Filter-Id := "filter"
+}
+
+update request {
+ Tmp-String-0 := "%{request:NAS-IP-Address}"
+}
+
+if (<ipaddr>Tmp-Integer-0 != NAS-IP-Address) {
+ update reply {
+ Filter-Id += 'Fail 0'
+ }
+}
+
+#
+# Update statements do implicit casts, so we can check
+# cast results are correct, by using the update to perform
+# the cast, and looking at the results.
+#
+update request {
+ Tmp-Cast-Ipaddr := 203.0.113.1
+ Tmp-Cast-IPv4Prefix := 203.0.113.0/24
+ Tmp-Cast-IPv4Prefix += 203.0.113.1/32
+ Tmp-Cast-IPv6Addr := 2001:DB8::1
+ Tmp-Cast-IPv6Addr += ::ffff:203.0.113.1
+ Tmp-Cast-IPv6Prefix := 2001:DB8::/32
+ Tmp-Cast-IPv6Prefix += ::ffff:203.0.113.1/128
+ Tmp-Cast-IPv6Prefix += ::ffff:203.0.113.1/64
+}
+
+#
+# IPv4 address to IPv6 address
+#
+update control {
+ Tmp-Cast-IPv6addr := &Tmp-Cast-IPaddr
+}
+
+if (&control:Tmp-Cast-IPv6addr != ::ffff:203.0.113.1) {
+ update reply {
+ Filter-Id += 'Fail 1'
+ }
+}
+
+#
+# IPv6 address to IPv4 address
+#
+update control {
+ Tmp-Cast-IPaddr := &control:Tmp-Cast-IPv6addr
+}
+
+if (&control:Tmp-Cast-IPaddr != 203.0.113.1) {
+ update reply {
+ Filter-Id += 'Fail 2'
+ }
+}
+
+#
+# IPv4 prefix to IPv6 prefix
+#
+update control {
+ Tmp-Cast-IPv6Prefix := &Tmp-Cast-IPv4Prefix
+}
+
+if (&control:Tmp-Cast-IPv6Prefix != ::ffff:203.0.113.0/120) {
+ update reply {
+ Filter-Id += 'Fail 31'
+ }
+}
+
+#
+# IPv6 prefix to IPv4 prefix
+#
+update control {
+ Tmp-Cast-IPv4Prefix := &control:Tmp-Cast-IPv6Prefix
+}
+
+if (&control:Tmp-Cast-IPv4Prefix != 203.0.113.1/24) {
+ update reply {
+ Filter-Id += 'Fail 4'
+ }
+}
+
+#
+# IPv4 prefix (32) to IPv6 address
+#
+update control {
+ Tmp-Cast-IPv6Addr := &Tmp-Cast-IPv4Prefix[1]
+}
+
+if (&control:Tmp-Cast-IPv6Addr != ::ffff:203.0.113.1) {
+ update reply {
+ Filter-Id += 'Fail 5'
+ }
+}
+
+#
+# IPv6 prefix (128) to IPv4 address
+#
+update control {
+ Tmp-Cast-Ipaddr := &Tmp-Cast-IPv6Prefix[1]
+}
+
+if (&control:Tmp-Cast-Ipaddr != 203.0.113.1/32) {
+ update reply {
+ Filter-Id += 'Fail 6'
+ }
+}
+
+#
+# IPv4 address to IPv6 prefix (128)
+#
+update control {
+ Tmp-Cast-IPv6Prefix := &Tmp-Cast-Ipaddr
+}
+
+if (&control:Tmp-Cast-IPv6Prefix != ::ffff:203.0.113.1/128) {
+ update reply {
+ Filter-Id += 'Fail 7'
+ }
+}
+
+#
+# IPv6 address to IPv4 prefix (32)
+#
+update control {
+ Tmp-Cast-IPv4Prefix := &Tmp-Cast-IPv6Addr[1]
+}
+
+if (&control:Tmp-Cast-IPv4Prefix != 203.0.113.1/32) {
+ update reply {
+ Filter-Id += 'Fail 8'
+ }
+}
+
+#
+# IPv4 address to IPv4 prefix (32)
+#
+update control {
+ Tmp-Cast-IPv4Prefix := &Tmp-Cast-Ipaddr
+}
+
+if (&control:Tmp-Cast-IPv4Prefix != 203.0.113.1/32) {
+ update reply {
+ Filter-Id += 'Fail 9'
+ }
+}
+
+#
+# IPv6 address to IPv6 prefix (128)
+#
+update control {
+ Tmp-Cast-IPv6Prefix := Tmp-Cast-Ipv6addr
+}
+
+if (&control:Tmp-Cast-IPv6Prefix != 2001:DB8::1/128) {
+ update reply {
+ Filter-Id += 'Fail 11'
+ }
+}
+
+#
+# IPv4 prefix (32) to IPv4 address
+#
+update control {
+ Tmp-Cast-Ipaddr := &Tmp-Cast-IPv4Prefix[1]
+}
+
+if (&control:Tmp-Cast-Ipaddr != 203.0.113.1) {
+ update reply {
+ Filter-Id += 'Fail 12'
+ }
+}
+
+#
+# IPv6 prefix (128) to IPv6 address
+#
+update control {
+ Tmp-Cast-IPv6Addr := &Tmp-Cast-IPv6Prefix[1]
+}
+
+if (&control:Tmp-Cast-IPv6Addr != ::ffff:203.0.113.1) {
+ update reply {
+ Filter-Id += 'Fail 13'
+ }
+}
+
+#
+# And the invalid cases...
+#
+
+#
+# IPv6 Prefix < 128 to IPv6 address
+#
+redundant {
+ group {
+ update control {
+ Tmp-Cast-IPv6Addr := Tmp-Cast-IPv6Prefix
+ }
+ update reply {
+ Filter-Id += 'Fail 14'
+ }
+ }
+ group {
+ if ("%{Module-Failure-Message}" != 'Attribute conversion failed: Invalid cast from ipv6prefix to ipv6addr. Only /128 prefixes may be cast to IP address types') {
+ update reply {
+ Filter-Id += 'Fail 14.5'
+ }
+ }
+ update request {
+ Module-Failure-Message !* ANY
+ }
+ ok
+ }
+}
+
+#
+# IPv6 Prefix < 128 to IPv4 address
+#
+redundant {
+ group {
+ update control {
+ Tmp-Cast-Ipaddr := &Tmp-Cast-IPv6Prefix[2]
+ }
+ update reply {
+ Filter-Id += 'Fail 15'
+ }
+ }
+ group {
+ if ("%{Module-Failure-Message}" != 'Attribute conversion failed: Invalid cast from ipv6prefix to ipaddr. Only /128 prefixes may be cast to IP address types') {
+ update reply {
+ Filter-Id += 'Fail 15.5'
+ }
+ }
+ update request {
+ Module-Failure-Message !* ANY
+ }
+ ok
+ }
+}
+
+#
+# IPv6 Prefix < 96 to IPv4 prefix (causes part of the IPv4/v6 mapping prefix to be masked off)
+#
+redundant {
+ group {
+ update control {
+ Tmp-Cast-Ipv4Prefix := &Tmp-Cast-IPv6Prefix[2]
+ }
+ update reply {
+ Filter-Id += 'Fail 16'
+ }
+ }
+ group {
+ if ("%{Module-Failure-Message}" != 'Attribute conversion failed: Invalid cast from ipv6prefix to ipv4prefix. No IPv4-IPv6 mapping prefix') {
+ update reply {
+ Filter-Id += 'Fail 16.5'
+ }
+ }
+ update request {
+ Module-Failure-Message !* ANY
+ }
+ ok
+ }
+}
+
+#
+# IPv4 Prefix < 32 to IPv6 address
+#
+redundant {
+ group {
+ update control {
+ Tmp-Cast-IPv6Addr := &Tmp-Cast-IPv4Prefix
+ }
+ update reply {
+ Filter-Id += 'Fail 17'
+ }
+ }
+ group {
+ if ("%{Module-Failure-Message}" != 'Attribute conversion failed: Invalid cast from ipv4prefix to ipv6addr. Only /32 prefixes may be cast to IP address types') {
+ update reply {
+ Filter-Id += 'Fail 17.5'
+ }
+ }
+ update request {
+ Module-Failure-Message !* ANY
+ }
+ ok
+ }
+}
+
+#
+# IPv4 Prefix < 32 to IPv4 address
+#
+redundant {
+ group {
+ update control {
+ Tmp-Cast-Ipaddr := &Tmp-Cast-IPv4Prefix
+ }
+ update reply {
+ Filter-Id += 'Fail 17'
+ }
+ }
+ group {
+ if ("%{Module-Failure-Message}" != 'Attribute conversion failed: Invalid cast from ipv4prefix to ipaddr. Only /32 prefixes may be cast to IP address types') {
+ update reply {
+ Filter-Id += 'Fail 17.5'
+ }
+ }
+ update request {
+ Module-Failure-Message !* ANY
+ }
+ ok
+ }
+}
+
+#
+# IPv6 Prefix outside mapping range to IPv4 address
+#
+redundant {
+ group {
+ update control {
+ Tmp-Cast-Ipaddr := &Tmp-Cast-IPv6Prefix
+ }
+ update reply {
+ Filter-Id += 'Fail 18'
+ }
+ }
+ group {
+ if ("%{Module-Failure-Message}" != 'Attribute conversion failed: Invalid cast from ipv6prefix to ipaddr. Only /128 prefixes may be cast to IP address types') {
+ update reply {
+ Filter-Id += 'Fail 18.5'
+ }
+ }
+ update request {
+ Module-Failure-Message !* ANY
+ }
+ ok
+ }
+}
+
+#
+# IPv6 Prefix outside mapping range to IPv4 prefix
+#
+redundant {
+ group {
+ update control {
+ Tmp-Cast-IPv4Prefix := &Tmp-Cast-IPv6Prefix
+ }
+ update reply {
+ Filter-Id += 'Fail 19'
+ }
+ }
+ group {
+ if ("%{Module-Failure-Message}" != 'Attribute conversion failed: Invalid cast from ipv6prefix to ipv4prefix. No IPv4-IPv6 mapping prefix') {
+ update reply {
+ Filter-Id += 'Fail 19.5'
+ }
+ }
+ update request {
+ Module-Failure-Message !* ANY
+ }
+ ok
+ }
+}
+
+#
+# IPv6 Address outside mapping range to IPv4 address
+#
+redundant {
+ group {
+ update control {
+ Tmp-Cast-Ipaddr := &Tmp-Cast-IPv6Addr
+ }
+ update reply {
+ Filter-Id += 'Fail 20'
+ }
+ }
+ group {
+ if ("%{Module-Failure-Message}" != 'Attribute conversion failed: Invalid cast from ipv6addr to ipaddr. No IPv4-IPv6 mapping prefix') {
+ update reply {
+ Filter-Id += 'Fail 20'
+ }
+ }
+ update request {
+ Module-Failure-Message !* ANY
+ }
+ ok
+ }
+}
+
+#
+# IPv6 Address outside mapping range to IPv4 prefix
+#
+redundant {
+ group {
+ update control {
+ Tmp-Cast-IPv4Prefix := &Tmp-Cast-IPv6Addr
+ }
+ update reply {
+ Filter-Id += 'Fail 21'
+ }
+ }
+ group {
+ if ("%{Module-Failure-Message}" != 'Attribute conversion failed: Invalid cast from ipv6addr to ipv4prefix. No IPv4-IPv6 mapping prefix') {
+ update reply {
+ Filter-Id += 'Fail 21.5'
+ }
+ }
+ update request {
+ Module-Failure-Message !* ANY
+ }
+ ok
+ }
+}
+
+#
+# IPv4 address to integer
+#
+update request {
+ Tmp-Integer-0 := &NAS-IP-Address
+}
+
+if (Tmp-Integer-0 != 0x7f000001) {
+ update reply {
+ Filter-Id += 'Fail 22'
+ }
+}
+
+#
+# Check the decimal value just for the heck of it
+#
+if (Tmp-Integer-0 != 2130706433) {
+ update reply {
+ Filter-Id += 'Fail 23'
+ }
+}
diff --git a/src/tests/keywords/cast-short b/src/tests/keywords/cast-short
new file mode 100644
index 0000000..a17b379
--- /dev/null
+++ b/src/tests/keywords/cast-short
@@ -0,0 +1,25 @@
+#
+# PRE: update if
+#
+update {
+ control:Cleartext-Password := 'hello'
+ request:Class := 0x0101
+}
+
+if (<short>Class == 257) {
+ update reply {
+ Filter-Id := "filter"
+ }
+}
+
+if (<short>Class < 256) {
+ update reply {
+ Filter-Id += "wrong"
+ }
+}
+
+if (<short>Class > 257) {
+ update reply {
+ Filter-Id += "wrong"
+ }
+}
diff --git a/src/tests/keywords/cmp b/src/tests/keywords/cmp
new file mode 100644
index 0000000..4cd59b8
--- /dev/null
+++ b/src/tests/keywords/cmp
@@ -0,0 +1,20 @@
+#
+# PRE: update
+#
+update control {
+ Cleartext-Password := 'hello'
+}
+
+update request {
+ Called-Station-Id := "This is a test"
+ Calling-Station-Id := "This is a test"
+}
+
+#
+# Check attribute references
+#
+if (Called-Station-Id == &Calling-Station-Id) {
+ update reply {
+ Filter-Id := "filter"
+ }
+}
diff --git a/src/tests/keywords/cmp-ipaddr b/src/tests/keywords/cmp-ipaddr
new file mode 100644
index 0000000..bf197af
--- /dev/null
+++ b/src/tests/keywords/cmp-ipaddr
@@ -0,0 +1,20 @@
+#
+# PRE: update
+#
+update control {
+ Cleartext-Password := 'hello'
+}
+
+update request {
+ NAS-IP-Address := 127.0.0.1
+ Framed-IP-Address := 127.0.0.1
+}
+
+#
+# Check attribute references
+#
+if (NAS-IP-Address == &Framed-IP-Address) {
+ update reply {
+ Filter-Id := "filter"
+ }
+}
diff --git a/src/tests/keywords/comments b/src/tests/keywords/comments
new file mode 100644
index 0000000..483bd05
--- /dev/null
+++ b/src/tests/keywords/comments
@@ -0,0 +1,48 @@
+#
+# PRE: update if
+#
+
+# One comment
+#{ Two comment
+#} Three comment
+#'Four'
+#"Five comment"
+##Six Comment#
+ #Seven comment (yes i'm meant to be tabbed in)
+ #Eight comment (yes i'm meant to have spaces before me)
+ #Nine comment (tabs and spaces, are you crazy?!)
+
+update { #}'{ Opening block with extra special chars {} '"
+ control:Cleartext-Password := 'hello' # This should update the password so the test passes
+ reply:Filter-Id := 'filter'# Eek! Too close
+} #{'} Closing block with extra special chars {} '"
+
+update { request:Tmp-String-0 := 'candy' } # Comment after unicorn block
+
+update request {
+ request:Reply-Message += 'I am #literally a comment #'
+ request:Reply-Message += "I am #literally a comment #"
+}
+
+if (&request:Tmp-String-0 != 'candy') {
+ update reply {
+ reply:Filter-Id += 'fail 0'
+ }
+}
+
+if (&request:Reply-Message[0] != 'I am #literally a comment #') {
+ update reply {
+ reply:Filter-Id += 'fail 1'
+ }
+}
+
+if (&request:Reply-Message[1] != "I am #literally a comment #") {
+ update reply {
+ reply:Filter-Id += 'fail 2'
+ }
+}
+
+ok # I'm a comment after a module call
+ok # I'm a comment # after a module {} call
+
+ok, ok, ok, ok, ok
diff --git a/src/tests/keywords/count-error b/src/tests/keywords/count-error
new file mode 100644
index 0000000..f0723cb
--- /dev/null
+++ b/src/tests/keywords/count-error
@@ -0,0 +1,11 @@
+update control {
+ Cleartext-Password := 'hello'
+}
+
+update reply {
+ Filter-Id := "filter"
+}
+
+update request {
+ Tmp-String-0 := &reply:Filter-Id[#] # ERROR
+}
diff --git a/src/tests/keywords/crypt b/src/tests/keywords/crypt
new file mode 100644
index 0000000..e6d63aa
--- /dev/null
+++ b/src/tests/keywords/crypt
@@ -0,0 +1,151 @@
+#
+# PRE: update if
+#
+
+# Skip all these tests if crypt_r was not available
+#
+if ("%{crypt:&User-Password}") {
+ noop
+}
+if ("%{request:Module-Failure-Message[0]}" !~ /^Crypt not available at compile time/) {
+
+
+# Set required attributes
+#
+update reply {
+ &Filter-Id := "filter"
+}
+
+update request {
+ &Tmp-String-0 := 'foo'
+ &Tmp-String-1 := 'foo:bar'
+ &Tmp-String-2 := 'f:'
+ &Tmp-String-3 := &User-Password
+ &Tmp-String-4 := &control:Cleartext-Password
+ &Tmp-String-5 := 'fwtLWDtMiSbH8lmXCMIVfrSMJjF'
+ &Tmp-String-8 := 'aa'
+ &Tmp-String-9 := '$1$abcdefgh'
+}
+
+
+# Check for error on no salt
+#
+if ("%{crypt:&User-Password}") {
+ update reply {
+ &Filter-Id += 'fail 1a'
+ }
+}
+
+if ("%{request:Module-Failure-Message[0]}" != 'No salt specified in crypt xlat') {
+ update reply {
+ &Filter-Id += 'fail 1b'
+ }
+}
+
+
+# Check DES - all crypt_r() implementations should do this.
+#
+if ("%{crypt:aa:foo}" != "aaKNIEDOaueR6") {
+ update reply {
+ &Filter-Id += 'fail 2a'
+ }
+}
+
+if ("%{crypt:&Tmp-String-8:foo}" != "aaKNIEDOaueR6") {
+ update reply {
+ &Filter-Id += 'fail 2b'
+ }
+}
+
+if ("%{crypt:aa:&User-Password}" != "aaPwJ9XL9Y99E") {
+ update reply {
+ &Filter-Id += 'fail 2c'
+ }
+}
+
+
+# Test we can encrypt and then authenticate
+#
+update {
+ &request:User-Password := &request:Tmp-String-5
+ &control:Crypt-Password := "%{crypt:AZ:&Tmp-String-5}"
+ &control:Cleartext-Password !* ""
+}
+
+group {
+ pap.authenticate {
+ fail = 1
+ reject = 1
+ }
+
+ if (!ok) {
+ update reply {
+ &Filter-Id += 'fail 3'
+ }
+ }
+}
+
+update {
+ &request:User-Password := &Tmp-String-3
+ &control:Cleartext-Password := &Tmp-String-4
+}
+
+
+# Clear Module-Failure-Message so below tests work no matter what
+# happened above
+#
+update request {
+ &Module-Failure-Message !* ""
+}
+
+
+# Check colons in password
+#
+if ("%{crypt:aa:foo:bar}" != "aadzEnaZwH90k") {
+ update reply {
+ &Filter-Id += 'fail 4a'
+ }
+}
+
+if ("%{crypt:aa:&Tmp-String-1}" != "aadzEnaZwH90k") {
+ update reply {
+ &Filter-Id += 'fail 4b'
+ }
+}
+
+
+# Check invalid chars in salt
+#
+# In this case, depending on the library implementation, crypt
+# seems to either return an empty string (null) and set an error,
+# or it will return an invalid hash beginning with '*'.
+#
+update request {
+ &Tmp-String-7 := "%{crypt:&Tmp-String-2:foo}"
+}
+
+if (&Tmp-String-7 !~ /^\*/ && \
+ "%{request:Module-Failure-Message[0]}" !~ /Crypt salt has the wrong format/) {
+ update reply {
+ &Filter-Id += 'fail 5a'
+ }
+}
+
+
+# Convert the Cleartext-Password to Password-With-Header and auth with that
+#
+update control {
+ &Password-With-Header := "{crypt}%{crypt:$1$abcdefgh:&Tmp-String-4}"
+ &Crypt-Password !* ""
+ &Cleartext-Password !* ""
+}
+
+
+# Crypt not available at compile time? Force the test to pass.
+#
+}
+else {
+ update reply {
+ &Filter-Id := "filter"
+ }
+}
diff --git a/src/tests/keywords/default-input.attrs b/src/tests/keywords/default-input.attrs
new file mode 100644
index 0000000..93566a6
--- /dev/null
+++ b/src/tests/keywords/default-input.attrs
@@ -0,0 +1,11 @@
+#
+# Input packet
+#
+User-Name = "bob"
+User-Password = "hello"
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+Filter-Id == 'filter'
diff --git a/src/tests/keywords/else-error b/src/tests/keywords/else-error
new file mode 100644
index 0000000..3816270
--- /dev/null
+++ b/src/tests/keywords/else-error
@@ -0,0 +1,14 @@
+#
+# PRE: update if
+#
+# "else" has to be preceded by an "if" or "elsif"
+#
+if (1) {
+ update reply {
+ Filter-Id := "filter"
+ }
+
+ else { # ERROR
+ ok
+ }
+}
diff --git a/src/tests/keywords/escape b/src/tests/keywords/escape
new file mode 100644
index 0000000..5d0b3bc
--- /dev/null
+++ b/src/tests/keywords/escape
@@ -0,0 +1,67 @@
+#
+# PRE: update if xlat-attr-index
+#
+update request {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := "filter"
+
+ Tmp-String-0 := '@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /'
+ Tmp-String-1 := '±§#$%^&+={[}];<,>?`|"'
+ Tmp-String-2 := '™¥¤'
+ Tmp-String-3 := '=C2=B1=C2=A7=23=24=25=5E=26=2B=3D=7B=5B=7D=5D=3B=3C=2C=3E=3F=60=7C=22'
+ Tmp-String-4 := '=E2=84=A2=C2=A5=C2=A4'
+ Tmp-String-5 := '=40=61=62=63=64=65=66=67'
+
+ # Mixture of safe and unsafe chars
+ Tmp-String-6 := 'ŒČÿ'
+ Tmp-String-7 := 'Œ=C4=8Cÿ'
+}
+
+if (<string>"%{escape:%{request:Tmp-String-0}}" != &Tmp-String-0) {
+ update reply {
+ Filter-Id += 'Fail 1'
+ }
+}
+
+if (<string>"%{escape:%{request:Tmp-String-1}}" != &Tmp-String-3) {
+ update reply {
+ Filter-Id += 'Fail 2'
+ }
+}
+
+if (<string>"%{escape:%{request:Tmp-String-2}}" != &Tmp-String-4) {
+ update reply {
+ Filter-Id += 'Fail 3'
+ }
+}
+
+if (<string>"%{unescape:%{request:Tmp-String-0}}" != &Tmp-String-0) {
+ update reply {
+ Filter-Id += 'Fail 4'
+ }
+}
+
+if (<string>"%{unescape:%{request:Tmp-String-3}}" != "%{Tmp-String-1}") {
+ update reply {
+ Filter-Id += 'Fail 5'
+ }
+}
+
+if (<string>"%{unescape:%{request:Tmp-String-4}}" != &Tmp-String-2) {
+ update reply {
+ Filter-Id += 'Fail 6'
+ }
+}
+
+if (<string>"%{escape:%{request:Tmp-String-6}}" != &Tmp-String-7) {
+ update reply {
+ Filter-Id += 'Fail 7'
+ }
+}
+
+if (<string>"%{unescape:%{request:Tmp-String-7}}" != &Tmp-String-6) {
+ update reply {
+ Filter-Id += 'Fail 8'
+ }
+}
+
diff --git a/src/tests/keywords/escape-sequences b/src/tests/keywords/escape-sequences
new file mode 100644
index 0000000..967656d
--- /dev/null
+++ b/src/tests/keywords/escape-sequences
@@ -0,0 +1,95 @@
+#
+# PRE: update if xlat-attr-index
+#
+update request {
+ control:Cleartext-Password := 'hello'
+ Tmp-Octets-0 := 0x69206861766520736361727920656d626564646564207468696e67730020696e73696465206d65
+ Tmp-Octets-1 := 0x30783031013078303707307830410A307830440D222230786230b0C2b0
+ Tmp-String-0 := "i have scary embedded things\000 inside me"
+ Tmp-String-0 += "0x01\0010x07\0070x0A\n0x0D\r\"\"0xb0\260°"
+
+ # and again with single quoted strings.
+ # unlike other languages, \r, \t, and \n have meaning inside of 'string'
+ Tmp-String-1 := 'i have scary embedded things\000 inside me'
+ Tmp-String-1 += '0x01\0010x07\0070x0A\n0x0D\r""0xb0\260°'
+
+ Tmp-String-2 := 'i have scary embedded things\000 inside me'
+ Tmp-String-2 += "0x01\0010x07\0070x0A\n0x0D\r''0xb0\260°"
+
+ reply:Filter-Id := "filter"
+}
+
+
+if ("%{length:&Tmp-String-0}" != 39) {
+ update reply {
+ Filter-Id += 'fail l-0'
+ }
+}
+
+if ("%{length:&Tmp-String-1}" != 42) {
+ update reply {
+ Filter-Id += 'fail l-1'
+ }
+}
+
+if ("%{string:Tmp-Octets-0}" != "i have scary embedded things\000 inside me") {
+ update reply {
+ Filter-Id += 'fail 1'
+ }
+}
+
+if (&Tmp-String-0 != "i have scary embedded things\000 inside me") {
+ update reply {
+ Filter-Id += 'fail 2'
+ }
+}
+
+if ("%{string:Tmp-Octets-1}" != "0x01\0010x07\0070x0A\n0x0D\r\"\"0xb0\260°") {
+ update reply {
+ Filter-Id += 'fail 3'
+ }
+}
+
+if ("%{Tmp-String-0[0]}" != "i have scary embedded things\000 inside me") {
+ update reply {
+ Filter-Id += 'fail 4'
+ }
+}
+
+if ("%{Tmp-String-0[1]}" != "0x01\0010x07\0070x0A\n0x0D\r\"\"0xb0\260°") {
+ update reply {
+ Filter-Id += 'fail 5'
+ }
+}
+
+# And another slightly different codepath...
+if ("%{Tmp-String-0[*]}" != "i have scary embedded things\000 inside me,0x01\0010x07\0070x0A\n0x0D\r\"\"0xb0\260°") {
+ update reply {
+ Filter-Id += 'fail 6'
+ }
+}
+
+if (&Tmp-String-0[0] != &Tmp-String-0[0]) {
+ update reply {
+ Filter-Id += 'fail 7'
+ }
+}
+
+#
+# This seems weird... double escapes for most things, but single escapes
+# for the quotation marks.
+#
+if ("%{Tmp-String-2[1]}" != "0x01\0010x07\0070x0A\n0x0D\r''0xb0\260°") {
+ update reply {
+ Filter-Id += 'fail 8'
+ }
+}
+
+#
+# And again as an attribute reference
+#
+if (&Tmp-String-2[1] != "0x01\0010x07\0070x0A\n0x0D\r''0xb0\260°") {
+ update reply {
+ Filter-Id += 'fail 9'
+ }
+}
diff --git a/src/tests/keywords/expand b/src/tests/keywords/expand
new file mode 100644
index 0000000..ada0ee9
--- /dev/null
+++ b/src/tests/keywords/expand
@@ -0,0 +1,39 @@
+#
+# PRE: update switch
+#
+
+#
+# This is a virtual attribute.
+# It is NOT optimized to
+#
+# switch &Request-Processing-Stage
+#
+# because it doesn't really exist.
+# The xlat expansion code will take care of
+# returning the string value of the "attribute"
+#
+switch "%{Request-Processing-Stage}" {
+ case authorize {
+ update reply {
+ Filter-Id := "filter"
+ }
+ }
+
+ case authenticate {
+ update reply {
+ Filter-Id := "authenticate"
+ }
+ }
+
+ case bob {
+ update reply {
+ Filter-Id := "bob"
+ }
+ }
+
+ case {
+ update reply {
+ Filter-Id := "default"
+ }
+ }
+}
diff --git a/src/tests/keywords/expr b/src/tests/keywords/expr
new file mode 100644
index 0000000..7645931
--- /dev/null
+++ b/src/tests/keywords/expr
@@ -0,0 +1,108 @@
+#
+# PRE: update if
+#
+update control {
+ Cleartext-Password := 'hello'
+}
+
+update reply {
+ Filter-Id := "filter"
+}
+
+#
+# Simple
+#
+if ("%{expr: 1 + 2 + 3 + 4}" != 10) {
+ update reply {
+ Filter-Id := "fail-1"
+ }
+}
+
+#
+# Precedence
+#
+if ("%{expr: 1 + 2 * 3 + 4}" != 11) {
+ update reply {
+ Filter-Id := "fail-2"
+ }
+}
+
+#
+# attribute references
+#
+update request {
+ Tmp-Integer-0 = 1
+ Tmp-Integer-1 = 3
+ Tmp-Integer-2 = 4
+ Tmp-Date-0 = "%l"
+}
+
+if ("%{expr: 1 + 2 * &Tmp-Integer-1 + 4}" != 11) {
+ update reply {
+ Filter-Id := "fail-3"
+ }
+}
+
+if ("%{expr: 1 + 2 * (&Tmp-Integer-1 + 4)}" != 15) {
+ update reply {
+ Filter-Id := "fail-4"
+ }
+}
+
+if ("%{expr: 1 + 2 * (&Tmp-Integer-1 + &Tmp-Integer-2)}" != 15) {
+ update reply {
+ Filter-Id := "fail-5"
+ }
+}
+
+if ("%{expr: 1 & ~1}" != 0) {
+ update reply {
+ Filter-Id := "fail-6"
+ }
+}
+
+if ("%{expr: 1 & ~2}" != 1) {
+ update reply {
+ Filter-Id := "fail-7"
+ }
+}
+
+if ("%{expr: -1 * 2}" != -2) {
+ update reply {
+ Filter-Id := "fail-8"
+ }
+}
+
+if ("%{expr: 2 - -1}" != 3) {
+ update reply {
+ Filter-Id := "fail-9"
+ }
+}
+
+if ("%{expr: 1 << 2 | 1}" != 5) {
+ update reply {
+ Filter-Id := "fail-10"
+ }
+}
+
+if ("%{expr: &Tmp-Date-0}" <= 0) {
+ update reply {
+ Filter-Id := "fail-11"
+ }
+}
+
+#
+# Unary negation
+#
+if ("%{expr: 6 + -(1 + 3)}" != 2) {
+ update reply {
+ Filter-Id := "fail-12"
+ }
+}
+
+if ("%{expr: 6 * -&Tmp-Integer-2}" != -24) {
+ update reply {
+ Filter-Id := "fail-13"
+ }
+}
+
diff --git a/src/tests/keywords/foreach b/src/tests/keywords/foreach
new file mode 100644
index 0000000..9a4c266
--- /dev/null
+++ b/src/tests/keywords/foreach
@@ -0,0 +1,5 @@
+foreach Filter-Id {
+ update reply {
+ Called-Station-Id += "%{Foreach-Variable-0}"
+ }
+}
diff --git a/src/tests/keywords/foreach-break b/src/tests/keywords/foreach-break
new file mode 100644
index 0000000..67812fe
--- /dev/null
+++ b/src/tests/keywords/foreach-break
@@ -0,0 +1,73 @@
+# PRE: foreach
+#
+
+#
+# We DON'T want to see this one.
+#
+update request {
+ Filter-Id += "broken"
+}
+
+foreach Filter-Id {
+ #
+ # If we see this one, "break" out of the
+ # foreach loop.
+ #
+ if ("%{Foreach-Variable-0}" == "broken") {
+ break
+ }
+
+ update reply {
+ Called-Station-Id += "%{Foreach-Variable-0}"
+ }
+}
+
+
+#
+# Adding attribute during request and immediately breaking
+#
+update {
+ request:Filter-Id += "1"
+ request:Filter-Id += "2"
+}
+
+foreach &request:Reply-Message {
+ if("%{Foreach-Variable-0}" == "1") {
+ update {
+ request:Filter-Id += "3"
+ }
+ break
+
+ update reply {
+ Filter-Id := "fail-break-1"
+ }
+ }
+}
+
+update {
+ request:Filter-Id !* ANY
+}
+
+#
+# Adding attribute during request and continuing
+#
+update {
+ request:Filter-Id += "1"
+ request:Filter-Id += "2"
+}
+
+foreach &request:Reply-Message {
+ if("%{Foreach-Variable-0}" == "1") {
+ update {
+ request:Filter-Id += "3"
+ }
+ }
+
+ if ("%{Foreach-Variable-0}" == "3") {
+ break
+
+ update reply {
+ Filter-Id := "fail-break-2"
+ }
+ }
+}
diff --git a/src/tests/keywords/foreach-break-2 b/src/tests/keywords/foreach-break-2
new file mode 100644
index 0000000..b1f6040
--- /dev/null
+++ b/src/tests/keywords/foreach-break-2
@@ -0,0 +1,46 @@
+#
+# PRE: foreach foreach-break
+#
+
+update request {
+ Calling-Station-Id := "ABCDEF_8"
+}
+
+update control {
+ &Tmp-String-0 := "0"
+ &Tmp-String-0 += "1"
+ &Tmp-String-0 += "2"
+ &Tmp-String-0 += "3"
+ &Tmp-String-0 += "4"
+ &Tmp-String-0 += "5"
+ &Tmp-String-0 += "6"
+ &Tmp-String-0 += "7"
+ &Tmp-String-0 += "8"
+ &Tmp-String-0 += "9"
+ &Tmp-String-0 += "a"
+ &Tmp-String-0 += "b"
+ &Tmp-String-0 += "c"
+ &Tmp-String-0 += "d"
+ &Tmp-String-0 += "e"
+ &Tmp-String-0 += "f"
+ &Tmp-String-0 += "g"
+}
+
+foreach control:Tmp-String-0 {
+ if ("%{Calling-Station-Id[*]}" =~ /([A-Z0-9\-]*)_%{Foreach-Variable-0}/) {
+ update request {
+ Called-Station-Id := "%{1}"
+ }
+ update reply {
+ Filter-Id := "filter"
+ }
+
+ break
+ }
+ elsif ("%{Foreach-Variable-0}" == '9') {
+ update reply {
+ Filter-Id := "fail-9"
+ }
+ reject
+ }
+}
diff --git a/src/tests/keywords/foreach-break-3 b/src/tests/keywords/foreach-break-3
new file mode 100644
index 0000000..af03da6
--- /dev/null
+++ b/src/tests/keywords/foreach-break-3
@@ -0,0 +1,44 @@
+#
+# PRE: foreach foreach-break
+#
+
+update request {
+ Calling-Station-Id := "8"
+}
+
+update control {
+ &Calling-Station-Id := "0"
+ &Calling-Station-Id += "1"
+ &Calling-Station-Id += "2"
+ &Calling-Station-Id += "3"
+ &Calling-Station-Id += "4"
+ &Calling-Station-Id += "5"
+ &Calling-Station-Id += "6"
+ &Calling-Station-Id += "7"
+ &Calling-Station-Id += "8"
+ &Calling-Station-Id += "9"
+ &Calling-Station-Id += "a"
+ &Calling-Station-Id += "b"
+ &Calling-Station-Id += "c"
+ &Calling-Station-Id += "d"
+ &Calling-Station-Id += "e"
+ &Calling-Station-Id += "f"
+ &Calling-Station-Id += "g"
+}
+
+foreach &control:Calling-Station-Id {
+ if (&request:Calling-Station-Id == "%{Foreach-Variable-0}") {
+ update reply {
+ Filter-Id := "filter"
+ }
+
+ break
+ }
+ elsif ("%{Foreach-Variable-0}" == '9') {
+ update reply {
+ Filter-Id := "fail-9"
+ }
+
+ reject
+ }
+}
diff --git a/src/tests/keywords/foreach-break-4 b/src/tests/keywords/foreach-break-4
new file mode 100644
index 0000000..037af8e
--- /dev/null
+++ b/src/tests/keywords/foreach-break-4
@@ -0,0 +1,44 @@
+#
+# PRE: foreach foreach-break-3
+#
+
+update request {
+ Calling-Station-Id := "8"
+}
+
+update control {
+ &Calling-Station-Id := "0"
+ &Calling-Station-Id += "1"
+ &Calling-Station-Id += "2"
+ &Calling-Station-Id += "3"
+ &Calling-Station-Id += "4"
+ &Calling-Station-Id += "5"
+ &Calling-Station-Id += "6"
+ &Calling-Station-Id += "7"
+ &Calling-Station-Id += "8"
+ &Calling-Station-Id += "9"
+ &Calling-Station-Id += "a"
+ &Calling-Station-Id += "b"
+ &Calling-Station-Id += "c"
+ &Calling-Station-Id += "d"
+ &Calling-Station-Id += "e"
+ &Calling-Station-Id += "f"
+ &Calling-Station-Id += "g"
+}
+
+foreach &control:Calling-Station-Id {
+ if (&request:Calling-Station-Id == "%{Foreach-Variable-0}") {
+ update reply {
+ Filter-Id := "filter"
+ }
+
+ break
+ }
+ elsif ("%{Foreach-Variable-0}" == '9') {
+ update reply {
+ Filter-Id := "fail-9"
+ }
+
+ reject
+ }
+}
diff --git a/src/tests/keywords/foreach-break.attrs b/src/tests/keywords/foreach-break.attrs
new file mode 100644
index 0000000..26c2876
--- /dev/null
+++ b/src/tests/keywords/foreach-break.attrs
@@ -0,0 +1,18 @@
+#
+# Input packet
+#
+User-Name = "bob"
+User-Password = "hello"
+Filter-Id = "1"
+Filter-Id += "2"
+Filter-Id += "3"
+Filter-Id += "4"
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+Called-Station-Id == "1"
+Called-Station-Id == "2"
+Called-Station-Id == "3"
+Called-Station-Id == "4"
diff --git a/src/tests/keywords/foreach-error b/src/tests/keywords/foreach-error
new file mode 100644
index 0000000..fb4a236
--- /dev/null
+++ b/src/tests/keywords/foreach-error
@@ -0,0 +1,5 @@
+foreach "%{expr:1 + 2}" { # ERROR
+ update reply {
+ Called-Station-Id += "%{Foreach-Variable-0}"
+ }
+}
diff --git a/src/tests/keywords/foreach-isolation b/src/tests/keywords/foreach-isolation
new file mode 100644
index 0000000..b77806d
--- /dev/null
+++ b/src/tests/keywords/foreach-isolation
@@ -0,0 +1,38 @@
+#
+# PRE: foreach if-multivalue
+#
+
+update {
+ &reply:Filter-Id := 'filter'
+ &control:Tmp-String-0 := '0'
+ &control:Tmp-String-0 += '1'
+ &control:Tmp-String-0 += '2'
+ &control:Tmp-String-0 += '3'
+}
+
+foreach control:Tmp-String-0 {
+ update control {
+ Tmp-String-0 -= "%{expr:%{Foreach-Variable-0} + 1}"
+ }
+ update request {
+ Tmp-String-0 += "%{Foreach-Variable-0}"
+ }
+}
+
+if (!&Tmp-String-0[0] || !&Tmp-String-0[1] || !&Tmp-String-0[2] || !&Tmp-String-0[3]) {
+ update reply {
+ Filter-Id += 'Fail 0'
+ }
+}
+
+if ((&Tmp-String-0[0] != '0') || (&Tmp-String-0[1] != '1') || (&Tmp-String-0[2] != '2') || (&Tmp-String-0[3] != '3')) {
+ update reply {
+ Filter-Id += 'Fail 1'
+ }
+}
+
+if (!&control:Tmp-String-0[0] || &control:Tmp-String-0[1] || &control:Tmp-String-0[2] || &control:Tmp-String-0[3]) {
+ update reply {
+ Filter-Id += 'Fail 2'
+ }
+}
diff --git a/src/tests/keywords/foreach-list b/src/tests/keywords/foreach-list
new file mode 100644
index 0000000..4780e4f
--- /dev/null
+++ b/src/tests/keywords/foreach-list
@@ -0,0 +1,5 @@
+foreach &request: {
+ update reply {
+ Called-Station-Id += "%{Foreach-Variable-0}"
+ }
+}
diff --git a/src/tests/keywords/foreach-list.attrs b/src/tests/keywords/foreach-list.attrs
new file mode 100644
index 0000000..aedd599
--- /dev/null
+++ b/src/tests/keywords/foreach-list.attrs
@@ -0,0 +1,21 @@
+#
+# Input packet
+#
+User-Name = "bob"
+User-Password = "hello"
+Filter-Id = "1"
+Filter-Id += "2"
+Filter-Id += "3"
+Filter-Id += "4"
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+Called-Station-Id == 'bob'
+Called-Station-Id == 'hello'
+Called-Station-Id == '1'
+Called-Station-Id == '2'
+Called-Station-Id == '3'
+Called-Station-Id == '4'
+
diff --git a/src/tests/keywords/foreach-nested b/src/tests/keywords/foreach-nested
new file mode 100644
index 0000000..b6109a3
--- /dev/null
+++ b/src/tests/keywords/foreach-nested
@@ -0,0 +1,9 @@
+# PRE: foreach
+#
+foreach Filter-Id {
+ foreach Calling-Station-Id {
+ update reply {
+ Called-Station-Id += "%{Foreach-Variable-0} %{Foreach-Variable-1}"
+ }
+ }
+}
diff --git a/src/tests/keywords/foreach-nested.attrs b/src/tests/keywords/foreach-nested.attrs
new file mode 100644
index 0000000..52d1f81
--- /dev/null
+++ b/src/tests/keywords/foreach-nested.attrs
@@ -0,0 +1,25 @@
+#
+# Input packet
+#
+User-Name = "bob"
+User-Password = "hello"
+Filter-Id = "1"
+Filter-Id += "2"
+Filter-Id += "3"
+Filter-Id += "4"
+Calling-Station-Id = "foo\n"
+Calling-Station-Id += "bar"
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+Called-Station-Id == '1 foo\n'
+Called-Station-Id == '1 bar'
+Called-Station-Id == '2 foo\n'
+Called-Station-Id == '2 bar'
+Called-Station-Id == '3 foo\n'
+Called-Station-Id == '3 bar'
+Called-Station-Id == '4 foo\n'
+Called-Station-Id == '4 bar'
+
diff --git a/src/tests/keywords/foreach-regex b/src/tests/keywords/foreach-regex
new file mode 100644
index 0000000..dab57a3
--- /dev/null
+++ b/src/tests/keywords/foreach-regex
@@ -0,0 +1,26 @@
+# PRE: foreach if-regex-match
+
+# This is what most people end up using foreach for,
+# so we should probably test it works.
+update request {
+ Tmp-String-0 := "cisco"
+}
+
+# Expanded regex
+foreach Cisco-AVPair {
+ if ("%{Foreach-Variable-0}" =~ /^%{Tmp-String-0}=(.*)$/i) {
+ update reply {
+ Called-Station-Id += "%{1}"
+ }
+ }
+}
+
+# Compiled regex
+foreach Cisco-AVPair {
+ if ("%{Foreach-Variable-0}" =~ /^stupid=(.*)$/i) {
+ update reply {
+ Called-Station-Id += "%{1}"
+ }
+ }
+}
+
diff --git a/src/tests/keywords/foreach-regex.attrs b/src/tests/keywords/foreach-regex.attrs
new file mode 100644
index 0000000..79996c7
--- /dev/null
+++ b/src/tests/keywords/foreach-regex.attrs
@@ -0,0 +1,16 @@
+#
+# Input packet
+#
+User-Name = "bob"
+User-Password = "hello"
+Cisco-AVPair = "stupid=1"
+Cisco-AVPair += "retarded=2"
+Cisco-AVPair += "cisco=3"
+Cisco-AVPair += "shit=4"
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+Called-Station-Id == "3"
+Called-Station-Id == "1"
diff --git a/src/tests/keywords/foreach-return b/src/tests/keywords/foreach-return
new file mode 100644
index 0000000..05409c9
--- /dev/null
+++ b/src/tests/keywords/foreach-return
@@ -0,0 +1,52 @@
+# PRE: foreach foreach-break
+#
+
+update control {
+ Cleartext-Password := 'hello'
+}
+
+#
+# Adding attribute during request and immediately returning should still work
+#
+update request {
+ Filter-Id := "1"
+ Filter-Id += "2"
+ Filter-Id += "3"
+ Filter-Id += "4"
+ Filter-Id += "5"
+}
+
+foreach &Filter-Id {
+ if ("%{Foreach-Variable-0}" == "3") {
+ update reply {
+ Filter-Id := "filter"
+ }
+
+ #
+ # We need this because the "return" below
+ # will prevent the "pap" module from being run
+ # in the "authorize" section.
+ #
+ update control {
+ Auth-Type := PAP
+ }
+
+ #
+ # Stop processing "authorize", and go to the next section.
+ #
+ return
+
+ #
+ # Shouldn't reach this
+ #
+ update reply {
+ Filter-Id := "fail"
+ }
+ }
+
+ if ("%{Foreach-Variable-0}" == "4") {
+ update reply {
+ Filter-Id := "fail-4"
+ }
+ }
+}
diff --git a/src/tests/keywords/foreach-varied-depth b/src/tests/keywords/foreach-varied-depth
new file mode 100644
index 0000000..3c3918d
--- /dev/null
+++ b/src/tests/keywords/foreach-varied-depth
@@ -0,0 +1,43 @@
+update {
+ control:Tmp-String-0 := "ssid=ABCDEF"
+ control:Tmp-String-0 += "ssid=GHIJKL"
+ reply:Filter-Id := "filter"
+}
+
+if (User-Name) {
+ foreach &control:Tmp-String-0 {
+ if ("%{Foreach-Variable-0}" =~ /(.*)/) {
+ update control {
+ Tmp-String-1 := "%{1}"
+ }
+ }
+ }
+}
+
+if (&control:Tmp-String-1 != 'ssid=GHIJKL') {
+ update reply {
+ Filter-Id += 'fail 0'
+ }
+}
+
+update control {
+ Tmp-String-1 !* ANY
+}
+
+foreach &control:Tmp-String-0 {
+ if ("%{Foreach-Variable-0}" =~ /(.*)/) {
+ update control {
+ Tmp-String-1 := "%{1}"
+ }
+ }
+}
+
+if (&control:Tmp-String-1 != 'ssid=GHIJKL') {
+ update reply {
+ Filter-Id += 'fail 1'
+ }
+}
+
+update control {
+ Tmp-String-1 !* ANY
+}
diff --git a/src/tests/keywords/foreach.attrs b/src/tests/keywords/foreach.attrs
new file mode 100644
index 0000000..26c2876
--- /dev/null
+++ b/src/tests/keywords/foreach.attrs
@@ -0,0 +1,18 @@
+#
+# Input packet
+#
+User-Name = "bob"
+User-Password = "hello"
+Filter-Id = "1"
+Filter-Id += "2"
+Filter-Id += "3"
+Filter-Id += "4"
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+Called-Station-Id == "1"
+Called-Station-Id == "2"
+Called-Station-Id == "3"
+Called-Station-Id == "4"
diff --git a/src/tests/keywords/hex b/src/tests/keywords/hex
new file mode 100644
index 0000000..b55bab2
--- /dev/null
+++ b/src/tests/keywords/hex
@@ -0,0 +1,141 @@
+#
+# PRE: update
+#
+update reply {
+ Filter-Id := "filter"
+}
+
+update request {
+ Tmp-String-0 := '9870'
+ Tmp-Octets-0 := 0x39383731
+ Tmp-IP-Address-0 := 57.56.55.50
+ Tmp-Date-0 := 959985459
+ Tmp-Integer-0 := 959985460
+ Tmp-Cast-Abinary := 'ip out forward srcip 57.56.55.53/32 udp dstport = 1812'
+ Tmp-Cast-IfId := '0000:0000:3938:3737'
+ Tmp-Cast-IPv6Addr := '::3938:3738'
+ Tmp-Cast-IPv6Prefix := '::3938:3739/128'
+ Tmp-Cast-Byte := 58
+ Tmp-Cast-Short := 14139
+ Tmp-Cast-Ethernet := 00:00:39:38:37:3c
+ Tmp-Cast-Integer64 := 1152921505566832445
+ Tmp-Cast-IPv4Prefix := 57.56.55.62/32
+}
+
+update request {
+ Tmp-String-0 := "%{hex:Tmp-String-0}"
+ Tmp-String-1 := "%{hex:Tmp-Octets-0}"
+ Tmp-String-2 := "%{hex:Tmp-IP-Address-0}"
+ Tmp-String-3 := "%{hex:Tmp-Date-0}"
+ Tmp-String-4 := "%{hex:Tmp-Integer-0}"
+ Tmp-String-5 := "%{hex:Tmp-Cast-Abinary}"
+ Tmp-String-6 := "%{hex:Tmp-Cast-Ifid}"
+ Tmp-String-7 := "%{hex:Tmp-Cast-IPv6Addr}"
+ Tmp-String-8 := "%{hex:Tmp-Cast-IPv6Prefix}"
+ Tmp-String-9 := "%{hex:Tmp-Cast-Byte}"
+}
+
+# String
+if (Tmp-String-0 != '39383730') {
+ update reply {
+ Filter-Id += 'fail 1'
+ }
+}
+
+# Octets
+if (Tmp-String-1 != '39383731') {
+ update reply {
+ Filter-Id += 'fail 2'
+ }
+}
+
+# IP Address
+if (Tmp-String-2 != '39383732') {
+ update reply {
+ Filter-Id += 'fail 3'
+ }
+}
+
+# Date
+if (Tmp-String-3 != '39383733') {
+ update reply {
+ Filter-Id += 'fail 4'
+ }
+}
+
+# Integer
+if (Tmp-String-4 != '39383734') {
+ update reply {
+ Filter-Id += 'fail 5'
+ }
+}
+
+# Abinary
+if (Tmp-String-5 != '0101000039383735000000002000110000000714000200000000000000000000') {
+ update reply {
+ Filter-Id += 'fail 6'
+ }
+}
+
+# ifid
+if (Tmp-String-6 != '0000000039383737') {
+ update reply {
+ Filter-Id += 'fail 7'
+ }
+}
+
+# ipv6addr
+if (Tmp-String-7 != '00000000000000000000000039383738') {
+ update reply {
+ Filter-ID += 'fail 8'
+ }
+}
+
+# ipv6addrprefix
+if (Tmp-String-8 != '008000000000000000000000000039383739') {
+ update reply {
+ Filter-ID += 'fail 9'
+ }
+}
+
+# byte
+if (Tmp-String-9 != '3a') {
+ update reply {
+ Filter-ID += "fail 10 - expected 3a got %{Tmp-String-9}"
+ }
+}
+
+update request {
+ Tmp-String-0 := "%{hex:Tmp-Cast-Short}"
+ Tmp-String-1 := "%{hex:Tmp-Cast-Ethernet}"
+ Tmp-String-2 := "%{hex:Tmp-Cast-Integer64}"
+ Tmp-String-3 := "%{hex:Tmp-Cast-IPv4Prefix}"
+}
+
+# short
+if (Tmp-String-0 != '373b') {
+ update reply {
+ Filter-ID += 'fail 11'
+ }
+}
+
+# ethernet
+if (Tmp-String-1 != '00003938373c') {
+ update reply {
+ Filter-Id += 'fail 12'
+ }
+}
+
+# integer64
+if (Tmp-String-2 != '100000003938373d') {
+ update reply {
+ Filter-Id += 'fail 13'
+ }
+}
+
+# ipv4prefix
+if (Tmp-String-3 != '00203938373e') {
+ update reply {
+ Filter-Id += 'fail 14 expected 00203938373e got %{Tmp-String-3}'
+ }
+}
diff --git a/src/tests/keywords/if b/src/tests/keywords/if
new file mode 100644
index 0000000..a146029
--- /dev/null
+++ b/src/tests/keywords/if
@@ -0,0 +1,10 @@
+#
+# PRE: update
+#
+# Static if condition
+#
+if (1) {
+ update reply {
+ Filter-Id := "filter"
+ }
+}
diff --git a/src/tests/keywords/if-bob b/src/tests/keywords/if-bob
new file mode 100644
index 0000000..4e8ae3c
--- /dev/null
+++ b/src/tests/keywords/if-bob
@@ -0,0 +1,15 @@
+# PRE: if
+#
+# Matching "if" conditions
+#
+if (User-Name == "bob") {
+ update reply {
+ Filter-Id := "filter"
+ }
+}
+
+if (User-Name != "bob") {
+ update reply {
+ Filter-Id := "not bob"
+ }
+} \ No newline at end of file
diff --git a/src/tests/keywords/if-else b/src/tests/keywords/if-else
new file mode 100644
index 0000000..788d606
--- /dev/null
+++ b/src/tests/keywords/if-else
@@ -0,0 +1,15 @@
+#
+# PRE: if
+#
+# Matching "if" conditions
+#
+if (User-Name != "bob") {
+ update reply {
+ Filter-Id := "not bob"
+ }
+}
+else {
+ update reply {
+ Filter-Id := "filter"
+ }
+}
diff --git a/src/tests/keywords/if-elsif b/src/tests/keywords/if-elsif
new file mode 100644
index 0000000..c0a41ed
--- /dev/null
+++ b/src/tests/keywords/if-elsif
@@ -0,0 +1,19 @@
+# PRE: if if-else
+#
+# Matching "if" conditions
+#
+if (User-Name != "bob") {
+ update reply {
+ Filter-Id := "not bob"
+ }
+}
+elsif (User-Name == "bob") {
+ update reply {
+ Filter-Id := "filter"
+ }
+}
+else {
+ update reply {
+ Filter-Id := "last else"
+ }
+}
diff --git a/src/tests/keywords/if-multivalue b/src/tests/keywords/if-multivalue
new file mode 100644
index 0000000..f12d6fe
--- /dev/null
+++ b/src/tests/keywords/if-multivalue
@@ -0,0 +1,173 @@
+#
+# PRE: update if
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := 'filter'
+}
+
+update request {
+ Tmp-String-0 := 'foo'
+ Tmp-String-0 += 'bar'
+ Tmp-String-0 += 'baz'
+
+ Tmp-String-1 := 'GROUP ADMINISTRATORS'
+ Tmp-String-1 += 'GROUP STUDENTS'
+ Tmp-String-1 += 'GROUP PEONS'
+
+ Tmp-String-2 := 'PEONS'
+ Tmp-String-2 += 'STUDENTS'
+ Tmp-String-2 += 'ADMINISTRATORS'
+
+ Tmp-String-3 := 'no'
+ Tmp-String-3 += 'no'
+ Tmp-String-3 += 'yes'
+
+ Tmp-Integer-0 := 1
+ Tmp-Integer-0 += 2
+ Tmp-Integer-0 += 5
+}
+
+update control {
+ Tmp-String-0 := 'foo'
+ Tmp-String-0 += 'bar'
+ Tmp-String-0 += 'baz'
+
+ Tmp-String-1 := 'boink'
+ Tmp-String-1 += 'tard'
+ Tmp-String-1 += 'dink'
+ Tmp-String-1 += 'slink'
+
+ Tmp-Integer-0 := 01
+ Tmp-Integer-0 += 02
+ Tmp-Integer-0 += 05
+ Tmp-Integer-0 += 04
+
+ Tmp-Integer-1 := 10
+ Tmp-Integer-1 += 20
+ Tmp-Integer-1 += 30
+}
+
+#
+# Mmmm O(N^2)
+#
+if (&request:Tmp-String-0[*] != &control:Tmp-String-0[*]) {
+ update reply {
+ Filter-Id += 'fail 0'
+ }
+}
+
+if (&request:Tmp-String-0[*] == &control:Tmp-String-1[*]) {
+ update reply {
+ Filter-Id += 'fail 1'
+ }
+}
+
+if (&request:Tmp-String-1[*] == &control:Tmp-String-0[*]) {
+ update reply {
+ Filter-Id += 'fail 2'
+ }
+}
+
+#
+# Integer comparison and normalisation
+#
+if (&request:Tmp-Integer-0 != &control:Tmp-Integer-0) {
+ update reply {
+ Filter-Id += 'fail 3'
+ }
+}
+
+#
+# if any value of request:Tmp-Integer-0 > any value of
+# request:Tmp-Integer-1 then evaluate to true
+#
+if (&request:Tmp-Integer-0[*] > &control:Tmp-Integer-1[*]) {
+ update reply {
+ Filter-Id += 'fail 4'
+ }
+}
+
+#
+# Compiled regex comparisons
+#
+if (&request:Tmp-String-1[*] !~ /PEONS$/) {
+ update reply {
+ Filter-Id += 'fail 5'
+ }
+}
+
+if (&control:Tmp-String-1 =~ /PEONS$/) {
+ update reply {
+ Filter-Id += 'fail 6'
+ }
+}
+
+if (&control:Tmp-String-1 =~ /DINKS$/) {
+ update reply {
+ Filter-Id += 'fail 7'
+ }
+}
+
+#
+# Dynamic regex comparisons
+#
+if (&request:Tmp-String-1[*] !~ /%{Tmp-String-2[0]}$/) {
+ update reply {
+ Filter-Id += 'fail 8'
+ }
+}
+
+if (&request:Tmp-String-1 =~ /%{Tmp-String-2[1]}$/) {
+ update reply {
+ Filter-Id += 'fail 9'
+ }
+}
+
+if (&request:Tmp-String-1 !~ /%{Tmp-String-2[2]}$/) {
+ update reply {
+ Filter-Id += 'fail 10'
+ }
+}
+
+if (&request:Tmp-String-1 =~ /%{Tmp-String-2[#]}$/) {
+ update reply {
+ Filter-Id += 'fail 11'
+ }
+}
+
+#
+# XLAT virtual comparisons
+#
+if (&control:Tmp-Integer-0[*] != "%{control:Tmp-Integer-0[#]}") {
+ update reply {
+ Filter-Id += 'fail 12'
+ }
+}
+
+#
+# Literal comparisons
+#
+if (&control:Tmp-String-1[*] != 'boink') {
+ update reply {
+ Filter-Id += 'fail 13'
+ }
+}
+
+if (&control:Tmp-String-1[*] == 'foo') {
+ update reply {
+ Filter-Id += 'fail 14'
+ }
+}
+
+if (&request:Tmp-Integer-0[*] > 10) {
+ update reply {
+ Filter-Id += 'fail 15'
+ }
+}
+
+if (!(&request:Tmp-Integer-0[*] < 10)) {
+ update reply {
+ Filter-Id += 'fail 16'
+ }
+}
diff --git a/src/tests/keywords/if-paircmp b/src/tests/keywords/if-paircmp
new file mode 100644
index 0000000..6ed06e3
--- /dev/null
+++ b/src/tests/keywords/if-paircmp
@@ -0,0 +1,27 @@
+#
+# PRE: update if
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := 'filter'
+}
+
+#
+# Paircmp
+#
+
+#
+# Passing 'yes' causes the test paircmp to return match
+# Passing 'no' causes the test paircmp to return a non-match
+#
+if (&Test-Paircmp != 'yes') {
+ update reply {
+ Filter-Id += 'fail 1'
+ }
+}
+
+if (&Test-Paircmp == 'no') {
+ update reply {
+ Filter-Id += 'fail 2'
+ }
+}
diff --git a/src/tests/keywords/if-rcode-error b/src/tests/keywords/if-rcode-error
new file mode 100644
index 0000000..fed8a49
--- /dev/null
+++ b/src/tests/keywords/if-rcode-error
@@ -0,0 +1,11 @@
+# PRE: if
+#
+# return code in an "if" section.
+#
+if (User-Name == "bob") {
+ update reply {
+ Filter-Id := "filter"
+ }
+
+ ok = reject # ERROR
+}
diff --git a/src/tests/keywords/if-regex-bad-attribute b/src/tests/keywords/if-regex-bad-attribute
new file mode 100644
index 0000000..f330fde
--- /dev/null
+++ b/src/tests/keywords/if-regex-bad-attribute
@@ -0,0 +1,21 @@
+#
+# PRE: if-regex-match if-regex-error
+#
+
+#
+# This should parse
+#
+if (&User-Name =~ /%{User-Name}/) {
+ update reply {
+ Filter-Id := "filter"
+ }
+}
+
+#
+# Check regexes which refer to unknown attributes
+#
+if (&User-Name =~ /%{What-The-Heck-Is-This-Thing}/) { # ERROR
+ update reply {
+ Filter-Id := "filter"
+ }
+}
diff --git a/src/tests/keywords/if-regex-error b/src/tests/keywords/if-regex-error
new file mode 100644
index 0000000..f618e82
--- /dev/null
+++ b/src/tests/keywords/if-regex-error
@@ -0,0 +1,12 @@
+#
+# PRE: if-regex-match
+#
+
+#
+# Check that bad regular expressions will fail
+#
+if (&User-Name =~ /[a-3]/) { # ERROR
+ update reply {
+ Filter-Id := "filter"
+ }
+}
diff --git a/src/tests/keywords/if-regex-match b/src/tests/keywords/if-regex-match
new file mode 100644
index 0000000..458e455
--- /dev/null
+++ b/src/tests/keywords/if-regex-match
@@ -0,0 +1,183 @@
+# PRE: if
+#
+update request {
+ Tmp-Integer-0 := '123456789'
+}
+
+# Non matching on attribute ref
+if (User-Name !~ /^([0-9])_([0-9])?_([0-9]*)_([0-9]+)_([^_])_(6)_([7-8])%{Tmp-String-0}/) {
+ update reply {
+ Filter-Id += 'Fail 0'
+ }
+}
+
+# Matching on xlat expanded value
+if ("%{User-Name}" !~ /^([0-9])_([0-9])?_([0-9]*)_([0-9]+)_([^_])_(6)_([7-8])%{Tmp-String-0}/) {
+ update reply {
+ Filter-Id += 'Fail 1'
+ }
+}
+
+# Matching on attribute ref with capture groups
+if (User-Name =~ /^([0-9])_([0-9])?_([0-9]*)_([0-9]+)_([^_])_(6)_([7-8])%{Tmp-String-0}/) {
+ # Test all the capture groups
+ update {
+ reply:User-Name := "%{7}_%{6}_%{5}_%{4}_%{3}_%{2}_%{1}_%{0}"
+ }
+}
+else {
+ update reply {
+ Filter-Id += 'Fail 2'
+ }
+}
+
+# Checking capture groups are cleared out correctly
+if (User-Name =~ /^([0-9])_%{Tmp-String-0}/) {
+ if ("%{0}%{1}%{2}%{3}%{4}%{5}%{6}%{7}" != '1_1') {
+ update reply {
+ Filter-Id += 'Fail 3'
+ }
+ }
+}
+else {
+ update reply {
+ Filter-Id += 'Fail 3.5'
+ }
+}
+
+# Checking capture groups are cleared out correctly when there are no matches
+if (User-Name =~ /^.%{Tmp-String-0}/) {
+ if ("%{0}%{1}%{2}%{3}%{4}%{5}%{6}%{7}" != '1') {
+ update reply {
+ Filter-Id += 'Fail 4'
+ }
+ }
+}
+else {
+ update reply {
+ Filter-Id += 'Fail 4.5'
+ }
+}
+
+# Checking full capture group range
+if ('a_b_c_d_e_f_g_h_i_j_k_l_m_n_o_p_q_r_s_t_u_v_w_x_y_z_A_B_C_D_E_F' =~ /^(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)$/) {
+ if ("%{0}%{1}%{2}%{3}%{4}%{5}%{6}%{7}%{8}%{9}%{10}%{11}%{12}%{13}%{14}%{15}%{16}%{17}%{18}%{19}%{20}%{21}%{22}%{23}%{24}%{25}%{26}%{27}%{28}%{29}%{30}%{31}%{32}" != 'a_b_c_d_e_f_g_h_i_j_k_l_m_n_o_p_q_r_s_t_u_v_w_x_y_z_A_B_C_D_E_FabcdefghijklmnopqrstuvwxyzABCDEF') {
+ update reply {
+ Filter-Id += 'Fail 6'
+ }
+ }
+}
+else {
+ update reply {
+ Filter-Id += 'Fail 6.5'
+ }
+}
+
+# Checking full capture group overun
+if ('a_b_c_d_e_f_g_h_i_j_k_l_m_n_o_p_q_r_s_t_u_v_w_x_y_z_A_B_C_D_E_F_G' =~ /^(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)$/) {
+ if ("%{0}%{1}%{2}%{3}%{4}%{5}%{6}%{7}%{8}%{9}%{10}%{11}%{12}%{13}%{14}%{15}%{16}%{17}%{18}%{19}%{20}%{21}%{22}%{23}%{24}%{25}%{26}%{27}%{28}%{29}%{30}%{31}%{32}" != 'a_b_c_d_e_f_g_h_i_j_k_l_m_n_o_p_q_r_s_t_u_v_w_x_y_z_A_B_C_D_E_F_GabcdefghijklmnopqrstuvwxyzABCDEF') {
+ update reply {
+ Filter-Id += 'Fail 7'
+ }
+ }
+}
+else {
+ update reply {
+ Filter-Id += 'Fail 7.5'
+ }
+}
+
+# uncompiled - ref - insensitive
+if (Calling-Station-Id !~ /:roamyroam%{Tmp-String-0}$/i) {
+ update reply {
+ Filter-Id += 'Fail 8'
+ }
+}
+
+# uncompiled - expansion - insensitive
+if ("%{Calling-Station-Id}" !~ /:roamyroam%{Tmp-String-0}$/i) {
+ update reply {
+ Filter-Id += 'Fail 9'
+ }
+}
+
+# uncompiled - enum - ref - insensitive
+if (Service-Type !~ /^framed-user%{Tmp-String-0}$/i) {
+ update reply {
+ Filter-Id += 'Fail 10'
+ }
+}
+
+# uncompiled - enum - expansion - insensitive
+if ("%{Service-Type}" !~ /^framed-user%{Tmp-String-0}$/i) {
+ update reply {
+ Filter-Id += 'Fail 11'
+ }
+}
+
+# uncompiled - enum - ref
+if (Service-Type =~ /^framed-user%{Tmp-String-0}$/) {
+ update reply {
+ Filter-Id += 'Fail 12'
+ }
+}
+
+# uncompiled - integer - ref
+if (Tmp-Integer-0 !~ /%{Tmp-Integer-0}/) {
+ update reply {
+ Filter-Id += 'Fail 13'
+ }
+}
+
+update request {
+ Tmp-String-0 := "foo\nbar"
+}
+
+# uncompiled - ref - multiline
+if (&Tmp-String-0 !~ /^foo$%{Tmp-String-8}/m) {
+ update reply {
+ Filter-Id += 'Fail 14'
+ }
+}
+
+# uncompiled - ref - non-multiline
+if (&Tmp-String-0 =~ /^foo$%{Tmp-String-8}/) {
+ update reply {
+ Filter-Id += 'Fail 15'
+ }
+}
+
+# uncompiled - ref - non-multiline
+if (&Tmp-String-0 !~ /^foo\nbar%{Tmp-String-8}$/) {
+ update reply {
+ Filter-Id += 'Fail 16'
+ }
+}
+
+# uncompiled - ref - multiline
+if (&Tmp-String-0 !~ /^bar%{Tmp-String-8}$/m) {
+ update reply {
+ Filter-Id += 'Fail 17'
+ }
+}
+
+# uncompiled - ref - multiline - sensitive
+if (&Tmp-String-0 =~ /^BAR%{Tmp-String-8}$/m) {
+ update reply {
+ Filter-Id += 'Fail 18'
+ }
+}
+
+# uncompiled - ref - multiline - insensitive
+if (&Tmp-String-0 !~ /^BAR%{Tmp-String-8}$/mi) {
+ update reply {
+ Filter-Id += 'Fail 19'
+ }
+}
+
+# uncompiled - ref - multiline - insensitive (flag order reversed)
+if (&Tmp-String-0 !~ /^BAR%{Tmp-String-8}$/im) {
+ update reply {
+ Filter-Id += 'Fail 20'
+ }
+}
diff --git a/src/tests/keywords/if-regex-match-comp b/src/tests/keywords/if-regex-match-comp
new file mode 100644
index 0000000..c9c2d15
--- /dev/null
+++ b/src/tests/keywords/if-regex-match-comp
@@ -0,0 +1,149 @@
+# PRE: if
+#
+
+# Non matching on attribute ref
+if (User-Name !~ /^([0-9])_([0-9])?_([0-9]*)_([0-9]+)_([^_])_(6)_([7-8])/) {
+ update reply {
+ Filter-Id += 'Fail 0'
+ }
+}
+
+# Matching on xlat expanded value
+if ("%{User-Name}" !~ /^([0-9])_([0-9])?_([0-9]*)_([0-9]+)_([^_])_(6)_([7-8])/) {
+ update reply {
+ Filter-Id += 'Fail 1'
+ }
+}
+
+# Matching on attribute ref with capture groups
+if (User-Name =~ /^([0-9])_([0-9])?_([0-9]*)_([0-9]+)_([^_])_(6)_([7-8])/) {
+ # Test all the capture groups
+ update {
+ reply:User-Name := "%{7}_%{6}_%{5}_%{4}_%{3}_%{2}_%{1}_%{0}"
+ }
+}
+else {
+ update reply {
+ Filter-Id += 'Fail 2'
+ }
+}
+
+# Checking capture groups are cleared out correctly
+if (User-Name =~ /^([0-9])_/) {
+ if ("%{0}%{1}%{2}%{3}%{4}%{5}%{6}%{7}" != '1_1') {
+ update reply {
+ Filter-Id += 'Fail 3'
+ }
+ }
+}
+else {
+ update reply {
+ Filter-Id += 'Fail 3.5'
+ }
+}
+
+# Checking capture groups are cleared out correctly when there are no matches
+if (User-Name =~ /^./) {
+ if ("%{0}%{1}%{2}%{3}%{4}%{5}%{6}%{7}" != '1') {
+ update reply {
+ Filter-Id += 'Fail 4'
+ }
+ }
+}
+else {
+ update reply {
+ Filter-Id += 'Fail 4.5'
+ }
+}
+
+# compiled - ref - insensitive
+if (Calling-Station-Id !~ /:roamyroam$/i) {
+ update reply {
+ Filter-Id += 'Fail 5'
+ }
+}
+
+# compiled - expansion - insensitive
+if ("%{Calling-Station-Id}" !~ /:roamyroam$/i) {
+ update reply {
+ Filter-Id += 'Fail 6'
+ }
+}
+
+# compiled - enum - ref - insensitive
+if (Service-Type !~ /^framed-user$/i) {
+ update reply {
+ Filter-Id += 'Fail 7'
+ }
+}
+
+# compiled - enum - expansion - insensitive
+if ("%{Service-Type}" !~ /^framed-user$/i) {
+ update reply {
+ Filter-Id += 'Fail 8'
+ }
+}
+
+# compiled - enum - ref
+if (Service-Type =~ /^framed-user$/) {
+ update reply {
+ Filter-Id += 'Fail 9'
+ }
+}
+
+update request {
+ Tmp-String-0 := "foo\nbar"
+}
+
+# compiled - ref - multiline
+if (&Tmp-String-0 !~ /^foo$/m) {
+ update reply {
+ Filter-Id += 'Fail 14'
+ }
+}
+
+# compiled - ref - non-multiline
+if (&Tmp-String-0 =~ /^foo$/) {
+ update reply {
+ Filter-Id += 'Fail 15'
+ }
+}
+
+# compiled - ref - non-multiline
+
+# Not all POSIX implementations support the \n character classes
+# so only run this test if the server was built with libpcre.
+if (("${feature.regex-pcre}" == 'yes') && (&Tmp-String-0 !~ /^foo\nbar$/)) {
+ update reply {
+ Filter-Id += 'Fail 16'
+ }
+}
+
+# compiled - ref - multiline
+if (&Tmp-String-0 !~ /^bar$/m) {
+ update reply {
+ Filter-Id += 'Fail 17'
+ }
+}
+
+# compiled - ref - multiline - sensitive
+if (&Tmp-String-0 =~ /^BAR$/m) {
+ update reply {
+ Filter-Id += 'Fail 17'
+ }
+}
+
+# compiled - ref - multiline - insensitive
+if (&Tmp-String-0 !~ /^BAR$/mi) {
+ update reply {
+ Filter-Id += 'Fail 17'
+ }
+}
+
+# compiled - ref - multiline - insensitive (flag order reversed)
+if (&Tmp-String-0 !~ /^BAR$/im) {
+ update reply {
+ Filter-Id += 'Fail 18'
+ }
+}
+
diff --git a/src/tests/keywords/if-regex-match-comp.attrs b/src/tests/keywords/if-regex-match-comp.attrs
new file mode 100644
index 0000000..ba7188d
--- /dev/null
+++ b/src/tests/keywords/if-regex-match-comp.attrs
@@ -0,0 +1,7 @@
+User-Name = '1_2_3_4_5_6_7'
+User-Password = 'hello'
+Service-Type := 'Framed-User'
+Calling-Station-ID := '00:11:22:33:44:55:66:ROAMYROAM'
+
+Response-Packet-Type == Access-Accept
+User-Name == '7_6_5_4_3_2_1_1_2_3_4_5_6_7'
diff --git a/src/tests/keywords/if-regex-match-named b/src/tests/keywords/if-regex-match-named
new file mode 100644
index 0000000..2aa665f
--- /dev/null
+++ b/src/tests/keywords/if-regex-match-named
@@ -0,0 +1,117 @@
+# PRE: if
+#
+if ('${feature.regex-pcre}' == 'yes') {
+update request {
+ Tmp-Integer-0 := '123456789'
+ Tmp-Integer-1 := 1
+}
+
+# Check failures when no previous capture
+if ("%{regex:}" != "") {
+ update reply {
+ Filter-Id += 'Fail 0.1'
+ }
+}
+
+if ("%{regex:foo}" != "") {
+ update reply {
+ Filter-Id += 'Fail 0.2'
+ }
+}
+
+if ("%{regex:%{Tmp-Integer-1}}" != "") {
+ update reply {
+ Filter-Id += 'Fail 0.3'
+ }
+}
+
+if ("%{regex:1}" != "") {
+ update reply {
+ Filter-Id += 'Fail 0.4'
+ }
+}
+
+# uncompiled - ref - named capture groups
+if (User-Name =~ /^(?<one>[0-9])_(?<two>[0-9])?_(?<three>[0-9]*)_(?<four>[0-9]+)_(?<five>[^_])_(?<six>6)_(?<seven>[7-8])%{Tmp-String-0}/) {
+ if ("%{regex:seven}_%{regex:six}_%{regex:five}_%{regex:four}_%{regex:three}_%{regex:two}_%{regex:one}_%{0}" != '7_6_5_4_3_2_1_1_2_3_4_5_6_7') {
+ update reply {
+ Filter-Id += 'Fail 1'
+ }
+ }
+}
+else {
+ update reply {
+ Filter-Id += 'Fail 1.5'
+ }
+}
+
+# Checking capture groups are cleared out correctly
+if (User-Name =~ /^(?<one>[0-9])_%{Tmp-String-0}/) {
+ if ("%{0}%{regex:one}%{regex:two}%{regex:three}%{regex:four}%{regex:five}%{regex:six}%{regex:seven}" != '1_1') {
+ update reply {
+ Filter-Id += 'Fail 2'
+ }
+ }
+}
+else {
+ update reply {
+ Filter-Id += 'Fail 2.5'
+ }
+}
+
+# Checking capture groups are cleared out correctly when there are no matches
+if (User-Name =~ /^.%{Tmp-String-0}/) {
+ if ("%{0}%{regex:one}%{regex:two}%{regex:three}%{regex:four}%{regex:five}%{regex:six}%{regex:seven}" != '1') {
+ update reply {
+ Filter-Id += 'Fail 3'
+ }
+ }
+}
+else {
+ update reply {
+ Filter-Id += 'Fail 3.5'
+ }
+}
+
+# compiled - ref - named capture groups
+if (User-Name =~ /^(?<one>[0-9])_(?<two>[0-9])?_(?<three>[0-9]*)_(?<four>[0-9]+)_(?<five>[^_])_(?<six>6)_(?<seven>[7-8])/) {
+ if ("%{regex:seven}_%{regex:six}_%{regex:five}_%{regex:four}_%{regex:three}_%{regex:two}_%{regex:one}_%{0}" != '7_6_5_4_3_2_1_1_2_3_4_5_6_7') {
+ update reply {
+ Filter-Id += 'Fail 4'
+ }
+ }
+}
+else {
+ update reply {
+ Filter-Id += 'Fail 4.5'
+ }
+}
+
+# compiled - xlat - named capture groups
+if ('1_2_3_4_5_6_7' =~ /^(?<one>[0-9])_(?<two>[0-9])?_(?<three>[0-9]*)_(?<four>[0-9]+)_(?<five>[^_])_(?<six>6)_(?<seven>[7-8])/) {
+ if ("%{regex:seven}_%{regex:six}_%{regex:five}_%{regex:four}_%{regex:three}_%{regex:two}_%{regex:one}_%{0}" != '7_6_5_4_3_2_1_1_2_3_4_5_6_7') {
+ update reply {
+ Filter-Id += 'Fail 5'
+ }
+ }
+}
+else {
+ update reply {
+ Filter-Id += 'Fail 5.5'
+ }
+}
+
+# compiled - ref - named capture groups (numeric indexes)
+if (User-Name =~ /^(?<one>[0-9])_(?<two>[0-9])?_(?<three>[0-9]*)_(?<four>[0-9]+)_(?<five>[^_])_(?<six>6)_(?<seven>[7-8])/) {
+ if ("%{7}_%{6}_%{5}_%{4}_%{3}_%{2}_%{1}_%{0}" != '7_6_5_4_3_2_1_1_2_3_4_5_6_7') {
+ update reply {
+ Filter-Id += 'Fail 4'
+ }
+ }
+}
+else {
+ update reply {
+ Filter-Id += 'Fail 4.5'
+ }
+}
+}
diff --git a/src/tests/keywords/if-regex-match-named.attrs b/src/tests/keywords/if-regex-match-named.attrs
new file mode 100644
index 0000000..867ed23
--- /dev/null
+++ b/src/tests/keywords/if-regex-match-named.attrs
@@ -0,0 +1,6 @@
+User-Name = '1_2_3_4_5_6_7'
+User-Password = 'hello'
+Service-Type := 'Framed-User'
+Calling-Station-ID := '00:11:22:33:44:55:66:ROAMYROAM'
+
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/keywords/if-regex-match.attrs b/src/tests/keywords/if-regex-match.attrs
new file mode 100644
index 0000000..ba7188d
--- /dev/null
+++ b/src/tests/keywords/if-regex-match.attrs
@@ -0,0 +1,7 @@
+User-Name = '1_2_3_4_5_6_7'
+User-Password = 'hello'
+Service-Type := 'Framed-User'
+Calling-Station-ID := '00:11:22:33:44:55:66:ROAMYROAM'
+
+Response-Packet-Type == Access-Accept
+User-Name == '7_6_5_4_3_2_1_1_2_3_4_5_6_7'
diff --git a/src/tests/keywords/if-regex-multivalue b/src/tests/keywords/if-regex-multivalue
new file mode 100644
index 0000000..7358c93
--- /dev/null
+++ b/src/tests/keywords/if-regex-multivalue
@@ -0,0 +1,26 @@
+#
+# PRE: update if
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := 'filter'
+}
+
+update request {
+ Cisco-AVPair := 'foo=bar'
+ Cisco-AVPair += 'bar=baz'
+ Cisco-AVPair += 'baz=foo'
+}
+
+if (&Cisco-AVPair[*] =~ /bar=(.*)/) {
+ if ("%{1}" != 'baz') {
+ update reply {
+ Filter-Id += 'Fail 1'
+ }
+ }
+}
+else {
+ update reply {
+ Filter-Id += 'Fail 2'
+ }
+}
diff --git a/src/tests/keywords/if-skip b/src/tests/keywords/if-skip
new file mode 100644
index 0000000..0e74f22
--- /dev/null
+++ b/src/tests/keywords/if-skip
@@ -0,0 +1,42 @@
+# PRE: if
+#
+# Conditions which statically evaluate to "false"
+# have their entire contents skipped on load.
+#
+# Conditions which statically evaluate to "true"
+# have the following "else" sections skipped, too.
+#
+# i.e. we can reference things which don't exist,
+# and they'll get ignored.
+#
+if (0) {
+ no-such-module
+}
+
+if (0) {
+ no-such-module
+}
+else {
+ ok
+}
+
+if (1) {
+ ok
+}
+else {
+ no-such-module
+}
+
+if (1) {
+ ok
+}
+elsif ("%{foo:bar}") { # no pass2
+ no-such-module
+}
+else {
+ no-such-module
+}
+
+update reply {
+ Filter-Id := "filter"
+}
diff --git a/src/tests/keywords/integer b/src/tests/keywords/integer
new file mode 100644
index 0000000..7e43270
--- /dev/null
+++ b/src/tests/keywords/integer
@@ -0,0 +1,209 @@
+#
+# PRE: update
+#
+update reply {
+ Filter-Id := "filter"
+}
+
+update request {
+ Tmp-String-0 := '9870'
+ Tmp-String-1 := '98709870'
+ Tmp-String-2 := '987098709870'
+ Tmp-Octets-0 := 0x39383731
+ Tmp-Octets-1 := 0x3938373139383731
+ Tmp-Octets-2 := 0x393837313938373139383731
+ Tmp-IP-Address-0 := 57.56.55.50
+ Tmp-Date-0 := 959985459
+ Tmp-Integer-0 := 959985460
+ Tmp-Cast-Abinary := 'ip out forward srcip 57.56.55.53/32 udp dstport = 1812'
+ Tmp-Cast-IfId := '0000:0000:3938:3737'
+ Tmp-Cast-IPv6Addr := '::3938:3738'
+ Tmp-Cast-IPv6Prefix := '::3938:3739/128'
+ Tmp-Cast-Byte := 58
+ Tmp-Cast-Short := 14139
+ Tmp-Cast-Ethernet := 00:00:39:38:37:3c
+ Tmp-Cast-Integer64 := 1152921505566832445
+ Tmp-Cast-IPv4Prefix := 57.56.55.62/32
+}
+
+update request {
+ Tmp-String-2 := "%{integer:Tmp-IP-Address-0}"
+ Tmp-String-3 := "%{integer:Tmp-Date-0}"
+ Tmp-String-4 := "%{integer:Tmp-Integer-0}"
+ Tmp-String-5 := "%{integer:Tmp-Cast-Abinary}"
+ Tmp-String-6 := "%{integer:Tmp-Cast-Ifid}"
+ Tmp-String-7 := "%{integer:Tmp-Cast-IPv6Addr}"
+ Tmp-String-8 := "%{integer:Tmp-Cast-IPv6Prefix}"
+}
+
+# String - network order representation of a 4 char string
+update request {
+ Tmp-Integer-0 := "%{integer:Tmp-String-0}"
+}
+if ((Tmp-String-0 != "%{string:Tmp-Integer-0}") || (Tmp-Integer-0 != 959985456)) {
+ update reply {
+ Filter-Id += 'fail 1'
+ }
+}
+
+# String - network order representation of a 8 char string
+update request {
+ Tmp-Integer64-0 := "%{integer:Tmp-String-1}"
+}
+if ((Tmp-String-1 != "%{string:Tmp-Integer64-0}") || (Tmp-Integer64-0 != 4123106139115632432)) {
+ update reply {
+ Filter-Id += 'fail 2'
+ }
+}
+
+# String - Can't convert 12 byte string to integer (our biggest native size is a 64bit unsigned int)
+if ("%{integer:Tmp-String-2}" != '') {
+ update reply {
+ Filter-Id += 'fail 3'
+ }
+}
+
+# Octets - network order representation of a 4 byte octet string
+update request {
+ Tmp-Integer-0 := "%{integer:Tmp-Octets-0}"
+}
+if (Tmp-Octets-0 != "0x%{hex:Tmp-Integer-0}") {
+ update reply {
+ Filter-Id += 'fail 4a'
+ }
+}
+
+if (Tmp-Integer-0 != 959985457) {
+ update reply {
+ Filter-Id += 'fail 4b'
+ }
+}
+
+# Octets - network order representation of a 8 byte octet string
+update request {
+ Tmp-Integer64-0 := "%{integer:Tmp-Octets-1}"
+}
+if (Tmp-Octets-1 != "0x%{hex:Tmp-Integer64-0}") {
+ update reply {
+ Filter-Id += 'fail 5a'
+ }
+}
+
+if (Tmp-Integer64-0 != 4123106143410599729) {
+ update reply {
+ Filter-Id += 'fail 5b'
+ }
+}
+
+# String - Can't convert 12 byte octet string to integer (our biggest native size is a 64bit unsigned int)
+if ("%{integer:Tmp-Octets-2}" != '') {
+ update reply {
+ Filter-Id += 'fail 6'
+ }
+}
+
+# IP Address
+if (Tmp-String-2 != '959985458') {
+ update reply {
+ Filter-Id += 'fail 7'
+ }
+}
+
+if (<ipaddr>Tmp-String-2 != &Tmp-IP-Address-0) {
+ update reply {
+ Filter-Id += 'fail 8'
+ }
+}
+
+# Date
+if (Tmp-String-3 != '959985459') {
+ update reply {
+ Filter-Id += 'fail 9'
+ }
+}
+
+# Integer
+if (Tmp-String-4 != '959985460') {
+ update reply {
+ Filter-Id += 'fail 10'
+ }
+}
+
+# Abinary - Can't convert ascend binary to an integer
+if (Tmp-String-5 != '') {
+ update reply {
+ Filter-Id += 'fail 11'
+ }
+}
+
+# ifid - Can't convert interface ID to an integer
+if (Tmp-String-6 != '') {
+ update reply {
+ Filter-Id += 'fail 12'
+ }
+}
+
+# ipv6addr - Can't convert IPv6 to integer
+if (Tmp-String-7 != '959985464') {
+ update reply {
+ Filter-ID += 'fail 13'
+ }
+}
+
+# ipv6addrprefix
+if (Tmp-String-8 != '959985465') {
+ update reply {
+ Filter-ID += 'fail 14'
+ }
+}
+update request {
+ Tmp-String-0 := "%{integer:Tmp-Cast-Byte}"
+ Tmp-String-1 := "%{integer:Tmp-Cast-Short}"
+ Tmp-String-2 := "%{integer:Tmp-Cast-Ethernet}"
+ Tmp-String-3 := "%{integer:Tmp-Cast-Integer64}"
+ Tmp-String-4 := "%{integer:Tmp-Cast-IPv4Prefix}"
+}
+
+# byte
+if (Tmp-String-0 != '58') {
+ update reply {
+ Filter-ID += 'fail 15'
+ }
+}
+
+# short
+if (Tmp-String-1 != '14139') {
+ update reply {
+ Filter-ID += 'fail 16'
+ }
+}
+
+# ethernet
+if (Tmp-String-2 != '62913607630848') {
+ update reply {
+ Filter-Id += 'fail 17'
+ }
+}
+if (<ether>Tmp-String-2 != &Tmp-Cast-Ethernet) {
+ update reply {
+ Filter-Id += 'fail 18'
+ }
+}
+
+# integer64
+if (Tmp-String-3 != '1152921505566832445') {
+ update reply {
+ Filter-Id += 'fail 19'
+ }
+}
+
+# ipv4prefix
+if (Tmp-String-4 != '959985470') {
+ update reply {
+ Filter-Id += 'fail 20'
+ }
+}
+
+
+
+
diff --git a/src/tests/keywords/ipaddr b/src/tests/keywords/ipaddr
new file mode 100644
index 0000000..3010a23
--- /dev/null
+++ b/src/tests/keywords/ipaddr
@@ -0,0 +1,51 @@
+#
+# PRE: update if
+#
+update control {
+ Cleartext-Password := 'hello'
+ reply:Filter-Id := "filter"
+}
+
+update request {
+ # Dotted Quad
+ Tmp-IP-Address-0 := 127.0.0.1
+
+ # Dotted Quad with prefix
+ Tmp-IP-Address-1 := 127.0.0.2/32
+
+ # Hex (0x)
+ Tmp-IP-Address-2 := 0x7f000003
+
+ # Decimal
+ Tmp-IP-Address-3 := 2130706436
+}
+
+if (NAS-IP-Address != 127.0.0.1) {
+ update reply {
+ Filter-Id += "fail 1"
+ }
+}
+
+if (Tmp-IP-Address-0 != 127.0.0.1) {
+ update reply {
+ Filter-Id += "fail 2"
+ }
+}
+
+if (Tmp-IP-Address-1 != 127.0.0.2) {
+ update reply {
+ Filter-Id += "fail 3"
+ }
+}
+
+if (Tmp-IP-Address-2 != 127.0.0.3) {
+ update reply {
+ Filter-Id += "fail 4"
+ }
+}
+
+if (Tmp-IP-Address-3 != 127.0.0.4) {
+ update reply {
+ Filter-Id += "fail 5"
+ }
+}
diff --git a/src/tests/keywords/ipaddr-error b/src/tests/keywords/ipaddr-error
new file mode 100644
index 0000000..5483a4f
--- /dev/null
+++ b/src/tests/keywords/ipaddr-error
@@ -0,0 +1,10 @@
+#
+# PRE: update ipaddr
+#
+update control {
+ Cleartext-Password := 'hello'
+}
+
+update reply {
+ Framed-IP-Address := 256.1 # ERROR
+}
diff --git a/src/tests/keywords/ipaddr.attrs b/src/tests/keywords/ipaddr.attrs
new file mode 100644
index 0000000..ab9c27e
--- /dev/null
+++ b/src/tests/keywords/ipaddr.attrs
@@ -0,0 +1,12 @@
+#
+# Input packet
+#
+User-Name = "bob"
+User-Password = "hello"
+NAS-IP-Address = 127.0.0.1
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+Filter-Id == 'filter'
diff --git a/src/tests/keywords/ipprefix b/src/tests/keywords/ipprefix
new file mode 100644
index 0000000..0ab8dce
--- /dev/null
+++ b/src/tests/keywords/ipprefix
@@ -0,0 +1,52 @@
+#
+# PRE: update if
+#
+update control {
+ Cleartext-Password := 'hello'
+}
+
+update reply {
+ Filter-Id := "filter"
+}
+
+update control {
+ Tmp-Cast-IPv4Prefix := 198.51.100.255/16
+ Tmp-Cast-IPv6Prefix := ::198.51.100.255/112
+ Framed-IP-Address := 198.51.0.1
+}
+
+if ("%{control:Tmp-Cast-IPv6Prefix}" != '::198.51.0.0/112') {
+ update reply {
+ Filter-Id += "Fail 0"
+ }
+}
+
+if ("%{control:Tmp-Cast-IPv4Prefix}" != '198.51.0.0/16') {
+ update reply {
+ Filter-Id += "Fail 1"
+ }
+}
+
+if (control:Tmp-Cast-IPv6Prefix != ::198.51.0.0/112) {
+ update reply {
+ Filter-Id += "Fail 2"
+ }
+}
+
+if (control:Tmp-Cast-IPv4Prefix != 198.51.0.0/16) {
+ update reply {
+ Filter-Id += "Fail 3"
+ }
+}
+
+if (!(&control:Tmp-Cast-IPv4Prefix < 198.0.0.0/8)) {
+ update reply {
+ Filter-Id += "Fail 4"
+ }
+}
+
+if (!(&control:Framed-IP-Address < 198.51.0.0/16)) {
+ update reply {
+ Filter-Id += "Fail 5"
+ }
+}
diff --git a/src/tests/keywords/length b/src/tests/keywords/length
new file mode 100644
index 0000000..ad37fc8
--- /dev/null
+++ b/src/tests/keywords/length
@@ -0,0 +1,155 @@
+#
+# PRE: hex
+#
+update reply {
+ Filter-Id := "filter"
+}
+
+update request {
+ Tmp-String-0 := '\
+abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\
+abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\
+abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz'
+ Tmp-String-2 := '9870'
+ Tmp-Octets-0 := 0x39383731
+ Tmp-IP-Address-0 := 57.56.55.50
+ Tmp-Date-0 := 959985459
+ Tmp-Integer-0 := 959985460
+ Tmp-Cast-Abinary := 'ip out forward srcip 57.56.55.53/32 udp dstport = 1812'
+ Tmp-Cast-IfId := '0000:0000:3938:3737'
+ Tmp-Cast-IPv6Addr := '::3938:3738'
+ Tmp-Cast-IPv6Prefix := '::3938:3739/128'
+ Tmp-Cast-Byte := 58
+ Tmp-Cast-Short := 14139
+ Tmp-Cast-Ethernet := 00:00:39:38:37:3c
+ Tmp-Cast-Integer64 := 1152921505566832445
+ Tmp-Cast-IPv4Prefix := 57.56.55.62/32
+}
+
+update request {
+ Tmp-Integer-0 := "%{length:Tmp-String-0}"
+}
+
+if (Tmp-Integer-0 != 260) {
+ update reply {
+ Filter-Id += 'fail 1'
+ }
+}
+
+update request {
+ Tmp-Integer-0 := "%{length:Tmp-String-2}"
+ Tmp-Integer-1 := "%{length:Tmp-Octets-0}"
+ Tmp-Integer-2 := "%{length:Tmp-IP-Address-0}"
+ Tmp-Integer-3 := "%{length:Tmp-Date-0}"
+ Tmp-Integer-4 := "%{length:Tmp-Integer-0}"
+ Tmp-Integer-5 := "%{length:Tmp-Cast-Abinary}"
+ Tmp-Integer-6 := "%{length:Tmp-Cast-Ifid}"
+ Tmp-Integer-7 := "%{length:Tmp-Cast-IPv6Addr}"
+ Tmp-Integer-8 := "%{length:Tmp-Cast-IPv6Prefix}"
+ Tmp-Integer-9 := "%{length:Tmp-Cast-Byte}"
+}
+
+# String - bin 0x39383730
+if (Tmp-Integer-0 != 4) {
+ update reply {
+ Filter-Id += 'fail 2'
+ }
+}
+
+# Octets - bin 0x39383731
+if (Tmp-Integer-1 != 4) {
+ update reply {
+ Filter-Id += 'fail 3'
+ }
+}
+
+# IP Address - bin 0x39383732
+if (Tmp-Integer-2 != 4) {
+ update reply {
+ Filter-Id += 'fail 4'
+ }
+}
+
+# Date - bin 0x39383733
+if (Tmp-Integer-3 != 4) {
+ update reply {
+ Filter-Id += 'fail 5'
+ }
+}
+
+# Integer - bin 0x39383734
+if (Tmp-Integer-4 != 4) {
+ update reply {
+ Filter-Id += 'fail 6'
+ }
+}
+
+# Abinary - bin 0x0101000039383735000000002000110000000714000200000000000000000000
+if (Tmp-Integer-5 != 32) {
+ update reply {
+ Filter-Id += 'fail 7'
+ }
+}
+
+# ifid - bin 0x0000000039383737
+if (Tmp-Integer-6 != 8) {
+ update reply {
+ Filter-Id += 'fail 8'
+ }
+}
+
+# ipv6addr - bin 0x00000000000000000000000039383738
+if (Tmp-Integer-7 != 16) {
+ update reply {
+ Filter-ID += 'fail 9'
+ }
+}
+
+# ipv6addrprefix - bin 0x008000000000000000000000000039383739
+if (Tmp-Integer-8 != 18) {
+ update reply {
+ Filter-ID += 'fail 10'
+ }
+}
+
+# byte - bin 0x3a
+if (Tmp-Integer-9 != 1) {
+ update reply {
+ Filter-ID += 'fail 11'
+ }
+}
+
+update request {
+ Tmp-Integer-0 := "%{length:Tmp-Cast-Short}"
+ Tmp-Integer-1 := "%{length:Tmp-Cast-Ethernet}"
+ Tmp-Integer-2 := "%{length:Tmp-Cast-Integer64}"
+ Tmp-Integer-3 := "%{length:Tmp-Cast-IPv4Prefix}"
+}
+
+# short - bin 0x373b
+if (Tmp-Integer-0 != 2) {
+ update reply {
+ Filter-ID += 'fail 12'
+ }
+}
+
+# ethernet - bin 0x00003938373c
+if (Tmp-Integer-1 != 6) {
+ update reply {
+ Filter-Id += 'fail 13'
+ }
+}
+
+# integer64 - bin 0x100000003938373d
+if (Tmp-Integer-2 != 8) {
+ update reply {
+ Filter-Id += 'fail 14'
+ }
+}
+
+# ipv4prefix - bin 0x00203938373e
+if (Tmp-Integer-3 != 6) {
+ update reply {
+ Filter-Id += 'fail 15'
+ }
+}
diff --git a/src/tests/keywords/load-balance b/src/tests/keywords/load-balance
new file mode 100644
index 0000000..d07939a
--- /dev/null
+++ b/src/tests/keywords/load-balance
@@ -0,0 +1,97 @@
+# PRE: update if foreach
+#
+# Load-Balance blocks.
+#
+# Should distribute load between the modules.
+#
+update request {
+ Tmp-Integer-0 := 0
+ Tmp-Integer-1 := 0
+
+ Tmp-Integer-2 += 0 # 0
+ Tmp-Integer-2 += 1
+ Tmp-Integer-2 += 2
+ Tmp-Integer-2 += 3
+ Tmp-Integer-2 += 4
+ Tmp-Integer-2 += 5
+ Tmp-Integer-2 += 6
+ Tmp-Integer-2 += 7
+ Tmp-Integer-2 += 8
+ Tmp-Integer-2 += 9 # 10
+ Tmp-Integer-2 += 0
+ Tmp-Integer-2 += 1
+ Tmp-Integer-2 += 2
+ Tmp-Integer-2 += 3
+ Tmp-Integer-2 += 4
+ Tmp-Integer-2 += 5
+ Tmp-Integer-2 += 6
+ Tmp-Integer-2 += 7
+ Tmp-Integer-2 += 8
+ Tmp-Integer-2 += 9 # 20
+ Tmp-Integer-2 += 0
+ Tmp-Integer-2 += 1
+ Tmp-Integer-2 += 2
+ Tmp-Integer-2 += 3
+ Tmp-Integer-2 += 4
+ Tmp-Integer-2 += 5
+ Tmp-Integer-2 += 6
+ Tmp-Integer-2 += 7
+ Tmp-Integer-2 += 8
+ Tmp-Integer-2 += 9 # 30
+ Tmp-Integer-2 += 0
+ Tmp-Integer-2 += 1
+ Tmp-Integer-2 += 2
+ Tmp-Integer-2 += 3
+ Tmp-Integer-2 += 4
+ Tmp-Integer-2 += 5
+ Tmp-Integer-2 += 6
+ Tmp-Integer-2 += 7
+ Tmp-Integer-2 += 8
+ Tmp-Integer-2 += 9 # 40
+ Tmp-Integer-2 += 0
+ Tmp-Integer-2 += 1
+ Tmp-Integer-2 += 2
+ Tmp-Integer-2 += 3
+ Tmp-Integer-2 += 4
+ Tmp-Integer-2 += 5
+ Tmp-Integer-2 += 6
+ Tmp-Integer-2 += 7
+ Tmp-Integer-2 += 8
+ Tmp-Integer-2 += 9 # 49
+}
+
+#
+# Loop 0..9
+#
+foreach &Tmp-Integer-2 {
+ load-balance {
+ group {
+ update request {
+ Tmp-Integer-0 := "%{expr:%{Tmp-Integer-0} + 1}"
+ Filter-Id += "PICKED GROUP 1 %{Tmp-Integer-0} TIME(S)"
+ }
+ ok
+ }
+ group {
+ update request {
+ Tmp-Integer-1 := "%{expr:%{Tmp-Integer-1} + 1}"
+ Filter-Id += "PICKED GROUP 2 %{Tmp-Integer-1} TIME(S)"
+ }
+ ok
+ }
+ }
+}
+
+# The chances of one group being used over another 50 times by random occurrence
+# is quite small, and if this happens repeatedly, it's likely there's a bug in
+# the load-balance code or random number generator.
+if ((&Tmp-Integer-0 == 0) || (&Tmp-Integer-1 == 0)) {
+ update reply {
+ Filter-Id += "fail 1 %{Tmp-Integer-0}/%{Tmp-Integer-1})"
+ }
+}
+else {
+ update reply {
+ Filter-Id := 'filter'
+ }
+}
diff --git a/src/tests/keywords/log b/src/tests/keywords/log
new file mode 100644
index 0000000..97a2557
--- /dev/null
+++ b/src/tests/keywords/log
@@ -0,0 +1,7 @@
+update control {
+ Cleartext-Password := 'hello'
+}
+
+update reply {
+ Filter-Id := "filter"
+}
diff --git a/src/tests/keywords/map-xlat b/src/tests/keywords/map-xlat
new file mode 100644
index 0000000..24446a5
--- /dev/null
+++ b/src/tests/keywords/map-xlat
@@ -0,0 +1,25 @@
+#
+# PRE: update
+#
+# Test the map xlat
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := "filter"
+}
+
+update {
+ Tmp-String-0 := '&control:Tmp-String-0 := \'testing123\''
+}
+
+if ("%{map:%{Tmp-String-0}}" != 1) {
+ update reply {
+ Filter-Id += 'Fail 0'
+ }
+}
+
+if (&control:Tmp-String-0 != 'testing123') {
+ update reply {
+ Filter-Id += 'Fail 1'
+ }
+}
diff --git a/src/tests/keywords/md4 b/src/tests/keywords/md4
new file mode 100644
index 0000000..7e9b1ff
--- /dev/null
+++ b/src/tests/keywords/md4
@@ -0,0 +1,58 @@
+#
+# PRE: update if
+#
+update reply {
+ Filter-Id := "filter"
+}
+
+update {
+ control:Cleartext-Password := 'hello'
+ request:Tmp-String-0 := "This is a string\n"
+ request:Tmp-Octets-0 := 0x000504030201
+ request:Tmp-String-1 := "what do ya want for nothing?"
+ request:Tmp-String-2 := "Jefe"
+}
+
+#
+# Put "This is a string" into a file and call "md5sum" on it.
+# You should get this string.
+#
+if ("%{md4:This is a string\n}" != '1f60d5cd85e17bfbdda7c923822f060c') {
+ update reply {
+ Filter-Id += 'fail 1'
+ }
+}
+
+if ("%{md4:&Tmp-String-0}" != '1f60d5cd85e17bfbdda7c923822f060c') {
+ update reply {
+ Filter-Id += 'fail 2'
+ }
+}
+
+if ("%{md4:&request:Tmp-String-0}" != '1f60d5cd85e17bfbdda7c923822f060c') {
+ update reply {
+ Filter-Id += 'fail 3'
+ }
+}
+
+if ("%{md4:%{request:Tmp-String-0}}" != '1f60d5cd85e17bfbdda7c923822f060c') {
+ update reply {
+ Filter-Id += 'fail 4'
+ }
+}
+
+#
+# MD4 should also be able to cope with references to octet attributes
+#
+if ("%{md4:&request:Tmp-Octets-0}" != 'ac3ed17b3cf19ec38352ec534a932fc6') {
+ update reply {
+ Filter-Id += 'fail 5'
+ }
+}
+
+if ("%{md4:&Tmp-String-1}" != 'f7b44afb9cfdc877aa99d44654fe808b') {
+ update reply {
+ Filter-Id += 'fail 6'
+ }
+}
+
diff --git a/src/tests/keywords/md5 b/src/tests/keywords/md5
new file mode 100644
index 0000000..a973660
--- /dev/null
+++ b/src/tests/keywords/md5
@@ -0,0 +1,60 @@
+#
+# PRE: update if
+#
+update reply {
+ Filter-Id := "filter"
+}
+
+update {
+ control:Cleartext-Password := 'hello'
+ request:Tmp-String-0 := "This is a string\n"
+ request:Tmp-Octets-0 := 0x000504030201
+ request:Tmp-String-1 := "what do ya want for nothing?"
+ request:Tmp-String-2 := "Jefe"
+}
+
+#
+# Put "This is a string" into a file and call "md5sum" on it.
+# You should get this string.
+#
+if ("%{md5:This is a string\n}" != '9ac4dbbc3c0ad2429e61d0df5dc28add') {
+ update reply {
+ Filter-Id += 'fail 1'
+ }
+}
+
+if ("%{md5:&Tmp-String-0}" != '9ac4dbbc3c0ad2429e61d0df5dc28add') {
+ update reply {
+ Filter-Id += 'fail 2'
+ }
+}
+
+if ("%{md5:&request:Tmp-String-0}" != '9ac4dbbc3c0ad2429e61d0df5dc28add') {
+ update reply {
+ Filter-Id += 'fail 3'
+ }
+}
+
+if ("%{md5:%{request:Tmp-String-0}}" != '9ac4dbbc3c0ad2429e61d0df5dc28add') {
+ update reply {
+ Filter-Id += 'fail 4'
+ }
+}
+
+#
+# MD5 should also be able to cope with references to octet attributes
+#
+if ("%{md5:&request:Tmp-Octets-0}" != 'c1e7fa505b2fc1fd0da6cac3db6f6f44') {
+ update reply {
+ Filter-Id += 'fail 5'
+ }
+}
+
+#
+# MD5 HMAC with attribute references
+#
+if ("%{hmacmd5:&Tmp-String-1 &Tmp-String-2}" != '750c783e6ab0b503eaa86e310a5db738') {
+ update reply {
+ Filter-Id += 'fail 6'
+ }
+}
diff --git a/src/tests/keywords/module-failure-message b/src/tests/keywords/module-failure-message
new file mode 100644
index 0000000..51b1ef4
--- /dev/null
+++ b/src/tests/keywords/module-failure-message
@@ -0,0 +1,40 @@
+#
+# PRE: update
+#
+# Remove all attributes in a list
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := 'filter'
+}
+
+test
+if ("%{request:Module-Failure-Message[0]}" != 'test: RERROR error message') {
+ update reply {
+ Filter-Id += 'Fail 0'
+ }
+}
+
+if ("%{request:Module-Failure-Message[1]}" != 'test: RDEBUG error message') {
+ update reply {
+ Filter-Id += 'Fail 1'
+ }
+}
+
+if ("%{request:Module-Failure-Message[2]}" != 'test: RDEBUG2 error message') {
+ update reply {
+ Filter-Id += 'Fail 2'
+ }
+}
+
+if ("%{request:Module-Failure-Message[3]}" != 'test: RDEBUG3 error message') {
+ update reply {
+ Filter-Id += 'Fail 3'
+ }
+}
+
+if ("%{request:Module-Failure-Message[4]}" != 'test: RDEBUG4 error message') {
+ update reply {
+ Filter-Id += 'Fail 4'
+ }
+}
diff --git a/src/tests/keywords/ok-return b/src/tests/keywords/ok-return
new file mode 100644
index 0000000..f79ef02
--- /dev/null
+++ b/src/tests/keywords/ok-return
@@ -0,0 +1,13 @@
+update {
+ control:Auth-Type = 'Accept'
+ reply:Reply-Message = 'pass'
+}
+
+# Section should exit after this statement
+ok {
+ ok = return
+}
+
+update {
+ reply:Reply-Message := 'fail'
+}
diff --git a/src/tests/keywords/ok-return.attrs b/src/tests/keywords/ok-return.attrs
new file mode 100644
index 0000000..ad30f4d
--- /dev/null
+++ b/src/tests/keywords/ok-return.attrs
@@ -0,0 +1,4 @@
+User-Name = 'test'
+
+Response-Packet-Type == Access-Accept
+Reply-Message == 'pass'
diff --git a/src/tests/keywords/pad b/src/tests/keywords/pad
new file mode 100644
index 0000000..0a1f9ce
--- /dev/null
+++ b/src/tests/keywords/pad
@@ -0,0 +1,62 @@
+#
+# PRE: if update return
+#
+
+update request {
+ Tmp-String-0 = "test"
+}
+
+#
+# rpad tests
+#
+
+if ("%{rpad:&Tmp-String-0 7}" != "test ") {
+ update reply {
+ Filter-Id := "fail 1"
+ }
+ return
+}
+
+if ("%{rpad:&Tmp-String-0 2}" != "te") {
+ update reply {
+ Filter-Id := "fail 2"
+ }
+ return
+}
+
+if ("%{rpad:&Tmp-String-0 7 x}" != "testxxx") {
+ update reply {
+ Filter-Id := "fail 1"
+ }
+ return
+}
+
+#
+# lpad tests
+#
+
+if ("%{lpad:&Tmp-String-0 7}" != " test") {
+ update reply {
+ Filter-Id := "fail 1"
+ }
+ return
+}
+
+if ("%{lpad:&Tmp-String-0 2}" != "te") {
+ update reply {
+ Filter-Id := "fail 2"
+ }
+ return
+}
+
+if ("%{lpad:&Tmp-String-0 7 x}" != "xxxtest") {
+ update reply {
+ Filter-Id := "fail 1"
+ }
+ return
+}
+
+update reply {
+ Filter-Id := "filter"
+}
+
diff --git a/src/tests/keywords/pairs b/src/tests/keywords/pairs
new file mode 100644
index 0000000..4230c57
--- /dev/null
+++ b/src/tests/keywords/pairs
@@ -0,0 +1,42 @@
+#
+# PRE: update if
+#
+update {
+ control:Cleartext-Password := 'hello'
+ request:Tmp-String-0 := "This is a string"
+ request:Tmp-String-0 += "This is another one"
+ request:Tmp-Octets-0 := 0x000504030201
+ request:Tmp-Integer-0 := 7331
+ request:Tunnel-Private-Group-Id:5 = 127.0.0.1
+ reply:Filter-Id = 'filter'
+}
+
+if ("%{pairs:request:}" != "User-Name = \"bob\", User-Password = \"hello\", Tmp-String-0 = \"This is a string\", Tmp-String-0 = \"This is another one\", Tmp-Octets-0 = 0x000504030201, Tmp-Integer-0 = 7331, Tunnel-Private-Group-Id:5 = \"127.0.0.1\"") {
+ update reply {
+ Filter-Id += 'fail 1'
+ }
+}
+
+if ("%{pairs:Tmp-String-0}" != "Tmp-String-0 = \"This is a string\"") {
+ update reply {
+ Filter-Id += 'fail 2'
+ }
+}
+
+if ("%{pairs:Tmp-String-0[*]}" != "Tmp-String-0 = \"This is a string\", Tmp-String-0 = \"This is another one\"") {
+ update reply {
+ Filter-Id += 'fail 3'
+ }
+}
+
+if ("%{pairs:control:}" != "Cleartext-Password = \"hello\"") {
+ update reply {
+ Filter-Id += 'fail 4'
+ }
+}
+
+if ("%{pairs:control:User-Name}" != '') {
+ update reply {
+ Filter-Id += 'fail 5'
+ }
+}
diff --git a/src/tests/keywords/pap b/src/tests/keywords/pap
new file mode 100644
index 0000000..a347b7c
--- /dev/null
+++ b/src/tests/keywords/pap
@@ -0,0 +1,146 @@
+#
+# PRE: update if
+#
+update {
+ reply:Filter-Id := 'filter'
+ control: !* ANY
+ request:Tmp-String-0 := "5RNqNl8iYLbkCc7JhR8as4TtDDCX6otuuWtcja8rITUyx9zrnHSe9tTHGmKK" # 60 byte salt
+}
+
+#
+# Unencoded Cleartext-Password in password with header
+#
+update {
+ control:Password-With-Header := "%{request:User-Password}"
+}
+pap.authorize
+pap.authenticate {
+ reject = 1
+}
+if (reject) {
+ update reply {
+ Filter-Id += 'fail 0'
+ }
+}
+
+update {
+ control: !* ANY
+}
+
+#
+# Base64 encoded Cleartext-Password in password with header
+#
+update {
+ Tmp-String-1 := "{clear}%{User-Password}"
+}
+update {
+ control:Password-With-Header := "%{base64:&request:Tmp-String-1}"
+}
+pap.authorize
+pap.authenticate {
+ reject = 1
+}
+if (reject) {
+ update reply {
+ Filter-Id += 'fail 0'
+ }
+}
+
+update {
+ control: !* ANY
+}
+
+#
+# Hex encoded SSHA password
+#
+update {
+ control:Password-With-Header += "{ssha}%{sha1:%{request:User-Password}%{&request:Tmp-String-0}}%{hex:&request:Tmp-String-0}"
+}
+
+pap.authorize
+pap.authenticate {
+ reject = 1
+}
+if (reject) {
+ update reply {
+ Filter-Id += 'fail 1'
+ }
+}
+
+update {
+ control: !* ANY
+}
+
+#
+# Base64 encoded SSHA password
+#
+update {
+ control:Tmp-String-1 := "%{sha1:%{request:User-Password}%{&request:Tmp-String-0}}%{hex:&request:Tmp-String-0}"
+}
+
+# To Binary
+update {
+ control:Tmp-Octets-0 := "0x%{control:Tmp-String-1}"
+}
+
+# To Base64
+update {
+ control:Tmp-String-1 := "%{base64:&control:Tmp-Octets-0}"
+}
+
+update {
+ control:Password-With-Header += "{ssha}%{control:Tmp-String-1}"
+}
+
+pap.authorize
+pap.authenticate {
+ reject = 1
+}
+if (reject) {
+ update reply {
+ Filter-Id += 'fail 2'
+ }
+}
+
+update {
+ control: !* ANY
+}
+
+#
+# Base64 of Base64 encoded SSHA password
+#
+update {
+ control:Tmp-String-1 := "%{sha1:%{request:User-Password}%{&request:Tmp-String-0}}%{hex:&request:Tmp-String-0}"
+}
+
+# To Binary
+update {
+ control:Tmp-Octets-0 := "0x%{control:Tmp-String-1}"
+}
+
+# To Base64
+update {
+ control:Tmp-String-1 := "{ssha}%{base64:&control:Tmp-Octets-0}"
+}
+
+update {
+ control:Password-With-Header += "%{base64:&control:Tmp-String-1}"
+}
+
+pap.authorize
+pap.authenticate {
+ reject = 1
+}
+if (reject) {
+ update reply {
+ Filter-Id += 'fail 3'
+ }
+}
+
+update {
+ control: !* ANY
+}
+
+update control {
+ Auth-Type := Accept
+}
diff --git a/src/tests/keywords/pap-ssha2 b/src/tests/keywords/pap-ssha2
new file mode 100644
index 0000000..a8c9c9b
--- /dev/null
+++ b/src/tests/keywords/pap-ssha2
@@ -0,0 +1,114 @@
+#
+# PRE: update if pap
+#
+
+#
+# Skip if the server wasn't built with openssl
+#
+if ('${feature.tls}' != 'yes') {
+ update control {
+ Response-Packet-Type := Access-Accept
+ }
+ handled
+}
+
+update {
+ reply:Filter-Id := 'filter'
+ control: !* ANY
+ request:Tmp-String-0 := "5RNqNl8iYLbkCc7JhR8as4TtDDCX6otuuWtcja8rITUyx9zrnHSe9tTHGmKK" # 60 byte salt
+}
+
+#
+# Hex encoded SSHA2-512 password
+#
+update {
+ control:Password-With-Header += "{ssha512}%{sha512:%{request:User-Password}%{&request:Tmp-String-0}}%{hex:&request:Tmp-String-0}"
+}
+
+pap.authorize
+pap.authenticate {
+ reject = 1
+}
+if (reject) {
+ update reply {
+ Filter-Id += 'fail 1'
+ }
+}
+
+update {
+ control: !* ANY
+}
+
+#
+# Base64 encoded SSHA2-512 password
+#
+update {
+ control:Tmp-String-1 := "%{sha512:%{request:User-Password}%{&request:Tmp-String-0}}%{hex:&request:Tmp-String-0}"
+}
+
+# To Binary
+update {
+ control:Tmp-Octets-0 := "0x%{control:Tmp-String-1}"
+}
+
+# To Base64
+update {
+ control:Tmp-String-1 := "%{base64:&control:Tmp-Octets-0}"
+}
+
+update {
+ control:Password-With-Header += "{ssha512}%{control:Tmp-String-1}"
+}
+
+pap.authorize
+pap.authenticate {
+ reject = 1
+}
+if (reject) {
+ update reply {
+ Filter-Id += 'fail 2'
+ }
+}
+
+update {
+ control: !* ANY
+}
+
+#
+# Base64 of Base64 encoded SSHA2-512 password
+#
+update {
+ control:Tmp-String-1 := "%{sha512:%{request:User-Password}%{&request:Tmp-String-0}}%{hex:&request:Tmp-String-0}"
+}
+
+# To Binary
+update {
+ control:Tmp-Octets-0 := "0x%{control:Tmp-String-1}"
+}
+
+# To Base64
+update {
+ control:Tmp-String-1 := "{ssha512}%{base64:&control:Tmp-Octets-0}"
+}
+
+update {
+ control:Password-With-Header += "%{base64:&control:Tmp-String-1}"
+}
+
+pap.authorize
+pap.authenticate {
+ reject = 1
+}
+if (reject) {
+ update reply {
+ Filter-Id += 'fail 3'
+ }
+}
+
+update {
+ control: !* ANY
+}
+
+update control {
+ Auth-Type := Accept
+}
diff --git a/src/tests/keywords/radiusd.conf b/src/tests/keywords/radiusd.conf
new file mode 100644
index 0000000..58cb356
--- /dev/null
+++ b/src/tests/keywords/radiusd.conf
@@ -0,0 +1,127 @@
+#
+# Minimal radiusd.conf for testing keywords
+#
+
+raddb = raddb
+keyword = src/tests/keywords
+
+modconfdir = ${raddb}/mods-config
+
+correct_escapes = true
+
+# Only for testing!
+# Setting this on a production system is a BAD IDEA.
+security {
+ allow_vulnerable_openssl = yes
+}
+
+modules {
+ $INCLUDE ${raddb}/mods-enabled/always
+
+ $INCLUDE ${raddb}/mods-enabled/pap
+
+ $INCLUDE ${raddb}/mods-enabled/expr
+
+ $INCLUDE ${raddb}/mods-enabled/unpack
+
+ test {
+
+ }
+
+ unix {
+ }
+
+ cache {
+ driver = "rlm_cache_rbtree"
+
+ key = "%{Tmp-String-0}"
+ ttl = 2
+
+ update {
+ &request:Tmp-String-1 := &control:Tmp-String-1
+ &request:Tmp-Integer-0 := &control:Tmp-Integer-0
+ &control: += &reply:
+ }
+
+ add_stats = yes
+ }
+}
+
+policy {
+ #
+ # Outputs the contents of the control list in debugging (-X) mode
+ #
+ debug_control {
+ if("%{debug_attr:control:}" == '') {
+ noop
+ }
+ }
+
+ #
+ # Outputs the contents of the request list in debugging (-X) mode
+ #
+ debug_request {
+ if("%{debug_attr:request:}" == '') {
+ noop
+ }
+ }
+
+ #
+ # Outputs the contents of the reply list in debugging (-X) mode
+ #
+ debug_reply {
+ if("%{debug_attr:reply:}" == '') {
+ noop
+ }
+ }
+
+ #
+ # Outputs the contents of the main lists in debugging (-X) mode
+ #
+ debug_all {
+ debug_control
+ debug_request
+ debug_reply
+ }
+
+ #
+ # Just check that this can be referred to as "virtual_policy.post-auth"
+ #
+ virtual_policy {
+ ok
+ }
+
+ with.dots {
+ ok
+ }
+}
+
+instantiate {
+ #
+ # Just check that this can be referred to as "virtual_instantiate.post-auth"
+ #
+ load-balance virtual_instantiate {
+ ok
+ ok
+ }
+}
+
+server default {
+ authorize {
+ update control {
+ Cleartext-Password := 'hello'
+ }
+
+ #
+ # Include the test file specified by the
+ # KEYWORD environment variable.
+ #
+ $INCLUDE ${keyword}/$ENV{KEYWORD}
+
+ pap
+ }
+
+ authenticate {
+ pap
+ }
+}
diff --git a/src/tests/keywords/redundant b/src/tests/keywords/redundant
new file mode 100644
index 0000000..cebe7be
--- /dev/null
+++ b/src/tests/keywords/redundant
@@ -0,0 +1,17 @@
+# PRE: update if foreach
+#
+# Redundant blocks.
+#
+# The first one fails, so the second one is used
+#
+redundant {
+ group {
+ fail
+ }
+
+ group {
+ update reply {
+ Filter-Id := "filter"
+ }
+ }
+}
diff --git a/src/tests/keywords/redundant-error b/src/tests/keywords/redundant-error
new file mode 100644
index 0000000..bec4335
--- /dev/null
+++ b/src/tests/keywords/redundant-error
@@ -0,0 +1,6 @@
+#
+# Check that redundant blocks can't be empty
+#
+redundant { # ERROR
+
+}
diff --git a/src/tests/keywords/redundant-load-balance b/src/tests/keywords/redundant-load-balance
new file mode 100644
index 0000000..27721f1
--- /dev/null
+++ b/src/tests/keywords/redundant-load-balance
@@ -0,0 +1,65 @@
+# PRE: update if foreach
+#
+# Redundant blocks.
+#
+# The first one fails, so the second one is used
+#
+update request {
+ Tmp-Integer-0 := 0
+ Tmp-Integer-1 += 0
+ Tmp-Integer-1 += 1
+ Tmp-Integer-1 += 2
+ Tmp-Integer-1 += 3
+ Tmp-Integer-1 += 4
+ Tmp-Integer-1 += 5
+ Tmp-Integer-1 += 6
+ Tmp-Integer-1 += 7
+ Tmp-Integer-1 += 8
+ Tmp-Integer-1 += 9
+
+}
+
+#
+# Loop 0..9
+#
+foreach &Tmp-Integer-1 {
+ redundant-load-balance {
+ group {
+ # fail on even numbered values, succeed on odd numbered ones
+ if ("%{expr:%{Foreach-Variable-0} %% 2}" == 0) {
+ fail
+ }
+ else {
+ update request {
+ Tmp-Integer-0 := "%{expr:%{Tmp-Integer-0} + 1}"
+ Filter-Id += "SUCCEED ODD %{Foreach-Variable-0} %{Tmp-Integer-0}"
+ }
+ ok
+ }
+ }
+ group {
+ # succeed on even-numbered values, fail on off-numbered ones.
+ if ("%{expr:%{Foreach-Variable-0} %% 2}" == 1) {
+ fail
+ }
+ else {
+ update request {
+ Tmp-Integer-0 := "%{expr:%{Tmp-Integer-0} + 1}"
+ Filter-Id += "SUCCEED EVEN %{Foreach-Variable-0} %{Tmp-Integer-0}"
+ }
+ ok
+ }
+ }
+ }
+}
+
+if (&Tmp-Integer-0 != "%{Tmp-Integer-1[#]}") {
+ update reply {
+ Filter-Id += "fail 2 (passed %{Tmp-Integer-0}/%{Tmp-Integer-1[#]})"
+ }
+}
+else {
+ update reply {
+ Filter-Id := 'filter'
+ }
+}
diff --git a/src/tests/keywords/redundant-redundant b/src/tests/keywords/redundant-redundant
new file mode 100644
index 0000000..294f53e
--- /dev/null
+++ b/src/tests/keywords/redundant-redundant
@@ -0,0 +1,73 @@
+# PRE: update if foreach redundant redundant-load-balance
+#
+# Nested redundant blocks.
+#
+#
+update request {
+ Tmp-Integer-2 := 0
+ Tmp-Integer-3 := 0
+ Tmp-Integer-4 := 0
+ Tmp-Integer-5 := 0
+}
+
+redundant {
+ redundant-load-balance {
+ group {
+ update request {
+ Tmp-Integer-2 := "%{expr:&Tmp-Integer-2 + 1}"
+ }
+ fail
+ }
+ group {
+ update request {
+ Tmp-Integer-3 := "%{expr:&Tmp-Integer-3 + 1}"
+ }
+ fail
+ }
+ group {
+ update request {
+ Tmp-Integer-4 := "%{expr:&Tmp-Integer-4 + 1}"
+ }
+ fail
+ }
+ group {
+ update request {
+ Tmp-Integer-5 := "%{expr:&Tmp-Integer-5 + 1}"
+ }
+ fail
+ }
+ }
+ ok
+}
+
+if (&Tmp-Integer-2 != 1) {
+ update reply {
+ Filter-Id += 'Fail 3a'
+ }
+ return
+}
+
+if (&Tmp-Integer-3 != 1) {
+ update reply {
+ Filter-Id += 'Fail 3b'
+ }
+ return
+}
+
+if (&Tmp-Integer-4 != 1) {
+ update reply {
+ Filter-Id += 'Fail 3c'
+ }
+ return
+}
+
+if (&Tmp-Integer-5 != 1) {
+ update reply {
+ Filter-Id += 'Fail 3d'
+ }
+ return
+}
+
+update reply {
+ Filter-Id := "filter"
+} \ No newline at end of file
diff --git a/src/tests/keywords/regex-escape b/src/tests/keywords/regex-escape
new file mode 100644
index 0000000..4ab1e5b
--- /dev/null
+++ b/src/tests/keywords/regex-escape
@@ -0,0 +1,29 @@
+#
+# PRE: update if
+#
+
+#
+# Strings which are expanded in a regex have regex special
+# characters escaped. Because the input strings are unsafe.
+#
+update request {
+ Tmp-String-0 := "example.com"
+ Tmp-String-1 := "exampleXcom"
+}
+
+if ("exampleXcom" =~ /%{Tmp-String-0}/) {
+ update reply {
+ Filter-Id := "fail 1"
+ }
+}
+
+elsif (&Tmp-String-1 =~ /%{Tmp-String-0}/) {
+ update reply {
+ Filter-Id := "fail 2"
+ }
+}
+else {
+ update reply {
+ Filter-Id := "filter"
+ }
+} \ No newline at end of file
diff --git a/src/tests/keywords/regex-lhs b/src/tests/keywords/regex-lhs
new file mode 100644
index 0000000..91b0b20
--- /dev/null
+++ b/src/tests/keywords/regex-lhs
@@ -0,0 +1,27 @@
+#
+# PRE: update if regex-escape
+#
+
+#
+# Strings which are expanded in a regex have regex special
+# characters escaped. Because the input strings are unsafe.
+#
+update request {
+ Tmp-String-0 := "example.com"
+ Tmp-String-1 := "^foo$bar"
+}
+
+if (&Tmp-String-0 !~ /example\.com$/) {
+ update reply {
+ Filter-Id := "fail 1"
+ }
+}
+elsif (&Tmp-String-1 !~ /\^foo\$bar/) {
+ update reply {
+ Filter-Id := "fail 1"
+ }
+} else {
+ update reply {
+ Filter-Id := "filter"
+ }
+} \ No newline at end of file
diff --git a/src/tests/keywords/return b/src/tests/keywords/return
new file mode 100644
index 0000000..49779a5
--- /dev/null
+++ b/src/tests/keywords/return
@@ -0,0 +1,33 @@
+#
+# PRE: update if
+#
+update control {
+ Cleartext-Password := 'hello'
+}
+
+if (User-Name == "bob") {
+ update reply {
+ Filter-Id := "filter"
+ }
+
+ #
+ # We need this because the "return" below
+ # will prevent the "pap" module from being run
+ # in the "authorize" section.
+ #
+ update control {
+ Auth-Type := PAP
+ }
+
+ #
+ # Stop processing "authorize", and go to the next section.
+ #
+ return
+
+ #
+ # Shouldn't reach this
+ #
+ update reply {
+ Filter-Id := "fail"
+ }
+}
diff --git a/src/tests/keywords/return-group b/src/tests/keywords/return-group
new file mode 100644
index 0000000..92978e4
--- /dev/null
+++ b/src/tests/keywords/return-group
@@ -0,0 +1,22 @@
+# PRE: return
+#
+update {
+ control:Auth-Type = 'Accept'
+}
+
+group {
+ # Section should exit after this statement
+ ok {
+ ok = return
+ }
+
+ # This entry should never be reached
+ update {
+ reply:Reply-Message := 'fail'
+ }
+}
+
+# We should continue processing after the previous group.
+update {
+ reply:Reply-Message += 'pass'
+}
diff --git a/src/tests/keywords/return-group.attrs b/src/tests/keywords/return-group.attrs
new file mode 100644
index 0000000..ad30f4d
--- /dev/null
+++ b/src/tests/keywords/return-group.attrs
@@ -0,0 +1,4 @@
+User-Name = 'test'
+
+Response-Packet-Type == Access-Accept
+Reply-Message == 'pass'
diff --git a/src/tests/keywords/return-section b/src/tests/keywords/return-section
new file mode 100644
index 0000000..21ecea1
--- /dev/null
+++ b/src/tests/keywords/return-section
@@ -0,0 +1,35 @@
+#
+# PRE: update if
+#
+update control {
+ Cleartext-Password := 'hello'
+}
+
+if (User-Name == "bob") {
+ update reply {
+ Filter-Id := "filter"
+ }
+
+ #
+ # We need this because the "return" below
+ # will prevent the "pap" module from being run
+ # in the "authorize" section.
+ #
+ update control {
+ Auth-Type := PAP
+ }
+
+ #
+ # Stop processing "authorize", and go to the next section.
+ #
+ return { # ERROR
+ ok = 1
+ }
+
+ #
+ # Shouldn't reach this
+ #
+ update reply {
+ Filter-Id := "fail"
+ }
+}
diff --git a/src/tests/keywords/sha1 b/src/tests/keywords/sha1
new file mode 100644
index 0000000..0d577a9
--- /dev/null
+++ b/src/tests/keywords/sha1
@@ -0,0 +1,60 @@
+#
+# PRE: update if
+#
+update {
+ control:Cleartext-Password := 'hello'
+ request:Tmp-String-0 := "This is a string\n"
+ request:Tmp-Octets-0 := 0x000504030201
+ request:Tmp-String-1 := "what do ya want for nothing?"
+ request:Tmp-String-2 := "Jefe"
+}
+
+update reply {
+ Filter-Id := 'filter'
+}
+
+#
+# Put "This is a string" into a file and call "sha1sum" on it.
+# You should get this string.
+#
+if ("%{sha1:This is a string\n}" != 'cc7edf1ccc4bdf1e0ec8f72b95388b65218ecf0c') {
+ update reply {
+ Filter-Id += 'fail 1'
+ }
+}
+
+if ("%{sha1:&Tmp-String-0}" != 'cc7edf1ccc4bdf1e0ec8f72b95388b65218ecf0c') {
+ update reply {
+ Filter-Id += 'fail 2'
+ }
+}
+
+if ("%{sha1:&request:Tmp-String-0}" != 'cc7edf1ccc4bdf1e0ec8f72b95388b65218ecf0c') {
+ update reply {
+ Filter-Id += 'fail 3'
+ }
+}
+
+if ("%{sha1:%{Tmp-String-0}}" != 'cc7edf1ccc4bdf1e0ec8f72b95388b65218ecf0c') {
+ update reply {
+ Filter-Id += 'fail 4'
+ }
+}
+
+#
+# SHA1 should also be able to cope with references to octet attributes
+#
+if ("%{sha1:&request:Tmp-Octets-0}" != '365b244645fe7294dff062174996572319d5a82c') {
+ update reply {
+ Filter-Id += 'fail 5'
+ }
+}
+
+#
+# SHA1 HMAC with attribute references
+#
+if ("%{hmacsha1:&Tmp-String-1 &Tmp-String-2}" != 'effcdf6ae5eb2fa2d27416d5f184df9c259a7c79') {
+ update reply {
+ Filter-Id += 'fail 6'
+ }
+}
diff --git a/src/tests/keywords/sha2 b/src/tests/keywords/sha2
new file mode 100644
index 0000000..89c54f4
--- /dev/null
+++ b/src/tests/keywords/sha2
@@ -0,0 +1,81 @@
+#
+# PRE: update if
+#
+if ("$ENV{OPENSSL_LIBS}" != "") {
+
+update {
+ control:Cleartext-Password := 'hello'
+ request:Tmp-String-0 := "This is a string\n"
+ request:Tmp-Octets-0 := 0x000504030201
+}
+
+update reply {
+ Filter-Id := 'filter'
+}
+
+#
+# Put "This is a string" into a file and call "sha256sum" on it.
+# You should get this string.
+#
+if ("%{sha256:This is a string\n}" != 'b3716a1ab53042bb392034f29071e13b0c38aa19b4edd75d9a76022f91189124') {
+ update reply {
+ Filter-Id += 'fail 1'
+ }
+}
+
+if ("%{sha256:&Tmp-String-0}" != 'b3716a1ab53042bb392034f29071e13b0c38aa19b4edd75d9a76022f91189124') {
+ update reply {
+ Filter-Id += 'fail 2'
+ }
+}
+
+if ("%{sha256:&request:Tmp-String-0}" != 'b3716a1ab53042bb392034f29071e13b0c38aa19b4edd75d9a76022f91189124') {
+ update reply {
+ Filter-Id += 'fail 3'
+ }
+}
+
+if ("%{sha256:%{Tmp-String-0}}" != 'b3716a1ab53042bb392034f29071e13b0c38aa19b4edd75d9a76022f91189124') {
+ update reply {
+ Filter-Id += 'fail 4'
+ }
+}
+
+#
+# SHA256 should also be able to cope with references to octet attributes
+#
+if ("%{sha256:&request:Tmp-Octets-0}" != 'f307e202b881fded70e58017aa0c4d7b29c76ab25d02bf078301a5f6635187eb') {
+ update reply {
+ Filter-Id += 'fail 5'
+ }
+}
+
+#
+# SHA512 and SHA256 share common code paths, so the tests don't need to be
+# as exhaustive.
+#
+if ("%{sha512:This is a string\n}" != '56b57df5cce42d4e35c644649798ea23ec16f4f4626e78faf4d2d8f430ea349bcc28cd5532457c82f0aa66bf68988346039fe75b900a92ff94fd53993d45990f') {
+ update reply {
+ Filter-Id += 'fail 6'
+ }
+}
+
+if ("%{sha512:&Tmp-String-0}" != '56b57df5cce42d4e35c644649798ea23ec16f4f4626e78faf4d2d8f430ea349bcc28cd5532457c82f0aa66bf68988346039fe75b900a92ff94fd53993d45990f') {
+ update reply {
+ Filter-Id += 'fail 7'
+ }
+}
+
+if ("%{sha512:&request:Tmp-Octets-0}" != 'de80271eb5e03a1c24dd0cd823a22305a743ee3a54f1de5bf97adbf56984561154bfb6928b1da4ccc3f5dde9f4032ad461937b60b9ace4ad3898cf45c90596d7') {
+ update reply {
+ Filter-Id += 'fail 8'
+ }
+}
+
+}
+
+else { # no OPENSSL. Force the test to pass
+ update reply {
+ Filter-Id := 'filter'
+ }
+}
diff --git a/src/tests/keywords/smash b/src/tests/keywords/smash
new file mode 100644
index 0000000..fd19d3f
--- /dev/null
+++ b/src/tests/keywords/smash
@@ -0,0 +1,6 @@
+#
+# PRE: update
+#
+# This gives the game away.
+#
+update { control:Cleartext-Password := 'hello', reply:Filter-Id := "filter" }
diff --git a/src/tests/keywords/string b/src/tests/keywords/string
new file mode 100644
index 0000000..bcf0fcf
--- /dev/null
+++ b/src/tests/keywords/string
@@ -0,0 +1,19 @@
+#
+# PRE: cmp
+#
+update control {
+ Cleartext-Password := 'hello'
+}
+
+update request {
+ Tmp-String-0 := "this\000is\000a\000string"
+}
+
+#
+# %{string:...} is explicitly not binary safe
+#
+if ("%{string:Tmp-String-0}" == "this") {
+ update reply {
+ Filter-Id := "filter"
+ }
+}
diff --git a/src/tests/keywords/substring b/src/tests/keywords/substring
new file mode 100644
index 0000000..0ce4e9d
--- /dev/null
+++ b/src/tests/keywords/substring
@@ -0,0 +1,418 @@
+#
+# PRE: update
+#
+# Check substring xlat works correctly
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := 'filter'
+}
+
+update request {
+ Tmp-String-0 := 'foo bar'
+ Tmp-Integer-0 := 54786512
+ Tmp-IP-Address-0 := 192.168.56.34
+ Tmp-Cast-Ethernet := 01:23:45:67:89:ab
+}
+
+update request {
+ Tmp-String-1 := "%{substring:&Tmp-String-0 2 3}"
+ Tmp-String-2 := "%{substring:&Tmp-String-0 -3 2}"
+ Tmp-String-3 := "%{substring:&Tmp-String-0 1 -3}"
+ Tmp-String-4 := "%{substring:&Tmp-String-0 -4 -1}"
+ Tmp-String-5 := "%{substring:&Tmp-String-0 8 5}"
+ Tmp-String-6 := "%{substring:&Tmp-String-0 4 -10}"
+ Tmp-String-7 := "%{substring:&Tmp-String-0 0 7}"
+ Tmp-String-8 := "%{substring:&Tmp-String-0 3 0}"
+ Tmp-String-9 := "%{substring:&Tmp-String-0 4 8}"
+}
+
+if (Tmp-String-1 != 'o b') {
+ update reply {
+ Filter-Id += 'fail 0.1'
+ }
+}
+
+if (Tmp-String-2 != 'ba') {
+ update reply {
+ Filter-Id += 'fail 0.2'
+ }
+}
+
+if (Tmp-String-3 != 'oo ') {
+ update reply {
+ Filter-Id += 'fail 0.3'
+ }
+}
+
+if (Tmp-String-4 != ' ba') {
+ update reply {
+ Filter-Id += 'fail 0.4'
+ }
+}
+
+if (Tmp-String-5 != '') {
+ update reply {
+ Filter-Id += 'fail 0.5'
+ }
+}
+
+if (Tmp-String-6 != '') {
+ update reply {
+ Filter-Id += 'fail 0.6'
+ }
+}
+
+if (Tmp-String-7 != 'foo bar') {
+ update reply {
+ Filter-Id += 'fail 0.7'
+ }
+}
+
+if (Tmp-String-8 != '') {
+ update reply {
+ Filter-Id += 'fail 0.8'
+ }
+}
+
+if (Tmp-String-9 != 'bar') {
+ update reply {
+ Filter-Id += 'fail 0.9'
+ }
+}
+
+update request {
+ Tmp-String-0 := ' foo bar '
+}
+
+update request {
+ Tmp-String-1 := "%{substring: &Tmp-String-0 2 3}"
+ Tmp-String-2 := "%{substring:&Tmp-String-0 -3 2}"
+ Tmp-String-3 := "%{substring:&Tmp-String-0 1 -3}"
+ Tmp-String-4 := "%{substring:&Tmp-String-0 -4 -1}"
+ Tmp-String-5 := "%{substring:&Tmp-String-0 10 5}"
+ Tmp-String-6 := "%{substring:&Tmp-String-0 4 -10}"
+ Tmp-String-7 := "%{substring:&Tmp-String-0 0 9}"
+ Tmp-String-8 := "%{substring:&Tmp-String-0 3 0}"
+ Tmp-String-9 := "%{substring:&Tmp-String-0 4 10}"
+}
+
+if (Tmp-String-1 != 'oo ') {
+ update reply {
+ Filter-Id += 'fail 1.1'
+ }
+}
+
+if (Tmp-String-2 != 'ar') {
+ update reply {
+ Filter-Id += 'fail 1.2'
+ }
+}
+
+if (Tmp-String-3 != 'foo b') {
+ update reply {
+ Filter-Id += 'fail 1.3'
+ }
+}
+
+if (Tmp-String-4 != 'bar') {
+ update reply {
+ Filter-Id += 'fail 1.4'
+ }
+}
+
+if (Tmp-String-5 != '') {
+ update reply {
+ Filter-Id += 'fail 1.5'
+ }
+}
+
+if (Tmp-String-6 != '') {
+ update reply {
+ Filter-Id += 'fail 1.6'
+ }
+}
+
+if (Tmp-String-7 != ' foo bar ') {
+ update reply {
+ Filter-Id += 'fail 1.7'
+ }
+}
+
+if (Tmp-String-8 != '') {
+ update reply {
+ Filter-Id += 'fail 1.8'
+ }
+}
+
+if (Tmp-String-9 != ' bar ') {
+ update reply {
+ Filter-Id += 'fail 1.9'
+ }
+}
+
+update request {
+ Tmp-String-1 := "%{substring:&Tmp-Integer-0 2 3}"
+ Tmp-String-2 := "%{substring:&Tmp-Integer-0 -3 2}"
+ Tmp-String-3 := "%{substring:&Tmp-Integer-0 1 -3}"
+ Tmp-String-4 := "%{substring:&Tmp-Integer-0 -4 -1}"
+ Tmp-String-5 := "%{substring:&Tmp-Integer-0 8 5}"
+ Tmp-String-6 := "%{substring:&Tmp-Integer-0 4 -10}"
+ Tmp-String-7 := "%{substring:&Tmp-Integer-0 0 8}"
+ Tmp-String-8 := "%{substring:&Tmp-Integer-0 5 0}"
+ Tmp-String-9 := "%{substring:&Tmp-Integer-0 4 10}"
+}
+
+if (Tmp-String-1 != '786') {
+ update reply {
+ Filter-Id += 'fail 2.1'
+ }
+}
+
+if (Tmp-String-2 != '51') {
+ update reply {
+ Filter-Id += 'fail 2.2'
+ }
+}
+
+if (Tmp-String-3 != '4786') {
+ update reply {
+ Filter-Id += 'fail 2.3'
+ }
+}
+
+if (Tmp-String-4 != '651') {
+ update reply {
+ Filter-Id += 'fail 2.4'
+ }
+}
+
+if (Tmp-String-5 != '') {
+ update reply {
+ Filter-Id += 'fail 2.5'
+ }
+}
+
+if (Tmp-String-6 != '') {
+ update reply {
+ Filter-Id += 'fail 2.6'
+ }
+}
+
+if (Tmp-String-7 != '54786512') {
+ update reply {
+ Filter-Id += 'fail 2.7'
+ }
+}
+
+if (Tmp-String-8 != '') {
+ update reply {
+ Filter-Id += 'fail 2.8'
+ }
+}
+
+if (Tmp-String-9 != '6512') {
+ update reply {
+ Filter-Id += 'fail 2.9'
+ }
+}
+
+update request {
+ Tmp-String-1 := "%{substring:&Tmp-IP-Address-0 2 3}"
+ Tmp-String-2 := "%{substring:&Tmp-IP-Address-0 -3 2}"
+ Tmp-String-3 := "%{substring:&Tmp-IP-Address-0 1 -3}"
+ Tmp-String-4 := "%{substring:&Tmp-IP-Address-0 -4 -1}"
+ Tmp-String-5 := "%{substring:&Tmp-IP-Address-0 15 5}"
+ Tmp-String-6 := "%{substring:&Tmp-IP-Address-0 4 -20}"
+ Tmp-String-7 := "%{substring:&Tmp-IP-Address-0 0 13}"
+ Tmp-String-8 := "%{substring:&Tmp-IP-Address-0 6 0}"
+ Tmp-String-9 := "%{substring:&Tmp-IP-Address-0 8 12}"
+}
+
+if (Tmp-String-1 != '2.1') {
+ update reply {
+ Filter-Id += 'fail 3.1'
+ }
+}
+
+if (Tmp-String-2 != '.3') {
+ update reply {
+ Filter-Id += 'fail 3.2'
+ }
+}
+
+if (Tmp-String-3 != '92.168.56') {
+ update reply {
+ Filter-Id += 'fail 3.3'
+ }
+}
+
+if (Tmp-String-4 != '6.3') {
+ update reply {
+ Filter-Id += 'fail 3.4'
+ }
+}
+
+if (Tmp-String-5 != '') {
+ update reply {
+ Filter-Id += 'fail 3.5'
+ }
+}
+
+if (Tmp-String-6 != '') {
+ update reply {
+ Filter-Id += 'fail 3.6'
+ }
+}
+
+if (Tmp-String-7 != '192.168.56.34') {
+ update reply {
+ Filter-Id += 'fail 3.7'
+ }
+}
+
+if (Tmp-String-8 != '') {
+ update reply {
+ Filter-Id += 'fail 3.8'
+ }
+}
+
+if (Tmp-String-9 != '56.34') {
+ update reply {
+ Filter-Id += 'fail 3.9'
+ }
+}
+
+update request {
+ Tmp-String-1 := "%{substring:&Tmp-Cast-Ethernet 2 3}"
+ Tmp-String-2 := "%{substring:&Tmp-Cast-Ethernet -3 2}"
+ Tmp-String-3 := "%{substring:&Tmp-Cast-Ethernet 1 -3}"
+ Tmp-String-4 := "%{substring:&Tmp-Cast-Ethernet -4 -1}"
+ Tmp-String-5 := "%{substring:&Tmp-Cast-Ethernet 20 5}"
+ Tmp-String-6 := "%{substring:&Tmp-Cast-Ethernet 4 -20}"
+ Tmp-String-7 := "%{substring:&Tmp-Cast-Ethernet 0 17}"
+ Tmp-String-8 := "%{substring:&Tmp-Cast-Ethernet 8 0}"
+ Tmp-String-9 := "%{substring:&Tmp-Cast-Ethernet 9 12}"
+}
+
+if (Tmp-String-1 != ':23') {
+ update reply {
+ Filter-Id += 'fail 4.1'
+ }
+}
+
+if (Tmp-String-2 != ':a') {
+ update reply {
+ Filter-Id += 'fail 4.2'
+ }
+}
+
+if (Tmp-String-3 != '1:23:45:67:89') {
+ update reply {
+ Filter-Id += 'fail 4.3'
+ }
+}
+
+if (Tmp-String-4 != '9:a') {
+ update reply {
+ Filter-Id += 'fail 4.4'
+ }
+}
+
+if (Tmp-String-5 != '') {
+ update reply {
+ Filter-Id += 'fail 4.5'
+ }
+}
+
+if (Tmp-String-6 != '') {
+ update reply {
+ Filter-Id += 'fail 4.6'
+ }
+}
+
+if (Tmp-String-7 != '01:23:45:67:89:ab') {
+ update reply {
+ Filter-Id += 'fail 4.7'
+ }
+}
+
+if (Tmp-String-8 != '') {
+ update reply {
+ Filter-Id += 'fail 4.8'
+ }
+}
+
+if (Tmp-String-9 != '67:89:ab') {
+ update reply {
+ Filter-Id += 'fail 4.9'
+ }
+}
+
+update request {
+ Tmp-String-1 := "%{substring:foo bar 2 3}"
+ Tmp-String-2 := "%{substring:foo bar -3 2}"
+ Tmp-String-3 := "%{substring:foo bar 1 -3}"
+ Tmp-String-4 := "%{substring:foo bar -4 -1}"
+ Tmp-String-5 := "%{substring:foo bar 8 5}"
+ Tmp-String-6 := "%{substring:foo bar 4 -10}"
+ Tmp-String-7 := "%{substring: foo bar 0 9}"
+ Tmp-String-8 := "%{substring: foo bar 5 0}"
+ Tmp-String-9 := "%{substring: foo bar 4 10}"
+}
+
+debug_request
+
+if (Tmp-String-1 != 'o b') {
+ update reply {
+ Filter-Id += 'fail 5.1'
+ }
+}
+
+if (Tmp-String-2 != 'ba') {
+ update reply {
+ Filter-Id += 'fail 5.2'
+ }
+}
+
+if (Tmp-String-3 != 'oo ') {
+ update reply {
+ Filter-Id += 'fail 5.3'
+ }
+}
+
+if (Tmp-String-4 != ' ba') {
+ update reply {
+ Filter-Id += 'fail 5.4'
+ }
+}
+
+if (Tmp-String-5 != '') {
+ update reply {
+ Filter-Id += 'fail 5.5'
+ }
+}
+
+if (Tmp-String-6 != '') {
+ update reply {
+ Filter-Id += 'fail 5.6'
+ }
+}
+
+if (Tmp-String-7 != ' foo bar ') {
+ update reply {
+ Filter-Id += 'fail 5.7'
+ }
+}
+
+if (Tmp-String-8 != '') {
+ update reply {
+ Filter-Id += 'fail 5.8'
+ }
+}
+
+if (Tmp-String-9 != ' bar ') {
+ update reply {
+ Filter-Id += 'fail 5.9'
+ }
+}
diff --git a/src/tests/keywords/switch b/src/tests/keywords/switch
new file mode 100644
index 0000000..f64aeaf
--- /dev/null
+++ b/src/tests/keywords/switch
@@ -0,0 +1,19 @@
+switch &User-Name {
+ case "bob" {
+ update reply {
+ Filter-Id := "filter"
+ }
+ }
+
+ case "doug" {
+ update reply {
+ Filter-Id := "doug"
+ }
+ }
+
+ case {
+ update reply {
+ Filter-Id := "default"
+ }
+ }
+}
diff --git a/src/tests/keywords/switch-attr-cast b/src/tests/keywords/switch-attr-cast
new file mode 100644
index 0000000..e271a18
--- /dev/null
+++ b/src/tests/keywords/switch-attr-cast
@@ -0,0 +1,34 @@
+#
+# PRE: switch switch-attr-cmp
+#
+
+update request {
+ Service-Type := Login-User
+ Filter-Id := "Login-User"
+}
+
+switch &Service-Type {
+ case "%{expr: 1 + 2}" {
+ update reply {
+ Filter-Id := "3"
+ }
+ }
+
+ #
+ # The Filter-Id will get printed to a string,
+ # have the string parsed as a Service-Type attr,
+ # and then that compared to the input Service-Type
+ #
+ case &Filter-Id {
+ update reply {
+ Filter-Id := "filter"
+ }
+ }
+
+ case {
+ update reply {
+ Filter-Id := "default"
+ }
+ }
+
+}
diff --git a/src/tests/keywords/switch-attr-cmp b/src/tests/keywords/switch-attr-cmp
new file mode 100644
index 0000000..e28ded8
--- /dev/null
+++ b/src/tests/keywords/switch-attr-cmp
@@ -0,0 +1,36 @@
+#
+# PRE: switch
+#
+update request {
+ Tmp-String-0 := &User-Name
+}
+
+#
+# A switch statement where we compare two attributes
+#
+switch &User-Name {
+ case &Tmp-String-0 {
+ update reply {
+ Filter-Id := "filter"
+ }
+ }
+
+ case "bob" {
+ update reply {
+ Filter-Id := "failed 0"
+ }
+ }
+
+ case "doug" {
+ update reply {
+ Filter-Id := "failed 1"
+ }
+ }
+
+ case {
+ update reply {
+ Filter-Id := "failed 2"
+ }
+ }
+
+}
diff --git a/src/tests/keywords/switch-default b/src/tests/keywords/switch-default
new file mode 100644
index 0000000..6115ed9
--- /dev/null
+++ b/src/tests/keywords/switch-default
@@ -0,0 +1,22 @@
+# PRE: switch
+#
+switch User-Name {
+ case "harry" {
+ update reply {
+ Filter-Id := "harry"
+ }
+ }
+
+ case "doug" {
+ update reply {
+ Filter-Id := "doug"
+ }
+ }
+
+ case {
+ update reply {
+ Filter-Id := "filter"
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/tests/keywords/switch-escape b/src/tests/keywords/switch-escape
new file mode 100644
index 0000000..50d9fdf
--- /dev/null
+++ b/src/tests/keywords/switch-escape
@@ -0,0 +1,43 @@
+update request {
+ &Tmp-String-0 := 'foo'
+}
+
+switch "%{tolower:%{request:Tmp-String-0}}" {
+ case 'foo' {
+ update reply {
+ Filter-Id := "filter"
+ }
+ }
+
+ case '' {
+ update reply {
+ Filter-Id += "fail-empty-1"
+ }
+ }
+
+ case {
+ update reply {
+ Filter-Id += "fail-default-1"
+ }
+ }
+}
+
+switch "%{request:Tmp-String-0}" {
+ case 'foo' {
+ update reply {
+ Filter-Id := "filter"
+ }
+ }
+
+ case '' {
+ update reply {
+ Filter-Id += "fail-empty-2"
+ }
+ }
+
+ case {
+ update reply {
+ Filter-Id += "fail-default-2"
+ }
+ }
+}
diff --git a/src/tests/keywords/switch-nodefault b/src/tests/keywords/switch-nodefault
new file mode 100644
index 0000000..5fb9469
--- /dev/null
+++ b/src/tests/keywords/switch-nodefault
@@ -0,0 +1,22 @@
+#
+# User-Name is "bob", and a switch statement
+# with no "default" should not crash the server.
+#
+switch &User-Name {
+ case "doug" {
+ update reply {
+ Filter-Id := "doug"
+ }
+ }
+}
+
+if (&reply:Filter-Id) {
+ update reply {
+ Filter-Id := "fail 1"
+ }
+}
+else {
+ update reply {
+ Filter-Id := "filter"
+ }
+} \ No newline at end of file
diff --git a/src/tests/keywords/switch-value-error b/src/tests/keywords/switch-value-error
new file mode 100644
index 0000000..18db9e1
--- /dev/null
+++ b/src/tests/keywords/switch-value-error
@@ -0,0 +1,29 @@
+#
+# PRE: switch
+#
+switch &Service-Type {
+ case "%{expr: 1 + 2}" {
+ update reply {
+ Filter-Id := "3"
+ }
+ }
+
+ case Login-User {
+ update reply {
+ Filter-Id := "Login-User"
+ }
+ }
+
+ case No-Such-Value { # ERROR
+ update reply {
+ Filter-Id := "FAILED"
+ }
+ }
+
+ case {
+ update reply {
+ Filter-Id := "default"
+ }
+ }
+
+}
diff --git a/src/tests/keywords/switch-value-error2 b/src/tests/keywords/switch-value-error2
new file mode 100644
index 0000000..a291e7b
--- /dev/null
+++ b/src/tests/keywords/switch-value-error2
@@ -0,0 +1,27 @@
+#
+# PRE: switch-value-error
+#
+# The same as "switch-value-error", but the attribute
+# is hidden inside of an xlat expansion. We now turn
+# simple attribute xlats into templates.
+#
+switch "%{Service-Type}" { # ERROR
+ case "%{expr: 1 + 2}" {
+ update reply {
+ Filter-Id := "3"
+ }
+ }
+
+ case Login-User {
+ update reply {
+ Filter-Id := "Login-User"
+ }
+ }
+
+ case {
+ update reply {
+ Filter-Id := "default"
+ }
+ }
+
+}
diff --git a/src/tests/keywords/switch-virtual b/src/tests/keywords/switch-virtual
new file mode 100644
index 0000000..659604d
--- /dev/null
+++ b/src/tests/keywords/switch-virtual
@@ -0,0 +1,23 @@
+#
+# PRE: update switch
+#
+update control {
+ Cleartext-Password := 'hello'
+}
+
+#
+# Virtual attribute references get mashed to xlats
+#
+switch &Packet-Type {
+ case Access-Request {
+ update reply {
+ Filter-Id := "filter"
+ }
+ }
+
+ case {
+ update reply {
+ Filter-Id := "fail"
+ }
+ }
+} \ No newline at end of file
diff --git a/src/tests/keywords/switch-xlat-error b/src/tests/keywords/switch-xlat-error
new file mode 100644
index 0000000..d03f6d3
--- /dev/null
+++ b/src/tests/keywords/switch-xlat-error
@@ -0,0 +1,17 @@
+#
+# PRE: switch
+#
+switch &User-Name {
+ case "%{no-such-module:bob}" { # ERROR
+ update reply {
+ Filter-Id := "fail"
+ }
+ }
+
+ case {
+ update reply {
+ Filter-Id := "default"
+ }
+ }
+
+}
diff --git a/src/tests/keywords/truncation b/src/tests/keywords/truncation
new file mode 100644
index 0000000..8217e36
--- /dev/null
+++ b/src/tests/keywords/truncation
@@ -0,0 +1,109 @@
+# cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-f0-9' | head -c <n>
+
+update reply {
+ Filter-Id := "filter"
+}
+
+# 8192 - 0x (2) - '' (2) there are unlikely to be any static buffers this big outside of the conffile parser
+update request {
+ Tmp-Octets-0 := '0x\
+d8abccb7834711af1de1812be2579febe946f5d7beef6fa5c7074c0cb917e9b91e23e14b016f27610097c16c0e0fad88176e077b24198c770746159\
+05b8810d1c8b774d98889fa5c6027cde5e9c56dd4f7c48298c7713aeca5ba5dcfd506032ad05b1396f50e825b633d5a6af0dce6181b640287e03a65\
+8734df46a86341556f28455b3f377313a5a2ac8c8267b8a5de559b95f9b493a68b9e0278485f9e3914d702b2b7b90ee85ff393461f197386d09b836\
+5a8ae85ea025aea095f5d834c2ddf21e9a16b945da03c97f8a52687f19c605af9a245878b4bc5ed15b7937371ad629081159cf7de613d02c43f5cab\
+3529abbe61a6da1e55c685c2310c8eccb452f9758bf63fddfa58cdffb5cbf912f90e628310978dd1b3b7c2d3a08dd7ca6ca51081a114b013cc9d4c1\
+9f5ce0b1af81166c9402c105019a0fa9d15996c4f3d5fa35226bf88166598ff4f619322866df276d8b92fad06d120470c29217d29abb96ff861751e\
+acebbe839e28287c8cd485769d9a09e5bab524bde5776e15037d19503a2d032658e21aec7090032707ba0d662fcfa99e5e5ec183f8c63c6022bf281\
+22cf090fb80263f70c523aa0e00dd9f610c5b669754092253edd6f83b57bdd7a251514cdd99ab86cfa98b11469d1d1118aba0a149db6f0ccceb2f17\
+06bf33f657c7b8547dc284dfc10fdc1678c184901aa17f756d18df43409e1c4ead239cb78c46a9c412ccb097bba2244bb680bb249c15004d796731f\
+3890c1f94d8e0eda81487da9a65f817b5dbac4e639fce063f61a7db87d8b9d4b855120f56c3a0d57cb61ced57092506da881337280a18b2729717f5\
+133978362ca7941f9dbe9462ae5a46886c4d3c51b633278d6ad58a331c95e481cbbae866f046cb1a59ae2545fa32b12d4772244fd24bb91f7611d0c\
+beffbc5dee309c671fb5a171eacf45b20641e1cb4db306965eb43e98cb900ee805065c7adf4db3cdb97bbf8116dbb29f5ac07c7041a5f02d1a4fd1f\
+a7be6da388146d6b80ae8e152ac873cbf1f88c98cdd2e1de02df76461a1c50139f558f43ac1a91824fc48ea35976fab29d412f1ad990e528893ad4c\
+b6045b63d2a7af0657f1c348302e674ef0b39bd689b3b33dd3664c3e2386d0ea3241e70d2a2990b6243638d68033cc6040758e11f82ef783db2ae94\
+d2a8dbccd25fe2dce554837a2ca424199e4bd5442215623a77bffd603b575c847f88763fff1d6d0ff314851b1af06f99fb44d0a3115ad4389462452\
+c2331be24d486f6b5f2224cdfe44909e0a3bec47cdf6fe49fa60518e37950f986677e7f45617adcb20bc02990d3fe60de54ad2d1950bdd423e752c4\
+60f4eeaf2eeece07f3dea26db63cc0bc222a6f3228131cdb3360b9ec7e8f861c3e0bc822f9da00fef17f44ab4de7c166e5cb07d5c671f0364d4bccb\
+dc7f24868e066998bc8b3ab9052171b967162d536082b0a11c24f263c0e5f656b8349f970124af712cf098e8b08910aefcf7d42dd8a74e84ae6f9c4\
+006aa09483715b514833291c9d2e2bcdda567a16a493ac4fa17d5dff7a04b9f7f967febe7a8bdf911b1b668716be73fbf6f2416609224494dca1a18\
+1c1c7bb298671d39bdc1f664c833c9d2d4c08a65866db885929611fcd0c33139bc2959a865cbb686694896b539daa1db265ba78298cb3a09a331ef0\
+1423a7fd04b38ccdfbf1182a493c7a53c720a2dcf486249ff2b674341e77c2c0d5eb78efc07f295d562a77667457df58ea419c488680b9ce51aac01\
+4cd78984f70f29aec2a9b77fd45d38e0b07257b71f2146ccb11cd07681cbfff787b39b7ce9c42ce60513eccec2b490c68378e86bd441735b30410be\
+8710e71b2a326bd8b929abde80f25a0adb312f8ed5bb748866ddd8fb5fa81855d194afbdb1d511be453e21ab3f6481e47fa86b0697342706f85b8e8\
+84c6bf427390ef4c62532797de25004bb9d293e196ed29950e14682c68a8b6ab7c03256bc493b61eb3e44dc16726fcedb6c6776e2e9e0b3450fe6c6\
+24eef42e4806ef7c8abc367b7f1c83b7955ff6579d03362b428ca7711d228cf88402b60fd162ef4819216b9db66b4a1b0a5e0651a7901290eafa3d5\
+c698e40f73b3d66345d9e328d50ef2c767d370879890980d0d989de122ab22d7ef9979832b022e676eba4630d49434ec70025b44cb7146f9688d102\
+ceb55e6ca8ee9b9a66327da80f46367f452de7e2a84bf702b155539c7fa488d86c83faf3f150fda6d75eb476e2c31d73cea88148d9f02d178b0c098\
+5b4061830231ca405fbf41547ed9c036f53fc706b0fd1bb084e84a9e6dec81b4b4eea46c5ea26e99993ee930ccfeff4a185ffbce8a937243fa4734b\
+c24abbf465b0cd1f87686d44762140b5dfe57ae904cd8ed121f055d8a1bbf9c8a977133215f135e563b935451a7f797e72ef8f96390bc2477ad7aac\
+d2679557bac3b9a030926ff0801cc692dd641cf4f8a7bd9bdee21cdab3f7fbcf7d1d0de51101c5d816b19db49109958a8e75662ac700653112cd86c\
+10cbf45d3ea16f861de2d46714dfba8a34c1ad7ed4d5484802270187c3c7f70f7a51858479d8504e3b6267194404011b416329401ec55940f890f33\
+97d4bb3eab46990a4a03cdae9951ccd0bd42584ec88262f7d80193f09843c1fca7627b9cf708c646e84883b19fe3a8987912d04f1c0d1c779b740a7\
+fea0270374416a3987dd91d706e22e8c9698ef870d7f80d1c3042914a34736c185e542976d0145531c8eb20c3a6c5a5f747e97228ff55425fd93222\
+2d802ca36ac61555f69be4d645532579ddadb24a821f0bab1fb1f3b724ed4759b5303fe0015626ad00879e1a363d5e8b3aa0b7f3e572df3dc5f7352\
+7eb30b78e70dbe6ca81052315925d3892485168dd6f900289d1a0f9285e52988480bfda6a2c8b28cbd175b4dec735240c8f79afd5c93ed058da5551\
+8f20fb09edf4b4112c352604876f4083b68e534874d0b9ce2f977be2bcea0f9c123e119a70d269bc26ece608c39c98dbb069118148b01e403c16076\
+330b5d7929a7a7d724a7030d514ff0e38bee78e0044ffa508fc9793d7fc59075fd825e68d34e58568bffd8b20791862a8d0686763e49f1e3ed8a728\
+e33bb32a57c6374d153f93ba3cd283ce0af9a0a3b0ced4f83e3d604ff66f6584a35b51938ff4a0282c51f6f8561bd77b842ed0fd39fc43be825652f\
+f2c021f2b4d2b8c8ed2cf8eeb16f86863e59b374eda5a17e1bfc0b5a54169dd3e62b84a81bc1afd5cc3a1b193484428bcc3afb5344da990697f6787\
+5f72bc1ab04f52f75bc671b8d1239fc811d44032822abc95f8ea3465cdabdce5f83d5895d50fcbd97ad88e6b193172a4cf5f51814d9348498b1736e\
+de25f8b5afc8556d3b9573cb2ae8f51e8f0bca2f048d883592c9ab317e87864d48ca901e2aa2774360c77ba3f0a76c21f10155e32e91a2239d00e6b\
+0279bbc8977fadbea04b7b67575437fd5f6611a8889ecbde5fe45fcab8db9b62e831aadcd0ade54a94d30b6963d727979c116ef9f4a9a16a0e7a76e\
+d4f1177ad0ac7022beb12581629a7fd36c8ca5eeaa40d1a0d88cb0f701b288baae6c00a74c0b0dc3ffa6e9710bf3264684c1b9ff220710b2348cc93\
+d658d9eddae36232a0d0e06439d5dafdc7271eafbb3d16a17e36de7470c7a5ed034d735e2c216073ba6b4be7b319718d5afbd634684f5468d26afd0\
+63edfbc7fe6c8e98914eff397fcfdf44a10b84ba90b30d1a5da6823aab1792d666b32bdd0516ce3590c5dc2b49a6eabdb28cdc72dbc24e60ba0883f\
+953f0a99d29c1089b06063588b8bd965ba42e27f618d2b046fb3db93161981bbcd67fc965b0084ac25993445732b52d18cd2868e6b545214a2d501a\
+0002d2043d9a9790905dde296f16517e295dd295a97991c205d50dba331e86f06b0195e52fdd21f8e65e6b00fe7165e1a951805e5947aaee125f180\
+a2127375d8898aba1af3a3db34470e78bed99917a6bb87725788c1e2c8f35d6022d51bafdc3e69673cbdd80dc9cfde7dc1bd06e3a6437c720cb7b6c\
+2b3bc808111593d5f2a88c6e5b499c51155f4ff0267606abcbfa03dc5e4c108e7f3d33989952df2f4f1476df8fe53eaa39d2338a87b6ccc91587a67\
+bc0da2f5667248ce969b91f1521c5bfda97c8f062b62d768a9d302f4a4bd0e67646c6666a9fea93749a65d06fdcd0168fe81edac542fc287503a592\
+61591a686b418ac2602e2464f6679ef7ecf5387af45e5fdd72224695e454d6676687b03a5858afe71dda7180a59ef2e1d8c81c4a9c8375e62c43711\
+cbd0e7d437a5913bc4d7e8fbb71f68e4805ad41bd624c06e84761dbda3014c1576a866c073ca32b37007884730ac9f831b95db8ce10f0db45e009d0\
+6e1ad8c96c340c98debee0dd0ca9e6694fe7a69306c15e6a7054cfd612e070f538c6ff0c1a34b257df10087b650da852a5b663a5795a335193dfe64\
+1950ea614ae05b0c202cedabd1ebe6da561e167fe81dd9fc190740a20db38f5356e61f8bed7b3db783a6b53e48f408a8b08f9f79738438a3ed0c4bc\
+f444d5922c76ff03c943a3c72c513047aa75caf51381b5c306fdbaedcd4001a2df456c3afef7559241f553dffac2dfd9ac94570e50c571329203bf0\
+38cbdac9bc76f896635356c402f2f5c105eb4e334f7792716e8e8883352870e949623f29bb8fac428bc858ec3297166afe358e1947b0c2337309315\
+062a040129f978b036657e9323550765cc4c14aaa4c24572eb17a0aa940ba336fd8fd385fb46b5b0c067c59ee42922d2702feb7b43d2ffef11cbdf0\
+1f11c92ac18197a387f0633fd19509b19c0b8a2f63983f6c0a6286593c84d5866ae8d91139b141e8bdf3b7ae7df7d92754186e545f4b600fbe69494\
+3ae4bcef324b9a3f47ba5c835b54a010ca42f3b9cc5368278c148c9b02ea8c4c9f244fd49fa69c9a5feac5376e07f52e64be9c873afca3ebf776fe9\
+fc813bd2e58c6d0dfd951570dbff4b5e73ce547cd100ab320b6944e887d611b3425bfcdf3bfa852ff5804119a1f69e33d624eda6f9e5d1fb2811f9f\
+9f5b3224d009e09621a8fcb524f89c6fcf38f7c933aa027de2b48aa9ddbfa55f56b383f06b8feef09e4709bc4fdcbab659bab4a277a2fbebb98970e\
+44bd68a61b264c695d8ae27a676e123b5fb60c7c4cbc88f24b6554dcfebfde607500f50ec85f589eaa4213ccaa237598660a66009bc56d55455487a\
+28fa24d62e50df57feab0d1c8ec77b1085002d922c52d4a3092f8693c3b9ba9725a4d225637350812b534e93209b414b4516642eca67490199a9217\
+614322972fa2fcc5c7fc2695e9b5762d442e2c7dd8403d14228aa90df58e2ffe1a3c9922f6c5d62649664b63c017fbf3859723e7e97ea6710097683\
+6408a97de3ad7d902c7be0296fb3d476de2460602f65eb7edacc2e4f49b1652fe8948f7afb1cc9f83f3283e6013304160cd2ac7c311c492133252a6\
+5ccf45c5af9c05a47905ac8bfe55f9b912c3fca856abe7863f87392b6f6b0069e3d8412a056b1034aaac506bf2ca51760aa180c5f43a751beb06e88\
+fc6afab4c5dd4aae4a2e6f0293c15278d557c2925acba90c73eeea09ebb95f0d469aa77ae983a0e69dacd55bd8a7e78a41df5227de35af05127fa3b\
+a02f4a3ab98d75992d68a15d393387fe9ef01041569570ad6fe884764e55567311bcacfcffae76554dcfebfde607500f50ec85f589eaade607500f5\
+3ae4bcef324b9a3f47ba5c835b54a010ca42f3b9cc5368278c148c9b02ea8c4c9f244fd49fab'
+}
+
+# Actual length of octet string is 4084 bytes
+update request {
+ Tmp-Integer-0 := "%{length:Tmp-Octets-0}"
+}
+
+if (Tmp-Integer-0 != 4084) {
+ update reply {
+ Filter-Id += 'Fail 0'
+ }
+}
+
+# Octets are expanded to 8168 hexits
+if ("%{Tmp-Octets-0}" !~ /^0x([0-9a-f]+)$/) {
+ update reply {
+ Filter-Id += 'Fail 1'
+ }
+}
+
+update request {
+ Tmp-String-0 := "%{1}"
+}
+
+if ("%{length:Tmp-String-0}" != 8168) {
+ update reply {
+ Filter-Id += 'Fail 2'
+ }
+}
+
+# We can't do any more until all the xlat code uses dynamically allocated buffers
diff --git a/src/tests/keywords/unknown b/src/tests/keywords/unknown
new file mode 100644
index 0000000..eb96591
--- /dev/null
+++ b/src/tests/keywords/unknown
@@ -0,0 +1,84 @@
+#
+# PRE: wimax
+#
+update control {
+ Cleartext-Password := 'hello'
+}
+
+update reply {
+ Filter-Id := "filter"
+}
+
+update request {
+ FreeRADIUS-Proxied-To := 127.0.0.2
+}
+
+#
+# Check that a known but malformed attr is malformed
+#
+update request {
+ Attr-26.24757.84.9.5.7 = 0xab
+}
+
+if (&Attr-26.24757.84.9.5.7 != 0xab) {
+ update reply {
+ Filter-Id += 'Fail 1'
+ }
+}
+
+#
+# Check that an unknown attr is OK
+#
+update request {
+ Attr-26.24757.84.9.5.15 = 0xabcdef
+}
+
+if (&Attr-26.24757.84.9.5.15 != 0xabcdef) {
+ update reply {
+ Filter-Id += 'Fail 2'
+ }
+}
+
+#
+# Check that unknown attributes which are defined
+# get automatically resolved to the real attribute.
+#
+if (&Vendor-11344-Attr-1 == 127.0.0.1) {
+ update reply {
+ Filter-Id += 'Fail 3'
+ }
+}
+
+if (&Vendor-11344-Attr-1 != 127.0.0.2) {
+ update reply {
+ Filter-Id += 'Fail 4'
+ }
+}
+
+update request {
+ &Vendor-11344-Attr-1 := 127.0.0.1
+}
+
+if (&FreeRADIUS-Proxied-To == 127.0.0.2) {
+ update reply {
+ Filter-Id += 'Fail 5'
+ }
+}
+
+if (&FreeRADIUS-Proxied-To != 127.0.0.1) {
+ update reply {
+ Filter-Id += 'Fail 6'
+ }
+}
+
+if (&Vendor-11344-Attr-1 == 127.0.0.2) {
+ update reply {
+ Filter-Id += 'Fail 7'
+ }
+}
+
+if (&Vendor-11344-Attr-1 != 127.0.0.1) {
+ update reply {
+ Filter-Id += 'Fail 8'
+ }
+}
diff --git a/src/tests/keywords/unknown-if b/src/tests/keywords/unknown-if
new file mode 100644
index 0000000..3dccbff
--- /dev/null
+++ b/src/tests/keywords/unknown-if
@@ -0,0 +1,8 @@
+#
+# PRE: unknown-update
+#
+if (&This-Does-Not-Exist == 1) { # ERROR
+ update reply {
+ Filter-Id := "fail"
+ }
+}
diff --git a/src/tests/keywords/unknown-name b/src/tests/keywords/unknown-name
new file mode 100644
index 0000000..5a99ac9
--- /dev/null
+++ b/src/tests/keywords/unknown-name
@@ -0,0 +1,15 @@
+#
+# PRE: update unknown
+#
+update control {
+ Cleartext-Password := 'hello'
+}
+
+#
+# This delayed binding should result in a parse error
+#
+if (&User-Name == &Foo-Bar-Baz) { # ERROR
+ update reply {
+ Filter-Id += "fail-2"
+ }
+}
diff --git a/src/tests/keywords/unknown-update b/src/tests/keywords/unknown-update
new file mode 100644
index 0000000..1403879
--- /dev/null
+++ b/src/tests/keywords/unknown-update
@@ -0,0 +1,6 @@
+#
+# PRE: update unknown
+#
+update control {
+ This-Does-Not-Exist = 1 # ERROR
+}
diff --git a/src/tests/keywords/update b/src/tests/keywords/update
new file mode 100644
index 0000000..97a2557
--- /dev/null
+++ b/src/tests/keywords/update
@@ -0,0 +1,7 @@
+update control {
+ Cleartext-Password := 'hello'
+}
+
+update reply {
+ Filter-Id := "filter"
+}
diff --git a/src/tests/keywords/update-add-ref-index b/src/tests/keywords/update-add-ref-index
new file mode 100644
index 0000000..7f5e74a
--- /dev/null
+++ b/src/tests/keywords/update-add-ref-index
@@ -0,0 +1,118 @@
+#
+# PRE: update array
+#
+
+update request {
+ reply:Filter-Id := "filter"
+ Class := 0x01020304
+ Class += 0x05060708
+ Class += 0x090a0b0c
+}
+
+#
+# Copy all the class attributes to Proxy-State
+#
+update request {
+ Proxy-State += &Class[*]
+}
+
+if (&Proxy-State[0] != 0x01020304) {
+ update reply {
+ Filter-Id := "fail 0a"
+ }
+}
+
+# Must be the same as above
+if (&Proxy-State[1] != 0x05060708) {
+ update reply {
+ Filter-Id += "fail 0b"
+ }
+}
+
+if (&Proxy-State[2] != 0x090a0b0c) {
+ update reply {
+ Filter-Id += "fail 0c"
+ }
+}
+
+# must not exist
+if (&Proxy-State[3]) {
+ update reply {
+ Filter-Id += "fail 1"
+ }
+}
+
+# Remove all the Proxy-State attributes
+update request {
+ Proxy-State !* ANY
+}
+
+#
+# Copy the first instance (implicitly) of class
+#
+update request {
+ Proxy-State += &Class
+}
+
+if (&Proxy-State[0] != 0x01020304) {
+ update reply {
+ Filter-Id := "fail 2a"
+ }
+}
+
+# Must be the same as above
+if (&Proxy-State[1]) {
+ update reply {
+ Filter-Id += "fail 2b"
+ }
+}
+
+# Remove all the Proxy-State attributes
+update request {
+ Proxy-State !* ANY
+}
+
+#
+# Copy the first instance (explicitly) of class
+#
+update request {
+ Proxy-State += &Class[0]
+}
+
+if (&Proxy-State[0] != 0x01020304) {
+ update reply {
+ Filter-Id := "fail 3a"
+ }
+}
+
+# Must be the same as above
+if (&Proxy-State[1]) {
+ update reply {
+ Filter-Id += "fail 3b"
+ }
+}
+
+# Remove all the Proxy-State attributes
+update request {
+ Proxy-State !* ANY
+}
+
+#
+# Copy the second instance of class
+#
+update request {
+ Proxy-State += &Class[1]
+}
+
+if (&Proxy-State[0] != 0x05060708) {
+ update reply {
+ Filter-Id := "fail 4a"
+ }
+}
+
+# Must be the same as above
+if (&Proxy-State[1]) {
+ update reply {
+ Filter-Id += "fail 4b"
+ }
+}
diff --git a/src/tests/keywords/update-add-ref-tag b/src/tests/keywords/update-add-ref-tag
new file mode 100644
index 0000000..39800b8
--- /dev/null
+++ b/src/tests/keywords/update-add-ref-tag
@@ -0,0 +1,118 @@
+#
+# PRE: update array
+#
+
+update request {
+ reply:Filter-Id := "filter"
+ request:Tunnel-Server-Endpoint += '192.0.1.1'
+ request:Tunnel-Server-Endpoint += '192.0.1.2'
+ request:Tunnel-Server-Endpoint:1 += '192.0.1.1'
+ request:Tunnel-Server-Endpoint:2 += '192.0.2.1'
+ request:Tunnel-Server-Endpoint:2 += '192.0.2.2'
+ request:Tunnel-Server-Endpoint:3 += '192.0.3.1'
+}
+
+#
+# Copy all Tunnel-Server-Endpoint attributes
+#
+update request {
+ control:Tunnel-Server-Endpoint += &Tunnel-Server-Endpoint[*]
+}
+
+if ((&control:Tunnel-Server-Endpoint[0] != '192.0.1.1') || \
+ (&control:Tunnel-Server-Endpoint[1] != '192.0.1.2') || \
+ (&control:Tunnel-Server-Endpoint[2] != '192.0.1.1') || \
+ (&control:Tunnel-Server-Endpoint[3] != '192.0.2.1') || \
+ (&control:Tunnel-Server-Endpoint[4] != '192.0.2.2') || \
+ (&control:Tunnel-Server-Endpoint[5] != '192.0.3.1') || \
+ &control:Tunnel-Server-Endpoint[6]) {
+ update reply {
+ Filter-Id := "fail 0"
+ }
+}
+
+#
+# Clear out control attributes...
+#
+update control {
+ Tunnel-Server-Endpoint !* ANY
+}
+
+#
+# Copy all Tunnel-Server-Endpoint attributes with tag 2
+#
+update request {
+ control:Tunnel-Server-Endpoint += &Tunnel-Server-Endpoint:2[*]
+}
+
+if ((&control:Tunnel-Server-Endpoint[0] != '192.0.2.1') || \
+ (&control:Tunnel-Server-Endpoint[1] != '192.0.2.2') || \
+ &control:Tunnel-Server-Endpoint[2]) {
+ update reply {
+ Filter-Id := "fail 1"
+ }
+}
+
+#
+# Clear out control attributes...
+#
+update control {
+ Tunnel-Server-Endpoint !* ANY
+}
+
+#
+# Copy all Tunnel-Server-Endpoint attributes with no tag
+#
+update request {
+ control:Tunnel-Server-Endpoint += &Tunnel-Server-Endpoint:0[*]
+}
+
+if ((&control:Tunnel-Server-Endpoint[0] != '192.0.1.1') || \
+ (&control:Tunnel-Server-Endpoint[1] != '192.0.1.2') || \
+ &control:Tunnel-Server-Endpoint[2]) {
+ update reply {
+ Filter-Id := "fail 2"
+ }
+}
+
+#
+# Clear out control attributes...
+#
+update control {
+ Tunnel-Server-Endpoint !* ANY
+}
+
+#
+# Copy the first attribute with tag 2 (implicit)
+#
+update request {
+ control:Tunnel-Server-Endpoint += &Tunnel-Server-Endpoint:2
+}
+
+if ((&control:Tunnel-Server-Endpoint[0] != '192.0.2.1') || \
+ &control:Tunnel-Server-Endpoint[1]) {
+ update reply {
+ Filter-Id := "fail 3"
+ }
+}
+
+#
+# Clear out control attributes...
+#
+update control {
+ Tunnel-Server-Endpoint !* ANY
+}
+
+#
+# Copy the first attribute with tag 2 (explicit)
+#
+update request {
+ control:Tunnel-Server-Endpoint += &Tunnel-Server-Endpoint:2[0]
+}
+
+if ((&control:Tunnel-Server-Endpoint[0] != '192.0.2.1') || \
+ &control:Tunnel-Server-Endpoint[1]) {
+ update reply {
+ Filter-Id := "fail 4"
+ }
+}
diff --git a/src/tests/keywords/update-all b/src/tests/keywords/update-all
new file mode 100644
index 0000000..549a122
--- /dev/null
+++ b/src/tests/keywords/update-all
@@ -0,0 +1,9 @@
+#
+# PRE: update
+#
+# A more generic "update" mechanism
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := "filter"
+}
diff --git a/src/tests/keywords/update-array b/src/tests/keywords/update-array
new file mode 100644
index 0000000..c872f4e
--- /dev/null
+++ b/src/tests/keywords/update-array
@@ -0,0 +1,63 @@
+#
+# PRE: update array
+#
+
+update request {
+ Class := 0x01020304
+ Class += 0x05060708
+ Class += 0x090a0b0c
+}
+
+
+#
+# Use array references in the RHS
+# of the update section
+#
+
+update request {
+ Proxy-State += &Class[0]
+ Proxy-State += &Class[1]
+ Proxy-State += &Class[2]
+}
+
+if (&Proxy-State != 0x01020304) {
+ update reply {
+ Filter-Id := "fail 0"
+ }
+}
+
+# Must be the same as above
+if (&Proxy-State[0] != 0x01020304) {
+ update reply {
+ Filter-Id += "fail 0a"
+ }
+}
+
+if (&Proxy-State[1] != 0x05060708) {
+ update reply {
+ Filter-Id += "fail 1"
+ }
+}
+
+if (&Proxy-State[2] != 0x090a0b0c) {
+ update reply {
+ Filter-Id += "fail 2"
+ }
+}
+
+# must not exist
+if (&Proxy-State[3]) {
+ update reply {
+ Filter-Id += "fail 3"
+ }
+}
+
+#
+# The test passes only if no test above
+# added a Filter-Id
+#
+if (!reply:Filter-Id) {
+ update reply {
+ Filter-Id := "filter"
+ }
+} \ No newline at end of file
diff --git a/src/tests/keywords/update-delete b/src/tests/keywords/update-delete
new file mode 100644
index 0000000..a5c2d5a
--- /dev/null
+++ b/src/tests/keywords/update-delete
@@ -0,0 +1,40 @@
+#
+# PRE: update
+#
+# Remove all attributes in a list
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := 'filter'
+}
+
+update request {
+ Tmp-String-0 := 'foobarbaz'
+ Tmp-Integer-0 := 123456789
+ Tmp-IP-Address-0 := 192.0.2.1
+}
+
+if ((Tmp-String-0 != 'foobarbaz') || (Tmp-Integer-0 != 123456789) || (Tmp-IP-Address-0 != 192.0.2.1)) {
+ update {
+ reply:Filter-Id := 'fail'
+ }
+}
+
+# Remove all attributes in the control list
+update {
+ request: !* ANY
+}
+
+# All attributes should now of been removed
+if ((Tmp-String-0 && (Tmp-String-0 == 'foobarbaz')) || \
+ (Tmp-Integer-0 && (Tmp-Integer-0 == 123456789)) || \
+ (Tmp-IP-Address-0 && (Tmp-IP-Address-0 == 192.0.2.1))) {
+ update {
+ reply:Filter-Id := 'fail'
+ }
+}
+
+# This will of been removed too
+update request {
+ User-Password := 'hello'
+}
diff --git a/src/tests/keywords/update-error b/src/tests/keywords/update-error
new file mode 100644
index 0000000..92e0ed2
--- /dev/null
+++ b/src/tests/keywords/update-error
@@ -0,0 +1,9 @@
+#
+# PRE: update
+#
+# It's an error to update lists that don't exist.
+#
+update no-such-list { # ERROR
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := "filter"
+}
diff --git a/src/tests/keywords/update-error-2 b/src/tests/keywords/update-error-2
new file mode 100644
index 0000000..1fe98f0
--- /dev/null
+++ b/src/tests/keywords/update-error-2
@@ -0,0 +1,9 @@
+#
+# PRE: update-error
+#
+# It's an error to update lists that don't exist.
+#
+update {
+ no-such-list:Cleartext-Password := 'hello' # ERROR
+ reply:Filter-Id := "filter"
+}
diff --git a/src/tests/keywords/update-error-3 b/src/tests/keywords/update-error-3
new file mode 100644
index 0000000..ffab73a
--- /dev/null
+++ b/src/tests/keywords/update-error-3
@@ -0,0 +1,10 @@
+#
+# PRE: update-error
+#
+# It's an error to assign literal values which are not
+# part of the set of enumerated values for an attribute
+#
+update {
+ Service-Type := 'hello' # ERROR
+ reply:Filter-Id := "filter"
+}
diff --git a/src/tests/keywords/update-exec b/src/tests/keywords/update-exec
new file mode 100644
index 0000000..b9a0f73
--- /dev/null
+++ b/src/tests/keywords/update-exec
@@ -0,0 +1,94 @@
+#
+# PRE: update if redundant
+#
+update control {
+ Cleartext-Password := 'hello'
+}
+
+update reply {
+ Filter-Id := "filter"
+}
+
+#
+# Exec with script output to attribute
+#
+update request {
+ Tmp-String-0 = `/bin/sh -c "echo 'foo bar baz'"`
+}
+
+if (Tmp-String-0 != "foo bar baz") {
+ update reply {
+ Filter-Id += "fail 1"
+ }
+}
+
+#
+# Exec with output to list (single attribute)
+#
+update {
+ request: = `/bin/sh -c "echo Tmp-String-0 := foo"`
+}
+
+if (Tmp-String-0 != 'foo') {
+ update reply {
+ Filter-Id += "fail 2"
+ }
+}
+
+#
+# Exec with output to list (multiple attributes)
+#
+update {
+ request: = `/bin/sh -c 'echo Tmp-String-0 := foo, Tmp-String-1 := bar'`
+}
+
+if ((Tmp-String-0 != 'foo') || (Tmp-String-1 != 'bar')) {
+ update reply {
+ Filter-Id += "fail 3"
+ }
+}
+
+#
+# Failed exec (malformed attributes) - check no attributes are added
+#
+update request {
+ Tmp-String-0 !* ANY
+ Tmp-String-1 !* ANY
+}
+
+redundant {
+ group {
+ update {
+ request: = `/bin/sh -c 'echo Tmp-String-0 := foo, Tmp-String-1 ?= bar'`
+ }
+ }
+ ok
+}
+if (Tmp-String-0 || Tmp-String-1) {
+ update reply {
+ Filter-Id += "fail 4"
+ }
+}
+
+#
+# Exec with output to list - error code
+#
+update request {
+ Tmp-String-0 !* ANY
+ Tmp-String-1 !* ANY
+}
+
+redundant {
+ group {
+ update {
+ request: = `/bin/sh -c 'echo Tmp-String-0 := foo; exit 64'`
+ }
+ }
+ ok
+}
+if (Tmp-String-0) {
+ update reply {
+ Filter-Id += "fail 5"
+ }
+}
+
diff --git a/src/tests/keywords/update-filter b/src/tests/keywords/update-filter
new file mode 100644
index 0000000..30f96be
--- /dev/null
+++ b/src/tests/keywords/update-filter
@@ -0,0 +1,75 @@
+#
+# PRE: update
+#
+update control {
+ Tmp-Integer-0 := 5
+ Tmp-Integer-0 += 10
+ Tmp-Integer-0 += 15
+ Tmp-Integer-0 += 20
+ Tmp-String-0 := 'foo'
+ Tmp-String-0 += 'baz'
+ Tmp-String-0 += 'boink'
+}
+
+#
+# Reset the request list
+#
+update {
+ &request: !* ANY
+ &request: += &control:[*]
+}
+
+debug_request
+
+#
+# Only matching attributes of the specified type should remain
+#
+update request {
+ &Tmp-Integer-0 == 10
+}
+
+if (&Tmp-Integer-0[0] != 10) {
+ update reply {
+ Filter-Id += "fail 1"
+ }
+}
+
+if ("%{Tmp-Integer-0[#]}" != 1) {
+ update reply {
+ Filter-Id += "fail 2"
+ }
+}
+
+if ("%{Tmp-String-0[#]}" != 3) {
+ update reply {
+ Filter-Id += "fail 3"
+ }
+}
+
+debug_request
+
+#
+# Only matching attributes of the specified type should remain
+#
+update request {
+ &Tmp-String-0 == 'baz'
+}
+
+if (&Tmp-String-0[0] != 'baz') {
+ update reply {
+ Filter-Id += "fail 4"
+ }
+}
+
+if ("%{Tmp-String-0[#]}" != 1) {
+ update reply {
+ Filter-Id += "fail 5"
+ }
+}
+
+update {
+ control:Auth-Type := Accept
+ reply:Filter-Id := "filter"
+}
+
+debug_request
diff --git a/src/tests/keywords/update-index b/src/tests/keywords/update-index
new file mode 100644
index 0000000..390aca7
--- /dev/null
+++ b/src/tests/keywords/update-index
@@ -0,0 +1,52 @@
+#
+# PRE: update update-remove-index
+#
+# A more generic "update" mechanism
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := "filter"
+}
+
+update {
+ control:Reply-Message += 'a'
+ control:Reply-Message += 'b'
+ control:Reply-Message += 'c'
+}
+
+if ((&control:Reply-Message[0] != 'a') || (&control:Reply-Message[1] != 'b') || (&control:Reply-Message[2] != 'c')) {
+ update {
+ reply:Filter-Id := 'Fail 0'
+ }
+}
+
+# Overwrite a specific index, and check the value here is replaced
+update {
+ &control:Reply-Message[1] := 'd'
+}
+
+if ((&control:Reply-Message[0] != 'a') || (&control:Reply-Message[1] != 'd') || (&control:Reply-Message[2] != 'c')) {
+ update {
+ reply:Filter-Id := 'Fail 1'
+ }
+}
+
+# Check isolation...
+update {
+ &control:Reply-Message[0] := &control:Reply-Message[0]
+}
+
+if ((&control:Reply-Message[0] != 'a') || (&control:Reply-Message[1] != 'd') || (&control:Reply-Message[2] != 'c')) {
+ update {
+ reply:Filter-Id := 'Fail 2'
+ }
+}
+
+# Verify we haven't acquired any extra..
+
+if ("%{control:Reply-Message[#]}" != 3) {
+ update {
+ reply:Filter-Id := 'Fail 3'
+ }
+}
+
diff --git a/src/tests/keywords/update-list-error b/src/tests/keywords/update-list-error
new file mode 100644
index 0000000..2613055
--- /dev/null
+++ b/src/tests/keywords/update-list-error
@@ -0,0 +1,19 @@
+#
+# PRE: update
+#
+# It's an error to update lists that don't exist.
+#
+update {
+ request := reply
+ config += request
+ reply !* ANY
+}
+
+update {
+ reply += `/path/to/foo bar baz`
+}
+
+
+update {
+ request := nope # ERROR
+}
diff --git a/src/tests/keywords/update-operator b/src/tests/keywords/update-operator
new file mode 100644
index 0000000..ccbcb16
--- /dev/null
+++ b/src/tests/keywords/update-operator
@@ -0,0 +1,85 @@
+#
+# PRE: update
+#
+
+#
+# Set it.
+#
+update request {
+ NAS-Port := 1000
+}
+
+#
+# Enforce it.
+#
+update request {
+ NAS-Port == 1000
+}
+
+if (NAS-Port != 1000) {
+ update reply {
+ Filter-Id += "fail 1"
+ }
+}
+
+#
+# Enforce to new lower value.
+#
+update request {
+ NAS-Port <= 500
+}
+
+if (NAS-Port != 500) {
+ update reply {
+ Filter-Id += "fail 2 - expected 500, got %{NAS-Port}"
+ }
+}
+
+#
+# Enforce to new higher value
+#
+update request {
+ NAS-Port >= 2000
+}
+
+if (NAS-Port != 2000) {
+ update reply {
+ Filter-Id += "fail 3 - expected 2000, got %{NAS-Port}"
+ }
+}
+
+#
+# Enforce value which previously didn't exist.
+#
+update request {
+ Idle-Timeout >= 14400
+}
+
+if (&request:Idle-Timeout != 14400) {
+ update reply {
+ Filter-Id += "fail Idle-Timeout >= 14400"
+ }
+}
+
+# non-existent attribute
+update request {
+ Class -= 0xabcdef
+}
+
+update request {
+ Class -= &Class
+}
+
+update request {
+ NAS-Port -= &NAS-Port
+}
+
+if (!reply:Filter-Id) {
+ update control {
+ Cleartext-Password := 'hello'
+ }
+
+ update reply {
+ Filter-Id := "filter"
+ }
+}
diff --git a/src/tests/keywords/update-prepend b/src/tests/keywords/update-prepend
new file mode 100644
index 0000000..4ba9335
--- /dev/null
+++ b/src/tests/keywords/update-prepend
@@ -0,0 +1,65 @@
+#
+# PRE: update
+#
+update control {
+ &Tmp-String-0 := 'foo'
+ &Tmp-String-0 += 'baz'
+}
+
+# Reset the request list
+update {
+ &request !* ANY
+ &request += &control
+}
+
+debug_request
+
+# Prepend a single value
+update request {
+ &Tmp-String-0 ^= 'boink'
+}
+
+# The prepended value should be first followd by the other two
+if (("%{Tmp-String-0[0]}" != 'boink') || ("%{Tmp-String-0[1]}" != 'foo') || ("%{Tmp-String-0[2]}" != 'baz')) {
+ update reply {
+ Filter-Id += "fail 1"
+ }
+}
+
+if ("%{Tmp-String-0[#]}" != 3) {
+ update reply {
+ Filter-Id += "fail 1a"
+ }
+}
+
+# Add an extra element to the start of control
+update control {
+ &Tmp-String-0 ^= 'wibble'
+}
+
+# Prepend control to request
+update {
+ &request ^= &control
+}
+
+debug_request
+
+# The attributes should now be "wibble", "foo", "baz", "boink", "foo", "baz"
+if (("%{Tmp-String-0[0]}" != 'wibble') || ("%{Tmp-String-0[1]}" != 'foo') || ("%{Tmp-String-0[2]}" != 'baz') || ("%{Tmp-String-0[3]}" != 'boink') || ("%{Tmp-String-0[4]}" != 'foo') || ("%{Tmp-String-0[5]}" != 'baz')) {
+ update reply {
+ Filter-Id += "fail 2"
+ }
+}
+
+if ("%{Tmp-String-0[#]}" != 6) {
+ update reply {
+ Filter-Id += "fail 2a"
+ }
+}
+
+if (!reply:Filter-Id) {
+ update {
+ &request:User-Password := 'hello'
+ &reply:Filter-Id := 'filter'
+ }
+}
diff --git a/src/tests/keywords/update-remove-any b/src/tests/keywords/update-remove-any
new file mode 100644
index 0000000..e0ef600
--- /dev/null
+++ b/src/tests/keywords/update-remove-any
@@ -0,0 +1,50 @@
+#
+# PRE: update
+#
+# Remove all attributes in a list
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := 'filter'
+}
+
+update {
+ request:Tmp-String-0 := 'foobarbaz'
+ request:Tmp-Integer-0 := 123456789
+ request:Tmp-IP-Address-0 := 192.0.2.1
+ request:Tmp-IP-Address-0 += 192.0.2.2
+ control:Tmp-IP-Address-0 := 192.0.2.1
+ control:Tmp-IP-Address-0 += 192.0.2.3
+}
+
+if (("%{Tmp-IP-Address-0[0]}" != 192.0.2.1) || ("%{Tmp-IP-Address-0[1]}" != 192.0.2.2)) {
+ update {
+ reply:Filter-Id := 'fail 1'
+ }
+}
+
+# Remove all attributes in the control list
+update {
+ request:Tmp-IP-Address-0 !* ANY
+}
+
+# Non Tmp-IP-Address-0 address attributes should still be in the request list
+if ((Tmp-String-0 != 'foobarbaz') || (Tmp-Integer-0 != 123456789)) {
+ update reply {
+ reply:Filter-Id += 'fail 2'
+ }
+}
+
+# There should be no Tmp-IP-Address attributes in the request list
+if (Tmp-IP-Address-0 || ("%{Tmp-IP-Address-0[1]}" != '')) {
+ update {
+ reply:Filter-Id += 'fail 3'
+ }
+}
+
+# But there should still be some in the control list
+if ((control:Tmp-IP-Address-0 != 192.0.2.1) || ("%{control:Tmp-IP-Address-0[1]}" != 192.0.2.3)) {
+ update {
+ reply:Filter-Id += 'fail 4'
+ }
+}
diff --git a/src/tests/keywords/update-remove-index b/src/tests/keywords/update-remove-index
new file mode 100644
index 0000000..58df9a5
--- /dev/null
+++ b/src/tests/keywords/update-remove-index
@@ -0,0 +1,100 @@
+#
+# PRE: update update-remove-value
+#
+# Remove all attributes in a list
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := 'filter'
+}
+
+update {
+ request:Tmp-String-0 := 'foobarbaz'
+ request:Tmp-Integer-0 := 123456789
+ request:Tmp-IP-Address-0 := 192.0.2.1
+ request:Tmp-IP-Address-0 += 192.0.2.2
+ request:Tmp-IP-Address-0 += 192.0.2.3
+ request:Tmp-IP-Address-0 += 192.0.2.2
+ request:Tmp-IP-Address-0 += 192.0.2.4
+}
+
+
+update request {
+ Tmp-IP-Address-0[3] -= 192.0.2.2
+}
+
+# Only the 1st, 2nd, 3rd and 5th Tmp-IP-Address attributes should still be in the list
+if (("%{Tmp-IP-Address-0[0]}" != '192.0.2.1') || \
+ ("%{Tmp-IP-Address-0[1]}" != '192.0.2.2') || \
+ ("%{Tmp-IP-Address-0[2]}" != '192.0.2.3') || \
+ ("%{Tmp-IP-Address-0[3]}" != '192.0.2.4') || \
+ ("%{Tmp-IP-Address-0[4]}" != '')) {
+ update reply {
+ Filter-Id += 'fail 1'
+ }
+}
+
+# There's still a 192.0.2.2 but it's not at index 3
+update request {
+ Tmp-IP-Address-0[3] -= 192.0.2.2
+}
+
+# Should be the same as the previous result
+if (("%{Tmp-IP-Address-0[0]}" != '192.0.2.1') || \
+ ("%{Tmp-IP-Address-0[1]}" != '192.0.2.2') || \
+ ("%{Tmp-IP-Address-0[2]}" != '192.0.2.3') || \
+ ("%{Tmp-IP-Address-0[3]}" != '192.0.2.4') || \
+ ("%{Tmp-IP-Address-0[4]}" != '')) {
+ update reply {
+ Filter-Id += 'fail 2'
+ }
+}
+
+# Remove whatever's at index 0
+update request {
+ Tmp-IP-Address-0[0] !* ANY
+}
+
+# IP address at index 0 should be removed
+if (("%{Tmp-IP-Address-0[0]}" != '192.0.2.2') || \
+ ("%{Tmp-IP-Address-0[1]}" != '192.0.2.3') || \
+ ("%{Tmp-IP-Address-0[2]}" != '192.0.2.4') || \
+ ("%{Tmp-IP-Address-0[3]}" != '')) {
+ update reply {
+ Filter-Id += 'fail 3'
+ }
+}
+
+# Remove whatever's at index 3 (should be nothing)
+update request {
+ Tmp-IP-Address-0[3] !* ANY
+}
+
+# Should be the same as the previous result
+if (("%{Tmp-IP-Address-0[0]}" != '192.0.2.2') || \
+ ("%{Tmp-IP-Address-0[1]}" != '192.0.2.3') || \
+ ("%{Tmp-IP-Address-0[2]}" != '192.0.2.4') || \
+ ("%{Tmp-IP-Address-0[3]}" != '')) {
+ update reply {
+ Filter-Id += 'fail 4'
+ }
+}
+
+# Remove all instances of Tmp-IP-Address
+update request {
+ Tmp-IP-Address-0 !* ANY
+}
+
+# No more IP address attributes!
+if ("%{Tmp-IP-Address-0[0]}" != '') {
+ update reply {
+ Filter-Id += 'fail 5'
+ }
+}
+
+# Non Tmp-IP-Address-0 address attributes should still be in the request list
+if ((Tmp-String-0 != 'foobarbaz') || (Tmp-Integer-0 != 123456789)) {
+ update reply {
+ Filter-Id += 'fail 6'
+ }
+}
diff --git a/src/tests/keywords/update-remove-list b/src/tests/keywords/update-remove-list
new file mode 100644
index 0000000..22ae577
--- /dev/null
+++ b/src/tests/keywords/update-remove-list
@@ -0,0 +1,40 @@
+#
+# PRE: update
+#
+# Remove all attributes in a list
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := 'filter'
+}
+
+update request {
+ Tmp-String-0 := 'foobarbaz'
+ Tmp-Integer-0 := 123456789
+ Tmp-IP-Address-0 := 192.0.2.1
+}
+
+if ((Tmp-String-0 != 'foobarbaz') || (Tmp-Integer-0 != 123456789) || (Tmp-IP-Address-0 != 192.0.2.1)) {
+ update reply {
+ Filter-Id += 'fail 0'
+ }
+}
+
+# Remove all attributes in the control list
+update {
+ request: !* ANY
+}
+
+# All attributes should now of been removed
+if ((Tmp-String-0 && (Tmp-String-0 == 'foobarbaz')) || \
+ (Tmp-Integer-0 && (Tmp-Integer-0 == 123456789)) || \
+ (Tmp-IP-Address-0 && (Tmp-IP-Address-0 == 192.0.2.1))) {
+ update reply {
+ Filter-Id := 'fail 1'
+ }
+}
+
+# This will of been removed too
+update request {
+ User-Password := 'hello'
+}
diff --git a/src/tests/keywords/update-remove-tag b/src/tests/keywords/update-remove-tag
new file mode 100644
index 0000000..3328789
--- /dev/null
+++ b/src/tests/keywords/update-remove-tag
@@ -0,0 +1,275 @@
+#
+# PRE: update update-remove-value update-remove-index update-tag
+#
+# Remove all attributes in a list
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := 'filter'
+}
+
+update {
+ request:Tunnel-Server-Endpoint += '192.0.1.1'
+ request:Tunnel-Server-Endpoint += '192.0.1.2'
+ request:Tunnel-Server-Endpoint:1 += '192.0.1.1'
+ request:Tunnel-Server-Endpoint:2 += '192.0.2.1'
+ request:Tunnel-Server-Endpoint:2 += '192.0.2.2'
+ request:Tunnel-Server-Endpoint:3 += '192.0.3.1'
+ request:Tunnel-Server-Endpoint:3 += '192.0.3.2'
+ request:Tunnel-Server-Endpoint:3 += '192.0.3.3'
+ control: += request:
+}
+
+# Check [#] is working correctly (should probably be another set of tests)
+if (("%{request:Tunnel-Server-Endpoint[#]}" != 8) || \
+ ("%{request:Tunnel-Server-Endpoint:0[#]}" != 2) || \
+ ("%{request:Tunnel-Server-Endpoint:1[#]}" != 1) || \
+ ("%{request:Tunnel-Server-Endpoint:2[#]}" != 2) || \
+ ("%{request:Tunnel-Server-Endpoint:3[#]}" != 3)) {
+ update reply {
+ Filter-Id += 'fail 0'
+ }
+}
+
+update {
+ Tunnel-Server-Endpoint !* ANY
+}
+
+# List should now be empty
+if ("%{request:Tunnel-Server-Endpoint[#]}" != 0) {
+ update reply {
+ Filter-Id += 'fail 1'
+ }
+}
+
+# Reset the list
+update {
+ request: += control:
+}
+
+# Now remove all Tunnel-Server-Endpoint attributes with :2
+update {
+ Tunnel-Server-Endpoint:2 !* ANY
+}
+
+if (("%{request:Tunnel-Server-Endpoint[#]}" != 6) || \
+ ("%{request:Tunnel-Server-Endpoint:0[#]}" != 2) || \
+ ("%{request:Tunnel-Server-Endpoint:1[#]}" != 1) || \
+ ("%{request:Tunnel-Server-Endpoint:2[#]}" != 0) || \
+ ("%{request:Tunnel-Server-Endpoint:3[#]}" != 3)) {
+ update reply {
+ Filter-Id += 'fail 2'
+ }
+}
+
+# Now remove all Tunnel-Server-Endpoint attributes with :0 (no tags)
+update {
+ Tunnel-Server-Endpoint:0 !* ANY
+}
+
+if (("%{request:Tunnel-Server-Endpoint[#]}" != 4) || \
+ ("%{request:Tunnel-Server-Endpoint:0[#]}" != 0) || \
+ ("%{request:Tunnel-Server-Endpoint:1[#]}" != 1) || \
+ ("%{request:Tunnel-Server-Endpoint:2[#]}" != 0) || \
+ ("%{request:Tunnel-Server-Endpoint:3[#]}" != 3)) {
+ update reply {
+ Filter-Id += 'fail 3'
+ }
+}
+
+# Now remove all Tunnel-Server-Endpoint attributes with :3
+update {
+ Tunnel-Server-Endpoint:3 !* ANY
+}
+
+if (("%{request:Tunnel-Server-Endpoint[#]}" != 1) || \
+ ("%{request:Tunnel-Server-Endpoint:0[#]}" != 0) || \
+ ("%{request:Tunnel-Server-Endpoint:1[#]}" != 1) || \
+ ("%{request:Tunnel-Server-Endpoint:2[#]}" != 0) || \
+ ("%{request:Tunnel-Server-Endpoint:3[#]}" != 0)) {
+ update reply {
+ Filter-Id += 'fail 4'
+ }
+}
+
+# Now remove all Tunnel-Server-Endpoint attributes with :1
+update {
+ Tunnel-Server-Endpoint:1 !* ANY
+}
+
+if (("%{request:Tunnel-Server-Endpoint[#]}" != 0) || \
+ ("%{request:Tunnel-Server-Endpoint:0[#]}" != 0) || \
+ ("%{request:Tunnel-Server-Endpoint:1[#]}" != 0) || \
+ ("%{request:Tunnel-Server-Endpoint:2[#]}" != 0) || \
+ ("%{request:Tunnel-Server-Endpoint:3[#]}" != 0)) {
+ update reply {
+ Filter-Id += 'fail 5'
+ }
+}
+
+# Reset the list
+update {
+ request: += control:
+}
+
+# Remove all Tunnel-Server-Endpoint attributes at :3[0] (none)
+update {
+ Tunnel-Server-Endpoint:1[3] !* ANY
+}
+
+if (Tunnel-Server-Endpoint:3[0] != '192.0.3.1') {
+ update reply {
+ Filter-Id += 'fail 6'
+ }
+}
+
+if (Tunnel-Server-Endpoint:3[1] != '192.0.3.2') {
+ update reply {
+ Filter-Id += 'fail 7'
+ }
+}
+
+if (Tunnel-Server-Endpoint:3[2] != '192.0.3.3') {
+ update reply {
+ Filter-Id += 'fail 8'
+ }
+}
+
+# Remove all Tunnel-Server-Endpoint attributes at :3[1]
+update {
+ Tunnel-Server-Endpoint:3[1] !* ANY
+}
+
+if (Tunnel-Server-Endpoint:3[0] != '192.0.3.1') {
+ update reply {
+ Filter-Id += 'fail 9'
+ }
+}
+
+if (Tunnel-Server-Endpoint:3[1] != '192.0.3.3') {
+ update reply {
+ Filter-Id += 'fail 10'
+ }
+}
+
+# Remove any Tunnel-Server-Endpoint with a value of '192.0.1.1' (should remove both tagged and untagged versions)
+update {
+ Tunnel-Server-Endpoint -= '192.0.1.1'
+}
+
+# Also checks whether presence checks for tagged attributes work correctly
+if (request:Tunnel-Server-Endpoint:1) {
+ update reply {
+ Filter-Id += 'fail 11'
+ }
+}
+
+if (request:Tunnel-Server-Endpoint:0[0] != '192.0.1.2') {
+ update reply {
+ Filter-Id += 'fail 12'
+ }
+}
+
+# Remove any Tunnel-Server-Endpoint with a value of '192.0.3.1'
+update {
+ Tunnel-Server-Endpoint:3 -= '192.0.3.2'
+}
+
+if (request:Tunnel-Server-Endpoint:3[0] != '192.0.3.1') {
+ update reply {
+ Filter-Id += 'fail 13'
+ }
+}
+
+if (request:Tunnel-Server-Endpoint:3[1] != '192.0.3.3') {
+ update reply {
+ Filter-Id += 'fail 14'
+ }
+}
+
+# Reset the list
+update {
+ request: !* ANY
+}
+update {
+ request: += control:
+}
+
+# Remove only the tagged version of '192.0.1.1'
+update {
+ request:Tunnel-Server-Endpoint:1 -= '192.0.1.1'
+}
+
+if (request:Tunnel-Server-Endpoint:0[0] != '192.0.1.1') {
+ update reply {
+ Filter-Id += 'fail 15'
+ }
+}
+
+# Reset the list
+update {
+ request: !* ANY
+}
+update {
+ request: += control:
+}
+
+# Remove only the untagged version of '192.0.1.1'
+update {
+ request:Tunnel-Server-Endpoint:0 -= '192.0.1.1'
+}
+
+if (request:Tunnel-Server-Endpoint:1[0] != '192.0.1.1') {
+ update reply {
+ Filter-Id += 'fail 16'
+ }
+}
+
+# Remove the value of Tunnel-Server-Endpoint:3 at index 1 only if it matches '192.0.3.3' (which it does)
+update {
+ Tunnel-Server-Endpoint:3[1] -= '192.0.3.2'
+}
+
+if (Tunnel-Server-Endpoint:3[0] != '192.0.3.1') {
+ update reply {
+ Filter-Id += 'fail 17'
+ }
+}
+
+if (Tunnel-Server-Endpoint:3[1] != '192.0.3.3') {
+ update reply {
+ Filter-Id += 'fail 18'
+ }
+}
+
+# Reset the list
+update {
+ request: !* ANY
+}
+update {
+ request: += control:
+}
+
+# Remove the value of Tunnel-Server-Endpoint:3 at index 1 only if it matches '192.0.3.4' (which it doesn't)
+update {
+ Tunnel-Server-Endpoint:3[1] -= '192.0.3.4'
+}
+
+if (Tunnel-Server-Endpoint:3[0] != '192.0.3.1') {
+ update reply {
+ Filter-Id += 'fail 19'
+ }
+}
+
+if (Tunnel-Server-Endpoint:3[1] != '192.0.3.2') {
+ update reply {
+ Filter-Id += 'fail 20'
+ }
+}
+
+if (Tunnel-Server-Endpoint:3[2] != '192.0.3.3') {
+ update reply {
+ Filter-Id += 'fail 21'
+ }
+}
+
diff --git a/src/tests/keywords/update-remove-value b/src/tests/keywords/update-remove-value
new file mode 100644
index 0000000..3fd1f94
--- /dev/null
+++ b/src/tests/keywords/update-remove-value
@@ -0,0 +1,116 @@
+#
+# PRE: update
+#
+# Remove all attributes in a list
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := 'filter'
+}
+
+update {
+ request:Tmp-String-0 := 'foobarbaz'
+ request:Tmp-Integer-0 := 123456789
+ request:Tmp-IP-Address-0 := 192.0.2.1
+ request:Tmp-IP-Address-0 += 192.0.2.2
+ request:Tmp-IP-Address-0 += 192.0.2.3
+ request:Tmp-IP-Address-0 += 192.0.2.4
+ control:Tmp-IP-Address-0 := 192.0.2.1
+ control:Tmp-IP-Address-0 += 192.0.2.3
+}
+
+if (("%{Tmp-IP-Address-0[0]}" != 192.0.2.1) || \
+ ("%{Tmp-IP-Address-0[1]}" != 192.0.2.2) || \
+ ("%{Tmp-IP-Address-0[2]}" != 192.0.2.3) || \
+ ("%{Tmp-IP-Address-0[3]}" != 192.0.2.4)) {
+ update reply {
+ Filter-Id += 'fail 0'
+ }
+}
+
+# Remove Tmp-IP-Address-0 with a specific value
+update {
+ request:Tmp-IP-Address-0 -= 192.0.2.1
+}
+
+# Only the 2nd, 3rd and 4th Tmp-IP-Address attributes should still be in the list
+if (("%{Tmp-IP-Address-0[0]}" != '192.0.2.2') || \
+ ("%{Tmp-IP-Address-0[1]}" != '192.0.2.3') || \
+ ("%{Tmp-IP-Address-0[2]}" != '192.0.2.4') || \
+ ("%{Tmp-IP-Address-0[3]}" != '')) {
+ update reply {
+ Filter-Id += 'fail 1'
+ }
+}
+
+# Remove Tmp-IP-Address-0 with a specific value (somewhere in the middle)
+update {
+ request:Tmp-IP-Address-0 -= 192.0.2.3
+}
+
+# Only the 1st, and 3rd Tmp-IP-Address attributes should still be in the list
+if (("%{Tmp-IP-Address-0[0]}" != '192.0.2.2') || \
+ ("%{Tmp-IP-Address-0[1]}" != '192.0.2.4') || \
+ ("%{Tmp-IP-Address-0[2]}" != '')) {
+ update reply {
+ Filter-Id += 'fail 2'
+ }
+}
+
+# Remove Tmp-IP-Address-0 with a specific value (which doesn't exist)
+update {
+ request:Tmp-IP-Address-0 -= 192.0.2.3
+}
+
+# Only the 1st, and 3rd Tmp-IP-Address attributes should still be in the list
+if (("%{Tmp-IP-Address-0[0]}" != '192.0.2.2') || \
+ ("%{Tmp-IP-Address-0[1]}" != '192.0.2.4') || \
+ ("%{Tmp-IP-Address-0[2]}" != '')) {
+ update reply {
+ Filter-Id += 'fail 3'
+ }
+}
+
+# Remove Tmp-IP-Address-4 (which doesn't exist - more to check for SEGV/assert)
+update {
+ request:Tmp-IP-Address-4 -= 192.0.2.3
+}
+
+# Remove Tmp-IP-Address-0 with a specific value
+update {
+ request:Tmp-IP-Address-0 -= 192.0.2.4
+}
+
+# Only the 1st, and 3rd Tmp-IP-Address attributes should still be in the list
+if (("%{Tmp-IP-Address-0[0]}" != '192.0.2.2') || \
+ ("%{Tmp-IP-Address-0[1]}" != '')) {
+ update reply {
+ Filter-Id += 'fail 4'
+ }
+}
+
+# Remove Tmp-IP-Address-0 with a specific value
+update {
+ request:Tmp-IP-Address-0 -= 192.0.2.2
+}
+
+# Only the 1st, and 3rd Tmp-IP-Address attributes should still be in the list
+if ("%{Tmp-IP-Address-0[0]}" != '') {
+ update reply {
+ Filter-Id += 'fail 5'
+ }
+}
+
+# Non Tmp-IP-Address-0 address attributes should still be in the request list
+if ((Tmp-String-0 != 'foobarbaz') || (Tmp-Integer-0 != 123456789)) {
+ update reply {
+ Filter-Id += 'fail 6'
+ }
+}
+
+# But there should still be some in the control list
+if (("%{control:Tmp-IP-Address-0[0]}" != 192.0.2.1) || ("%{control:Tmp-IP-Address-0[1]}" != 192.0.2.3)) {
+ update {
+ Filter-Id += 'fail 7'
+ }
+}
diff --git a/src/tests/keywords/update-tag b/src/tests/keywords/update-tag
new file mode 100644
index 0000000..15afd59
--- /dev/null
+++ b/src/tests/keywords/update-tag
@@ -0,0 +1,176 @@
+#
+# PRE: update
+#
+# Remove all attributes in a list
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := 'filter'
+}
+
+update request {
+ Tunnel-Server-Endpoint:0 := '192.0.1.1' # Should not be tagged
+ Tunnel-Server-Endpoint:0 += '192.0.1.2' # Should not be tagged
+ Tunnel-Server-Endpoint:1 := '192.0.2.1'
+ Tunnel-Server-Endpoint:1 += '192.0.2.2'
+ Tunnel-Server-Endpoint:2 := '192.0.3.1'
+ Tunnel-Server-Endpoint:2 += '192.0.3.2'
+}
+
+update request {
+ Tmp-Integer-0 := "%{debug_attr:request:}"
+}
+
+
+#
+# Selecting on attributes which have no tag (0)
+#
+if (Tunnel-Server-Endpoint:0[0] != '192.0.1.1') {
+ update {
+ reply:Filter-Id += 'fail 1'
+ }
+}
+
+if (Tunnel-Server-Endpoint:0[1] != '192.0.1.2') {
+ update {
+ reply:Filter-Id += 'fail 2'
+ }
+}
+
+#
+# Selecting on attributes with no tag specified (should match all of that type)
+#
+if (Tunnel-Server-Endpoint[0] != '192.0.1.1') {
+ update {
+ reply:Filter-Id += 'fail 3'
+ }
+}
+
+if (Tunnel-Server-Endpoint[1] != '192.0.1.2') {
+ update {
+ reply:Filter-Id += 'fail 4'
+ }
+}
+
+if (Tunnel-Server-Endpoint[2] != '192.0.2.1') {
+ update {
+ reply:Filter-Id += 'fail 5'
+ }
+}
+
+#
+# Now the none xlat version
+#
+# Check that access attributes by tag works first
+if (Tunnel-Server-Endpoint:2 != '192.0.3.1') {
+ update {
+ reply:Filter-Id += 'fail 6'
+ }
+}
+
+if (Tunnel-Server-Endpoint:2 == '192.0.3.2') {
+ update {
+ reply:Filter-Id += 'fail 7'
+ }
+}
+
+if (Tunnel-Server-Endpoint:1 != '192.0.2.1') {
+ update {
+ reply:Filter-Id += 'fail 8'
+ }
+}
+
+# Get the first instance of Tunnel-Server-Endpoint:2
+if (Tunnel-Server-Endpoint:2[0] != '192.0.3.1') {
+ update {
+ reply:Filter-Id += 'fail 9'
+ }
+}
+
+# Get the first instance of Tunnel-Server-Endpoint:2
+if (Tunnel-Server-Endpoint:2[1] != '192.0.3.2') {
+ update {
+ reply:Filter-Id += 'fail 10'
+ }
+}
+
+#
+# Assignment (bare)
+#
+update request {
+ Tmp-String-1 += &Tunnel-Server-Endpoint:2 # 0
+ Tmp-String-1 += &Tunnel-Server-Endpoint:2 # 1
+ Tmp-String-1 += &Tunnel-Server-Endpoint:1 # 2
+ Tmp-String-1 += &Tunnel-Server-Endpoint:2[0] # 3
+ Tmp-String-1 += &Tunnel-Server-Endpoint:2[1] # 4
+ Tmp-String-1 += &Tunnel-Server-Endpoint:0[0] # 5
+ Tmp-String-1 += &Tunnel-Server-Endpoint:0[1] # 6
+ Tmp-String-1 += &Tunnel-Server-Endpoint:0[2] # 7 (No attribute should be added here)
+ Tmp-String-1 += &Tunnel-Server-Endpoint[0] # 8
+ Tmp-String-1 += &Tunnel-Server-Endpoint[1] # 9
+ Tmp-String-1 += &Tunnel-Server-Endpoint[2] # 10
+}
+
+# Check that access attributes by tag works first
+if (Tmp-String-1[0] != '192.0.3.1') {
+ update {
+ reply:Filter-Id += 'fail 11'
+ }
+}
+
+if (Tmp-String-1[1] == '192.0.3.2') {
+ update {
+ reply:Filter-Id += 'fail 12'
+ }
+}
+
+if (Tmp-String-1[2] != '192.0.2.1') {
+ update {
+ reply:Filter-Id += 'fail 13'
+ }
+}
+
+# Get the first instance of Tunnel-Server-Endpoint:2
+if (Tmp-String-1[3] != '192.0.3.1') {
+ update {
+ reply:Filter-Id += 'fail 14'
+ }
+}
+
+# Get the first instance of Tunnel-Server-Endpoint:2
+if (Tmp-String-1[4] != '192.0.3.2') {
+ update {
+ reply:Filter-Id += 'fail 15'
+ }
+}
+
+# Now check the assignment
+if (Tmp-String-1[5] != '192.0.1.1') {
+ update {
+ reply:Filter-Id += 'fail 16'
+ }
+}
+
+if (Tmp-String-1[6] != '192.0.1.2') {
+ update {
+ reply:Filter-Id += 'fail 17'
+ }
+}
+
+if (Tmp-String-1[7] != '192.0.1.1') {
+ update {
+ reply:Filter-Id += 'fail 19'
+ }
+}
+
+if (Tmp-String-1[8] != '192.0.1.2') {
+ update {
+ reply:Filter-Id += 'fail 20'
+ }
+}
+
+if (Tmp-String-1[9] != '192.0.2.1') {
+ update {
+ reply:Filter-Id += 'fail 21'
+ }
+}
diff --git a/src/tests/keywords/update-xlat b/src/tests/keywords/update-xlat
new file mode 100644
index 0000000..59230dc
--- /dev/null
+++ b/src/tests/keywords/update-xlat
@@ -0,0 +1,61 @@
+#
+# PRE: update
+#
+# Form attribute references with xlats
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := 'filter'
+}
+
+
+update request {
+ Tmp-String-0 := 'Tmp-String-1'
+}
+
+#
+# Shouldn't update Tmp-String-0, should instead update Tmp-String-1
+# ... maybe this is what Alan meant when he was talking about people
+# doing stupid things with this feature.
+#
+update request {
+ "%{Tmp-String-0}" := 'hello'
+}
+
+if (&Tmp-String-1 != 'hello') {
+ update reply {
+ Filter-Id += 'Fail 0'
+ }
+}
+
+if (&Tmp-String-0 == 'hello') {
+ update reply {
+ Filter-Id += 'Fail 1'
+ }
+}
+
+#
+# Try updating an attribute specified by an OID string
+#
+update {
+ Tmp-Integer-0 := 11344
+}
+update {
+ "Vendor-%{Tmp-Integer-0}-Attr-1" := 127.0.0.1
+}
+
+if (&FreeRADIUS-Proxied-To != 127.0.0.1) {
+ update reply {
+ Filter-Id += 'Fail 2'
+ }
+}
+
+update {
+ "Attr-%{attr_num:Tmp-String-1}" := 'hello2'
+}
+
+if (&Tmp-String-1 != 'hello2') {
+ update reply {
+ Filter-Id += 'Fail 3'
+ }
+}
diff --git a/src/tests/keywords/urlquote b/src/tests/keywords/urlquote
new file mode 100644
index 0000000..35057d8
--- /dev/null
+++ b/src/tests/keywords/urlquote
@@ -0,0 +1,50 @@
+#
+# PRE: update if
+#
+update {
+ # Some encoders replace ~ with %7E RFC3986 Section 2.4 says this should not be done.
+ request:Tmp-String-0 := '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_.~'
+ request:Tmp-String-1 := '±§!@#$%^&*()+={[}]:;"\'|\<,>?/`'
+ request:Tmp-String-2 := '™œ¥¤'
+ request:Tmp-String-3 := '%C2%B1%C2%A7%21%40%23%24%25%5E%26%2A%28%29%2B%3D%7B%5B%7D%5D%3A%3B%22%27%7C%5C%3C%2C%3E%3F%2F%60'
+
+ request:Tmp-String-4 := '%E2%84%A2%C5%93%C2%A5%C2%A4'
+ reply:Filter-ID := 'filter'
+}
+
+
+if (<string>"%{urlquote:%{request:Tmp-String-0}}" != &Tmp-String-0) {
+ update reply {
+ Filter-Id += 'Fail 1'
+ }
+}
+
+if (<string>"%{urlquote:%{request:Tmp-String-1}}" != &Tmp-String-3) {
+ update reply {
+ Filter-Id += 'Fail 2'
+ }
+}
+
+if (<string>"%{urlquote:%{request:Tmp-String-2}}" != &Tmp-String-4) {
+ update reply {
+ Filter-Id += 'Fail 3'
+ }
+}
+
+if (<string>"%{urlunquote:%{request:Tmp-String-0}}" != &Tmp-String-0) {
+ update reply {
+ Filter-Id += 'Fail 4'
+ }
+}
+
+if (<string>"%{urlunquote:%{request:Tmp-String-3}}" != &Tmp-String-1) {
+ update reply {
+ Filter-Id += 'Fail 5'
+ }
+}
+
+if (<string>"%{urlunquote:%{request:Tmp-String-4}}" != &Tmp-String-2) {
+ update reply {
+ Filter-Id += 'Fail 6'
+ }
+}
diff --git a/src/tests/keywords/virtual b/src/tests/keywords/virtual
new file mode 100644
index 0000000..d6dbe32
--- /dev/null
+++ b/src/tests/keywords/virtual
@@ -0,0 +1,12 @@
+#
+# PRE: update if
+#
+update control {
+ Cleartext-Password := 'hello'
+}
+
+if (request:Packet-Type == Access-Request) {
+ update reply {
+ Filter-Id := "filter"
+ }
+}
diff --git a/src/tests/keywords/virtual-exists b/src/tests/keywords/virtual-exists
new file mode 100644
index 0000000..7a8e8f3
--- /dev/null
+++ b/src/tests/keywords/virtual-exists
@@ -0,0 +1,12 @@
+#
+# PRE: update if
+#
+update control {
+ Cleartext-Password := 'hello'
+}
+
+if (&Client-Shortname) {
+ update reply {
+ Filter-Id := "filter"
+ }
+}
diff --git a/src/tests/keywords/virtual-load-balance b/src/tests/keywords/virtual-load-balance
new file mode 100644
index 0000000..256c2ff
--- /dev/null
+++ b/src/tests/keywords/virtual-load-balance
@@ -0,0 +1,14 @@
+# PRE: update if foreach
+#
+# Virtual Load-Balance blocks.
+#
+
+#
+# Both of these should parse.
+#
+virtual_instantiate
+virtual_instantiate.post-auth
+
+update reply {
+ Filter-Id := 'filter'
+}
diff --git a/src/tests/keywords/virtual-rhs b/src/tests/keywords/virtual-rhs
new file mode 100644
index 0000000..0d21e7f
--- /dev/null
+++ b/src/tests/keywords/virtual-rhs
@@ -0,0 +1,16 @@
+#
+# PRE: update if
+#
+update control {
+ Cleartext-Password := 'hello'
+}
+
+update request {
+ Tmp-String-0 := "<UNKNOWN-CLIENT>"
+}
+
+if (&Tmp-String-0 == &Client-Shortname) {
+ update reply {
+ Filter-Id := "filter"
+ }
+}
diff --git a/src/tests/keywords/virtual_policy b/src/tests/keywords/virtual_policy
new file mode 100644
index 0000000..4ab00e2
--- /dev/null
+++ b/src/tests/keywords/virtual_policy
@@ -0,0 +1,15 @@
+# PRE: update if foreach
+#
+# Virtual policies
+#
+
+
+#
+# Both of these should parse.
+#
+virtual_policy
+virtual_policy.post-auth
+
+update reply {
+ Filter-Id := 'filter'
+}
diff --git a/src/tests/keywords/wimax b/src/tests/keywords/wimax
new file mode 100644
index 0000000..f149da4
--- /dev/null
+++ b/src/tests/keywords/wimax
@@ -0,0 +1,31 @@
+update control {
+ Cleartext-Password := 'hello'
+}
+
+update request {
+ WiMAX-PFDv2-Src-Port = 6809
+}
+
+if (WiMAX-PFDv2-Src-Port != 6809) {
+ update reply {
+ Filter-Id := "fail-1"
+ }
+}
+
+#
+# This is known, and should be renamed
+update request {
+ Attr-26.24757.84.9.5.7 = 0x01
+}
+
+if (WiMAX-PFDv2-Src-Assigned != 1) {
+ update reply {
+ Filter-Id := "fail-2"
+ }
+}
+
+if (!reply:Filter-Id) {
+ update reply {
+ Filter-Id := "filter"
+ }
+} \ No newline at end of file
diff --git a/src/tests/keywords/wimax-comboip b/src/tests/keywords/wimax-comboip
new file mode 100644
index 0000000..c4c8a3f
--- /dev/null
+++ b/src/tests/keywords/wimax-comboip
@@ -0,0 +1,19 @@
+update control {
+ Cleartext-Password := 'hello'
+}
+
+update request {
+ WiMAX-DHCPv4-Server := 192.0.2.23
+}
+
+if (WiMAX-DHCPv4-Server != 192.0.2.23) {
+ update reply {
+ Filter-Id := "fail-1"
+ }
+}
+
+if (!reply:Filter-Id) {
+ update reply {
+ Filter-Id := "filter"
+ }
+} \ No newline at end of file
diff --git a/src/tests/keywords/with_dots b/src/tests/keywords/with_dots
new file mode 100644
index 0000000..4fc6b06
--- /dev/null
+++ b/src/tests/keywords/with_dots
@@ -0,0 +1,19 @@
+#
+# PRE: update
+#
+
+#
+# Ensure that policies can have dots.
+#
+# The main problem is that conf section references
+# also have dots in them...
+#
+with.dots
+
+update control {
+ Cleartext-Password := 'hello'
+}
+
+update reply {
+ Filter-Id := "filter"
+}
diff --git a/src/tests/keywords/xlat-attr b/src/tests/keywords/xlat-attr
new file mode 100644
index 0000000..d19495a
--- /dev/null
+++ b/src/tests/keywords/xlat-attr
@@ -0,0 +1,62 @@
+#
+# PRE: update
+#
+# Check attribute info xlats work correctly
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := 'filter'
+}
+
+update request {
+ Reply-Message := 'foo'
+ FreeRADIUS-Proxied-To := 127.0.0.1
+}
+
+if ("%{attr:&FreeRADIUS-Proxied-To}" != 'FreeRADIUS-Proxied-To') {
+ update reply {
+ Filter-Id += 'Fail 0'
+ }
+}
+
+if ("%{attr_num:&FreeRADIUS-Proxied-To}" != 1) {
+ update reply {
+ Filter-Id += 'Fail 2'
+ }
+}
+
+if ("%{vendor:&FreeRADIUS-Proxied-To}" != 'FreeRADIUS') {
+ update reply {
+ Filter-Id += 'Fail 3'
+ }
+}
+
+if ("%{vendor_num:&FreeRADIUS-Proxied-To}" != 11344) {
+ update reply {
+ Filter-Id += 'Fail 4'
+ }
+}
+
+if ("%{attr:&Reply-Message}" != 'Reply-Message') {
+ update reply {
+ Filter-Id += 'Fail 5'
+ }
+}
+
+if ("%{attr_num:&Reply-Message}" != 18) {
+ update reply {
+ Filter-Id += 'Fail 6'
+ }
+}
+
+if ("%{vendor:&Reply-Message}" != '') {
+ update reply {
+ Filter-Id += 'Fail 7'
+ }
+}
+
+if ("%{vendor_num:&Reply-Message}" != 0) {
+ update reply {
+ Filter-Id += 'Fail 8'
+ }
+}
diff --git a/src/tests/keywords/xlat-attr-index b/src/tests/keywords/xlat-attr-index
new file mode 100644
index 0000000..c967dd6
--- /dev/null
+++ b/src/tests/keywords/xlat-attr-index
@@ -0,0 +1,53 @@
+#
+# PRE: update
+#
+# Remove all attributes in a list
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := 'filter'
+}
+
+update request {
+ Tmp-IP-Address-0 := 192.0.2.1
+ Tmp-IP-Address-0 += 192.0.2.2
+}
+
+if ("%{Tmp-IP-Address-0[#]}" != 2) {
+ update {
+ reply:Filter-Id += 'fail 0'
+ }
+}
+
+if (("%{Tmp-IP-Address-0[0]}" != 192.0.2.1) || ("%{Tmp-IP-Address-0[1]}" != 192.0.2.2)) {
+ update {
+ reply:Filter-Id += 'fail 1'
+ }
+}
+
+if ("%{Tmp-IP-Address-0[*]}" != '192.0.2.1,192.0.2.2') {
+ update {
+ reply:Filter-Id += 'fail 2'
+ }
+}
+
+# Try calling these xlats in mapping too, they may get optimised to VPTs which is a
+# different code path.
+update request {
+ Tmp-IP-Address-1 += "%{Tmp-IP-Address-0[1]}"
+ Tmp-IP-Address-1 += "%{Tmp-IP-Address-0[0]}"
+ Tmp-String-0 = "%{Tmp-IP-Address-0[*]}"
+ Tmp-Integer-0 = "%{Tmp-IP-Address-0[#]}"
+}
+
+if (Tmp-String-0 != '192.0.2.1,192.0.2.2') {
+ update {
+ reply:Filter-Id += 'fail 3'
+ }
+}
+
+if (Tmp-Integer-0 != 2) {
+ update {
+ reply:Filter-Id += 'fail 4'
+ }
+}
diff --git a/src/tests/keywords/xlat-attr-tag b/src/tests/keywords/xlat-attr-tag
new file mode 100644
index 0000000..c0bd8b6
--- /dev/null
+++ b/src/tests/keywords/xlat-attr-tag
@@ -0,0 +1,225 @@
+#
+# PRE: update
+#
+# Remove all attributes in a list
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := 'filter'
+}
+
+update request {
+ Tunnel-Server-Endpoint := '192.0.1.1' # Should not be tagged
+ Tunnel-Server-Endpoint:0 += '192.0.1.2' # Should not be tagged
+ Tunnel-Server-Endpoint:1 := '192.0.2.1'
+ Tunnel-Server-Endpoint:1 += '192.0.2.2'
+ Tunnel-Server-Endpoint:2 := '192.0.3.1'
+ Tunnel-Server-Endpoint:2 += '192.0.3.2'
+}
+
+update request {
+ Tmp-Integer-0 := "%{debug_attr:request:}"
+}
+
+# Check the tag printing xlat works correctly
+if ("%{tag:Tunnel-Server-Endpoint[0]}" != '') {
+ update {
+ reply:Filter-Id += 'fail 0a'
+ }
+}
+
+if ("%{tag:Tunnel-Server-Endpoint[1]}" != '') {
+ update {
+ reply:Filter-Id += 'fail 0b'
+ }
+}
+
+
+if ("%{tag:Tunnel-Server-Endpoint[2]}" != '1') {
+ update {
+ reply:Filter-Id += 'fail 0c'
+ }
+}
+
+if ("%{tag:Tunnel-Server-Endpoint[5]}" != '2') {
+ update {
+ reply:Filter-Id += 'fail 0d'
+ }
+}
+
+if ("%{tag:Tunnel-Server-Endpoint[6]}" != '') {
+ update {
+ reply:Filter-Id += 'fail 0e'
+ }
+}
+
+if ("%{tag:control:Cleartext-Password}" != '') {
+ update {
+ reply:Filter-Id += 'fail 0f'
+ }
+}
+
+# Check that access attributes by tag works first
+if ("%{Tunnel-Server-Endpoint:2}" != '192.0.3.1') {
+ update {
+ reply:Filter-Id += 'fail 1'
+ }
+}
+
+if ("%{Tunnel-Server-Endpoint:2}" == '192.0.3.2') {
+ update {
+ reply:Filter-Id += 'fail 2'
+ }
+}
+
+if ("%{Tunnel-Server-Endpoint:1}" != '192.0.2.1') {
+ update {
+ reply:Filter-Id += 'fail 3'
+ }
+}
+
+# Get the first instance of Tunnel-Server-Endpoint:2
+if ("%{Tunnel-Server-Endpoint:2[0]}" != '192.0.3.1') {
+ update {
+ reply:Filter-Id += 'fail 4'
+ }
+}
+
+# Get the first instance of Tunnel-Server-Endpoint:2
+if ("%{Tunnel-Server-Endpoint:2[1]}" != '192.0.3.2') {
+ update {
+ reply:Filter-Id += 'fail 5'
+ }
+}
+
+if ("%{Tunnel-Server-Endpoint:0[2]}" != '') {
+ update {
+ reply:Filter-Id += 'fail 6'
+ }
+}
+
+if ("%{Tunnel-Server-Endpoint:0[0]}" != '192.0.1.1') {
+ update {
+ reply:Filter-Id += 'fail 7'
+ }
+}
+
+if ("%{Tunnel-Server-Endpoint:0[1]}" != '192.0.1.2') {
+ update {
+ reply:Filter-Id += 'fail 8'
+ }
+}
+
+if ("%{Tunnel-Server-Endpoint:0[2]}" != '') {
+ update {
+ reply:Filter-Id += 'fail 9'
+ }
+}
+
+#
+# Selecting on attributes with no tag specified (should match all of that type)
+#
+if ("%{Tunnel-Server-Endpoint[0]}" != '192.0.1.1') {
+ update {
+ reply:Filter-Id += 'fail 10'
+ }
+}
+
+if ("%{Tunnel-Server-Endpoint[1]}" != '192.0.1.2') {
+ update {
+ reply:Filter-Id += 'fail 11'
+ }
+}
+
+if ("%{Tunnel-Server-Endpoint[2]}" != '192.0.2.1') {
+ update {
+ reply:Filter-Id += 'fail 12'
+ }
+}
+
+#
+# Assignment (xlat)
+#
+update request {
+ Tmp-String-0 += "%{Tunnel-Server-Endpoint:2}" #0
+ Tmp-String-0 += "%{Tunnel-Server-Endpoint:2}" #1
+ Tmp-String-0 += "%{Tunnel-Server-Endpoint:1}" #2
+ Tmp-String-0 += "%{Tunnel-Server-Endpoint:2[0]}" #3
+ Tmp-String-0 += "%{Tunnel-Server-Endpoint:2[1]}" #4
+ Tmp-String-0 += "%{Tunnel-Server-Endpoint:0[0]}" #5
+ Tmp-String-0 += "%{Tunnel-Server-Endpoint:0[1]}" #6
+ Tmp-String-0 += "%{Tunnel-Server-Endpoint:0[2]}" #7
+ Tmp-String-0 += "%{Tunnel-Server-Endpoint[0]}" #8
+ Tmp-String-0 += "%{Tunnel-Server-Endpoint[1]}" #9
+ Tmp-String-0 += "%{Tunnel-Server-Endpoint[2]}" #10
+}
+
+# Check that access attributes by tag works first
+if (Tmp-String-0[0] != '192.0.3.1') {
+ update {
+ reply:Filter-Id += 'fail 13'
+ }
+}
+
+if (Tmp-String-0[1] == '192.0.3.2') {
+ update {
+ reply:Filter-Id += 'fail 14'
+ }
+}
+
+if (Tmp-String-0[2] != '192.0.2.1') {
+ update {
+ reply:Filter-Id += 'fail 15'
+ }
+}
+
+# Get the first instance of Tunnel-Server-Endpoint:2
+if (Tmp-String-0[3] != '192.0.3.1') {
+ update {
+ reply:Filter-Id += 'fail 16'
+ }
+}
+
+# Get the first instance of Tunnel-Server-Endpoint:2
+if (Tmp-String-0[4] != '192.0.3.2') {
+ update {
+ reply:Filter-Id += 'fail 17'
+ }
+}
+
+# Now check the assignment
+if (Tmp-String-0[5] != '192.0.1.1') {
+ update {
+ reply:Filter-Id += 'fail 18'
+ }
+}
+
+if (Tmp-String-0[6] != '192.0.1.2') {
+ update {
+ reply:Filter-Id += 'fail 19'
+ }
+}
+
+if (Tmp-String-0[7] != '') {
+ update {
+ reply:Filter-Id += 'fail 20'
+ }
+}
+
+if (Tmp-String-0[8] != '192.0.1.1') {
+ update {
+ reply:Filter-Id += 'fail 21'
+ }
+}
+
+if (Tmp-String-0[9] != '192.0.1.2') {
+ update {
+ reply:Filter-Id += 'fail 22'
+ }
+}
+
+if (Tmp-String-0[10] != '192.0.2.1') {
+ update {
+ reply:Filter-Id += 'fail 23'
+ }
+}
diff --git a/src/tests/keywords/xlat-concat b/src/tests/keywords/xlat-concat
new file mode 100644
index 0000000..e0c55a9
--- /dev/null
+++ b/src/tests/keywords/xlat-concat
@@ -0,0 +1,40 @@
+#
+# PRE: xlat-list
+#
+# concat xlat
+#
+
+update control {
+ control !* ANY
+}
+
+update control {
+ Tmp-IP-Address-0 := 192.0.2.1
+ Tmp-IP-Address-0 += 192.0.2.2
+}
+
+update request {
+ Tmp-String-0 := "%{concat:control:[*] ;}"
+}
+
+if (Tmp-String-0 != '192.0.2.1;192.0.2.2') {
+ update {
+ reply:Filter-Id += 'fail 1'
+ }
+}
+
+update request {
+ Tmp-String-0 := "%{concat:control:[*] X}"
+}
+
+if (Tmp-String-0 != '192.0.2.1X192.0.2.2') {
+ update {
+ reply:Filter-Id += 'fail 2'
+ }
+}
+
+# Boilerplate junk
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := 'filter'
+}
diff --git a/src/tests/keywords/xlat-error b/src/tests/keywords/xlat-error
new file mode 100644
index 0000000..b6a2587
--- /dev/null
+++ b/src/tests/keywords/xlat-error
@@ -0,0 +1,12 @@
+#
+# PRE: if xlat-attr-tag
+#
+
+#
+# missing a closing } for the expansion in the string.
+#
+if ("%{expr:1 + 2" == 3) { # ERROR
+ update reply {
+ Filter-Id := "fail"
+ }
+}
diff --git a/src/tests/keywords/xlat-explode b/src/tests/keywords/xlat-explode
new file mode 100644
index 0000000..ea727d9
--- /dev/null
+++ b/src/tests/keywords/xlat-explode
@@ -0,0 +1,91 @@
+#
+# PRE: update
+#
+# Check explode works correctly
+#
+update {
+ request:Class := '1=1|my_attr=2|my_attr=hello|'
+ request:Calling-Station-ID += '|'
+ control:User-Name += '|hello|goodbye'
+ control:User-Name += '|morning|night|1|'
+ control:Reply-Message := 'Can\'t touch this'
+ reply:Filter-Id = 'filter'
+}
+
+if ("%{explode:&Class |}" != 3) {
+ update reply {
+ Filter-Id += 'Fail 0'
+ }
+}
+
+if ("%{Class[#]}" != 3) {
+ update reply {
+ Filter-Id += 'Fail 1'
+ }
+}
+
+if ((&Class[0] != '1=1') || (&Class[1] != 'my_attr=2') || (&Class[2] != 'my_attr=hello')) {
+ update reply {
+ Filter-Id += 'Fail 2'
+ }
+}
+
+if (&Class[3]) {
+ update reply {
+ Filter-Id += 'Fail 3'
+ }
+}
+
+if ("%{explode:&control:Calling-Station-Id |}" != 0) {
+ update reply {
+ filter-Id += 'Fail 4'
+ }
+}
+
+if ("%{explode:&control:User-Name[*] |}" != 5) {
+ update reply {
+ Filter-Id += 'Fail 5'
+ }
+}
+
+if ("%{control:User-Name[#]}" != 5) {
+ update reply {
+ Filter-Id += 'Fail 6'
+ }
+}
+
+if ((&control:User-Name[0] != 'hello') || \
+ (&control:User-Name[1] != 'goodbye') || \
+ (&control:User-Name[2] != 'morning') || \
+ (&control:User-Name[3] != 'night') || \
+ (&control:User-Name[4] != '1')) {
+ update reply {
+ Filter-Id += 'Fail 7'
+ }
+}
+
+if (&control:User-Name[5]) {
+ update reply {
+ Filter-Id += 'Fail 8'
+ }
+}
+
+if ("%{explode:&control:Reply-Message |}" != 0) {
+ update reply {
+ Filter-Id += 'Fail 9'
+ }
+}
+
+if ("%{control:Reply-Message[#]}" != 1) {
+ update reply {
+ Filter-Id += 'Fail 10'
+ }
+}
+
+if (&control:Reply-Message != 'Can\'t touch this') {
+ update reply {
+ Filter-Id += 'Fail 11'
+ }
+}
+
+debug_all
diff --git a/src/tests/keywords/xlat-list b/src/tests/keywords/xlat-list
new file mode 100644
index 0000000..fcd9e84
--- /dev/null
+++ b/src/tests/keywords/xlat-list
@@ -0,0 +1,64 @@
+#
+# PRE: update
+#
+update control {
+ control !* ANY
+}
+
+update control {
+ Tmp-IP-Address-0 := 192.0.2.1
+ Tmp-IP-Address-0 += 192.0.2.2
+}
+
+if ("%{control:[#]}" != 2) {
+ update {
+ reply:Filter-Id += 'fail 0'
+ }
+}
+
+debug_control
+
+if (("%{control:[0]}" != 192.0.2.1) || ("%{control:[1]}" != 192.0.2.2)) {
+ update {
+ reply:Filter-Id += 'fail 1'
+ }
+}
+
+if (("%{control:[n]}" != 192.0.2.2)) {
+ update {
+ reply:Filter-Id += 'fail 1a'
+ }
+}
+
+if ("%{control:[*]}" != '192.0.2.1,192.0.2.2') {
+ update {
+ reply:Filter-Id += 'fail 2'
+ }
+}
+
+# Try calling these xlats in mapping too, they may get optimised to VPTs which is a
+# different code path.
+update request {
+ Tmp-IP-Address-1 += "%{control:[1]}"
+ Tmp-IP-Address-1 += "%{control:[0]}"
+ Tmp-String-0 = "%{control:[*]}"
+ Tmp-Integer-0 = "%{control:[#]}"
+}
+
+if (Tmp-String-0 != '192.0.2.1,192.0.2.2') {
+ update {
+ reply:Filter-Id += 'fail 3'
+ }
+}
+
+if (Tmp-Integer-0 != 2) {
+ update {
+ reply:Filter-Id += 'fail 4'
+ }
+}
+
+# Boilerplate junk
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := 'filter'
+}
diff --git a/src/tests/keywords/xlat-octets b/src/tests/keywords/xlat-octets
new file mode 100644
index 0000000..ea9543c
--- /dev/null
+++ b/src/tests/keywords/xlat-octets
@@ -0,0 +1,36 @@
+#
+# PRE: update if
+#
+# Remove all attributes in a list
+#
+update {
+ control:Cleartext-Password := 'hello'
+ reply:Filter-Id := 'filter'
+}
+
+#
+# Regression test for 0x prefix. xlat expanded
+# octet strings must NOT have a 0x prefix added
+#
+update request {
+ Tmp-Octets-0 := 0x0001020304050607
+ Tmp-Octets-0 += 0x0706050403020100
+}
+
+if ("%{Tmp-Octets-0}" != '0x0001020304050607') {
+ update {
+ reply:Filter-Id := 'fail 1'
+ }
+}
+
+if ("%{Tmp-Octets-0[0]}" != '0x0001020304050607') {
+ update {
+ reply:Filter-Id += 'fail 2'
+ }
+}
+
+if ("%{Tmp-Octets-0[*]}" != '0x0001020304050607,0x0706050403020100') {
+ update {
+ reply:Filter-Id += 'fail 3'
+ }
+}
diff --git a/src/tests/keywords/xlat-virtual-attr b/src/tests/keywords/xlat-virtual-attr
new file mode 100644
index 0000000..e476993
--- /dev/null
+++ b/src/tests/keywords/xlat-virtual-attr
@@ -0,0 +1,131 @@
+#
+# PRE: if
+#
+
+update reply {
+ Filter-Id := "filter"
+}
+
+if ("%{Client-Shortname}" != '<UNKNOWN-CLIENT>') {
+ update reply {
+ Filter-Id += "fail 0"
+ }
+}
+
+if ("%{Request-Processing-Stage}" != 'authorize') {
+ update reply {
+ Filter-Id += "fail 1"
+ }
+}
+
+if ("%{Virtual-Server}" != 'default') {
+ update reply {
+ Filter-Id += "fail 2"
+ }
+}
+
+if ("%{Module-Return-Code}" != '') {
+ update reply {
+ Filter-Id += "fail 3a"
+ }
+}
+
+ok
+if ("%{Module-Return-Code}" != 'ok') {
+ update reply {
+ Filter-Id += "fail 3b"
+ }
+}
+
+if ("%{Packet-Type}" != 'Access-Request') {
+ update reply {
+ Filter-Id += "fail 4"
+ }
+}
+
+# Response hasn't been set yet
+if ("%{Response-Packet-Type}" != '') {
+ update reply {
+ Filter-Id += "fail 5"
+ }
+}
+
+if ("%{Packet-Authentication-Vector}" != '0x00000000000000000000000000000000') {
+ update reply {
+ Filter-Id += "fail 6"
+ }
+}
+
+if ("%{Client-IP-Address}" != 127.0.0.1) {
+ update reply {
+ Filter-Id += "fail 7a"
+ }
+}
+
+if ("%{Packet-Src-IP-Address}" != 127.0.0.1) {
+ update reply {
+ Filter-Id += "fail 7b"
+ }
+}
+
+if ("%{Packet-Dst-IP-Address}" != 127.0.0.1) {
+ update reply {
+ Filter-Id += "fail 8"
+ }
+}
+
+# Can't have both...
+if ("%{Packet-Src-IPv6-Address}" != '') {
+ update reply {
+ Filter-Id += "fail 9"
+ }
+}
+
+if ("%{Packet-Dst-IPv6-Address}" != '') {
+ update reply {
+ Filter-Id += "fail 10"
+ }
+}
+
+if ("%{Packet-Src-Port}" != '18120') {
+ update reply {
+ Filter-Id += "fail 11"
+ }
+}
+
+if ("%{Packet-Dst-Port}" != '1812') {
+ update reply {
+ Filter-Id += "fail 12"
+ }
+}
+
+
+# We should allow the user to overload virtual attributes
+update request {
+ Client-Shortname := 'my_test_client'
+}
+
+if ("%{Client-Shortname}" != 'my_test_client') {
+ update reply {
+ Filter-Id += "fail 13"
+ }
+}
+
+# Operations on virtual attributes should be the same as on real ones
+if ("%{Virtual-Server[0]}" != 'default') {
+ update reply {
+ Filter-Id += "fail 14"
+ }
+}
+
+if ("%{Virtual-Server[*]}" != 'default') {
+ update reply {
+ Filter-Id += "fail 15"
+ }
+}
+
+if ("%{Virtual-Server[#]}" != 1) {
+ update reply {
+ Filter-Id += "fail 16"
+ }
+}
diff --git a/src/tests/map/all.mk b/src/tests/map/all.mk
new file mode 100644
index 0000000..fedd29d
--- /dev/null
+++ b/src/tests/map/all.mk
@@ -0,0 +1 @@
+SUBMAKEFILES := map_unit.mk map_tests.mk
diff --git a/src/tests/map/base b/src/tests/map/base
new file mode 100644
index 0000000..633c32a
--- /dev/null
+++ b/src/tests/map/base
@@ -0,0 +1,6 @@
+update request {
+ Filter-Id := "filter"
+ User-Name := "blah"
+
+ &reply:Filter-Id += &request:Filter-Id[*]
+}
diff --git a/src/tests/map/base.out b/src/tests/map/base.out
new file mode 100644
index 0000000..34c519b
--- /dev/null
+++ b/src/tests/map/base.out
@@ -0,0 +1,5 @@
+update request {
+ &Filter-Id := "filter"
+ &User-Name := "blah"
+ &reply:Filter-Id += &Filter-Id[*]
+}
diff --git a/src/tests/map/count-error b/src/tests/map/count-error
new file mode 100644
index 0000000..925360d
--- /dev/null
+++ b/src/tests/map/count-error
@@ -0,0 +1,6 @@
+#
+# This should be an xlat, not a direct assignment
+#
+update request {
+ Tmp-Integer-0 := &Filter-Id[#] # ERROR
+} \ No newline at end of file
diff --git a/src/tests/map/count-list-error b/src/tests/map/count-list-error
new file mode 100644
index 0000000..a7beae1
--- /dev/null
+++ b/src/tests/map/count-list-error
@@ -0,0 +1,6 @@
+#
+# Updating lists isn't allowed
+#
+update {
+ &request:Filter-Id := &Filter-Id[#] # ERROR
+}
diff --git a/src/tests/map/map_tests.mk b/src/tests/map/map_tests.mk
new file mode 100644
index 0000000..7474489
--- /dev/null
+++ b/src/tests/map/map_tests.mk
@@ -0,0 +1,50 @@
+MAP_TESTS := $(patsubst $(top_srcdir)/src/tests/map/%,%,$(filter-out %.conf %.md %.attrs %.c %.mk %~ %.rej %.out,$(wildcard $(top_srcdir)/src/tests/map/*)))
+MAP_OUTPUT := $(addsuffix .out,$(addprefix $(BUILD_DIR)/tests/map/,$(MAP_TESTS)))
+MAP_UNIT_BIN := $(BUILD_DIR)/bin/local/map_unit
+MAP_UNIT := ./build/make/jlibtool --silent --mode=execute $(MAP_UNIT_BIN)
+
+.PHONY: $(BUILD_DIR)/tests/map/
+$(BUILD_DIR)/tests/map/:
+ @mkdir -p $@
+
+#
+# Re-run the tests if the test program changes
+#
+# Create the output directory before the files
+#
+$(MAP_OUTPUT): $(MAP_UNIT_BIN) | $(BUILD_DIR)/tests/map/
+
+#
+# Re-run the tests if the input file changes
+#
+$(BUILD_DIR)/tests/map/%.out: $(top_srcdir)/src/tests/map/%
+ @echo MAP_TEST $(notdir $<)
+ @if ! $(MAP_UNIT) -d $(top_srcdir)/raddb -D $(top_srcdir)/share $< > $@ 2>&1; then \
+ if ! grep ERROR $< 2>&1 > /dev/null; then \
+ cat $@; \
+ echo "# $@"; \
+ echo FAILED: "$(MAP_UNIT) -d $(top_srcdir)/raddb -D $(top_srcdir)/share $<"; \
+ exit 1; \
+ fi; \
+ FOUND=$$(grep $< $@ | head -1 | sed 's,^.*$(top_srcdir),,;s/:.*//;s/.*\[//;s/\].*//'); \
+ EXPECTED=$$(grep -n ERROR $< | sed 's/:.*//'); \
+ if [ "$$EXPECTED" != "$$FOUND" ]; then \
+ cat $@; \
+ echo "# $@"; \
+ echo "E $$EXPECTED F $$FOUND"; \
+ echo UNEXPECTED ERROR: "$(MAP_UNIT) -d $(top_srcdir)/raddb -D $(top_srcdir)/share $<"; \
+ exit 1; \
+ fi; \
+ else \
+ if ! diff $<.out $@; then \
+ echo FAILED: " diff $<.out $@"; \
+ echo FAILED: "$(MAP_UNIT) -d $(top_srcdir)/raddb -D $(top_srcdir)/share $<"; \
+ exit 1; \
+ fi; \
+ fi
+
+TESTS.MAP_FILES := $(MAP_OUTPUT)
+
+$(TESTS.MAP_FILES): $(TESTS.UNIT_FILES)
+
+tests.map: $(MAP_OUTPUT)
diff --git a/src/tests/map/map_unit.c b/src/tests/map/map_unit.c
new file mode 100644
index 0000000..af6d016
--- /dev/null
+++ b/src/tests/map/map_unit.c
@@ -0,0 +1,219 @@
+/*
+ * radattr.c Map debugging tool.
+ *
+ * Version: $Id$
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Copyright 2015 Alan DeKok <aland@freeradius.org>
+ */
+
+RCSID("$Id$")
+
+#include <freeradius-devel/libradius.h>
+
+#include <freeradius-devel/conf.h>
+#include <freeradius-devel/modpriv.h>
+#include <freeradius-devel/modcall.h>
+
+#include <ctype.h>
+
+#ifdef HAVE_GETOPT_H
+# include <getopt.h>
+#endif
+
+#include <assert.h>
+
+#include <freeradius-devel/log.h>
+
+#include <sys/wait.h>
+
+/* Linker hacks */
+
+#ifdef HAVE_PTHREAD_H
+pid_t rad_fork(void)
+{
+ return fork();
+}
+
+pid_t rad_waitpid(pid_t pid, int *status)
+{
+ return waitpid(pid, status, 0);
+}
+#endif
+
+rlm_rcode_t indexed_modcall(UNUSED rlm_components_t comp, UNUSED int idx, UNUSED REQUEST *request)
+{
+ return RLM_MODULE_OK;
+}
+
+char const *get_radius_dir(void)
+{
+ return NULL;
+}
+
+module_instance_t *module_instantiate(UNUSED CONF_SECTION *modules, UNUSED char const *askedname)
+{
+ return NULL;
+}
+
+module_instance_t *module_instantiate_method(UNUSED CONF_SECTION *modules, UNUSED char const *name, UNUSED rlm_components_t *method)
+{
+ return NULL;
+}
+
+/* Linker hacks */
+
+static void NEVER_RETURNS usage(void)
+{
+ fprintf(stderr, "usage: map_unit [OPTS] filename ...\n");
+ fprintf(stderr, " -d <raddb> Set user dictionary directory (defaults to " RADDBDIR ").\n");
+ fprintf(stderr, " -D <dictdir> Set main dictionary directory (defaults to " DICTDIR ").\n");
+ fprintf(stderr, " -O <output_dir> Set output directory\n");
+ fprintf(stderr, " -x Debugging mode.\n");
+ fprintf(stderr, " -M Show program version information.\n");
+
+ exit(1);
+}
+
+static int process_file(char const *filename)
+{
+ int rcode;
+ char const *name1, *name2;
+ CONF_SECTION *cs, *main_cs;
+ vp_map_t *head, *map;
+ char buffer[8192];
+
+ main_cs = cf_section_alloc(NULL, "main", NULL);
+ if (cf_file_read(main_cs, filename) < 0) {
+ fprintf(stderr, "map_unit: Failed parsing %s\n",
+ filename);
+ exit(1);
+ }
+
+ /*
+ * Always has to be an "update" section.
+ */
+ cs = cf_section_sub_find(main_cs, "update");
+ if (!cs) {
+ talloc_free(main_cs);
+ return -1;
+ }
+
+ /*
+ * Convert the update section to a list of maps.
+ */
+ rcode = map_afrom_cs(&head, cs, PAIR_LIST_REQUEST, PAIR_LIST_REQUEST, modcall_fixup_update, NULL, 128);
+ if (rcode < 0) return -1; /* message already printed */
+ if (!head) {
+ cf_log_err_cs(cs, "'update' sections cannot be empty");
+ return -1;
+ }
+
+ buffer[0] = '\t';
+
+ name1 = cf_section_name1(cs);
+ name2 = cf_section_name2(cs);
+
+ /*
+ * And print it all out.
+ */
+ if (!name2) {
+ printf("%s {\n", name1);
+ } else {
+ printf("%s %s {\n", name1, name2);
+ }
+
+ for (map = head; map != NULL; map = map->next) {
+ map_prints(buffer + 1, sizeof(buffer) - 1, map);
+ puts(buffer);
+ }
+ printf("}\n");
+
+ talloc_free(main_cs);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ int c, rcode = 0;
+ bool report = false;
+ char const *radius_dir = RADDBDIR;
+ char const *dict_dir = DICTDIR;
+
+ cf_new_escape = true; /* fix the tests */
+
+#ifndef NDEBUG
+ if (fr_fault_setup(getenv("PANIC_ACTION"), argv[0]) < 0) {
+ fr_perror("radattr");
+ exit(EXIT_FAILURE);
+ }
+#endif
+
+ while ((c = getopt(argc, argv, "d:D:xMh")) != EOF) switch (c) {
+ case 'd':
+ radius_dir = optarg;
+ break;
+ case 'D':
+ dict_dir = optarg;
+ break;
+ case 'x':
+ fr_debug_lvl++;
+ rad_debug_lvl = fr_debug_lvl;
+ break;
+ case 'M':
+ report = true;
+ break;
+ case 'h':
+ default:
+ usage();
+ }
+ argc -= (optind - 1);
+ argv += (optind - 1);
+
+ /*
+ * Mismatch between the binary and the libraries it depends on
+ */
+ if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) {
+ fr_perror("radattr");
+ return 1;
+ }
+
+ if (dict_init(dict_dir, RADIUS_DICTIONARY) < 0) {
+ fr_perror("radattr");
+ return 1;
+ }
+
+ if (dict_read(radius_dir, RADIUS_DICTIONARY) == -1) {
+ fr_perror("radattr");
+ return 1;
+ }
+
+ if (argc < 2) {
+ rcode = process_file("-");
+
+ } else {
+ rcode = process_file(argv[1]);
+ }
+
+ if (report) {
+ dict_free();
+ fr_log_talloc_report(NULL);
+ }
+
+ if (rcode < 0) rcode = 1; /* internal to Unix process return code */
+
+ return rcode;
+}
diff --git a/src/tests/map/map_unit.mk b/src/tests/map/map_unit.mk
new file mode 100644
index 0000000..88d319b
--- /dev/null
+++ b/src/tests/map/map_unit.mk
@@ -0,0 +1,5 @@
+TARGET := map_unit
+SOURCES := map_unit.c ${top_srcdir}/src/main/modcall.c
+
+TGT_PREREQS := libfreeradius-server.a libfreeradius-radius.a
+TGT_LDLIBS := $(LIBS)
diff --git a/src/tests/modules/README.rst b/src/tests/modules/README.rst
new file mode 100644
index 0000000..164509d
--- /dev/null
+++ b/src/tests/modules/README.rst
@@ -0,0 +1,18 @@
+Module Tests
+------------
+
+To test module `foo`, create a directory `foo`, and put a file `all.mk` into it, e.g.
+
+ foo/all.mk
+
+All of the tests for the module should go here. The tests will be run
+*only* if the module is available, and has been built correctly on the system.
+
+The file should contain a target "MODULE.test". This is the main
+target used to test the module. The framework automatically makes the
+tests depend on the module (i.e. library). So if the module source
+changes, you can just do `make MODULE.test`. The module will be
+re-built, and the tests will be run.
+
+Note: all SQL tests share the same tests definitions (see sql directory).
+The modules themselves simply link to the actual tests files.
diff --git a/src/tests/modules/all.mk b/src/tests/modules/all.mk
new file mode 100644
index 0000000..9960df7
--- /dev/null
+++ b/src/tests/modules/all.mk
@@ -0,0 +1,40 @@
+#
+# Find the subdirs which have "all.mk"
+#
+TEST_SUBDIRS := $(patsubst src/tests/modules/%/all.mk,%,$(wildcard src/tests/modules/*/all.mk))
+
+#
+# Find out which of those have a similar target. i.e. modules/foo -> rlm_foo.la
+#
+TEST_TARGETS := $(foreach x,$(TEST_SUBDIRS),$(findstring rlm_$x.la,$(ALL_TGTS)))
+
+TEST_BUILT := $(patsubst rlm_%.la,%,$(TEST_TARGETS))
+
+#
+# Ensure that the tests depend on the module, so that changes to the
+# module will re-run the test
+#
+$(foreach x,$(TEST_BUILT),$(eval $x.test: rlm_$x.la))
+
+######################################################################
+
+#
+# And do the same thing for sub-directories
+#
+TEST_SUBSUBDIRS := $(patsubst src/tests/modules/%/all.mk,%,$(wildcard src/tests/modules/*/*/all.mk))
+
+TEST_SUBTARGETS := $(foreach x,$(TEST_SUBSUBDIRS),$(findstring rlm_$(subst /,_,$x).la,$(ALL_TGTS)))
+
+TEST_SUBBUILT := $(patsubst rlm_%.la,%,$(TEST_SUBTARGETS))
+
+$(foreach x,$(TEST_SUBBUILT),$(eval $x.test: rlm_$(subst /,_,$x).la))
+
+######################################################################
+#
+# For the remaining subdirs, add on the directory to include.
+# test.mk will run the tests for all modules
+# It is included last so that the module specific makefiles can be processed first
+# (modules that require a test server can set the corresponding require_test_server variable)
+#
+SUBMAKEFILES := $(addsuffix /all.mk,$(TEST_BUILT) $(subst _,/,$(TEST_SUBBUILT))) test.mk
+
diff --git a/src/tests/modules/always/all.mk b/src/tests/modules/always/all.mk
new file mode 100644
index 0000000..8f1127f
--- /dev/null
+++ b/src/tests/modules/always/all.mk
@@ -0,0 +1,3 @@
+#
+# Test the "always" module
+#
diff --git a/src/tests/modules/always/module.conf b/src/tests/modules/always/module.conf
new file mode 100644
index 0000000..39995e5
--- /dev/null
+++ b/src/tests/modules/always/module.conf
@@ -0,0 +1,7 @@
+always my_reject {
+ rcode = reject
+}
+
+always db_status {
+ rcode = ok
+}
diff --git a/src/tests/modules/always/replace.unlang b/src/tests/modules/always/replace.unlang
new file mode 100644
index 0000000..1d502f7
--- /dev/null
+++ b/src/tests/modules/always/replace.unlang
@@ -0,0 +1,11 @@
+%{poke:my_reject.rcode=ok}
+
+my_reject # should be "ok"
+
+update control {
+ Cleartext-Password := "hello"
+}
+
+update reply {
+ Filter-Id := "success"
+}
diff --git a/src/tests/modules/always/set_rcode.unlang b/src/tests/modules/always/set_rcode.unlang
new file mode 100644
index 0000000..faaed28
--- /dev/null
+++ b/src/tests/modules/always/set_rcode.unlang
@@ -0,0 +1,44 @@
+#
+# Set status to "notfound". xlat should expand to previous status, "alive"
+#
+if ("%{db_status:notfound}" != "alive") {
+ update reply {
+ Filter-Id += "failed"
+ }
+}
+
+
+#
+# Verify that the status was changed
+#
+db_status
+if (!notfound) {
+ update reply {
+ Filter-Id += "failed"
+ }
+}
+
+
+#
+# Fetch status using xlat without setting the status
+#
+if ("%{db_status:}" != "notfound") {
+ update reply {
+ Filter-Id += "failed"
+ }
+}
+
+
+#
+# Verify that the status did not change
+#
+db_status
+if (notfound) {
+ update reply {
+ Filter-Id += "success"
+ }
+}
+
+update control {
+ Cleartext-Password := "hello"
+}
diff --git a/src/tests/modules/always/set_status_dead.unlang b/src/tests/modules/always/set_status_dead.unlang
new file mode 100644
index 0000000..6b29ede
--- /dev/null
+++ b/src/tests/modules/always/set_status_dead.unlang
@@ -0,0 +1,18 @@
+#
+# Set the module status to dead, call it and check that it fails
+#
+%{db_status:dead}
+
+db_status {
+ fail = 1
+}
+
+if (fail) {
+ update reply {
+ Filter-Id := "success"
+ }
+}
+
+update control {
+ Cleartext-Password := "hello"
+}
diff --git a/src/tests/modules/always/set_status_revive.unlang b/src/tests/modules/always/set_status_revive.unlang
new file mode 100644
index 0000000..3e71d39
--- /dev/null
+++ b/src/tests/modules/always/set_status_revive.unlang
@@ -0,0 +1,28 @@
+#
+# Fail a module...
+#
+%{db_status:dead}
+db_status {
+ fail = 1
+}
+if (!fail) {
+ update reply {
+ Filter-Id += "failed"
+ }
+}
+
+
+#
+# ... Now revive it
+#
+%{db_status:alive}
+db_status
+if (ok) {
+ update reply {
+ Filter-Id += "success"
+ }
+}
+
+update control {
+ Cleartext-Password := "hello"
+}
diff --git a/src/tests/modules/cache/rbtree/all.mk b/src/tests/modules/cache/rbtree/all.mk
new file mode 100644
index 0000000..8f89aa6
--- /dev/null
+++ b/src/tests/modules/cache/rbtree/all.mk
@@ -0,0 +1,2 @@
+cache_rbtree.test:
+
diff --git a/src/tests/modules/default-input.attrs b/src/tests/modules/default-input.attrs
new file mode 100644
index 0000000..d24ac4b
--- /dev/null
+++ b/src/tests/modules/default-input.attrs
@@ -0,0 +1,11 @@
+#
+# Input packet
+#
+User-Name = "bob"
+User-Password = "hello"
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+Filter-Id == 'success'
diff --git a/src/tests/modules/files/addcontrol.attrs b/src/tests/modules/files/addcontrol.attrs
new file mode 100644
index 0000000..7588b9c
--- /dev/null
+++ b/src/tests/modules/files/addcontrol.attrs
@@ -0,0 +1,13 @@
+#
+# Input packet
+#
+User-Name = "addcontrol"
+User-Password = "testing123"
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+Cleartext-Password == 'testing123'
+Reply-Message == "success1"
+Reply-Message == "success2"
diff --git a/src/tests/modules/files/addcontrol.unlang b/src/tests/modules/files/addcontrol.unlang
new file mode 100644
index 0000000..5b431f1
--- /dev/null
+++ b/src/tests/modules/files/addcontrol.unlang
@@ -0,0 +1,8 @@
+#
+# Run the "files" module
+#
+files
+
+update {
+ &reply: += &control:[*]
+}
diff --git a/src/tests/modules/files/addreply.attrs b/src/tests/modules/files/addreply.attrs
new file mode 100644
index 0000000..69e1a19
--- /dev/null
+++ b/src/tests/modules/files/addreply.attrs
@@ -0,0 +1,12 @@
+#
+# Input packet
+#
+User-Name = "addreply"
+User-Password = "testing123"
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+Reply-Message == 'success1'
+Reply-Message == 'success2'
diff --git a/src/tests/modules/files/addreply.unlang b/src/tests/modules/files/addreply.unlang
new file mode 100644
index 0000000..456c666
--- /dev/null
+++ b/src/tests/modules/files/addreply.unlang
@@ -0,0 +1,4 @@
+#
+# Run the "files" module
+#
+files
diff --git a/src/tests/modules/files/all.mk b/src/tests/modules/files/all.mk
new file mode 100644
index 0000000..07449db
--- /dev/null
+++ b/src/tests/modules/files/all.mk
@@ -0,0 +1,3 @@
+#
+# Test the "files" module
+#
diff --git a/src/tests/modules/files/authorize b/src/tests/modules/files/authorize
new file mode 100644
index 0000000..b85f6a2
--- /dev/null
+++ b/src/tests/modules/files/authorize
@@ -0,0 +1,92 @@
+#
+# Test if the "users" file works
+#
+
+
+#
+# Basic syntax tests with comments. Parsing only.
+#
+
+user Cleartext-Password := "hello" # comment!
+
+
+user2 # comment!
+ Reply-Message := "24"
+
+
+#
+# Setting ":=" of reply and control items
+#
+
+bob Cleartext-Password := "hello"
+ Reply-Message := "success"
+
+
+#
+# Detect erroneous Fall-Through
+#
+
+doug Cleartext-Password := "goodbye"
+ Reply-Message := "success"
+
+doug
+ Reply-Message := "unreachable"
+
+
+#
+# Fall-Through across a non-matching entry
+#
+
+famous Cleartext-Password := "bradpitt"
+ Fall-Through = yes
+
+unused Cleartext-Password := "jabberwocky"
+ Reply-Message := "fail"
+
+famous
+ Reply-Message := "success"
+
+
+#
+# Modification of the reply list
+#
+
+addreply Cleartext-Password := "testing123"
+ Reply-Message := "success1",
+ Fall-Through = yes
+
+addreply
+ Reply-Message += "success2"
+
+
+subreply Cleartext-Password := "testing123"
+ Reply-Message := "success1",
+ Reply-Message += "success2",
+ Reply-Message += "success3",
+ Fall-Through = yes
+
+subreply Cleartext-Password := "testing123"
+ Reply-Message -= "success2"
+
+
+filterreply Cleartext-Password := "testing123"
+ Reply-Message := "success1",
+ Reply-Message += "success2",
+ Fall-Through = yes
+
+filterreply Cleartext-Password := "testing123"
+ Reply-Message !* ANY
+
+
+#
+# Addition "+=" to the control list
+#
+# Note: Set ":=" of control items is already tested with Cleartext-Password
+# Note: Filtering "!*" does not apply to control items as this would overload
+# the operator syntax since "!*" checks that no such attribute in the
+# request.
+
+addcontrol Cleartext-Password := "testing123", Reply-Message := "success1"
+ Fall-Through = yes
+
+addcontrol Reply-Message += "success2"
diff --git a/src/tests/modules/files/bob.attrs b/src/tests/modules/files/bob.attrs
new file mode 100644
index 0000000..a4acfab
--- /dev/null
+++ b/src/tests/modules/files/bob.attrs
@@ -0,0 +1,11 @@
+#
+# Input packet
+#
+User-Name = "doug"
+User-Password = "goodbye"
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+Reply-Message == 'success'
diff --git a/src/tests/modules/files/bob.unlang b/src/tests/modules/files/bob.unlang
new file mode 100644
index 0000000..456c666
--- /dev/null
+++ b/src/tests/modules/files/bob.unlang
@@ -0,0 +1,4 @@
+#
+# Run the "files" module
+#
+files
diff --git a/src/tests/modules/files/doug.attrs b/src/tests/modules/files/doug.attrs
new file mode 100644
index 0000000..a4acfab
--- /dev/null
+++ b/src/tests/modules/files/doug.attrs
@@ -0,0 +1,11 @@
+#
+# Input packet
+#
+User-Name = "doug"
+User-Password = "goodbye"
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+Reply-Message == 'success'
diff --git a/src/tests/modules/files/doug.unlang b/src/tests/modules/files/doug.unlang
new file mode 100644
index 0000000..456c666
--- /dev/null
+++ b/src/tests/modules/files/doug.unlang
@@ -0,0 +1,4 @@
+#
+# Run the "files" module
+#
+files
diff --git a/src/tests/modules/files/fall-through.attrs b/src/tests/modules/files/fall-through.attrs
new file mode 100644
index 0000000..899d3d9
--- /dev/null
+++ b/src/tests/modules/files/fall-through.attrs
@@ -0,0 +1,11 @@
+#
+# Input packet
+#
+User-Name = "famous"
+User-Password = "bradpitt"
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+Reply-Message == 'success'
diff --git a/src/tests/modules/files/fall-through.unlang b/src/tests/modules/files/fall-through.unlang
new file mode 100644
index 0000000..456c666
--- /dev/null
+++ b/src/tests/modules/files/fall-through.unlang
@@ -0,0 +1,4 @@
+#
+# Run the "files" module
+#
+files
diff --git a/src/tests/modules/files/filterreply.attrs b/src/tests/modules/files/filterreply.attrs
new file mode 100644
index 0000000..c1add29
--- /dev/null
+++ b/src/tests/modules/files/filterreply.attrs
@@ -0,0 +1,10 @@
+#
+# Input packet
+#
+User-Name = "filterreply"
+User-Password = "testing123"
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/files/filterreply.unlang b/src/tests/modules/files/filterreply.unlang
new file mode 100644
index 0000000..456c666
--- /dev/null
+++ b/src/tests/modules/files/filterreply.unlang
@@ -0,0 +1,4 @@
+#
+# Run the "files" module
+#
+files
diff --git a/src/tests/modules/files/module.conf b/src/tests/modules/files/module.conf
new file mode 100644
index 0000000..12b46ac
--- /dev/null
+++ b/src/tests/modules/files/module.conf
@@ -0,0 +1,9 @@
+files {
+ # The default key attribute to use for matches. The content
+ # of this attribute is used to match the "name" of the
+ # entry.
+ #key = "%{%{Stripped-User-Name}:-%{User-Name}}"
+
+ # The old "users" style file is now located here.
+ filename = $ENV{MODULE_TEST_DIR}/authorize
+}
diff --git a/src/tests/modules/files/subreply.attrs b/src/tests/modules/files/subreply.attrs
new file mode 100644
index 0000000..6fe6237
--- /dev/null
+++ b/src/tests/modules/files/subreply.attrs
@@ -0,0 +1,12 @@
+#
+# Input packet
+#
+User-Name = "subreply"
+User-Password = "testing123"
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+Reply-Message == 'success1'
+Reply-Message == 'success3'
diff --git a/src/tests/modules/files/subreply.unlang b/src/tests/modules/files/subreply.unlang
new file mode 100644
index 0000000..456c666
--- /dev/null
+++ b/src/tests/modules/files/subreply.unlang
@@ -0,0 +1,4 @@
+#
+# Run the "files" module
+#
+files
diff --git a/src/tests/modules/json/all.mk b/src/tests/modules/json/all.mk
new file mode 100644
index 0000000..4d3197d
--- /dev/null
+++ b/src/tests/modules/json/all.mk
@@ -0,0 +1,3 @@
+#
+# Test the "json" module
+#
diff --git a/src/tests/modules/json/encode.attrs b/src/tests/modules/json/encode.attrs
new file mode 100644
index 0000000..ea8d653
--- /dev/null
+++ b/src/tests/modules/json/encode.attrs
@@ -0,0 +1,13 @@
+#
+# Input packet
+#
+User-Name = 'john'
+Filter-Id = "f1"
+Filter-Id += "f2"
+NAS-Port = 999
+Service-Type = Login-User
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/json/encode.unlang b/src/tests/modules/json/encode.unlang
new file mode 100644
index 0000000..6c7a1fe
--- /dev/null
+++ b/src/tests/modules/json/encode.unlang
@@ -0,0 +1,233 @@
+#
+# json_encode tests
+#
+
+
+# 0. Check basic xlat parsing
+
+update control {
+ &Tmp-String-1 := "%{json_encode:&request:[*]}"
+ &Tmp-String-2 := "%{json_encode:&request:[*] }"
+ &Tmp-String-3 := "%{json_encode: &request:[*]}"
+ &Tmp-String-4 := "%{json_encode: &request:[*] }"
+ &Tmp-String-5 := "%{json_encode: &request:[*] !&Filter-Id }"
+ &Tmp-String-6 := "%{json_encode:&request:[*] ! }"
+## Check defaults are the same as output_mode "object":
+ &Tmp-String-7 := "%{json_object_encode:&request:[*]}"
+ &Tmp-String-8 := "%{json_object_no_encode:&request:[*]}"
+}
+
+
+if (&control:Tmp-String-1 != '{"User-Name":{"type":"string","value":"john"},"Filter-Id":{"type":"string","value":["f1","f2"]},"NAS-Port":{"type":"integer","value":999},"Service-Type":{"type":"integer","value":"Login-User"}}') {
+ test_fail
+}
+
+# Check xlat input formats
+if (&control:Tmp-String-1 != &control:Tmp-String-2 || \
+ &control:Tmp-String-1 != &control:Tmp-String-3 || \
+ &control:Tmp-String-1 != &control:Tmp-String-4) {
+ test_fail
+}
+
+# Check defaults
+if (&control:Tmp-String-1 != &control:Tmp-String-7 || \
+ &control:Tmp-String-1 != &control:Tmp-String-8) {
+ test_fail
+}
+
+if (&control:Tmp-String-5 != '{"User-Name":{"type":"string","value":"john"},"NAS-Port":{"type":"integer","value":999},"Service-Type":{"type":"integer","value":"Login-User"}}') {
+ test_fail
+}
+
+if (&control:Tmp-String-6 != '') {
+ test_fail
+}
+
+update control {
+ &Tmp-String-1 !* ANY
+ &Tmp-String-2 !* ANY
+ &Tmp-String-3 !* ANY
+ &Tmp-String-4 !* ANY
+ &Tmp-String-5 !* ANY
+ &Tmp-String-6 !* ANY
+ &Tmp-String-7 !* ANY
+ &Tmp-String-8 !* ANY
+}
+
+
+# 1a. Output mode "object" tests
+
+# These are unsorted dictionaries. Hopefully json-c doesn't suddenly
+# decide that it's going to use a different ordering of the keys...
+
+update control {
+ &Tmp-String-1 := "%{json_object_encode:&request:[*]}"
+ &Tmp-String-2 := "%{json_object_ex_encode:&request:[*]}"
+}
+
+if (&control:Tmp-String-1 != '{"User-Name":{"type":"string","value":"john"},"Filter-Id":{"type":"string","value":["f1","f2"]},"NAS-Port":{"type":"integer","value":999},"Service-Type":{"type":"integer","value":"Login-User"}}') {
+ test_fail
+}
+
+if (&control:Tmp-String-2 != '{"pf:User-Name":{"type":"string","value":["john"]},"pf:Filter-Id":{"type":"string","value":["f1","f2"]},"pf:NAS-Port":{"type":"integer","value":["999"]},"pf:Service-Type":{"type":"integer","value":["1"]}}') {
+ test_fail
+}
+
+# 1b. "object" empty inputs
+
+update control {
+ &Tmp-String-1 := "%{json_object_encode:!&request:[*]}"
+}
+
+if (&control:Tmp-String-1 != '{}') {
+ test_fail
+}
+
+update control {
+ &Tmp-String-1 !* ANY
+ &Tmp-String-2 !* ANY
+ &Module-Failure-Message !* ANY
+}
+
+
+# 2a. Output mode "object_simple" tests
+
+update control {
+ &Tmp-String-1 := "%{json_object_simple_encode:&request:[*]}"
+ &Tmp-String-2 := "%{json_object_simple_ex_encode:&request:[*]}"
+}
+
+if (&control:Tmp-String-1 != '{"User-Name":"john","Filter-Id":["f1","f2"],"NAS-Port":999,"Service-Type":"Login-User"}') {
+ test_fail
+}
+
+if (&control:Tmp-String-2 != '{"pf:User-Name":["john"],"pf:Filter-Id":["f1","f2"],"pf:NAS-Port":["999"],"pf:Service-Type":["1"]}') {
+ test_fail
+}
+
+# 2b. "object_simple" empty inputs
+
+update control {
+ &Tmp-String-1 := "%{json_object_simple_encode:!&request:[*]}"
+}
+
+if (&control:Tmp-String-1 != '{}') {
+ test_fail
+}
+
+update control {
+ &Tmp-String-1 !* ANY
+ &Tmp-String-2 !* ANY
+ &Module-Failure-Message !* ANY
+}
+
+
+# 3a. Output mode "array" tests
+
+update control {
+ &Tmp-String-1 := "%{json_array_encode:&request:[*]}"
+ &Tmp-String-2 := "%{json_array_ex_encode:&request:[*]}"
+}
+
+if (&control:Tmp-String-1 != '[{"name":"User-Name","type":"string","value":"john"},{"name":"Filter-Id","type":"string","value":"f1"},{"name":"Filter-Id","type":"string","value":"f2"},{"name":"NAS-Port","type":"integer","value":999},{"name":"Service-Type","type":"integer","value":"Login-User"}]') {
+ test_fail
+}
+
+if (&control:Tmp-String-2 != '[{"name":"pf:User-Name","type":"string","value":["john"]},{"name":"pf:Filter-Id","type":"string","value":["f1","f2"]},{"name":"pf:NAS-Port","type":"integer","value":["999"]},{"name":"pf:Service-Type","type":"integer","value":["1"]}]') {
+ test_fail
+}
+
+# 3b. "array" empty inputs
+
+update control {
+ &Tmp-String-1 := "%{json_array_encode:!&request:[*]}"
+}
+
+if (&control:Tmp-String-1 != '[]') {
+ test_fail
+}
+
+update control {
+ &Tmp-String-1 !* ANY
+ &Tmp-String-2 !* ANY
+ &Module-Failure-Message !* ANY
+}
+
+
+# 4a. Output mode "array_of_names" tests
+
+update control {
+ &Tmp-String-1 := "%{json_array_names_encode:&request:[*]}"
+ &Tmp-String-2 := "%{json_array_names_ex_encode:&request:[*]}"
+}
+
+if (&control:Tmp-String-1 != '["User-Name","Filter-Id","Filter-Id","NAS-Port","Service-Type"]') {
+ test_fail
+}
+
+if (&control:Tmp-String-2 != '["pf:User-Name","pf:Filter-Id","pf:Filter-Id","pf:NAS-Port","pf:Service-Type"]') {
+ test_fail
+}
+
+# 4b. "array_of_names" empty inputs
+
+update control {
+ &Tmp-String-1 := "%{json_array_names_encode:!&request:[*]}"
+}
+
+if (&control:Tmp-String-1 != '[]') {
+ test_fail
+}
+
+update control {
+ &Tmp-String-1 !* ANY
+ &Tmp-String-2 !* ANY
+ &Module-Failure-Message !* ANY
+}
+
+
+# 5a. Output mode "array_of_values" tests
+
+update control {
+ &Tmp-String-1 := "%{json_array_values_encode:&request:[*]}"
+ &Tmp-String-2 := "%{json_array_values_ex_encode:&request:[*]}"
+}
+
+if (&control:Tmp-String-1 != '["john","f1","f2",999,"Login-User"]') {
+ test_fail
+}
+
+if (&control:Tmp-String-2 != '["john","f1","f2","999","1"]') {
+ test_fail
+}
+
+# 5b. "array_of_values" empty inputs
+
+update control {
+ &Tmp-String-1 := "%{json_array_values_encode:!&request:[*]}"
+}
+
+if (&control:Tmp-String-1 != '[]') {
+ test_fail
+}
+
+update control {
+ &Tmp-String-1 !* ANY
+ &Tmp-String-2 !* ANY
+ &Module-Failure-Message !* ANY
+}
+
+
+# Convert `make json.test` unlang update output to tests, for when
+# things need updating.
+#
+# cat \
+# | cut -c44- \
+# | sed -e 's/\\"/"/g' \
+# -e 's/\s*$//' \
+# -e "s/:= \"/== '/" \
+# -e 's/^/if (/' \
+# -e "s/\"$/') {/" \
+# -e "s/$/\n test_pass\n} else {\n test_fail\n}\n/"
+
+test_pass
diff --git a/src/tests/modules/json/module.conf b/src/tests/modules/json/module.conf
new file mode 100644
index 0000000..04d1b1d
--- /dev/null
+++ b/src/tests/modules/json/module.conf
@@ -0,0 +1,150 @@
+json {
+}
+
+
+#
+# Output mode "object"
+#
+
+json json_object {
+ encode {
+ output_mode = object
+ }
+}
+
+json json_object_no {
+ encode {
+ output_mode = object
+
+ value {
+ single_value_as_array = no
+ enum_as_integer = no
+ always_string = no
+ }
+ }
+}
+
+
+json json_object_ex {
+ encode {
+ output_mode = object
+
+ attribute {
+ prefix = "pf"
+ }
+
+ value {
+ single_value_as_array = yes
+ enum_as_integer = yes
+ always_string = yes
+ }
+ }
+}
+
+
+#
+# Output mode "object_simple"
+#
+
+json json_object_simple {
+ encode {
+ output_mode = object_simple
+ }
+}
+
+json json_object_simple_ex {
+ encode {
+ output_mode = object_simple
+
+ attribute {
+ prefix = "pf"
+ }
+
+ value {
+ single_value_as_array = yes
+ enum_as_integer = yes
+ always_string = yes
+ }
+ }
+}
+
+
+#
+# Output mode "array"
+#
+
+json json_array {
+ encode {
+ output_mode = array
+ }
+}
+
+json json_array_ex {
+ encode {
+ output_mode = array
+
+ attribute {
+ prefix = "pf"
+ }
+
+ value {
+ single_value_as_array = yes
+ enum_as_integer = yes
+ always_string = yes
+ }
+ }
+}
+
+
+#
+# Output mode "array_of_names"
+#
+
+json json_array_names {
+ encode {
+ output_mode = array_of_names
+ }
+}
+
+json json_array_names_ex {
+ encode {
+ output_mode = array_of_names
+
+ attribute {
+ prefix = "pf"
+ }
+
+ value {
+ single_value_as_array = yes # not valid
+ enum_as_integer = yes # not valid
+ always_string = yes # not valid
+ }
+ }
+}
+
+
+#
+# Output mode "array_of_values"
+#
+
+json json_array_values {
+ encode {
+ output_mode = array_of_values
+ }
+}
+
+json json_array_values_ex {
+ encode {
+ output_mode = array_of_values
+
+ attribute {
+ prefix = "pf" # not valid
+ }
+
+ value {
+ single_value_as_array = yes # not valid
+ enum_as_integer = yes
+ always_string = yes
+ }
+ }
+}
diff --git a/src/tests/modules/ldap/acct.attrs b/src/tests/modules/ldap/acct.attrs
new file mode 100644
index 0000000..1d57034
--- /dev/null
+++ b/src/tests/modules/ldap/acct.attrs
@@ -0,0 +1,35 @@
+#
+# Input packet
+#
+User-Name = 'john'
+NAS-Port = 17826193
+NAS-IP-Address = 192.0.2.10
+Framed-IP-Address = 198.51.100.59
+NAS-Identifier = 'nas.example.org'
+Acct-Status-Type = Start
+Acct-Delay-Time = 1
+Acct-Input-Octets = 0
+Acct-Output-Octets = 0
+Acct-Session-Id = '00000000'
+Acct-Unique-Session-Id = '00000000'
+Acct-Authentic = RADIUS
+Acct-Session-Time = 0
+Acct-Input-Packets = 0
+Acct-Output-Packets = 0
+Acct-Input-Gigawords = 0
+Acct-Output-Gigawords = 0
+Event-Timestamp = 'Feb 1 2015 08:28:58 WIB'
+NAS-Port-Type = Ethernet
+NAS-Port-Id = 'port 001'
+Service-Type = Framed-User
+Framed-Protocol = PPP
+Acct-Link-Count = 0
+Idle-Timeout = 0
+Session-Timeout = 604800
+Access-Loop-Encapsulation = 0x000000
+Proxy-State = 0x323531
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/ldap/acct.unlang b/src/tests/modules/ldap/acct.unlang
new file mode 100644
index 0000000..2297ea7
--- /dev/null
+++ b/src/tests/modules/ldap/acct.unlang
@@ -0,0 +1,23 @@
+#
+# Run the "ldap" module
+# PRE: auth
+#
+ldap.accounting {
+}
+if (ok) {
+ test_pass
+}
+else {
+ test_fail
+}
+
+update {
+ Tmp-String-0 := "%{ldap:ldap://$ENV{TEST_SERVER}/uid=john,ou=people,dc=example,dc=com?description}"
+}
+
+if (&Tmp-String-0 != "User john is online") {
+ test_fail
+}
+else {
+ test_pass
+}
diff --git a/src/tests/modules/ldap/all.mk b/src/tests/modules/ldap/all.mk
new file mode 100644
index 0000000..1fc53d1
--- /dev/null
+++ b/src/tests/modules/ldap/all.mk
@@ -0,0 +1,8 @@
+#
+# Test the "ldap" module
+#
+
+# MODULE.test is the main target for this module.
+
+# Don't test ldap if TEST_SERVER ENV is not set
+ldap_require_test_server := 1
diff --git a/src/tests/modules/ldap/auth.attrs b/src/tests/modules/ldap/auth.attrs
new file mode 100644
index 0000000..be988ee
--- /dev/null
+++ b/src/tests/modules/ldap/auth.attrs
@@ -0,0 +1,15 @@
+#
+# Input packet
+#
+User-Name = "john"
+User-Password = "password"
+NAS-IP-Address = 1.2.3.5
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+Idle-Timeout == 3600
+Session-Timeout == 7200
+Acct-Interim-Interval == 1800
+Framed-IP-Netmask == "255.255.0.0"
diff --git a/src/tests/modules/ldap/auth.unlang b/src/tests/modules/ldap/auth.unlang
new file mode 100644
index 0000000..edf14bf
--- /dev/null
+++ b/src/tests/modules/ldap/auth.unlang
@@ -0,0 +1,72 @@
+#
+# Run the "ldap" module
+#
+ldap
+
+if (&control:NAS-IP-Address != 1.2.3.4) {
+ test_fail
+}
+else {
+ test_pass
+}
+
+if (&control:Reply-Message != "Hello world") {
+ test_fail
+}
+else {
+ test_pass
+}
+
+# Cmp operator means Framed-IP-Address is ignored
+if (&control:Framed-IP-Address) {
+ test_fail
+}
+else {
+ test_pass
+}
+
+# IP netmask defined in profile1 should overwrite radprofile value.
+if (&reply:Framed-IP-Netmask != 255.255.0.0) {
+ test_fail
+}
+else {
+ test_pass
+}
+
+if (&reply:Acct-Interim-Interval != 1800) {
+ test_fail
+}
+else {
+ test_pass
+}
+
+if (&reply:Idle-Timeout != 3600) {
+ test_fail
+}
+else {
+ test_pass
+}
+
+if (&reply:Session-Timeout != 7200) {
+ test_fail
+}
+else {
+ test_pass
+}
+
+if ("%{pairs:reply:}" == "") {
+ test_fail
+}
+
+ldap.post-auth
+
+update {
+ Tmp-String-0 := "%{ldap:ldap://$ENV{TEST_SERVER}/uid=john,ou=people,dc=example,dc=com?description}"
+}
+
+if (&Tmp-String-0 != "User %{User-Name} authenticated") {
+ test_fail
+}
+else {
+ test_pass
+}
diff --git a/src/tests/modules/ldap/example.com.ldif b/src/tests/modules/ldap/example.com.ldif
new file mode 120000
index 0000000..055d379
--- /dev/null
+++ b/src/tests/modules/ldap/example.com.ldif
@@ -0,0 +1 @@
+../../salt-test-server/salt/ldap/base.ldif \ No newline at end of file
diff --git a/src/tests/modules/ldap/groups_rfc2307bis.attrs b/src/tests/modules/ldap/groups_rfc2307bis.attrs
new file mode 100644
index 0000000..be988ee
--- /dev/null
+++ b/src/tests/modules/ldap/groups_rfc2307bis.attrs
@@ -0,0 +1,15 @@
+#
+# Input packet
+#
+User-Name = "john"
+User-Password = "password"
+NAS-IP-Address = 1.2.3.5
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+Idle-Timeout == 3600
+Session-Timeout == 7200
+Acct-Interim-Interval == 1800
+Framed-IP-Netmask == "255.255.0.0"
diff --git a/src/tests/modules/ldap/groups_rfc2307bis.unlang b/src/tests/modules/ldap/groups_rfc2307bis.unlang
new file mode 100644
index 0000000..b8f48b5
--- /dev/null
+++ b/src/tests/modules/ldap/groups_rfc2307bis.unlang
@@ -0,0 +1,41 @@
+#
+# Run the "ldap" module
+#
+ldap
+
+#
+# Resolve using group name attribute
+#
+if (LDAP-Group == 'foo') {
+ test_pass
+}
+else {
+ test_fail
+}
+
+#
+# Resolve using group DN
+#
+if (LDAP-Group == 'cn=foo,ou=groups,dc=example,dc=com') {
+ test_pass
+}
+else {
+ test_fail
+}
+
+#
+# Check we have these values cached
+#
+if (&control:LDAP-Cached-Membership[*] == 'foo') {
+ test_pass
+}
+else {
+ test_fail
+}
+
+if (&control:LDAP-Cached-Membership[*] == 'cn=foo,ou=groups,dc=example,dc=com') {
+ test_pass
+}
+else {
+ test_fail
+}
diff --git a/src/tests/modules/ldap/module.conf b/src/tests/modules/ldap/module.conf
new file mode 100644
index 0000000..dcf3126
--- /dev/null
+++ b/src/tests/modules/ldap/module.conf
@@ -0,0 +1,537 @@
+# -*- text -*-
+#
+# $Id$
+
+#
+# Lightweight Directory Access Protocol (LDAP)
+#
+ldap {
+ # Note that this needs to match the name(s) in the LDAP server
+ # certificate, if you're using ldaps. See OpenLDAP documentation
+ # for the behavioral semantics of specifying more than one host.
+ #
+ # Depending on the libldap in use, server may be an LDAP URI.
+ # In the case of OpenLDAP this allows additional the following
+ # additional schemes:
+ # - ldaps:// (LDAP over SSL)
+ # - ldapi:// (LDAP over Unix socket)
+ # - ldapc:// (Connectionless LDAP)
+ server = $ENV{LDAP_TEST_SERVER}
+# server = 'ldap.rrdns.example.org'
+
+ # Port to connect on, defaults to 389, will be ignored for LDAP URIs.
+ port = $ENV{LDAP_TEST_SERVER_PORT}
+
+ # Administrator account for searching and possibly modifying.
+ identity = 'cn=admin,dc=example,dc=com'
+ password = secret
+
+ # Unless overridden in another section, the dn from which all
+ # searches will start from.
+ base_dn = 'dc=example,dc=com'
+
+ # SASL parameters to use for admin binds
+ #
+ # When we're prompted by the SASL library, these control
+ # the responses given.
+ #
+ sasl {
+ # SASL mechanism
+# mech = 'PLAIN'
+
+ # SASL authorisation identity to proxy.
+# proxy = 'autz_id'
+
+ # SASL realm. Used for kerberos.
+# realm = 'example.org'
+ }
+
+ #
+ # Generic valuepair attribute
+ #
+
+ # If set, this will attribute will be retrieved in addition to any
+ # mapped attributes.
+ #
+ # Values should be in the format:
+ # <radius attr> <op> <value>
+ #
+ # Where:
+ # <radius attr>: Is the attribute you wish to create
+ # with any valid list and request qualifiers.
+ # <op>: Is any assignment operator (=, :=, +=, -=).
+ # <value>: Is the value to parse into the new valuepair.
+ # If the value is wrapped in double quotes it
+ # will be xlat expanded.
+ valuepair_attribute = 'radiusAttribute'
+
+ #
+ # Mapping of LDAP directory attributes to RADIUS dictionary attributes.
+ #
+
+ # WARNING: Although this format is almost identical to the unlang
+ # update section format, it does *NOT* mean that you can use other
+ # unlang constructs in module configuration files.
+ #
+ # Configuration items are in the format:
+ # <radius attr> <op> <ldap attr>
+ #
+ # Where:
+ # <radius attr>: Is the destination RADIUS attribute
+ # with any valid list and request qualifiers.
+ # <op>: Is any assignment attribute (=, :=, +=, -=).
+ # <ldap attr>: Is the attribute associated with user or
+ # profile objects in the LDAP directory.
+ # If the attribute name is wrapped in double
+ # quotes it will be xlat expanded.
+ #
+ # Request and list qualifiers may also be placed after the 'update'
+ # section name to set defaults destination requests/lists
+ # for unqualified RADIUS attributes.
+ #
+ # Note: LDAP attribute names should be single quoted unless you want
+ # the name value to be derived from an xlat expansion, or an
+ # attribute ref.
+ update {
+ control:Password-With-Header += 'userPassword'
+ reply:Idle-Timeout := 'radiusIdleTimeout'
+ reply:Framed-IP-Netmask := 'radiusFramedIPNetmask'
+# control:NT-Password := 'ntPassword'
+# reply:Reply-Message := 'radiusReplyMessage'
+# reply:Tunnel-Type := 'radiusTunnelType'
+# reply:Tunnel-Medium-Type := 'radiusTunnelMediumType'
+# reply:Tunnel-Private-Group-ID := 'radiusTunnelPrivategroupId'
+
+ # Where only a list is specified as the RADIUS attribute,
+ # the value of the LDAP attribute is parsed as a valuepair
+ # in the same format as the 'valuepair_attribute' (above).
+ control: += 'radiusControlAttribute'
+ request: += 'radiusRequestAttribute'
+ reply: += 'radiusReplyAttribute'
+ }
+
+ # Set to yes if you have eDirectory and want to use the universal
+ # password mechanism.
+# edir = no
+
+ # Set to yes if you want to bind as the user after retrieving the
+ # Cleartext-Password. This will consume the login grace, and
+ # verify user authorization.
+# edir_autz = no
+
+ # Note: set_auth_type was removed in v3.x.x
+ # Equivalent functionality can be achieved by adding the following
+ # stanza to the authorize {} section of your virtual server.
+ #
+ # ldap
+ # if ((ok || updated) && User-Password) {
+ # update {
+ # control:Auth-Type := ldap
+ # }
+ # }
+
+ #
+ # User object identification.
+ #
+ user {
+ # Where to start searching in the tree for users
+ base_dn = "ou=people,${..base_dn}"
+
+ # Filter for user objects, should be specific enough
+ # to identify a single user object.
+ filter = "(uid=%{%{Stripped-User-Name}:-%{User-Name}})"
+
+ # SASL parameters to use for user binds
+ #
+ # When we're prompted by the SASL library, these control
+ # the responses given.
+ #
+ # Any of the config items below may be an attribute ref
+ # or and expansion, so different SASL mechs, proxy IDs
+ # and realms may be used for different users.
+ sasl {
+ # SASL mechanism
+# mech = 'PLAIN'
+
+ # SASL authorisation identity to proxy.
+# proxy = &User-Name
+
+ # SASL realm. Used for kerberos.
+# realm = 'example.org'
+ }
+
+ # Search scope, may be 'base', 'one', sub' or 'children'
+# scope = 'sub'
+
+ # If this is undefined, anyone is authorised.
+ # If it is defined, the contents of this attribute
+ # determine whether or not the user is authorised
+# access_attribute = 'dialupAccess'
+
+ # Control whether the presence of 'access_attribute'
+ # allows access, or denys access.
+ #
+ # If 'yes', and the access_attribute is present, or
+ # 'no' and the access_attribute is absent then access
+ # will be allowed.
+ #
+ # If 'yes', and the access_attribute is absent, or
+ # 'no' and the access_attribute is present, then
+ # access will not be allowed.
+ #
+ # If the value of the access_attribute is 'false', it
+ # will negate the result.
+ #
+ # e.g.
+ # access_positive = yes
+ # access_attribute = userAccessAllowed
+ #
+ # With an LDAP object containing:
+ # userAccessAllowed: false
+ #
+ # Will result in the user being locked out.
+# access_positive = yes
+ }
+
+ #
+ # User membership checking.
+ #
+ group {
+ # Where to start searching in the tree for groups
+ base_dn = "ou=groups,${..base_dn}"
+
+ # Filter for group objects, should match all available
+ # group objects a user might be a member of.
+ filter = '(objectClass=groupOfNames)'
+
+ # Search scope, may be 'base', 'one', sub' or 'children'
+ scope = 'sub'
+
+ # Attribute that uniquely identifies a group.
+ # Is used when converting group DNs to group
+ # names.
+ name_attribute = cn
+
+ # Filter to find group objects a user is a member of.
+ # That is, group objects with attributes that
+ # identify members (the inverse of membership_attribute).
+ membership_filter = "(|(member=%{control:Ldap-UserDn})(memberUid=%{%{Stripped-User-Name}:-%{User-Name}}))"
+
+ # The attribute in user objects which contain the names
+ # or DNs of groups a user is a member of.
+ #
+ # Unless a conversion between group name and group DN is
+ # needed, there's no requirement for the group objects
+ # referenced to actually exist.
+ membership_attribute = 'memberOf'
+
+ # If cacheable_name or cacheable_dn are enabled,
+ # all group information for the user will be
+ # retrieved from the directory and written to LDAP-Group
+ # attributes appropriate for the instance of rlm_ldap.
+ #
+ # For group comparisons these attributes will be checked
+ # instead of querying the LDAP directory directly.
+ #
+ # This feature is intended to be used with rlm_cache.
+ #
+ # If you wish to use this feature, you should enable
+ # the type that matches the format of your check items
+ # i.e. if your groups are specified as DNs then enable
+ # cacheable_dn else enable cacheable_name.
+ cacheable_name = yes
+ cacheable_dn = yes
+
+ # Override the normal cache attribute (<inst>-LDAP-Group)
+ # and create a custom attribute. This can help if multiple
+ # module instances are used in fail-over.
+ cache_attribute = 'LDAP-Cached-Membership'
+ }
+
+ #
+ # User profiles. RADIUS profile objects contain sets of attributes
+ # to insert into the request. These attributes are mapped using
+ # the same mapping scheme applied to user objects.
+ #
+ profile {
+ # Filter for RADIUS profile objects
+ filter = '(objectclass=radiusprofile)'
+
+ # The default profile applied to all users.
+ default = 'cn=radprofile,ou=profiles,dc=example,dc=com'
+
+ # The list of profiles which are applied (after the default)
+ # to all users.
+ # The 'User-Profile' attribute in the control list
+ # will override this setting at run-time.
+ attribute = 'radiusProfileDn'
+ }
+
+ #
+ # Bulk load clients from the directory
+ #
+ client {
+ # Where to start searching in the tree for clients
+ base_dn = "ou=clients,${..base_dn}"
+
+ #
+ # Filter to match client objects
+ #
+ filter = '(objectClass=radiusClient)'
+
+ # Search scope, may be 'base', 'one', 'sub' or 'children'
+# scope = 'sub'
+
+ #
+ # Sets default values (not obtained from LDAP) for new client entries
+ #
+ template {
+# login = 'test'
+# password = 'test'
+# proto = tcp
+# require_message_authenticator = yes
+
+ # Uncomment to add a home_server with the same
+ # attributes as the client.
+# coa_server {
+# response_window = 2.0
+# }
+ }
+
+ #
+ # Client attribute mappings are in the format:
+ # <client attribute> = <ldap attribute>
+ #
+ # The following attributes are required:
+ # * ipaddr | ipv4addr | ipv6addr - Client IP Address.
+ # * secret - RADIUS shared secret.
+ #
+ # All other attributes usually supported in a client
+ # definition are also supported here.
+ #
+ # Schemas are available in doc/schemas/ldap for openldap and eDirectory
+ #
+ attribute {
+ ipaddr = 'radiusClientIdentifier'
+ secret = 'radiusClientSecret'
+# shortname = 'radiusClientShortname'
+# nas_type = 'radiusClientType'
+# virtual_server = 'radiusClientVirtualServer'
+# require_message_authenticator = 'radiusClientRequireMa'
+ }
+ }
+
+ # Load clients on startup
+# read_clients = no
+
+ #
+ # Modify user object on receiving Accounting-Request
+ #
+
+ # Useful for recording things like the last time the user logged
+ # in, or the Acct-Session-ID for CoA/DM.
+ #
+ # LDAP modification items are in the format:
+ # <ldap attr> <op> <value>
+ #
+ # Where:
+ # <ldap attr>: The LDAP attribute to add modify or delete.
+ # <op>: One of the assignment operators:
+ # (:=, +=, -=, ++).
+ # Note: '=' is *not* supported.
+ # <value>: The value to add modify or delete.
+ #
+ # WARNING: If using the ':=' operator with a multi-valued LDAP
+ # attribute, all instances of the attribute will be removed and
+ # replaced with a single attribute.
+ accounting {
+ reference = "%{tolower:type.%{Acct-Status-Type}}"
+
+ type {
+ start {
+ update {
+ description := "User %{User-Name} is online"
+ }
+ }
+
+ interim-update {
+ update {
+ description := "Last seen at %S"
+ }
+ }
+
+ stop {
+ update {
+ description := "Offline at %S"
+ }
+ }
+ }
+ }
+
+ #
+ # Post-Auth can modify LDAP objects too
+ #
+ post-auth {
+ update {
+ description := "User %{User-Name} authenticated"
+ }
+ }
+
+ #
+ # LDAP connection-specific options.
+ #
+ # These options set timeouts, keep-alives, etc. for the connections.
+ #
+ options {
+ # Control under which situations aliases are followed.
+ # May be one of 'never', 'searching', 'finding' or 'always'
+ # default: libldap's default which is usually 'never'.
+ #
+ # LDAP_OPT_DEREF is set to this value.
+# dereference = 'always'
+
+ #
+ # The following two configuration items control whether the
+ # server follows references returned by LDAP directory.
+ # They are mostly for Active Directory compatibility.
+ # If you set these to 'no', then searches will likely return
+ # 'operations error', instead of a useful result.
+ #
+ chase_referrals = yes
+ rebind = yes
+
+ # Seconds to wait for LDAP query to finish. default: 20
+ timeout = 10
+
+ # Seconds LDAP server has to process the query (server-side
+ # time limit). default: 20
+ #
+ # LDAP_OPT_TIMELIMIT is set to this value.
+ timelimit = 3
+
+ # Seconds to wait for response of the server. (network
+ # failures) default: 10
+ #
+ # LDAP_OPT_NETWORK_TIMEOUT is set to this value.
+ net_timeout = 1
+
+ # LDAP_OPT_X_KEEPALIVE_IDLE
+ idle = 60
+
+ # LDAP_OPT_X_KEEPALIVE_PROBES
+ probes = 3
+
+ # LDAP_OPT_X_KEEPALIVE_INTERVAL
+ interval = 3
+
+ # ldap_debug: debug flag for LDAP SDK
+ # (see OpenLDAP documentation). Set this to enable
+ # huge amounts of LDAP debugging on the screen.
+ # You should only use this if you are an LDAP expert.
+ #
+ # default: 0x0000 (no debugging messages)
+ # Example:(LDAP_DEBUG_FILTER+LDAP_DEBUG_CONNS)
+ ldap_debug = 0x0801
+ }
+
+ #
+ # This subsection configures the tls related items
+ # that control how FreeRADIUS connects to an LDAP
+ # server. It contains all of the 'tls_*' configuration
+ # entries used in older versions of FreeRADIUS. Those
+ # configuration entries can still be used, but we recommend
+ # using these.
+ #
+ tls {
+ # Set this to 'yes' to use TLS encrypted connections
+ # to the LDAP database by using the StartTLS extended
+ # operation.
+ #
+ # The StartTLS operation is supposed to be
+ # used with normal ldap connections instead of
+ # using ldaps (port 636) connections
+# start_tls = yes
+
+# ca_file = ${certdir}/cacert.pem
+
+# ca_path = ${certdir}
+# certificate_file = /path/to/radius.crt
+# private_key_file = /path/to/radius.key
+# random_file = ${certdir}/random
+
+ # Certificate Verification requirements. Can be:
+ # 'never' (don't even bother trying)
+ # 'allow' (try, but don't fail if the certificate
+ # can't be verified)
+ # 'demand' (fail if the certificate doesn't verify.)
+ #
+ # The default is 'allow'
+# require_cert = 'demand'
+ }
+
+
+ # As of version 3.0, the 'pool' section has replaced the
+ # following configuration items:
+ #
+ # ldap_connections_number
+
+ # The connection pool is new for 3.0, and will be used in many
+ # modules, for all kinds of connection-related activity.
+ #
+ # When the server is not threaded, the connection pool
+ # limits are ignored, and only one connection is used.
+ pool {
+ # Number of connections to start
+ start = 5
+
+ # Minimum number of connections to keep open
+ min = 4
+
+ # Maximum number of connections
+ #
+ # If these connections are all in use and a new one
+ # is requested, the request will NOT get a connection.
+ #
+ # Setting 'max' to LESS than the number of threads means
+ # that some threads may starve, and you will see errors
+ # like 'No connections available and at max connection limit'
+ #
+ # Setting 'max' to MORE than the number of threads means
+ # that there are more connections than necessary.
+ max = 4
+
+ # Spare connections to be left idle
+ #
+ # NOTE: Idle connections WILL be closed if 'idle_timeout'
+ # is set.
+ spare = 3
+
+ # Number of uses before the connection is closed
+ #
+ # 0 means 'infinite'
+ uses = 0
+
+ # The lifetime (in seconds) of the connection
+ lifetime = 0
+
+ # Idle timeout (in seconds). A connection which is
+ # unused for this length of time will be closed.
+ idle_timeout = 60
+
+ # The number of seconds to wait after the server tries
+ # to open a connection, and fails. During this time,
+ # no new connections will be opened.
+ #
+ retry_delay = 1
+
+ # NOTE: All configuration settings are enforced. If a
+ # connection is closed because of 'idle_timeout',
+ # 'uses', or 'lifetime', then the total number of
+ # connections MAY fall below 'min'. When that
+ # happens, it will open a new connection. It will
+ # also log a WARNING message.
+ #
+ # The solution is to either lower the 'min' connections,
+ # or increase lifetime/idle_timeout.
+ }
+}
diff --git a/src/tests/modules/pap/all.mk b/src/tests/modules/pap/all.mk
new file mode 100644
index 0000000..5c1de6f
--- /dev/null
+++ b/src/tests/modules/pap/all.mk
@@ -0,0 +1,3 @@
+#
+# Test the "pap" module
+#
diff --git a/src/tests/modules/pap/module.conf b/src/tests/modules/pap/module.conf
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/tests/modules/pap/module.conf
@@ -0,0 +1 @@
+
diff --git a/src/tests/modules/pap/pbkfd2_dig_big.attrs b/src/tests/modules/pap/pbkfd2_dig_big.attrs
new file mode 100644
index 0000000..90fc451
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_dig_big.attrs
@@ -0,0 +1,11 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_dig_big'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+
diff --git a/src/tests/modules/pap/pbkfd2_dig_big.unlang b/src/tests/modules/pap/pbkfd2_dig_big.unlang
new file mode 100644
index 0000000..449967f
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_dig_big.unlang
@@ -0,0 +1,17 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_dig_big') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA2+256:AAAAAQ:E+VXOSsE8RwyYGdygQoW9Q==:UivlvrwHML4VtZHMJLiT/xlH7oyoyvbXQceivptq9TI='
+ }
+ pap.authorize
+ pap.authenticate
+ if (!ok) {
+ test_fail
+ } else {
+ test_pass
+ }
+}
diff --git a/src/tests/modules/pap/pbkfd2_dig_small.attrs b/src/tests/modules/pap/pbkfd2_dig_small.attrs
new file mode 100644
index 0000000..dbc5bdd
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_dig_small.attrs
@@ -0,0 +1,11 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_dig_small'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+
diff --git a/src/tests/modules/pap/pbkfd2_dig_small.unlang b/src/tests/modules/pap/pbkfd2_dig_small.unlang
new file mode 100644
index 0000000..37f08ee
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_dig_small.unlang
@@ -0,0 +1,17 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_dig_small') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA2+256:AAAAAQ:E+VXOSsE8RwyYGdygQoW9Q==:UivlvrwHML4VtZHMJLiT/xlH7oyoyvbXQceivptq9TI'
+ }
+ pap.authorize
+ pap.authenticate
+ if (!ok) {
+ test_fail
+ } else {
+ test_pass
+ }
+}
diff --git a/src/tests/modules/pap/pbkfd2_iter0.attrs b/src/tests/modules/pap/pbkfd2_iter0.attrs
new file mode 100644
index 0000000..871017e
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_iter0.attrs
@@ -0,0 +1,11 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_iter0'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+
diff --git a/src/tests/modules/pap/pbkfd2_iter0.unlang b/src/tests/modules/pap/pbkfd2_iter0.unlang
new file mode 100644
index 0000000..ca362c9
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_iter0.unlang
@@ -0,0 +1,17 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_iter0') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA2+256:AAAAAP:CuNDJ9NimZoP5ljnPNCBUA==:f09zV7dReGg5SIv/EXY9tCL4XQRr5guhL0Q6UXSKI3c='
+ }
+ pap.authorize
+ pap.authenticate
+ if (!ok) {
+ test_fail
+ } else {
+ test_pass
+ }
+}
diff --git a/src/tests/modules/pap/pbkfd2_iter1.attrs b/src/tests/modules/pap/pbkfd2_iter1.attrs
new file mode 100644
index 0000000..e3d62cb
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_iter1.attrs
@@ -0,0 +1,11 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_iter1'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+
diff --git a/src/tests/modules/pap/pbkfd2_iter1.unlang b/src/tests/modules/pap/pbkfd2_iter1.unlang
new file mode 100644
index 0000000..6758c9b
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_iter1.unlang
@@ -0,0 +1,17 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_iter1') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA2+256:AAAAAQ:OErtptMl2hOxhQqvNw7sNw==:4KkrgL+3Q9j8KlHPivtApBKRZAjyWjtDWmZEz2UjNko='
+ }
+ pap.authorize
+ pap.authenticate
+ if (!ok) {
+ test_fail
+ } else {
+ test_pass
+ }
+}
diff --git a/src/tests/modules/pap/pbkfd2_iter1000.attrs b/src/tests/modules/pap/pbkfd2_iter1000.attrs
new file mode 100644
index 0000000..10a19c3
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_iter1000.attrs
@@ -0,0 +1,11 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_iter1000'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+
diff --git a/src/tests/modules/pap/pbkfd2_iter1000.unlang b/src/tests/modules/pap/pbkfd2_iter1000.unlang
new file mode 100644
index 0000000..18fe680
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_iter1000.unlang
@@ -0,0 +1,17 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_iter1000') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA2+256:AAAD6A:yhmqoKrtPLY2KYK6cNjnfw==:Y6gkSZEo4TRtlsryHqnGYZhoe2qn5tJ4IUyyVHb/3WU='
+ }
+ pap.authorize
+ pap.authenticate
+ if (!ok) {
+ test_fail
+ } else {
+ test_pass
+ }
+}
diff --git a/src/tests/modules/pap/pbkfd2_iter100000.attrs b/src/tests/modules/pap/pbkfd2_iter100000.attrs
new file mode 100644
index 0000000..8da916c
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_iter100000.attrs
@@ -0,0 +1,10 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_iter100000'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/pap/pbkfd2_iter100000.unlang b/src/tests/modules/pap/pbkfd2_iter100000.unlang
new file mode 100644
index 0000000..a1253e6
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_iter100000.unlang
@@ -0,0 +1,17 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_iter100000') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA2+256:AA9CQA:fCfnJGMVC1QLtTOPiaSICA==:KCmjMpQ+lokMvyFTl4f4pPJNc0xJq4iHZPdtHa0OEXM='
+ }
+ pap.authorize
+ pap.authenticate
+ if (!ok) {
+ test_fail
+ } else {
+ test_pass
+ }
+}
diff --git a/src/tests/modules/pap/pbkfd2_iter_big.attrs b/src/tests/modules/pap/pbkfd2_iter_big.attrs
new file mode 100644
index 0000000..9f8dddb
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_iter_big.attrs
@@ -0,0 +1,11 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_iter_big'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+
diff --git a/src/tests/modules/pap/pbkfd2_iter_big.unlang b/src/tests/modules/pap/pbkfd2_iter_big.unlang
new file mode 100644
index 0000000..464d944
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_iter_big.unlang
@@ -0,0 +1,17 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_iter_big') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA2+256:AAAAAQ==:E+VXOSsE8RwyYGdygQoW9Q==:UivlvrwHML4VtZHMJLiT/xlH7oyoyvbXQceivptq9TI='
+ }
+ pap.authorize
+ pap.authenticate
+ if (!ok) {
+ test_fail
+ } else {
+ test_pass
+ }
+}
diff --git a/src/tests/modules/pap/pbkfd2_iter_miss.attrs b/src/tests/modules/pap/pbkfd2_iter_miss.attrs
new file mode 100644
index 0000000..983db26
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_iter_miss.attrs
@@ -0,0 +1,11 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_iter_miss'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+
diff --git a/src/tests/modules/pap/pbkfd2_iter_miss.unlang b/src/tests/modules/pap/pbkfd2_iter_miss.unlang
new file mode 100644
index 0000000..44d961a
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_iter_miss.unlang
@@ -0,0 +1,19 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_iter_miss') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA2+256::E+VXOSsE8RwyYGdygQoW9Q==:UivlvrwHML4VtZHMJLiT/xlH7oyoyvbXQceivptq9TI='
+ }
+ pap.authorize
+ pap.authenticate {
+ invalid = 1
+ }
+ if (invalid) {
+ test_pass
+ } else {
+ test_fail
+ }
+}
diff --git a/src/tests/modules/pap/pbkfd2_iter_small.attrs b/src/tests/modules/pap/pbkfd2_iter_small.attrs
new file mode 100644
index 0000000..af8351b
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_iter_small.attrs
@@ -0,0 +1,11 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_iter_small'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+
diff --git a/src/tests/modules/pap/pbkfd2_iter_small.unlang b/src/tests/modules/pap/pbkfd2_iter_small.unlang
new file mode 100644
index 0000000..7edee80
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_iter_small.unlang
@@ -0,0 +1,19 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_iter_small') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA2+256:AAAAA:E+VXOSsE8RwyYGdygQoW9Q==:UivlvrwHML4VtZHMJLiT/xlH7oyoyvbXQceivptq9TI='
+ }
+ pap.authorize
+ pap.authenticate {
+ invalid = 1
+ }
+ if (invalid) {
+ test_pass
+ } else {
+ test_fail
+ }
+}
diff --git a/src/tests/modules/pap/pbkfd2_passlib.attrs b/src/tests/modules/pap/pbkfd2_passlib.attrs
new file mode 100644
index 0000000..29738bb
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_passlib.attrs
@@ -0,0 +1,10 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_passlib'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/pap/pbkfd2_passlib.unlang b/src/tests/modules/pap/pbkfd2_passlib.unlang
new file mode 100644
index 0000000..6d0f27d
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_passlib.unlang
@@ -0,0 +1,20 @@
+# Fixme - Base64 decode seems off for alt base64
+test_pass
+
+#if ("${feature.tls}" == no) {
+# test_pass
+# return
+#}
+
+#if (&User-Name == 'pbkdf2_passlib') {
+# update control {
+# &PBKDF2-Password := '$pbkdf2-sha256$29000$9t7be09prfXee2/NOUeotQ$Y.RDnnq8vsezSZSKy1QNy6xhKPdoBIwc.0XDdRm9sJ8'
+# }
+# pap.authorize
+# pap.authenticate
+# if (!ok) {
+# test_fail
+# } else {
+# test_pass
+# }
+#}
diff --git a/src/tests/modules/pap/pbkfd2_salt0.attrs b/src/tests/modules/pap/pbkfd2_salt0.attrs
new file mode 100644
index 0000000..7e6d209
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_salt0.attrs
@@ -0,0 +1,10 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_salt0'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/pap/pbkfd2_salt0.unlang b/src/tests/modules/pap/pbkfd2_salt0.unlang
new file mode 100644
index 0000000..173f768
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_salt0.unlang
@@ -0,0 +1,19 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_salt0') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA2+256:AAAnEA::4RJEKVFQ5nE8126aURI0cJO9tqy/DIAhq64piBEwshA='
+ }
+ pap.authorize
+ pap.authenticate {
+ invalid = 1
+ }
+ if (invalid) {
+ test_pass
+ } else {
+ test_fail
+ }
+}
diff --git a/src/tests/modules/pap/pbkfd2_salt1.attrs b/src/tests/modules/pap/pbkfd2_salt1.attrs
new file mode 100644
index 0000000..20ff1fe
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_salt1.attrs
@@ -0,0 +1,10 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_salt1'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/pap/pbkfd2_salt1.unlang b/src/tests/modules/pap/pbkfd2_salt1.unlang
new file mode 100644
index 0000000..4aa0fce
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_salt1.unlang
@@ -0,0 +1,17 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_salt1') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA2+256:AAAnEA:qg==:KQzCdedgOZYFwx+mQp1TKA8VM4fwf02pqSdJEh2ekwM='
+ }
+ pap.authorize
+ pap.authenticate
+ if (!ok) {
+ test_fail
+ } else {
+ test_pass
+ }
+}
diff --git a/src/tests/modules/pap/pbkfd2_salt1024.attrs b/src/tests/modules/pap/pbkfd2_salt1024.attrs
new file mode 100644
index 0000000..30f2706
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_salt1024.attrs
@@ -0,0 +1,10 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_salt1024'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/pap/pbkfd2_salt1024.unlang b/src/tests/modules/pap/pbkfd2_salt1024.unlang
new file mode 100644
index 0000000..a4aab5e
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_salt1024.unlang
@@ -0,0 +1,17 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_salt1024') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA2+256:AAAnEA:/IUrkJKe+1kzJNBw7aAMbnQuSFZpjbCqPeKso3cbuSUzWinxngxjK8yyZLiWwF+WE/0Gplfx25zZEQNTdRTvjZZNefoxQBR8Hht0FpdU9YiEBaeErwVo63EDEu83+ycvB18uH0IXpJKGSSkIPRfGpHT3BkwJDGo5SqjRJadDsyQzkc/WJCMrrfJ0igaWMxb5eR5J8qfXIjBFepRrOOU6acZGtANW8qvDYLJwN+TMd9Jb1wDDY14eoAlKglTF21S3kewNMkDDyeP+oDYv29t1S/soFUnnB+Pb5IdR6pDy2VDGx4jFZMQGshSHWTYQFqgulavS/tGEF8TvzcorrJZKuksAjKdTSmfZ6j4aBY3U+oMSQ+2lO131pkNfNQuMsDfr72r9wUA2xRgUiL/J7CgKn7mamL2OCaksl0Rw2PGqqIaHvAYS6Q1EoIzsmLNrWBYYqTRLyCGZw6+hUOahYRon2lglGmnuWHPfowU+LgcaR5gF1QjvTXhXQ8I39mB3ePgdi+7TUn644Z1FB+JTqGJbue92x4V40Zyyy+Qdt52QsR49iYokbKAwQRiqfVJ7J8NzCY/kIQnqT9RE0NCxZoMBRzboZxVPchxdpmWGQ9dXP06PqIuDCFFiJlVQUfyPMgOAxIlVJ/9NAmj5MWFdWMrmlBNDx9ihEV1FdTv23iFZH5Ejg+x4D3qN5oOyCDL2i9lobzFXh5z4EDpbbogQaFkUzqKEaxRGPBrfYVOi6XXYujVUnxHJaRxbs2UqjpJNsXMg8f7P78aRvOKCIbW70CHWlt7nF0pA5+kFUQRLXKuq7bW+ivoXKeDW5o4FVP3+Pcr67+DOsUXuehALLj9Mu2ICWlMIV/AWcM2szaqk1bwSo7bAeG4RtDKmNjGA7gpnT+w2x+/qS1eWbc832Sumqc1IA8aY6HNVDPsJZf99To4BR+N0rCoQQ/KIZybI31mQagR3+FR9yNzqWzKIl+qf69RTc1CbUCkKVF8pxWZ0ocP+CAdoKadgpdF8evQIiGcUD73HiJ0RsDWo21y0tN0P5jfzWo3WMhCk9e2wl6o1JAfKw54uHzWJnNlGLBK1LXF+R2m+WvNGBgvUhh4PtYV9gPSudumFdk614oak/Aqcn6xi+YZqOMPkW4WYaiczhHyS7qAyefqKaQkRVYS0Af+79CSjlxZJq57HrD7/1E+d/i0gKmSAbPe80uGHs2a13V3VxztFMBi4xD7zj9Mq7+0goVPD4MNXcR651MZ7vxDRGbvPPmclddZe/nkTEn1YB/909b9mC5P/XzximZYW8gEhBReZouukADRTAjuH8zgSIv6/uyTURnmSVoOumVLBpL7veJIzDm4dZ38BWiasiBnzgMuG9A==:RUoCF5O11OgwLFMTqnKY/yRJy6DYh+yNq4xHZC7COGM='
+ }
+ pap.authorize
+ pap.authenticate
+ if (!ok) {
+ test_fail
+ } else {
+ test_pass
+ }
+}
diff --git a/src/tests/modules/pap/pbkfd2_salt64.attrs b/src/tests/modules/pap/pbkfd2_salt64.attrs
new file mode 100644
index 0000000..da036f3
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_salt64.attrs
@@ -0,0 +1,10 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_salt64'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept \ No newline at end of file
diff --git a/src/tests/modules/pap/pbkfd2_salt64.unlang b/src/tests/modules/pap/pbkfd2_salt64.unlang
new file mode 100644
index 0000000..754cdba
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_salt64.unlang
@@ -0,0 +1,17 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_salt64') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA2+256:AAAnEA:msGxE1XuC+wlgRr+H4+ioyxZuiN3KYLUSky2FINDTq7KJylKt4XnqloV+FuHGXUbOu1EWcsFp51u2z8wdXVnQQ==:rAV9BeEJH5kt9uZ6pJt0o5pYpN5LQRe4MAYyk2jvjpU='
+ }
+ pap.authorize
+ pap.authenticate
+ if (!ok) {
+ test_fail
+ } else {
+ test_pass
+ }
+}
diff --git a/src/tests/modules/pap/pbkfd2_salt_big.attrs b/src/tests/modules/pap/pbkfd2_salt_big.attrs
new file mode 100644
index 0000000..ccb593e
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_salt_big.attrs
@@ -0,0 +1,10 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_salt_big'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/pap/pbkfd2_salt_big.unlang b/src/tests/modules/pap/pbkfd2_salt_big.unlang
new file mode 100644
index 0000000..cfc96c5
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_salt_big.unlang
@@ -0,0 +1,17 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_salt_big') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA2+256:AAAAAQ:E+VXOSsE8RwyYGdygQoW9QA==:pF23EcxNBhJLQ+9JRtd9wQ1Gz+k4i6YjeNZq+7DRBX8='
+ }
+ pap.authorize
+ pap.authenticate
+ if (!ok) {
+ test_fail
+ } else {
+ test_pass
+ }
+}
diff --git a/src/tests/modules/pap/pbkfd2_salt_small.attrs b/src/tests/modules/pap/pbkfd2_salt_small.attrs
new file mode 100644
index 0000000..1c2fa20
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_salt_small.attrs
@@ -0,0 +1,10 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_salt_small'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/pap/pbkfd2_salt_small.unlang b/src/tests/modules/pap/pbkfd2_salt_small.unlang
new file mode 100644
index 0000000..e46982f
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_salt_small.unlang
@@ -0,0 +1,17 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_salt_small') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA2+256:AAAAAQ:E+VXOSsE8RwyYGdygQoW9Q=:UivlvrwHML4VtZHMJLiT/xlH7oyoyvbXQceivptq9TI='
+ }
+ pap.authorize
+ pap.authenticate
+ if (!ok) {
+ test_fail
+ } else {
+ test_pass
+ }
+}
diff --git a/src/tests/modules/pap/pbkfd2_sha1.attrs b/src/tests/modules/pap/pbkfd2_sha1.attrs
new file mode 100644
index 0000000..ef7538f
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_sha1.attrs
@@ -0,0 +1,10 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_sha1'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/pap/pbkfd2_sha1.unlang b/src/tests/modules/pap/pbkfd2_sha1.unlang
new file mode 100644
index 0000000..5b79691
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_sha1.unlang
@@ -0,0 +1,17 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_sha1') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA1:AAAD6A:Xw1P133xrwk=:dtQBXQRiR/No5A8Ip3JFGF/qUC0='
+ }
+ pap.authorize
+ pap.authenticate
+ if (!ok) {
+ test_fail
+ } else {
+ test_pass
+ }
+}
diff --git a/src/tests/modules/pap/pbkfd2_sha2_224.attrs b/src/tests/modules/pap/pbkfd2_sha2_224.attrs
new file mode 100644
index 0000000..413f893
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_sha2_224.attrs
@@ -0,0 +1,10 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_sha2_224'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/pap/pbkfd2_sha2_224.unlang b/src/tests/modules/pap/pbkfd2_sha2_224.unlang
new file mode 100644
index 0000000..00fa626
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_sha2_224.unlang
@@ -0,0 +1,17 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_sha2_224') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA2+224:AAAnEA:UHScBrg/ZWOyBKqQdAh7bw==:tcFp6CDrkIYdhwa60g24U4ko+mBxzAiFxlpPnA=='
+ }
+ pap.authorize
+ pap.authenticate
+ if (!ok) {
+ test_fail
+ } else {
+ test_pass
+ }
+}
diff --git a/src/tests/modules/pap/pbkfd2_sha2_256.attrs b/src/tests/modules/pap/pbkfd2_sha2_256.attrs
new file mode 100644
index 0000000..3066682
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_sha2_256.attrs
@@ -0,0 +1,10 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_sha2_256'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/pap/pbkfd2_sha2_256.unlang b/src/tests/modules/pap/pbkfd2_sha2_256.unlang
new file mode 100644
index 0000000..5c4efce
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_sha2_256.unlang
@@ -0,0 +1,17 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_sha2_256') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA2+256:AAAnEA:a/8HbYW2HWsMthN27JI+Ew==:3nPlXYOlOuDCFOfethUomHxTXkG9JCivOdvh6FDNdGw='
+ }
+ pap.authorize
+ pap.authenticate
+ if (!ok) {
+ test_fail
+ } else {
+ test_pass
+ }
+}
diff --git a/src/tests/modules/pap/pbkfd2_sha2_384.attrs b/src/tests/modules/pap/pbkfd2_sha2_384.attrs
new file mode 100644
index 0000000..9e43450
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_sha2_384.attrs
@@ -0,0 +1,10 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_sha2_384'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/pap/pbkfd2_sha2_384.unlang b/src/tests/modules/pap/pbkfd2_sha2_384.unlang
new file mode 100644
index 0000000..034bb83
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_sha2_384.unlang
@@ -0,0 +1,17 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_sha2_384') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA2+384:AAAnEA:pyHRsYLfNZdjszRcu6eHrA==:ktGfNmZ6PyD8FNEgPzFK1fypKERZ13pgvFl+PQdyKouaMXsXIiWPuTMXHqDUCWsx'
+ }
+ pap.authorize
+ pap.authenticate
+ if (!ok) {
+ test_fail
+ } else {
+ test_pass
+ }
+}
diff --git a/src/tests/modules/pap/pbkfd2_sha2_512.attrs b/src/tests/modules/pap/pbkfd2_sha2_512.attrs
new file mode 100644
index 0000000..b908615
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_sha2_512.attrs
@@ -0,0 +1,10 @@
+#
+# Input packet
+#
+User-Name = 'pbkdf2_sha2_512'
+User-Password = 'password'
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/pap/pbkfd2_sha2_512.unlang b/src/tests/modules/pap/pbkfd2_sha2_512.unlang
new file mode 100644
index 0000000..95c1f3e
--- /dev/null
+++ b/src/tests/modules/pap/pbkfd2_sha2_512.unlang
@@ -0,0 +1,17 @@
+if ("${feature.tls}" == no) {
+ test_pass
+ return
+}
+
+if (&User-Name == 'pbkdf2_sha2_512') {
+ update control {
+ &PBKDF2-Password := 'HMACSHA2+512:AAAnEA:TG8Mb94NEmfPLaePwi5CFA==:SYSFeRf9jr4Uo5DB4NvNUEuc1gmEiLjTac5J4WgyKa7mO58KHKWop9xWmcFeuLtUN/iexLTNSgcubOugAyZcog=='
+ }
+ pap.authorize
+ pap.authenticate
+ if (!ok) {
+ test_fail
+ } else {
+ test_pass
+ }
+}
diff --git a/src/tests/modules/preprocess/all.mk b/src/tests/modules/preprocess/all.mk
new file mode 100644
index 0000000..5cfad60
--- /dev/null
+++ b/src/tests/modules/preprocess/all.mk
@@ -0,0 +1,3 @@
+#
+# Test the "preprocess" module
+#
diff --git a/src/tests/modules/preprocess/hints b/src/tests/modules/preprocess/hints
new file mode 100644
index 0000000..14ceafc
--- /dev/null
+++ b/src/tests/modules/preprocess/hints
@@ -0,0 +1,2 @@
+DEFAULT
+ Calling-Station-Id := "%{User-Name}@%{NAS-IP-Address}"
diff --git a/src/tests/modules/preprocess/huntgroups b/src/tests/modules/preprocess/huntgroups
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/tests/modules/preprocess/huntgroups
diff --git a/src/tests/modules/preprocess/module.conf b/src/tests/modules/preprocess/module.conf
new file mode 100644
index 0000000..7c51fa6
--- /dev/null
+++ b/src/tests/modules/preprocess/module.conf
@@ -0,0 +1,4 @@
+preprocess {
+ hints = $ENV{MODULE_TEST_DIR}/hints
+ huntgroups = $ENV{MODULE_TEST_DIR}/huntgroups
+}
diff --git a/src/tests/modules/preprocess/xlat.attrs b/src/tests/modules/preprocess/xlat.attrs
new file mode 100644
index 0000000..e7170d1
--- /dev/null
+++ b/src/tests/modules/preprocess/xlat.attrs
@@ -0,0 +1,12 @@
+#
+# Input packet
+#
+User-Name = "bob"
+User-Password = "bob"
+NAS-IP-Address = 127.0.0.1
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+Filter-Id == 'success'
diff --git a/src/tests/modules/preprocess/xlat.unlang b/src/tests/modules/preprocess/xlat.unlang
new file mode 100644
index 0000000..da53982
--- /dev/null
+++ b/src/tests/modules/preprocess/xlat.unlang
@@ -0,0 +1,14 @@
+#
+# Run the preprocess module
+#
+preprocess
+
+if (Calling-Station-Id == "bob@127.0.0.1") {
+ update reply {
+ Filter-Id := "success"
+ }
+}
+
+update control {
+ Cleartext-Password := "%{User-Name}"
+}
diff --git a/src/tests/modules/radiusd.conf b/src/tests/modules/radiusd.conf
new file mode 100644
index 0000000..220f341
--- /dev/null
+++ b/src/tests/modules/radiusd.conf
@@ -0,0 +1,103 @@
+#
+# Minimal radiusd.conf for testing modules
+#
+
+raddb = raddb
+
+modconfdir = ${raddb}/mods-config
+
+correct_escapes = true
+
+# Only for testing!
+# Setting this on a production system is a BAD IDEA.
+security {
+ allow_vulnerable_openssl = yes
+}
+
+modules {
+ $INCLUDE ${raddb}/mods-enabled/always
+
+ $INCLUDE ${raddb}/mods-enabled/pap
+
+ $INCLUDE ${raddb}/mods-enabled/expr
+
+ $INCLUDE $ENV{MODULE_TEST_DIR}/module.conf
+}
+
+server default {
+ authorize {
+ #
+ # Include the test file specified by the
+ # KEYWORD environment variable.
+ #
+ $INCLUDE $ENV{MODULE_TEST_UNLANG}
+
+ pap
+ }
+
+ authenticate {
+ pap
+ }
+}
+
+policy {
+ test_pass {
+ update control {
+ Tmp-String-8 := "%{expr:%{%{control:Tmp-String-8}:-0} + 1}"
+ Auth-Type := Accept
+ }
+ }
+
+ test_fail {
+ update reply {
+ Reply-Message := "fail %{%{control:Tmp-String-8}:-0}"
+ }
+ reject
+ }
+
+ #
+ # Outputs the contents of the control list in debugging (-X) mode
+ #
+ debug_control {
+ if("%{debug_attr:control:}" == '') {
+ noop
+ }
+ }
+
+ #
+ # Outputs the contents of the request list in debugging (-X) mode
+ #
+ debug_request {
+ if("%{debug_attr:request:}" == '') {
+ noop
+ }
+ }
+
+ #
+ # Outputs the contents of the reply list in debugging (-X) mode
+ #
+ debug_reply {
+ if("%{debug_attr:reply:}" == '') {
+ noop
+ }
+ }
+
+ #
+ # Outputs the contents of the session state list in debugging (-X) mode
+ #
+ debug_session_state {
+ if("%{debug_attr:session-state:}" == '') {
+ noop
+ }
+ }
+
+ #
+ # Outputs the contents of the main lists in debugging (-X) mode
+ #
+ debug_all {
+ debug_control
+ debug_request
+ debug_reply
+ debug_session_state
+ }
+}
diff --git a/src/tests/modules/sql/.gitignore b/src/tests/modules/sql/.gitignore
new file mode 100644
index 0000000..405551a
--- /dev/null
+++ b/src/tests/modules/sql/.gitignore
@@ -0,0 +1 @@
+rlm_sql_sqlite.db
diff --git a/src/tests/modules/sql/acct_0_start.attrs b/src/tests/modules/sql/acct_0_start.attrs
new file mode 100644
index 0000000..01257ce
--- /dev/null
+++ b/src/tests/modules/sql/acct_0_start.attrs
@@ -0,0 +1,37 @@
+#
+# Input packet
+#
+User-Name = 'user0@example.org'
+NAS-Port = 17826193
+NAS-IP-Address = 192.0.2.10
+Framed-IP-Address = 198.51.100.59
+NAS-Identifier = 'nas.example.org'
+Acct-Status-Type = Start
+Acct-Delay-Time = 1
+Acct-Input-Octets = 0
+Acct-Output-Octets = 0
+Acct-Session-Id = '00000000'
+Acct-Unique-Session-Id = '00000000'
+Acct-Authentic = RADIUS
+Acct-Session-Time = 0
+Acct-Input-Packets = 0
+Acct-Output-Packets = 0
+Acct-Input-Gigawords = 0
+Acct-Output-Gigawords = 0
+Event-Timestamp = 'Feb 1 2015 08:28:58 WIB'
+NAS-Port-Type = Ethernet
+NAS-Port-Id = 'port 001'
+Service-Type = Framed-User
+Framed-Protocol = PPP
+Acct-Link-Count = 0
+Idle-Timeout = 0
+Session-Timeout = 604800
+Access-Loop-Encapsulation = 0x000000
+Proxy-State = 0x323531
+
+#
+# Expected answer
+#
+# There's not an Accounting-Failed packet type in RADIUS...
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/sql/acct_0_start.unlang b/src/tests/modules/sql/acct_0_start.unlang
new file mode 100644
index 0000000..64921b1
--- /dev/null
+++ b/src/tests/modules/sql/acct_0_start.unlang
@@ -0,0 +1,40 @@
+#
+# Clear out old data
+#
+update {
+ Tmp-String-0 := "%{sql:DELETE FROM radacct WHERE AcctSessionId = '00000000'}"
+}
+if (!&Tmp-String-0) {
+ test_fail
+}
+else {
+ test_pass
+}
+
+sql.accounting
+if (ok) {
+ test_pass
+}
+else {
+ test_fail
+}
+
+update {
+ Tmp-Integer-0 := "%{sql:SELECT count(*) FROM radacct WHERE AcctSessionId = '00000000'}"
+}
+if (!&Tmp-Integer-0 || (&Tmp-Integer-0 != 1)) {
+ test_fail
+}
+else {
+ test_pass
+}
+
+update {
+ Tmp-Integer-0 := "%{sql:SELECT acctsessiontime FROM radacct WHERE AcctSessionId = '00000000'}"
+}
+if (!&Tmp-Integer-0 || (&Tmp-Integer-0 != 0)) {
+ test_fail
+}
+else {
+ test_pass
+}
diff --git a/src/tests/modules/sql/acct_1_update.attrs b/src/tests/modules/sql/acct_1_update.attrs
new file mode 100644
index 0000000..28db958
--- /dev/null
+++ b/src/tests/modules/sql/acct_1_update.attrs
@@ -0,0 +1,37 @@
+#
+# Input packet
+#
+User-Name = 'user1@example.org'
+NAS-Port = 17826193
+NAS-IP-Address = 192.0.2.10
+Framed-IP-Address = 198.51.100.59
+NAS-Identifier = 'nas.example.org'
+Acct-Status-Type = Interim-Update
+Acct-Delay-Time = 1
+Acct-Input-Octets = 10
+Acct-Output-Octets = 10
+Acct-Session-Id = '00000001'
+Acct-Unique-Session-Id = '00000001'
+Acct-Authentic = RADIUS
+Acct-Session-Time = 30
+Acct-Input-Packets = 10
+Acct-Output-Packets = 10
+Acct-Input-Gigawords = 1
+Acct-Output-Gigawords = 1
+Event-Timestamp = 'Feb 1 2015 08:28:28 WIB'
+NAS-Port-Type = Ethernet
+NAS-Port-Id = 'port 001'
+Service-Type = Framed-User
+Framed-Protocol = PPP
+Acct-Link-Count = 0
+Idle-Timeout = 0
+Session-Timeout = 604800
+Access-Loop-Encapsulation = 0x000000
+Proxy-State = 0x323531
+
+#
+# Expected answer
+#
+# There's not an Accounting-Failed packet type in RADIUS...
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/sql/acct_1_update.unlang b/src/tests/modules/sql/acct_1_update.unlang
new file mode 100644
index 0000000..e566a4a
--- /dev/null
+++ b/src/tests/modules/sql/acct_1_update.unlang
@@ -0,0 +1,30 @@
+#
+# PRE: acct_0_start
+#
+sql.accounting
+if (ok) {
+ test_pass
+}
+else {
+ test_fail
+}
+
+update {
+ Tmp-Integer-0 := "%{sql:SELECT count(*) FROM radacct WHERE AcctSessionId = '00000001'}"
+}
+if (!&Tmp-Integer-0 || (&Tmp-Integer-0 != 1)) {
+ test_fail
+}
+else {
+ test_pass
+}
+
+update {
+ Tmp-Integer-0 := "%{sql:SELECT acctsessiontime FROM radacct WHERE AcctSessionId = '00000001'}"
+}
+if (!&Tmp-Integer-0 || (&Tmp-Integer-0 != 30)) {
+ test_fail
+}
+else {
+ test_pass
+}
diff --git a/src/tests/modules/sql/acct_2_stop.attrs b/src/tests/modules/sql/acct_2_stop.attrs
new file mode 100644
index 0000000..e932f84
--- /dev/null
+++ b/src/tests/modules/sql/acct_2_stop.attrs
@@ -0,0 +1,38 @@
+#
+# Input packet
+#
+User-Name = 'user2@example.org'
+NAS-Port = 17826193
+NAS-IP-Address = 192.0.2.10
+Framed-IP-Address = 198.51.100.59
+NAS-Identifier = 'nas.example.org'
+Acct-Status-Type = Stop
+Acct-Terminate-Cause = User-Request
+Acct-Delay-Time = 1
+Acct-Input-Octets = 15
+Acct-Output-Octets = 15
+Acct-Session-Id = '00000002'
+Acct-Unique-Session-Id = '00000002'
+Acct-Authentic = RADIUS
+Acct-Session-Time = 120
+Acct-Input-Packets = 15
+Acct-Output-Packets = 15
+Acct-Input-Gigawords = 1
+Acct-Output-Gigawords = 1
+Event-Timestamp = 'Feb 1 2015 08:28:58 WIB'
+NAS-Port-Type = Ethernet
+NAS-Port-Id = 'port 001'
+Service-Type = Framed-User
+Framed-Protocol = PPP
+Acct-Link-Count = 0
+Idle-Timeout = 0
+Session-Timeout = 604800
+Access-Loop-Encapsulation = 0x000000
+Proxy-State = 0x323531
+
+#
+# Expected answer
+#
+# There's not an Accounting-Failed packet type in RADIUS...
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/sql/acct_2_stop.unlang b/src/tests/modules/sql/acct_2_stop.unlang
new file mode 100644
index 0000000..3386c71
--- /dev/null
+++ b/src/tests/modules/sql/acct_2_stop.unlang
@@ -0,0 +1,40 @@
+#
+# PRE: acct_1_update
+#
+sql.accounting
+if (ok) {
+ test_pass
+}
+else {
+ test_fail
+}
+
+update {
+ Tmp-Integer-0 := "%{sql:SELECT count(*) FROM radacct WHERE AcctSessionId = '00000002'}"
+}
+if (!&Tmp-Integer-0 || (&Tmp-Integer-0 != 1)) {
+ test_fail
+}
+else {
+ test_pass
+}
+
+update {
+ Tmp-Integer-0 := "%{sql:SELECT acctsessiontime FROM radacct WHERE AcctSessionId = '00000002'}"
+}
+if (!&Tmp-Integer-0 || (&Tmp-Integer-0 != 120)) {
+ test_fail
+}
+else {
+ test_pass
+}
+
+update {
+ Tmp-String-0 := "%{sql:SELECT AcctTerminateCause FROM radacct WHERE AcctSessionId = '00000002'}"
+}
+if (!&Tmp-String-0 || (&Tmp-String-0 != 'User-Request')) {
+ test_fail
+}
+else {
+ test_pass
+}
diff --git a/src/tests/modules/sql/acct_start_conflict.attrs b/src/tests/modules/sql/acct_start_conflict.attrs
new file mode 100644
index 0000000..2bcade3
--- /dev/null
+++ b/src/tests/modules/sql/acct_start_conflict.attrs
@@ -0,0 +1,37 @@
+#
+# Input packet
+#
+User-Name = 'user3@example.org'
+NAS-Port = 17826193
+NAS-IP-Address = 192.0.2.10
+Framed-IP-Address = 198.51.100.59
+NAS-Identifier = 'nas.example.org'
+Acct-Status-Type = Start
+Acct-Delay-Time = 1
+Acct-Input-Octets = 0
+Acct-Output-Octets = 0
+Acct-Session-Id = '00000003'
+Acct-Unique-Session-Id = '00000003'
+Acct-Authentic = RADIUS
+Acct-Session-Time = 0
+Acct-Input-Packets = 0
+Acct-Output-Packets = 0
+Acct-Input-Gigawords = 0
+Acct-Output-Gigawords = 0
+Event-Timestamp = 'Feb 1 2015 08:28:58 WIB'
+NAS-Port-Type = Ethernet
+NAS-Port-Id = 'port 001'
+Service-Type = Framed-User
+Framed-Protocol = PPP
+Acct-Link-Count = 0
+Idle-Timeout = 0
+Session-Timeout = 604800
+Access-Loop-Encapsulation = 0x000000
+Proxy-State = 0x323531
+
+#
+# Expected answer
+#
+# There's not an Accounting-Failed packet type in RADIUS...
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/sql/acct_start_conflict.unlang b/src/tests/modules/sql/acct_start_conflict.unlang
new file mode 100644
index 0000000..65e69e0
--- /dev/null
+++ b/src/tests/modules/sql/acct_start_conflict.unlang
@@ -0,0 +1,76 @@
+#
+# Check that conflicting unique IDs triggers failover to alternative query
+#
+
+#
+# Clear out old data
+#
+update {
+ Tmp-String-0 := "%{sql:DELETE FROM radacct WHERE AcctSessionId = '00000003'}"
+}
+if (!&Tmp-String-0) {
+ test_fail
+}
+else {
+ test_pass
+}
+
+#
+# Insert the Accounting-Request start
+#
+sql.accounting
+if (ok) {
+ test_pass
+}
+else {
+ test_fail
+}
+
+#
+# Check the database has at least one row
+#
+update {
+ Tmp-Integer-0 := "%{sql:SELECT count(*) FROM radacct WHERE AcctSessionId = '00000003'}"
+}
+if (!&Tmp-Integer-0 || (&Tmp-Integer-0 != 1)) {
+ test_fail
+}
+else {
+ test_pass
+}
+
+#
+# Check acctsessiontime matches the value in the request
+#
+update {
+ Tmp-Integer-0 := "%{sql:SELECT acctsessiontime FROM radacct WHERE AcctSessionId = '00000003'}"
+}
+if (!&Tmp-Integer-0 || (&Tmp-Integer-0 != 0)) {
+ test_fail
+}
+else {
+ test_pass
+}
+
+#
+# Change acctsessiontime and verify it's updated
+#
+update request {
+ Connect-Info = 'updated'
+}
+sql.accounting
+if (ok) {
+ test_pass
+}
+else {
+ test_fail
+}
+update {
+ Tmp-String-0 := "%{sql:SELECT connectinfo_start FROM radacct WHERE AcctSessionId = '00000003'}"
+}
+if (!&Tmp-Integer-0 || (&Tmp-String-0 != 'updated')) {
+ test_fail
+}
+else {
+ test_pass
+}
diff --git a/src/tests/modules/sql/acct_update_no_start.attrs b/src/tests/modules/sql/acct_update_no_start.attrs
new file mode 100644
index 0000000..6f3049e
--- /dev/null
+++ b/src/tests/modules/sql/acct_update_no_start.attrs
@@ -0,0 +1,37 @@
+#
+# Input packet
+#
+User-Name = 'user4@example.org'
+NAS-Port = 17826193
+NAS-IP-Address = 192.0.2.10
+Framed-IP-Address = 198.51.100.59
+NAS-Identifier = 'nas.example.org'
+Acct-Status-Type = Interim-Update
+Acct-Delay-Time = 1
+Acct-Input-Octets = 10
+Acct-Output-Octets = 10
+Acct-Session-Id = '00000004'
+Acct-Unique-Session-Id = '00000004'
+Acct-Authentic = RADIUS
+Acct-Session-Time = 30
+Acct-Input-Packets = 10
+Acct-Output-Packets = 10
+Acct-Input-Gigawords = 1
+Acct-Output-Gigawords = 1
+Event-Timestamp = 'Feb 1 2015 08:28:28 WIB'
+NAS-Port-Type = Ethernet
+NAS-Port-Id = 'port 001'
+Service-Type = Framed-User
+Framed-Protocol = PPP
+Acct-Link-Count = 0
+Idle-Timeout = 0
+Session-Timeout = 604800
+Access-Loop-Encapsulation = 0x000000
+Proxy-State = 0x323531
+
+#
+# Expected answer
+#
+# There's not an Accounting-Failed packet type in RADIUS...
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/sql/acct_update_no_start.unlang b/src/tests/modules/sql/acct_update_no_start.unlang
new file mode 100644
index 0000000..3875b2d
--- /dev/null
+++ b/src/tests/modules/sql/acct_update_no_start.unlang
@@ -0,0 +1,40 @@
+#
+# Clear out old data
+#
+update {
+ Tmp-String-0 := "%{sql:DELETE FROM radacct WHERE AcctSessionId = '00000004'}"
+}
+if (!&Tmp-String-0) {
+ test_fail
+}
+else {
+ test_pass
+}
+
+sql.accounting
+if (ok) {
+ test_pass
+}
+else {
+ test_fail
+}
+
+update {
+ Tmp-Integer-0 := "%{sql:SELECT count(*) FROM radacct WHERE AcctSessionId = '00000004'}"
+}
+if (!&Tmp-Integer-0 || (&Tmp-Integer-0 != 1)) {
+ test_fail
+}
+else {
+ test_pass
+}
+
+update {
+ Tmp-Integer-0 := "%{sql:SELECT acctsessiontime FROM radacct WHERE AcctSessionId = '00000004'}"
+}
+if (!&Tmp-Integer-0 || (&Tmp-Integer-0 != 30)) {
+ test_fail
+}
+else {
+ test_pass
+}
diff --git a/src/tests/modules/sql/auth.attrs b/src/tests/modules/sql/auth.attrs
new file mode 100644
index 0000000..e7d1498
--- /dev/null
+++ b/src/tests/modules/sql/auth.attrs
@@ -0,0 +1,12 @@
+#
+# Input packet
+#
+User-Name = "user_auth"
+User-Password = "password"
+NAS-IP-Address = "1.2.3.4"
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
+Idle-Timeout == 3600
diff --git a/src/tests/modules/sql/auth.unlang b/src/tests/modules/sql/auth.unlang
new file mode 100644
index 0000000..0d76538
--- /dev/null
+++ b/src/tests/modules/sql/auth.unlang
@@ -0,0 +1,39 @@
+#
+# Clear out old data
+#
+update {
+ Tmp-String-0 := "%{sql:DELETE FROM radcheck WHERE username = 'user_auth'}"
+}
+if (!&Tmp-String-0) {
+ test_fail
+}
+
+update {
+ Tmp-String-0 := "%{sql:INSERT INTO radcheck (username, attribute, op, value) VALUES ('user_auth', 'NAS-IP-Address', '==', '1.2.3.4')}"
+}
+if (!&Tmp-String-0) {
+ test_fail
+}
+
+update {
+ Tmp-String-0 := "%{sql:INSERT INTO radcheck (username, attribute, op, value) VALUES ('user_auth', 'Cleartext-Password', ':=', 'password')}"
+}
+if (!&Tmp-String-0) {
+ test_fail
+}
+
+update {
+ Tmp-String-0 := "%{sql:DELETE FROM radreply WHERE username = 'user_auth'}"
+}
+if (!&Tmp-String-0) {
+ test_fail
+}
+
+update {
+ Tmp-String-0 := "%{sql:INSERT INTO radreply (username, attribute, op, value) VALUES ('user_auth', 'Idle-Timeout', ':=', '3600')}"
+}
+if (!&Tmp-String-0) {
+ test_fail
+}
+
+sql
diff --git a/src/tests/modules/sql/reject.attrs b/src/tests/modules/sql/reject.attrs
new file mode 100644
index 0000000..cb0b9a2
--- /dev/null
+++ b/src/tests/modules/sql/reject.attrs
@@ -0,0 +1,12 @@
+#
+# Input packet
+#
+User-Name = "user_reject"
+User-Password = "password"
+NAS-IP-Address = "1.2.3.4"
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Reject
+Reply-Message == "Authentication failed"
diff --git a/src/tests/modules/sql/reject.unlang b/src/tests/modules/sql/reject.unlang
new file mode 100644
index 0000000..b4afb09
--- /dev/null
+++ b/src/tests/modules/sql/reject.unlang
@@ -0,0 +1,39 @@
+#
+# Clear out old data
+#
+update {
+ Tmp-String-0 := "%{sql:DELETE FROM radcheck WHERE username = 'user_reject'}"
+}
+if (!&Tmp-String-0) {
+ test_fail
+}
+
+update {
+ Tmp-String-0 := "%{sql:INSERT INTO radcheck (username, attribute, op, value) VALUES ('user_reject', 'NAS-IP-Address', '==', '1.2.3.4')}"
+}
+if (!&Tmp-String-0) {
+ test_fail
+}
+
+update {
+ Tmp-String-0 := "%{sql:INSERT INTO radcheck (username, attribute, op, value) VALUES ('user_reject', 'Cleartext-Password', ':=', 'wrong-password')}"
+}
+if (!&Tmp-String-0) {
+ test_fail
+}
+
+update {
+ Tmp-String-0 := "%{sql:DELETE FROM radreply WHERE username = 'user_reject'}"
+}
+if (!&Tmp-String-0) {
+ test_fail
+}
+
+update {
+ Tmp-String-0 := "%{sql:INSERT INTO radreply (username, attribute, op, value) VALUES ('user_reject', 'Reply-Message', ':=', 'Authentication failed')}"
+}
+if (!&Tmp-String-0) {
+ test_fail
+}
+
+sql
diff --git a/src/tests/modules/sql_mysql/.gitignore b/src/tests/modules/sql_mysql/.gitignore
new file mode 100644
index 0000000..405551a
--- /dev/null
+++ b/src/tests/modules/sql_mysql/.gitignore
@@ -0,0 +1 @@
+rlm_sql_sqlite.db
diff --git a/src/tests/modules/sql_mysql/acct_0_start.attrs b/src/tests/modules/sql_mysql/acct_0_start.attrs
new file mode 120000
index 0000000..24e17ae
--- /dev/null
+++ b/src/tests/modules/sql_mysql/acct_0_start.attrs
@@ -0,0 +1 @@
+../sql/acct_0_start.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_mysql/acct_0_start.unlang b/src/tests/modules/sql_mysql/acct_0_start.unlang
new file mode 120000
index 0000000..3fe3e99
--- /dev/null
+++ b/src/tests/modules/sql_mysql/acct_0_start.unlang
@@ -0,0 +1 @@
+../sql/acct_0_start.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_mysql/acct_1_update.attrs b/src/tests/modules/sql_mysql/acct_1_update.attrs
new file mode 120000
index 0000000..1ab772d
--- /dev/null
+++ b/src/tests/modules/sql_mysql/acct_1_update.attrs
@@ -0,0 +1 @@
+../sql/acct_1_update.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_mysql/acct_1_update.unlang b/src/tests/modules/sql_mysql/acct_1_update.unlang
new file mode 120000
index 0000000..b69ff9b
--- /dev/null
+++ b/src/tests/modules/sql_mysql/acct_1_update.unlang
@@ -0,0 +1 @@
+../sql/acct_1_update.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_mysql/acct_2_stop.attrs b/src/tests/modules/sql_mysql/acct_2_stop.attrs
new file mode 120000
index 0000000..ea73931
--- /dev/null
+++ b/src/tests/modules/sql_mysql/acct_2_stop.attrs
@@ -0,0 +1 @@
+../sql/acct_2_stop.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_mysql/acct_2_stop.unlang b/src/tests/modules/sql_mysql/acct_2_stop.unlang
new file mode 120000
index 0000000..ea0be56
--- /dev/null
+++ b/src/tests/modules/sql_mysql/acct_2_stop.unlang
@@ -0,0 +1 @@
+../sql/acct_2_stop.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_mysql/acct_start_conflict.attrs b/src/tests/modules/sql_mysql/acct_start_conflict.attrs
new file mode 120000
index 0000000..117a505
--- /dev/null
+++ b/src/tests/modules/sql_mysql/acct_start_conflict.attrs
@@ -0,0 +1 @@
+../sql/acct_start_conflict.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_mysql/acct_start_conflict.unlang b/src/tests/modules/sql_mysql/acct_start_conflict.unlang
new file mode 120000
index 0000000..da35798
--- /dev/null
+++ b/src/tests/modules/sql_mysql/acct_start_conflict.unlang
@@ -0,0 +1 @@
+../sql/acct_start_conflict.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_mysql/acct_update_no_start.attrs b/src/tests/modules/sql_mysql/acct_update_no_start.attrs
new file mode 120000
index 0000000..328867f
--- /dev/null
+++ b/src/tests/modules/sql_mysql/acct_update_no_start.attrs
@@ -0,0 +1 @@
+../sql/acct_update_no_start.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_mysql/acct_update_no_start.unlang b/src/tests/modules/sql_mysql/acct_update_no_start.unlang
new file mode 120000
index 0000000..6837977
--- /dev/null
+++ b/src/tests/modules/sql_mysql/acct_update_no_start.unlang
@@ -0,0 +1 @@
+../sql/acct_update_no_start.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_mysql/all.mk b/src/tests/modules/sql_mysql/all.mk
new file mode 100644
index 0000000..337528b
--- /dev/null
+++ b/src/tests/modules/sql_mysql/all.mk
@@ -0,0 +1,6 @@
+#
+# Test the mysql module
+#
+
+# Don't test sql_mysql if TEST_SERVER ENV is not set
+sql_mysql_require_test_server := 1
diff --git a/src/tests/modules/sql_mysql/auth.attrs b/src/tests/modules/sql_mysql/auth.attrs
new file mode 120000
index 0000000..6b30b6b
--- /dev/null
+++ b/src/tests/modules/sql_mysql/auth.attrs
@@ -0,0 +1 @@
+../sql/auth.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_mysql/auth.unlang b/src/tests/modules/sql_mysql/auth.unlang
new file mode 120000
index 0000000..3ccd80e
--- /dev/null
+++ b/src/tests/modules/sql_mysql/auth.unlang
@@ -0,0 +1 @@
+../sql/auth.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_mysql/module.conf b/src/tests/modules/sql_mysql/module.conf
new file mode 100644
index 0000000..e3aa02d
--- /dev/null
+++ b/src/tests/modules/sql_mysql/module.conf
@@ -0,0 +1,53 @@
+sql {
+ driver = "rlm_sql_mysql"
+ dialect = "mysql"
+
+ # Connection info:
+ #
+ server = $ENV{SQL_MYSQL_TEST_SERVER}
+ port = 3306
+ login = "radius"
+ password = "radpass"
+
+ # Database table configuration for everything except Oracle
+ radius_db = "radius"
+ radius_db = "radius"
+
+ acct_table1 = "radacct"
+ acct_table2 = "radacct"
+ postauth_table = "radpostauth"
+ authcheck_table = "radcheck"
+ groupcheck_table = "radgroupcheck"
+ authreply_table = "radreply"
+ groupreply_table = "radgroupreply"
+ usergroup_table = "radusergroup"
+ read_groups = yes
+ read_profiles = yes
+
+ # Remove stale session if checkrad does not see a double login
+ delete_stale_sessions = yes
+
+ pool {
+ start = 1
+ min = 0
+ max = 1
+ spare = 3
+ uses = 2
+ lifetime = 1
+ idle_timeout = 60
+ retry_delay = 1
+ }
+
+ # Set to 'yes' to read radius clients from the database ('nas' table)
+ # Clients will ONLY be read on server startup.
+# read_clients = yes
+
+ # Table to keep radius client info
+ client_table = "nas"
+
+ # The group attribute specific to this instance of rlm_sql
+ group_attribute = "SQL-Group"
+
+ # Read database-specific queries
+ $INCLUDE ${modconfdir}/${.:name}/main/${dialect}/queries.conf
+}
diff --git a/src/tests/modules/sql_mysql/reject.attrs b/src/tests/modules/sql_mysql/reject.attrs
new file mode 120000
index 0000000..71a187f
--- /dev/null
+++ b/src/tests/modules/sql_mysql/reject.attrs
@@ -0,0 +1 @@
+../sql/reject.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_mysql/reject.unlang b/src/tests/modules/sql_mysql/reject.unlang
new file mode 120000
index 0000000..379839f
--- /dev/null
+++ b/src/tests/modules/sql_mysql/reject.unlang
@@ -0,0 +1 @@
+../sql/reject.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_postgresql/.gitignore b/src/tests/modules/sql_postgresql/.gitignore
new file mode 100644
index 0000000..405551a
--- /dev/null
+++ b/src/tests/modules/sql_postgresql/.gitignore
@@ -0,0 +1 @@
+rlm_sql_sqlite.db
diff --git a/src/tests/modules/sql_postgresql/acct_0_start.attrs b/src/tests/modules/sql_postgresql/acct_0_start.attrs
new file mode 120000
index 0000000..24e17ae
--- /dev/null
+++ b/src/tests/modules/sql_postgresql/acct_0_start.attrs
@@ -0,0 +1 @@
+../sql/acct_0_start.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_postgresql/acct_0_start.unlang b/src/tests/modules/sql_postgresql/acct_0_start.unlang
new file mode 120000
index 0000000..3fe3e99
--- /dev/null
+++ b/src/tests/modules/sql_postgresql/acct_0_start.unlang
@@ -0,0 +1 @@
+../sql/acct_0_start.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_postgresql/acct_1_update.attrs b/src/tests/modules/sql_postgresql/acct_1_update.attrs
new file mode 120000
index 0000000..1ab772d
--- /dev/null
+++ b/src/tests/modules/sql_postgresql/acct_1_update.attrs
@@ -0,0 +1 @@
+../sql/acct_1_update.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_postgresql/acct_1_update.unlang b/src/tests/modules/sql_postgresql/acct_1_update.unlang
new file mode 120000
index 0000000..b69ff9b
--- /dev/null
+++ b/src/tests/modules/sql_postgresql/acct_1_update.unlang
@@ -0,0 +1 @@
+../sql/acct_1_update.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_postgresql/acct_2_stop.attrs b/src/tests/modules/sql_postgresql/acct_2_stop.attrs
new file mode 120000
index 0000000..ea73931
--- /dev/null
+++ b/src/tests/modules/sql_postgresql/acct_2_stop.attrs
@@ -0,0 +1 @@
+../sql/acct_2_stop.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_postgresql/acct_2_stop.unlang b/src/tests/modules/sql_postgresql/acct_2_stop.unlang
new file mode 120000
index 0000000..ea0be56
--- /dev/null
+++ b/src/tests/modules/sql_postgresql/acct_2_stop.unlang
@@ -0,0 +1 @@
+../sql/acct_2_stop.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_postgresql/acct_start_conflict.attrs b/src/tests/modules/sql_postgresql/acct_start_conflict.attrs
new file mode 120000
index 0000000..117a505
--- /dev/null
+++ b/src/tests/modules/sql_postgresql/acct_start_conflict.attrs
@@ -0,0 +1 @@
+../sql/acct_start_conflict.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_postgresql/acct_start_conflict.unlang b/src/tests/modules/sql_postgresql/acct_start_conflict.unlang
new file mode 120000
index 0000000..da35798
--- /dev/null
+++ b/src/tests/modules/sql_postgresql/acct_start_conflict.unlang
@@ -0,0 +1 @@
+../sql/acct_start_conflict.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_postgresql/acct_update_no_start.attrs b/src/tests/modules/sql_postgresql/acct_update_no_start.attrs
new file mode 120000
index 0000000..328867f
--- /dev/null
+++ b/src/tests/modules/sql_postgresql/acct_update_no_start.attrs
@@ -0,0 +1 @@
+../sql/acct_update_no_start.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_postgresql/acct_update_no_start.unlang b/src/tests/modules/sql_postgresql/acct_update_no_start.unlang
new file mode 120000
index 0000000..6837977
--- /dev/null
+++ b/src/tests/modules/sql_postgresql/acct_update_no_start.unlang
@@ -0,0 +1 @@
+../sql/acct_update_no_start.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_postgresql/all.mk b/src/tests/modules/sql_postgresql/all.mk
new file mode 100644
index 0000000..efd20d9
--- /dev/null
+++ b/src/tests/modules/sql_postgresql/all.mk
@@ -0,0 +1,6 @@
+#
+# Test the postgresql module
+#
+
+# Don't test sql_postgresql if TEST_SERVER ENV is not set
+sql_postgresql_require_test_server := 1
diff --git a/src/tests/modules/sql_postgresql/auth.attrs b/src/tests/modules/sql_postgresql/auth.attrs
new file mode 120000
index 0000000..6b30b6b
--- /dev/null
+++ b/src/tests/modules/sql_postgresql/auth.attrs
@@ -0,0 +1 @@
+../sql/auth.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_postgresql/auth.unlang b/src/tests/modules/sql_postgresql/auth.unlang
new file mode 120000
index 0000000..3ccd80e
--- /dev/null
+++ b/src/tests/modules/sql_postgresql/auth.unlang
@@ -0,0 +1 @@
+../sql/auth.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_postgresql/module.conf b/src/tests/modules/sql_postgresql/module.conf
new file mode 100644
index 0000000..ee9a8a9
--- /dev/null
+++ b/src/tests/modules/sql_postgresql/module.conf
@@ -0,0 +1,52 @@
+sql {
+ driver = "rlm_sql_postgresql"
+ dialect = "postgresql"
+
+ # Connection info:
+ #
+ server = $ENV{SQL_POSTGRESQL_TEST_SERVER}
+ port = 5432
+ login = "radius"
+ password = "radpass"
+
+ # Database table configuration for everything except Oracle
+ radius_db = "radius"
+
+ acct_table1 = "radacct"
+ acct_table2 = "radacct"
+ postauth_table = "radpostauth"
+ authcheck_table = "radcheck"
+ groupcheck_table = "radgroupcheck"
+ authreply_table = "radreply"
+ groupreply_table = "radgroupreply"
+ usergroup_table = "radusergroup"
+ read_groups = yes
+ read_profiles = yes
+
+ # Remove stale session if checkrad does not see a double login
+ delete_stale_sessions = yes
+
+ pool {
+ start = 1
+ min = 0
+ max = 1
+ spare = 3
+ uses = 2
+ lifetime = 1
+ idle_timeout = 60
+ retry_delay = 1
+ }
+
+ # Set to 'yes' to read radius clients from the database ('nas' table)
+ # Clients will ONLY be read on server startup.
+# read_clients = yes
+
+ # Table to keep radius client info
+ client_table = "nas"
+
+ # The group attribute specific to this instance of rlm_sql
+ group_attribute = "SQL-Group"
+
+ # Read database-specific queries
+ $INCLUDE ${modconfdir}/${.:name}/main/${dialect}/queries.conf
+}
diff --git a/src/tests/modules/sql_postgresql/reject.attrs b/src/tests/modules/sql_postgresql/reject.attrs
new file mode 120000
index 0000000..71a187f
--- /dev/null
+++ b/src/tests/modules/sql_postgresql/reject.attrs
@@ -0,0 +1 @@
+../sql/reject.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_postgresql/reject.unlang b/src/tests/modules/sql_postgresql/reject.unlang
new file mode 120000
index 0000000..379839f
--- /dev/null
+++ b/src/tests/modules/sql_postgresql/reject.unlang
@@ -0,0 +1 @@
+../sql/reject.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_sqlite/.gitignore b/src/tests/modules/sql_sqlite/.gitignore
new file mode 100644
index 0000000..405551a
--- /dev/null
+++ b/src/tests/modules/sql_sqlite/.gitignore
@@ -0,0 +1 @@
+rlm_sql_sqlite.db
diff --git a/src/tests/modules/sql_sqlite/acct_0_start.attrs b/src/tests/modules/sql_sqlite/acct_0_start.attrs
new file mode 120000
index 0000000..24e17ae
--- /dev/null
+++ b/src/tests/modules/sql_sqlite/acct_0_start.attrs
@@ -0,0 +1 @@
+../sql/acct_0_start.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_sqlite/acct_0_start.unlang b/src/tests/modules/sql_sqlite/acct_0_start.unlang
new file mode 120000
index 0000000..3fe3e99
--- /dev/null
+++ b/src/tests/modules/sql_sqlite/acct_0_start.unlang
@@ -0,0 +1 @@
+../sql/acct_0_start.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_sqlite/acct_1_update.attrs b/src/tests/modules/sql_sqlite/acct_1_update.attrs
new file mode 120000
index 0000000..1ab772d
--- /dev/null
+++ b/src/tests/modules/sql_sqlite/acct_1_update.attrs
@@ -0,0 +1 @@
+../sql/acct_1_update.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_sqlite/acct_1_update.unlang b/src/tests/modules/sql_sqlite/acct_1_update.unlang
new file mode 120000
index 0000000..b69ff9b
--- /dev/null
+++ b/src/tests/modules/sql_sqlite/acct_1_update.unlang
@@ -0,0 +1 @@
+../sql/acct_1_update.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_sqlite/acct_2_stop.attrs b/src/tests/modules/sql_sqlite/acct_2_stop.attrs
new file mode 120000
index 0000000..ea73931
--- /dev/null
+++ b/src/tests/modules/sql_sqlite/acct_2_stop.attrs
@@ -0,0 +1 @@
+../sql/acct_2_stop.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_sqlite/acct_2_stop.unlang b/src/tests/modules/sql_sqlite/acct_2_stop.unlang
new file mode 120000
index 0000000..ea0be56
--- /dev/null
+++ b/src/tests/modules/sql_sqlite/acct_2_stop.unlang
@@ -0,0 +1 @@
+../sql/acct_2_stop.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_sqlite/acct_start_conflict.attrs b/src/tests/modules/sql_sqlite/acct_start_conflict.attrs
new file mode 120000
index 0000000..117a505
--- /dev/null
+++ b/src/tests/modules/sql_sqlite/acct_start_conflict.attrs
@@ -0,0 +1 @@
+../sql/acct_start_conflict.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_sqlite/acct_start_conflict.unlang b/src/tests/modules/sql_sqlite/acct_start_conflict.unlang
new file mode 120000
index 0000000..da35798
--- /dev/null
+++ b/src/tests/modules/sql_sqlite/acct_start_conflict.unlang
@@ -0,0 +1 @@
+../sql/acct_start_conflict.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_sqlite/acct_update_no_start.attrs b/src/tests/modules/sql_sqlite/acct_update_no_start.attrs
new file mode 120000
index 0000000..328867f
--- /dev/null
+++ b/src/tests/modules/sql_sqlite/acct_update_no_start.attrs
@@ -0,0 +1 @@
+../sql/acct_update_no_start.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_sqlite/acct_update_no_start.unlang b/src/tests/modules/sql_sqlite/acct_update_no_start.unlang
new file mode 120000
index 0000000..6837977
--- /dev/null
+++ b/src/tests/modules/sql_sqlite/acct_update_no_start.unlang
@@ -0,0 +1 @@
+../sql/acct_update_no_start.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_sqlite/all.mk b/src/tests/modules/sql_sqlite/all.mk
new file mode 100644
index 0000000..a7907f1
--- /dev/null
+++ b/src/tests/modules/sql_sqlite/all.mk
@@ -0,0 +1,3 @@
+#
+# Test the sqlite module
+#
diff --git a/src/tests/modules/sql_sqlite/auth.attrs b/src/tests/modules/sql_sqlite/auth.attrs
new file mode 120000
index 0000000..6b30b6b
--- /dev/null
+++ b/src/tests/modules/sql_sqlite/auth.attrs
@@ -0,0 +1 @@
+../sql/auth.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_sqlite/auth.unlang b/src/tests/modules/sql_sqlite/auth.unlang
new file mode 120000
index 0000000..3ccd80e
--- /dev/null
+++ b/src/tests/modules/sql_sqlite/auth.unlang
@@ -0,0 +1 @@
+../sql/auth.unlang \ No newline at end of file
diff --git a/src/tests/modules/sql_sqlite/module.conf b/src/tests/modules/sql_sqlite/module.conf
new file mode 100644
index 0000000..1d8ac74
--- /dev/null
+++ b/src/tests/modules/sql_sqlite/module.conf
@@ -0,0 +1,52 @@
+sql {
+ driver = "rlm_sql_sqlite"
+ dialect = "sqlite"
+ sqlite {
+ # Path to the sqlite database
+ filename = "$ENV{MODULE_TEST_DIR}/sql_sqlite/rlm_sql_sqlite.db"
+
+ # If the file above does not exist and bootstrap is set
+ # a new database file will be created, and the SQL statements
+ # contained within the file will be executed.
+ bootstrap = "${modconfdir}/${..:name}/main/${..dialect}/schema.sql"
+ }
+ radius_db = "radius"
+
+ acct_table1 = "radacct"
+ acct_table2 = "radacct"
+ postauth_table = "radpostauth"
+ authcheck_table = "radcheck"
+ groupcheck_table = "radgroupcheck"
+ authreply_table = "radreply"
+ groupreply_table = "radgroupreply"
+ usergroup_table = "radusergroup"
+ read_groups = yes
+ read_profiles = yes
+
+ # Remove stale session if checkrad does not see a double login
+ delete_stale_sessions = yes
+
+ pool {
+ start = 1
+ min = 0
+ max = 1
+ spare = 3
+ uses = 2
+ lifetime = 1
+ idle_timeout = 60
+ retry_delay = 1
+ }
+
+ # Set to 'yes' to read radius clients from the database ('nas' table)
+ # Clients will ONLY be read on server startup.
+# read_clients = yes
+
+ # Table to keep radius client info
+ client_table = "nas"
+
+ # The group attribute specific to this instance of rlm_sql
+ group_attribute = "SQL-Group"
+
+ # Read database-specific queries
+ $INCLUDE ${modconfdir}/${.:name}/main/${dialect}/queries.conf
+}
diff --git a/src/tests/modules/sql_sqlite/reject.attrs b/src/tests/modules/sql_sqlite/reject.attrs
new file mode 120000
index 0000000..71a187f
--- /dev/null
+++ b/src/tests/modules/sql_sqlite/reject.attrs
@@ -0,0 +1 @@
+../sql/reject.attrs \ No newline at end of file
diff --git a/src/tests/modules/sql_sqlite/reject.unlang b/src/tests/modules/sql_sqlite/reject.unlang
new file mode 120000
index 0000000..379839f
--- /dev/null
+++ b/src/tests/modules/sql_sqlite/reject.unlang
@@ -0,0 +1 @@
+../sql/reject.unlang \ No newline at end of file
diff --git a/src/tests/modules/test.mk b/src/tests/modules/test.mk
new file mode 100644
index 0000000..b217ff7
--- /dev/null
+++ b/src/tests/modules/test.mk
@@ -0,0 +1,165 @@
+#
+# Add the module tests to the overall dependencies
+#
+
+TESTS.MODULES_FILES :=
+
+# If module requires test server, make sure TEST_SERVER of <MODULE>_TEST_SERVER variables are defined
+# If TEST_SERVER is defined, define <MODULE>_TEST_SERVER for all modules that have CHECK_MODULE_TEST_CAN_BE_RUN
+define CHECK_MODULE_TEST_CAN_BE_RUN
+ ifndef ${1}_require_test_server
+ tests.modules: ${1}.test
+ else
+ ifdef TEST_SERVER
+ tests.modules: ${1}.test
+ export $(shell echo ${1} | tr a-z A-Z)_TEST_SERVER := $(TEST_SERVER)
+ endif
+ ifdef $(shell echo ${1} | tr a-z A-Z)_TEST_SERVER
+ tests.modules: ${1}.test
+ endif
+ endif
+endef
+$(foreach x,$(TEST_BUILT) $(TEST_SUBBUILT),$(eval $(call CHECK_MODULE_TEST_CAN_BE_RUN,$x)))
+
+######################################################################
+#
+# And now more makefile magic to automatically run the tests
+# for each module.
+#
+
+define DEFAULT_ATTRS
+ifeq "$(wildcard ${1}.attrs)"
+${1}.attrs
+else
+src/tests/modules/default-input.attrs
+endif
+endef
+
+#
+# Files in the output dir depend on the unit tests
+#
+# src/tests/$(MODULE_DIR)/FOO.unlang unlang for the test
+# src/tests/$(MODULE_DIR)/FOO.attrs input RADIUS and output filter
+# build/tests/$(MODULE_DIR)/FOO.out updated if the test succeeds
+# build/tests/$(MODULE_DIR)/FOO.log debug output for the test
+#
+# If the test fails, then look for ERROR in the input. No error
+# means it's unexpected, so we die.
+#
+# Otherwise, check the log file for a parse error which matches the
+# ERROR line in the input.
+#
+$(BUILD_DIR)/tests/modules/%: src/tests/modules/%.unlang $(BUILD_DIR)/tests/modules/%.attrs $(TESTBINDIR)/unittest | build.raddb
+ @mkdir -p $(dir $@)
+ @echo MODULE-TEST $(lastword $(subst /, ,$(dir $@))) $(basename $(notdir $@))
+ @if ! MODULE_TEST_DIR=$(dir $<) MODULE_TEST_UNLANG=$< $(TESTBIN)/unittest -D share -d src/tests/modules/ -i $@.attrs -f $@.attrs -xxx > $@.log 2>&1; then \
+ if ! grep ERROR $< 2>&1 > /dev/null; then \
+ cat $@.log; \
+ echo "# $@.log"; \
+ echo MODULE_TEST_DIR=$(dir $<) MODULE_TEST_UNLANG=$< $(TESTBIN)/unittest -D share -d src/tests/modules/ -i $@.attrs -f $@.attrs -xx; \
+ exit 1; \
+ fi; \
+ FOUND=$$(grep ^$< $@.log | head -1 | sed 's/:.*//;s/.*\[//;s/\].*//'); \
+ EXPECTED=$$(grep -n ERROR $< | sed 's/:.*//'); \
+ if [ "$$EXPECTED" != "$$FOUND" ]; then \
+ cat $@.log; \
+ echo "# $@.log"; \
+ echo MODULE_TEST_DIR=$(dir $<) MODULE_TEST_UNLANG=$< $(TESTBIN)/unittest -D share -d src/tests/modules/ -i $@.attrs -f $@.attrs -xx; \
+ exit 1; \
+ fi \
+ fi
+ @touch $@
+
+#
+# Sometimes we have a default input. So use that. Otherwise, use
+# the input specific to the test.
+#
+MODULE_UNLANG := $(wildcard src/tests/modules/*/*.unlang src/tests/modules/*/*/*.unlang)
+MODULE_ATTRS_REQUIRES := $(patsubst %.unlang,%.attrs,$(MODULE_UNLANG))
+MODULE_ATTRS_EXISTS := $(wildcard src/tests/modules/*/*.attrs src/tests/modules/*/*/*.attrs)
+MODULE_ATTRS_NEEDS := $(filter-out $(MODULE_ATTRS_EXISTS),$(MODULE_ATTRS_REQUIRES))
+
+MODULE_CONF_REQUIRES := $(patsubst %.unlang,%.conf,$(MODULE_UNLANG))
+MODULE_CONF_EXISTS := $(wildcard src/tests/modules/*/*.conf src/tests/modules/*/*/*.attrs)
+MODULE_CONF_NEEDS := $(filter-out $(MODULE_CONF_EXISTS),$(MODULE_CONF_REQUIRES))
+
+#
+# The complete list of tests which are to be run
+#
+MODULE_TESTS := $(patsubst src/tests/modules/%/all.mk,%,$(wildcard src/tests/modules/*/all.mk))
+
+
+#
+# Target-specific rules
+#
+define MODULE_COPY_FILE
+$(BUILD_DIR)/${1}: src/${1}
+ @mkdir -p $$(@D)
+ @cp $$< $$@
+
+endef
+
+#
+# Default rules
+#
+define MODULE_COPY_ATTR
+$(BUILD_DIR)/${1}: src/tests/modules/default-input.attrs
+ @mkdir -p $$(@D)
+ @cp $$< $$@
+endef
+
+#
+# FIXME: get this working
+#
+define MODULE_COPY_CONF
+$(BUILD_DIR)/${1}: src/tests/modules/${2}/module.conf
+ @mkdir -p $$(@D)
+ @cp $$< $$@
+endef
+
+define MODULE_FILE_TARGET
+$(BUILD_DIR)/${1}: src/${1}.unlang $(BUILD_DIR)/${1}.attrs
+
+endef
+
+define MODULE_TEST_TARGET
+${1}.test: $(patsubst %.unlang,%,$(subst src,$(BUILD_DIR),$(filter src/tests/modules/${1}/%,$(MODULE_UNLANG))))
+
+TESTS.MODULES_FILES += $(patsubst %.unlang,%,$(subst src,$(BUILD_DIR),$(filter src/tests/modules/${1}/%,$(MODULE_UNLANG))))
+endef
+
+#
+# Create the rules from the list of input files
+#
+$(foreach x,$(MODULE_ATTRS_EXISTS),$(eval $(call MODULE_COPY_FILE,$(subst src/,,$x))))
+$(foreach x,$(MODULE_CONF_EXISTS),$(eval $(call MODULE_COPY_FILE,$(subst src/,,$x))))
+
+$(foreach x,$(MODULE_ATTRS_NEEDS),$(eval $(call MODULE_COPY_ATTR,$(subst src/,,$x))))
+# FIXME: copy src/tests/modules/*/module.conf to the right place, too
+
+$(foreach x,$(MODULE_UNLANG),$(eval $(call MODULE_FILE_TARGET,$(patsubst %.unlang,%,$(subst src/,,$x)))))
+$(foreach x,$(MODULE_TESTS),$(eval $(call MODULE_TEST_TARGET,$x)))
+
+$(TESTS.MODULES_FILES): $(TESTS.AUTH_FILES)
+
+.PHONY: clean.modules.test
+clean.modules.test:
+ @rm -rf $(BUILD_DIR)/tests/modules/
+
+#
+# For each file, look for precursor test.
+# Ensure that each test depends on its precursors.
+#
+-include $(BUILD_DIR)/tests/modules/depends.mk
+
+$(BUILD_DIR)/tests/modules/depends.mk: $(MODULE_UNLANG) | $(BUILD_DIR)/tests/modules
+ @rm -f $@
+ @for x in $^; do \
+ y=`grep PRE $$x | awk '{ print $$3 }'`; \
+ if [ "$$y" != "" ]; then \
+ z=`echo $$x | sed 's,src/,$(BUILD_DIR)/', | sed 's/.unlang//'`; \
+ d=$$(basename $$(dirname $$x)); \
+ echo "$$z: $(BUILD_DIR)/tests/modules/$$d/$$y" >> $@; \
+ echo "" >> $@; \
+ fi \
+ done
diff --git a/src/tests/modules/unbound/all.mk b/src/tests/modules/unbound/all.mk
new file mode 100644
index 0000000..d64039f
--- /dev/null
+++ b/src/tests/modules/unbound/all.mk
@@ -0,0 +1,3 @@
+#
+# Test the "unbound" module
+#
diff --git a/src/tests/modules/unbound/dns.attrs b/src/tests/modules/unbound/dns.attrs
new file mode 100644
index 0000000..1cce1c5
--- /dev/null
+++ b/src/tests/modules/unbound/dns.attrs
@@ -0,0 +1,11 @@
+#
+# Input packet
+#
+Packet-Type = Access-Request
+User-Name = "bob"
+User-Password = "hello"
+
+#
+# Expected answer
+#
+Response-Packet-Type == Access-Accept
diff --git a/src/tests/modules/unbound/dns.unlang b/src/tests/modules/unbound/dns.unlang
new file mode 100644
index 0000000..d53e433
--- /dev/null
+++ b/src/tests/modules/unbound/dns.unlang
@@ -0,0 +1,53 @@
+# Use builtin "local" zone
+update request {
+ &Tmp-IP-Address-0 := "%{dns-a:localhost}"
+}
+
+if (&Tmp-IP-Address-0 != 127.0.0.1) {
+ test_fail
+}
+
+update request {
+ &Tmp-String-0 := "%{dns-aaaa:localhost}"
+}
+
+if (&Tmp-String-0 != "::1") {
+ test_fail
+}
+
+update request {
+ &Tmp-String-1 := "%{dns-ptr:1.0.0.127.in-addr.arpa}"
+}
+
+if (&Tmp-String-1 != "localhost") {
+ test_fail
+}
+
+# Use local data in module config to allow for dotted names
+update request {
+ &Tmp-IP-Address-0 := "%{dns-a:www.example.com}"
+}
+
+if (&Tmp-IP-Address-0 != 192.168.1.1) {
+ test_fail
+}
+
+update request {
+ &Tmp-String-0 := "%{dns-ptr:1.1.168.192.in-addr.arpa}"
+}
+
+if (&Tmp-String-0 != "www.example.com") {
+ test_fail
+}
+
+# Try a real, known, network response
+# Temporarily disabled while there is a bug in unbound
+#update request {
+# &Tmp-String-0 := "%{dns-ptr:8.8.8.8.in-addr.arpa}"
+#}
+
+#if (&Tmp-String-0 != "dns.google") {
+# test_fail
+#}
+
+test_pass \ No newline at end of file
diff --git a/src/tests/modules/unbound/module.conf b/src/tests/modules/unbound/module.conf
new file mode 100644
index 0000000..c0430d2
--- /dev/null
+++ b/src/tests/modules/unbound/module.conf
@@ -0,0 +1,4 @@
+unbound dns {
+ filename = "$ENV{MODULE_TEST_DIR}/unbound.conf"
+ timeout = 3000
+}
diff --git a/src/tests/modules/unbound/unbound.conf b/src/tests/modules/unbound/unbound.conf
new file mode 100644
index 0000000..33fc461
--- /dev/null
+++ b/src/tests/modules/unbound/unbound.conf
@@ -0,0 +1,6 @@
+server:
+ num-threads: 2
+ local-data: 'www.example.com. A 192.168.1.1'
+ local-data: 'example.com. MX 10 mail.example.com'
+ local-data: 'example.com. MX 20 mail2.example.com'
+ local-data-ptr: '192.168.1.1 www.example.com'
diff --git a/src/tests/mschapv1 b/src/tests/mschapv1
new file mode 100644
index 0000000..338bf8a
--- /dev/null
+++ b/src/tests/mschapv1
@@ -0,0 +1,16 @@
+#
+# bob Cleartext-Password := "bob"
+#
+# TESTS 1
+#
+# SHOULD get:
+#
+# MS-CHAP-MPPE-Keys = 0x4318b176c3d8e3de9a936faf344359a0f1e3c9b5585b9f1f0000000000000000
+# MS-MPPE-Encryption-Policy = 0x00000001
+# MS-MPPE-Encryption-Types = 0x00000006
+#
+# NT Hash hash = 0x9a936faf344359a0f1e3c9b5585b9f1f
+#
+User-Name = "bob",
+MS-CHAP-Challenge = 0xb9634adc358b2ab3,
+MS-CHAP-Response = 0xb9010000000000000000000000000000000000000000000000007a42408782f745ef90a86fd21b0d9294132750f4af66a419
diff --git a/src/tests/panic.gdb b/src/tests/panic.gdb
new file mode 100644
index 0000000..3ae253a
--- /dev/null
+++ b/src/tests/panic.gdb
@@ -0,0 +1,4 @@
+info locals
+info args
+thread apply all bt full
+quit
diff --git a/src/tests/peap-client-mschapv2.conf b/src/tests/peap-client-mschapv2.conf
new file mode 100644
index 0000000..1c60933
--- /dev/null
+++ b/src/tests/peap-client-mschapv2.conf
@@ -0,0 +1,18 @@
+#
+# ./eapol_test -c peap-mschapv2.conf -s testing123
+#
+network={
+ ssid="example"
+ key_mgmt=WPA-EAP
+ eap=PEAP
+ identity="bob"
+ anonymous_identity="anonymous"
+ password="bob"
+ phase2="auth=MSCHAPV2"
+ phase1="peapver=0"
+
+ ca_cert="../../raddb/certs/ca.pem"
+ client_cert="../../raddb/certs/client.crt"
+ private_key="../../raddb/certs/client.key"
+ private_key_passwd="whatever"
+}
diff --git a/src/tests/peap-eap-tls.conf b/src/tests/peap-eap-tls.conf
new file mode 100644
index 0000000..ef5cd3d
--- /dev/null
+++ b/src/tests/peap-eap-tls.conf
@@ -0,0 +1,14 @@
+network={
+ key_mgmt=IEEE8021X
+ eap=PEAP
+ identity="user@example.org"
+ phase1=""
+ phase2="auth=TLS"
+
+ ca_cert="../../raddb/certs/ca.pem"
+
+ ca_cert2="../../raddb/certs/ca.pem"
+ client_cert2="../../raddb/certs/client.crt"
+ private_key2="../../raddb/certs/client.key"
+ private_key2_passwd="whatever"
+}
diff --git a/src/tests/peap-mschapv2.conf b/src/tests/peap-mschapv2.conf
new file mode 100644
index 0000000..ec9df68
--- /dev/null
+++ b/src/tests/peap-mschapv2.conf
@@ -0,0 +1,13 @@
+#
+# ./eapol_test -c peap-mschapv2.conf -s testing123
+#
+network={
+ ssid="example"
+ key_mgmt=WPA-EAP
+ eap=PEAP
+ identity="bob"
+ anonymous_identity="anonymous"
+ password="bob"
+ phase1="peapver=0"
+ phase2="auth=MSCHAPV2"
+}
diff --git a/src/tests/proxy.conf b/src/tests/proxy.conf
new file mode 100644
index 0000000..a6536e7
--- /dev/null
+++ b/src/tests/proxy.conf
@@ -0,0 +1,61 @@
+#
+# This is a LOCAL realm
+#
+realm example.com {
+ nostrip
+}
+
+#
+# And another one, where we strip the realm
+#
+realm stripped.example.com {
+
+}
+
+#
+# Some home servers, server pools, and realms. This tests that
+# the server can load them. Functionality is in another test.
+#
+home_server auth_one {
+ type = auth
+ ipaddr = 127.0.0.1
+ port = 12360
+ secret = testing123
+}
+
+home_server auth_two {
+ type = auth
+ ipaddr = 127.0.0.1
+ port = 12370
+ secret = testing123
+}
+
+server_pool fail-over {
+ type = fail-over
+ home_server = auth_one
+ home_server = auth_two
+}
+
+server_pool load-balance {
+ type = load-balance
+ home_server = auth_one
+ home_server = auth_two
+}
+
+server_pool client-balance {
+ type = client-balance
+ home_server = auth_one
+ home_server = auth_two
+}
+
+realm fail-over {
+ auth_pool = fail-over
+}
+
+realm load-balance {
+ auth_pool = load-balance
+}
+
+realm client-balance {
+ auth_pool = client-balance
+}
diff --git a/src/tests/radiusd.mk b/src/tests/radiusd.mk
new file mode 100644
index 0000000..eed4c79
--- /dev/null
+++ b/src/tests/radiusd.mk
@@ -0,0 +1,115 @@
+#
+# The "RADIUSD_SERVICE" macro is charged to start/stop the radiusd instances
+# from the mostly test targets. It expects the below variables.
+#
+# - Already defined by scripts/boiler.mk
+#
+# DIR = src/tests/$target
+# BUILD_DIR = build/
+#
+# - Defined by the target
+#
+# PORT := Run the service
+# TEST := test.$target
+#
+# - Parameter
+#
+# ${1} config-name found in $(DIR)/config, e.g: src/tests/$target/config/${config-name}.conf
+# ${2} output directory
+#
+# - How to use
+#
+# 1. $(eval $(call RADIUSD_SERVICE,myconfig,directory/path/))
+#
+# 2. It will defined the targets.
+#
+# $(TEST).radiusd_kill and $(TEST).radiusd_start
+#
+# 3. The target 'radiusd_start' define the variable $(RADIUSD_RUN) with the
+# exactly command used to start the service.
+#
+# 4. You could use the 'RADIUSD_BIN' to set such path to the "radiusd" binary
+# that you want to against the tests.
+#
+# e.g:
+#
+# make RADIUSD_BIN=/path/to/my/radiusd test
+#
+include Make.inc
+
+define RADIUSD_SERVICE
+$$(eval RADIUSD_BIN := $(JLIBTOOL) --silent --mode=execute $$(TESTBIN)/radiusd)
+
+#
+# Kill it. We don't care if it failed or not. However, we do care
+# if we can't kill it.
+#
+.PHONY: $(TEST).radiusd_kill
+$(TEST).radiusd_kill: | ${2}
+ ${Q}if [ -f ${2}/radiusd.pid ]; then \
+ if ! ps `cat ${2}/radiusd.pid` >/dev/null 2>&1; then \
+ rm -f ${2}/radiusd.pid; \
+ echo "FreeRADIUS terminated during test called by $(TEST).radiusd_kill"; \
+ echo "GDB output was:"; \
+ cat "${2}/gdb.log" 2> /dev/null; \
+ echo "--------------------------------------------------"; \
+ echo "Last entries in server log (${2}/radiusd.log):"; \
+ tail -n 100 "${2}/radiusd.log" 2> /dev/null; \
+ exit 0; \
+ fi; \
+ if ! kill -9 `cat ${2}/radiusd.pid` >/dev/null 2>&1; then \
+ exit 1; \
+ fi; \
+ rm -f ${2}/radiusd.pid; \
+ exit 0; \
+ fi
+
+#
+# Stop it politely.
+#
+.PHONY: $(TEST).radiusd_stop
+$(TEST).radiusd_stop: | ${2}
+ ${Q}if [ -f ${2}/radiusd.pid ]; then \
+ if ! ps `cat ${2}/radiusd.pid` >/dev/null 2>&1; then \
+ rm -f ${2}/radiusd.pid; \
+ echo "FreeRADIUS terminated during test called by $(TEST).radiusd_stop"; \
+ echo "GDB output was:"; \
+ cat "${2}/gdb.log" 2> /dev/null; \
+ echo "--------------------------------------------------"; \
+ echo "Last entries in server log (${2}/radiusd.log):"; \
+ tail -n 100 "${2}/radiusd.log" 2> /dev/null; \
+ exit 1; \
+ fi; \
+ if ! kill -TERM `cat ${2}/radiusd.pid` >/dev/null 2>&1; then \
+ exit 1; \
+ fi; \
+ rm -f ${2}/radiusd.pid; \
+ exit 0; \
+ fi
+
+#
+# Start radiusd instance
+#
+${2}/radiusd.pid: ${2}
+ $$(eval RADIUSD_RUN := TOP_SRCDIR=$(top_srcdir) TESTDIR=$(DIR) OUTPUT=$(OUTPUT) TEST_PORT=$(PORT) $$(RADIUSD_BIN) -Pxxx -d $(DIR)/config -n ${1} -D $(DICT_PATH) -l ${2}/radiusd.log)
+ ${Q}rm -f ${2}/radiusd.log
+ ${Q}if ! $$(RADIUSD_RUN); then \
+ echo "FAILED STARTING RADIUSD"; \
+ grep 'Error :' "${2}/radiusd.log"; \
+ echo "Last entries in server log (${2}/radiusd.log):"; \
+ tail -n 100 "${2}/radiusd.log" 2> /dev/null; \
+ echo "RADIUSD_RUN: $$(RADIUSD_RUN)"; \
+ fi
+
+.PHONY: $(TEST).radiusd_start
+$(TEST).radiusd_start: ${2}/radiusd.pid
+
+#
+# If this test framework needs radiusd to be started / stopped, then ensure that
+# the output files depend on the radiusd binary.
+#
+ifneq "$(FILES.$(TEST))" ""
+$(foreach x, $(FILES.$(TEST)), $(eval $x: $(TESTBINDIR)/radiusd $(TESTBINDIR)/$(CLIENT) $(top_srcdir)/src/tests/$(subst test.,,$(TEST))/config/${1}.conf))
+endif
+
+endef
diff --git a/src/tests/radsec/.gitignore b/src/tests/radsec/.gitignore
new file mode 100644
index 0000000..9daa835
--- /dev/null
+++ b/src/tests/radsec/.gitignore
@@ -0,0 +1,6 @@
+dictionary
+radiusd.conf
+sites-enabled
+mods-enabled
+radrelay.conf
+test.conf
diff --git a/src/tests/radsec/1.basic-auth.reply b/src/tests/radsec/1.basic-auth.reply
new file mode 100644
index 0000000..77aa6b1
--- /dev/null
+++ b/src/tests/radsec/1.basic-auth.reply
@@ -0,0 +1,2 @@
+Received Access-Accept
+
diff --git a/src/tests/radsec/1.basic-auth.request b/src/tests/radsec/1.basic-auth.request
new file mode 100644
index 0000000..a6ddb8e
--- /dev/null
+++ b/src/tests/radsec/1.basic-auth.request
@@ -0,0 +1,3 @@
+User-Name = "bob",
+NAS-IP-Address = "1.2.3.4",
+Called-Station-Id = "key0"
diff --git a/src/tests/radsec/2.ipaddrudp-coa.reply b/src/tests/radsec/2.ipaddrudp-coa.reply
new file mode 100644
index 0000000..8ea0bfd
--- /dev/null
+++ b/src/tests/radsec/2.ipaddrudp-coa.reply
@@ -0,0 +1,4 @@
+delay 2.5
+Received CoA-ACK
+Acct-Session-Id = "coa-buffered-reader:accounting:coa-request" "coa-buffered-reader:pre-proxy" "proxy-tls-default:recv-coa" "proxy-originate-coa-relay:pre-proxy" "coa:recv-coa" "coa:send-coa" "proxy-originate-coa-relay:post-proxy-coa-ack" "proxy-tls-default:send-coa" "coa-buffered-reader:post-proxy"$
+
diff --git a/src/tests/radsec/2.ipaddrudp-coa.request b/src/tests/radsec/2.ipaddrudp-coa.request
new file mode 100644
index 0000000..9d3f5eb
--- /dev/null
+++ b/src/tests/radsec/2.ipaddrudp-coa.request
@@ -0,0 +1,3 @@
+User-Name = "IpAddress",
+NAS-IP-Address = "127.0.0.1",
+Called-Station-Id = "12341",
diff --git a/src/tests/radsec/3.homepooludp-coa.reply b/src/tests/radsec/3.homepooludp-coa.reply
new file mode 100644
index 0000000..ab9f0a1
--- /dev/null
+++ b/src/tests/radsec/3.homepooludp-coa.reply
@@ -0,0 +1,4 @@
+delay 2.5
+Received CoA-ACK
+Acct-Session-Id = "coa-buffered-reader:accounting:coa-request" "home-originate-coa-relay:pre-proxy" "coa:recv-coa" "coa:send-coa" "home-originate-coa-relay:post-proxy-coa-ack"$
+
diff --git a/src/tests/radsec/3.homepooludp-coa.request b/src/tests/radsec/3.homepooludp-coa.request
new file mode 100644
index 0000000..e3bff09
--- /dev/null
+++ b/src/tests/radsec/3.homepooludp-coa.request
@@ -0,0 +1,2 @@
+User-Name = "HomePoolCoA",
+Called-Station-Id = "coa-nas"
diff --git a/src/tests/radsec/4.homepooltls-coa.reply b/src/tests/radsec/4.homepooltls-coa.reply
new file mode 100644
index 0000000..4666894
--- /dev/null
+++ b/src/tests/radsec/4.homepooltls-coa.reply
@@ -0,0 +1,4 @@
+delay 2.5
+Received CoA-ACK
+Acct-Session-Id = "coa-buffered-reader:accounting:coa-request" "home-originate-coa-relay:pre-proxy" "proxy-tls-default:recv-coa" "proxy-originate-coa-relay:pre-proxy" "coa:recv-coa" "coa:send-coa" "proxy-originate-coa-relay:post-proxy-coa-ack" "proxy-tls-default:send-coa" "home-originate-coa-relay:post-proxy-coa-ack"$
+
diff --git a/src/tests/radsec/4.homepooltls-coa.request b/src/tests/radsec/4.homepooltls-coa.request
new file mode 100644
index 0000000..7038e25
--- /dev/null
+++ b/src/tests/radsec/4.homepooltls-coa.request
@@ -0,0 +1,2 @@
+User-Name = "HomePoolCoA",
+Called-Station-Id = "coa-nas-tls"
diff --git a/src/tests/radsec/5.singletunnel_proxy-coa.reply b/src/tests/radsec/5.singletunnel_proxy-coa.reply
new file mode 100644
index 0000000..81a4173
--- /dev/null
+++ b/src/tests/radsec/5.singletunnel_proxy-coa.reply
@@ -0,0 +1,6 @@
+# We don't need delay since proxy flow will be finished
+# just after final CoA home server will return response.
+#delay 2.5
+Received CoA-ACK
+Acct-Session-Id = "default:pre-proxy" "coa_tls:recv-coa" "proxy-originate-coa-relay:pre-proxy" "coa:recv-coa" "coa:send-coa" "proxy-originate-coa-relay:post-proxy-coa-ack" "coa_tls:send-coa" "default:post-proxy-coa-ack"$
+
diff --git a/src/tests/radsec/5.singletunnel_proxy-coa.request b/src/tests/radsec/5.singletunnel_proxy-coa.request
new file mode 100644
index 0000000..72ace4d
--- /dev/null
+++ b/src/tests/radsec/5.singletunnel_proxy-coa.request
@@ -0,0 +1,2 @@
+User-Name = "TcpSessionKey-Proxy",
+Called-Station-Id = "key0"
diff --git a/src/tests/radsec/6.singletunnel_originate-coa.reply b/src/tests/radsec/6.singletunnel_originate-coa.reply
new file mode 100644
index 0000000..6a242b0
--- /dev/null
+++ b/src/tests/radsec/6.singletunnel_originate-coa.reply
@@ -0,0 +1,4 @@
+delay 2.5
+Received CoA-ACK
+Acct-Session-Id = "coa-buffered-reader:accounting:coa-request" "default:pre-proxy" "coa_tls:recv-coa" "proxy-originate-coa-relay:pre-proxy" "coa:recv-coa" "coa:send-coa" "proxy-originate-coa-relay:post-proxy-coa-ack" "coa_tls:send-coa" "default:post-proxy-coa-ack"
+
diff --git a/src/tests/radsec/6.singletunnel_originate-coa.request b/src/tests/radsec/6.singletunnel_originate-coa.request
new file mode 100644
index 0000000..a838730
--- /dev/null
+++ b/src/tests/radsec/6.singletunnel_originate-coa.request
@@ -0,0 +1,2 @@
+User-Name = "TcpSessionKey",
+Called-Station-Id = "key0"
diff --git a/src/tests/radsec/7.coareply-auth.reply b/src/tests/radsec/7.coareply-auth.reply
new file mode 100644
index 0000000..62e680e
--- /dev/null
+++ b/src/tests/radsec/7.coareply-auth.reply
@@ -0,0 +1,4 @@
+delay 2.5
+Received Access-Accept
+Acct-Session-Id = "default:post-auth" "default:pre-proxy" "coa_tls:recv-coa" "proxy-originate-coa-relay:pre-proxy" "coa:recv-coa" "coa:send-coa" "proxy-originate-coa-relay:post-proxy-coa-ack" "coa_tls:send-coa" "default:post-proxy-coa-ack"$
+
diff --git a/src/tests/radsec/7.coareply-auth.request b/src/tests/radsec/7.coareply-auth.request
new file mode 100644
index 0000000..bd2e2b4
--- /dev/null
+++ b/src/tests/radsec/7.coareply-auth.request
@@ -0,0 +1,2 @@
+User-Name = "PostAuthCoA",
+Called-Station-Id = "key0",
diff --git a/src/tests/radsec/Makefile b/src/tests/radsec/Makefile
new file mode 100644
index 0000000..d732b29
--- /dev/null
+++ b/src/tests/radsec/Makefile
@@ -0,0 +1,10 @@
+include ../../../Make.inc
+
+all: tests.radsec
+ @echo "All tests done"
+
+include all.mk
+
+
+.PHONY: clean
+clean: clean.tests.radsec \ No newline at end of file
diff --git a/src/tests/radsec/README.rst b/src/tests/radsec/README.rst
new file mode 100644
index 0000000..a016a02
--- /dev/null
+++ b/src/tests/radsec/README.rst
@@ -0,0 +1,103 @@
+=======================
+Tests for radsec flows.
+=======================
+
+ RADIUS CoA
+ ┌─────────────────────────────────────────────────────────────┐
+ │ │
+┌──────▼───────┐ ┌────────────────┐ ┌───────┴────────┐
+│ │ │ │ RADSEC CoA │ │
+│ radiusd │ RADIUS CoA │ radiusd ◄──────────────┤ radiusd │
+│ ◄─────────────┤ │ RADSEC Auth │ │
+│ CoA Server │ │ Proxy Server ├──────────────► Home Server │
+│ │ │ │ │ │
+└──────────────┘ └───────▲────────┘ └───────▲────────┘
+ │ │
+ │ RADIUS │ RADIUS
+ │ Auth │ CoA
+ ┌───────┴────────┐ ┌───────┴────────┐
+ │ radclient │ │ radclient │
+ └────────────────┘ └────────────────┘
+
+
+FreeRADIUS common configuration is located (obviously) in
+src/tests/radsec/radddb directory. Specific configurations for separate radiusd
+instances are located under their respective directories: config-coa,
+config-proxy, config-home.
+
+Each test is a pair of two files ending with \*.request and \*.reply.
+
+To run these tests separately, make sure you run 'make test' from the root
+directory beforehand.
+
+Request files.
+==============
+
+\*.request file specifies attributes to be sent.
+
+The name of the file (the part after the dash) specifies the type of the request
+to be sent.
+
+For example 1.basic-auth.request sends an auth request and 2.basic-coa.request
+sends coa.
+
+* Authentication requests.
+--------------------------
+Radclient sends plain RADIUS Access-Request to Proxy Server. Proxy Server then
+proxies this authentication request with RADSEC to Home Server. An opened TLS
+tunnel is used later to accept CoA requests from Home Server.
+
+* CoA requests.
+---------------
+Radclient sends plain RADIUS CoA request to Home Server. Depending on the
+attributes Home Server does one of the following:
+
+- Originates CoA request to Proxy Server with RADSEC - original flow. This is
+the regular flow where Proxy Server acts as a TCP server and Home Server (as
+a TCP client) first needs to establish a connection to it.
+
+- Originates CoA request to Proxy Server with RADSEC - 'single tunnel flow'.
+This is the new flow where Proxy Server can accept CoA requests from Home Server
+within the same tunnel that it has opened for Access-Request. In this case, the
+Proxy Server is still a TCP client yet in terms of RADIUS protocol it acts as
+a CoA Server.
+
+In both of these two cases, the Proxy Server forwards a CoA request to CoA
+Server to complete the flow. As an example CoA Server responds with CoA-ACK,
+then in turn Proxy Server responds with CoA-ACK to Home Server and the flow
+completes.
+
+- Originates CoA request directly to CoA Server. Although this is not a RADSEC
+flow, that is also good to check.
+
+
+Reply files.
+============
+
+\*.reply file specify a result to be expected for the corresponding \*.request
+file.
+
+
+For each such pair of \*.request \*.reply files runtest.sh is run.
+
+This shell script sends a request with radclient.
+
+Several freeRADIUS instances process requests and add attributes to be checked.
+In the end of the flow all cumulative attributes are written to the detail_test
+file for later checking.
+
+The runtest.sh checks the result following a \*.reply file.
+
+After test is performed a new directory is created with name "$TEST_NAME.result"
+where all intermediate files realted to the test are located, an example of the
+directory structure is like follows:
+
+ok - status file: either ok or fail
+detail_test - helper file to save attributes by freeRADIUS
+2.ipaddrtls-coa.reply.tmp - reply file w/o internal commands (e.g delay)
+fr-home-2.ipaddrtls-coa.log - a part of freeRADIUS logs related to the test
+fr-coa-2.ipaddrtls-coa.log - the same just for radiusd CoA Server
+fr-proxy-2.ipaddrtls-coa.log - the same just for radiusd Proxy Server
+radclient.log - logs for radclient
+result-2.ipaddrtls-coa.log - combined and aggregated radclient.log and
+ - detail_test to be checked against \*.reply file
diff --git a/src/tests/radsec/all.mk b/src/tests/radsec/all.mk
new file mode 100644
index 0000000..1d6140e
--- /dev/null
+++ b/src/tests/radsec/all.mk
@@ -0,0 +1,150 @@
+BUILD_PATH := $(top_builddir)/build
+TEST_PATH := $(top_builddir)/src/tests/radsec
+BIN_PATH := $(BUILD_PATH)/bin/local
+LIB_PATH := $(BUILD_PATH)/lib/.libs/
+RADDB_PATH := $(top_builddir)/raddb
+
+# Naming convention for ports is like follows: port-<owner>-<description>.
+# Owner may be either CoA Server, Proxy Server or Home Server
+port-proxy-auth = 12340
+port-proxy-coa = 12341
+port-home-auth = 12342
+port-home-coa = 12343
+port-coa = 12344
+
+# Port difines for request types: auth or coa
+auth-port = $(port-proxy-auth)
+coa-port = $(port-home-coa)
+
+
+#
+# You can watch what it's doing by:
+#
+# $ VERBOSE=1 make ... args ...
+#
+ifeq "${VERBOSE}" ""
+ Q=@
+else
+ Q=
+endif
+
+raddb:
+ ${Q}echo "Setting up raddb directory"
+ ${Q}cp -r $(top_builddir)/raddb $(TEST_PATH)
+ ${Q}rm -rf $(TEST_PATH)/raddb/sites-enabled/* # we have per server config
+ ${Q}echo 'detail detail_test {' >> $(TEST_PATH)/raddb/mods-enabled/detail
+ ${Q}echo ' filename = $${radacctdir}/detail_test' >> $(TEST_PATH)/raddb/mods-enabled/detail
+ ${Q}echo '}' >> $(TEST_PATH)/raddb/mods-enabled/detail
+ ${Q}echo 'detail detail_coa {' >> $(TEST_PATH)/raddb/mods-enabled/detail
+ ${Q}echo ' filename = $${radacctdir}/detail_coa' >> $(TEST_PATH)/raddb/mods-enabled/detail
+ ${Q}echo '}' >> $(TEST_PATH)/raddb/mods-enabled/detail
+
+ ${Q}$(MAKE) -C $(TEST_PATH)/raddb/certs
+
+dictionary:
+ ${Q}echo "# test dictionary not install. Delete at any time." > $(TEST_PATH)/dictionary
+ ${Q}echo '$$INCLUDE ' $(top_builddir)/share/dictionary >> $(TEST_PATH)/dictionary
+
+
+define TEST_CONF
+ ${Q}printf "Configuring radiusd $(1) -> "
+ ${Q}echo "# radiusd test configuration file. Do not install. Delete at any time." > $(TEST_PATH)/test-$(1).conf
+ ${Q}echo "libdir =" $(LIB_PATH) >> $(TEST_PATH)/test-$(1).conf
+ ${Q}echo "testdir =" $(TEST_PATH) >> $(TEST_PATH)/test-$(1).conf
+ ${Q}echo 'logdir = $${testdir}' >> $(TEST_PATH)/test-$(1).conf
+ ${Q}echo 'maindir = ${TEST_PATH}/raddb/' >> $(TEST_PATH)/test-$(1).conf
+ ${Q}echo 'radacctdir = $${testdir}' >> $(TEST_PATH)/test-$(1).conf
+ ${Q}echo 'pidfile = $${testdir}/radiusd-$(1).pid' >> $(TEST_PATH)/test-$(1).conf
+ ${Q}echo 'panic_action = "gdb -batch -x $${testdir}/panic.gdb %e %p > $${testdir}/gdb-$(1).log 2>&1; cat $${testdir}/gdb-$(1).log"' >> $(TEST_PATH)/test-$(1).conf
+ ${Q}echo 'security {' >> $(TEST_PATH)/test-$(1).conf
+ ${Q}echo ' allow_vulnerable_openssl = yes' >> $(TEST_PATH)/test-$(1).conf
+ ${Q}echo '}' >> $(TEST_PATH)/test-$(1).conf
+ ${Q}echo >> $(TEST_PATH)/test-$(1).conf
+ ${Q}echo 'modconfdir = $${maindir}mods-config' >> $(TEST_PATH)/test-$(1).conf
+ ${Q}echo 'certdir = $${maindir}/certs' >> $(TEST_PATH)/test-$(1).conf
+ ${Q}echo 'cadir = $${maindir}/certs' >> $(TEST_PATH)/test-$(1).conf
+ ${Q}echo '$$INCLUDE $${testdir}/config-$(1)/main.conf' >> $(TEST_PATH)/test-$(1).conf
+ ${Q}echo '$$INCLUDE $${maindir}/radiusd.conf' >> $(TEST_PATH)/test-$(1).conf
+ ${Q}rm -f $(TEST_PATH)/gdb-$(1).log $(TEST_PATH)/fr-$(1).log
+endef
+
+define START_SERVER
+ ${Q}printf "Starting $(1) server... "
+ ${Q}if ! $(BIN_PATH)/radiusd -Pxxxxml $(TEST_PATH)/fr-$(1).log -d $(TEST_PATH) -n test-$(1) -D $(TEST_PATH); then \
+ echo "failed"; \
+ echo "Last log entries were:"; \
+ tail -n 20 "$(TEST_PATH)/fr-$(1).log"; \
+ else \
+ echo "ok"; \
+ fi
+endef
+
+define PID_SERVER
+ ${Q}sed 's/$${{port-proxy-auth}}/$(port-proxy-auth)/g; \
+ s/$${{port-proxy-coa}}/$(port-proxy-coa)/g; \
+ s/$${{port-home-auth}}/$(port-home-auth)/g; \
+ s/$${{port-home-coa}}/$(port-home-coa)/g; \
+ s/$${{port-coa}}/$(port-coa)/g' \
+ $(TEST_PATH)/config-$(1)/main.conf > $(TEST_PATH)/config-$(1)/main.conf
+ $(call TEST_CONF,$(1))
+ $(call START_SERVER,$(1))
+endef
+
+radiusd.pid: raddb dictionary
+ $(call PID_SERVER,coa)
+ $(call PID_SERVER,home)
+ $(call PID_SERVER,proxy)
+
+define KILL_SERVER
+ ${Q}if [ -f $(TEST_PATH)/radiusd-$(1).pid ]; then \
+ if ! ps `cat $(TEST_PATH)/radiusd-$(1).pid` >/dev/null 2>&1; then \
+ rm -f $(TEST_PATH)/radiusd-$(1).pid; \
+ echo "FreeRADIUS terminated during test"; \
+ echo "GDB output was:"; \
+ cat "$(TEST_PATH)/gdb-$(1).log"; \
+ echo "Last log entries were:"; \
+ tail -n 20 $(TEST_PATH)/fr-$(1).log; \
+ fi; \
+ if ! kill -TERM `cat $(TEST_PATH)/radiusd-$(1).pid` >/dev/null 2>&1; then \
+ echo "Cannot kill $(TEST_PATH)/radiusd-$(1).pid"; \
+ fi; \
+ fi
+ ${Q}rm -f $(TEST_PATH)/radiusd-$(1).pid $(TEST_PATH)/config-$(1)/*.conf
+endef
+
+radiusd-proxy.kill:
+ $(call KILL_SERVER,proxy)
+radiusd-home.kill:
+ $(call KILL_SERVER,home)
+radiusd-coa.kill:
+ $(call KILL_SERVER,coa)
+
+radiusd.kill: radiusd-proxy.kill radiusd-home.kill radiusd-coa.kill
+
+# E.g: basis-auth.request -> TEST_NAME=basic-auth TYPE=auth, PORT=$(auth-port)
+%.request.test:
+ ${Q}printf "RADSEC-TEST $@... "
+ ${Q}if ! TEST_NAME=$(patsubst %.request.test,%,$@) \
+ TYPE=$(word 2, $(subst -, ,$(patsubst %.request.test,%,$@))) \
+ PORT=$($(word 2, $(subst -, ,$(patsubst %.request.test,%,$@)))-port) \
+ TEST_PATH=$(TEST_PATH) $(TEST_PATH)/runtest.sh 2>&1 > /dev/null; then \
+ echo "failed"; \
+ else \
+ echo "ok"; \
+ fi
+
+# kill the server (if it's running)
+# start the server
+# run the tests
+# kill the server
+#TEST_FILES = 2.basic-coa.request.test
+TEST_FILES = $(sort $(addsuffix .test,$(notdir $(wildcard $(TEST_PATH)/*.request))))
+tests.radsec: radiusd.kill radiusd.pid $(TEST_FILES)
+ ${Q}$(MAKE) radiusd.kill
+
+.PHONY: clean.tests.radsec
+clean.tests.radsec: radiusd.kill
+ ${Q}cd $(TEST_PATH) && rm -rf raddb/ detail_coa detail_test *.result *.conf dictionary *.ok *.log *.tmp
+
+
+.PHONY: radiusd.kill radiusd-proxy.kill radiusd-home.kill radiusd-coa.kill dictionary raddb
diff --git a/src/tests/radsec/config-coa/main.conf.template b/src/tests/radsec/config-coa/main.conf.template
new file mode 100644
index 0000000..5baf4b7
--- /dev/null
+++ b/src/tests/radsec/config-coa/main.conf.template
@@ -0,0 +1,37 @@
+listen {
+ type = coa
+ ipaddr = 127.0.0.1
+ port = ${{port-coa}}
+ virtual_server = coa
+}
+
+server coa {
+
+ authenticate {
+ Auth-Type PAP {
+ pap
+ }
+
+ Auth-Type MS-CHAP {
+ mschap
+ }
+
+ Auth-Type EAP {
+ eap
+ }
+ }
+
+ recv-coa {
+ update request {
+ &Acct-Session-Id += "coa:recv-coa"
+ }
+ }
+
+ send-coa {
+ update reply {
+ &reply: += request:[*]
+ &reply:Acct-Session-Id += "coa:send-coa"
+ }
+ }
+}
+
diff --git a/src/tests/radsec/config-home/main.conf b/src/tests/radsec/config-home/main.conf
new file mode 100644
index 0000000..98966fd
--- /dev/null
+++ b/src/tests/radsec/config-home/main.conf
@@ -0,0 +1,322 @@
+listen {
+
+ ipaddr = 127.0.0.1
+ port = ${{port-home-auth}}
+ type = auth+coa
+ proto = tcp
+
+ virtual_server = default
+
+ clients = radsec
+
+ tls {
+ tls_max_version="1.2"
+ private_key_password = whatever
+ private_key_file = ${certdir}/server.pem
+ certificate_file = ${certdir}/server.pem
+ ca_file = ${cadir}/ca.pem
+ fragment_size = 8192
+ ca_path = ${cadir}
+ cipher_list = "DEFAULT"
+ cipher_server_preference = no
+
+ cache {
+ enable = no
+ lifetime = 24 # hours
+ }
+
+ require_client_cert = yes
+ }
+
+ # Specify the CoA retransmit parameters for CoA single tunnel
+ coa {
+ irt = 1
+ mrt = 16
+ mrc = 0
+ mrd = 5
+ }
+}
+
+clients radsec {
+ client localhost {
+ ipaddr = 127.0.0.1
+ secret = radsec
+ proto = tls
+
+ limit {
+ max_connections = 16
+ lifetime = 0 # do not close connection
+ idle_timeout = 0 # do not close connection even after an idle period
+ }
+ }
+}
+
+server default {
+ authorize {
+ update control {
+ Originating-Realm-Key := &Called-Station-Id
+ Auth-Type := Accept
+ }
+ }
+
+ authenticate {
+ Auth-Type PAP {
+ pap
+ }
+
+ Auth-Type MS-CHAP {
+ mschap
+ }
+
+ Auth-Type EAP {
+ eap
+ }
+ }
+
+ post-auth {
+ if(User-Name && User-Name == "PostAuthCoA") {
+ update coa {
+ &Acct-Session-Id += "default:post-auth"
+ &Proxy-To-Originating-Realm := &Called-Station-Id
+ }
+ }
+ }
+
+ pre-proxy {
+ update {
+ &proxy-request:Acct-Session-Id += "default:pre-proxy"
+ }
+ }
+
+ post-proxy {
+ switch &proxy-reply:Packet-Type {
+ case CoA-ACK {
+ update proxy-reply {
+ &Acct-Session-Id += "default:post-proxy-coa-ack"
+ }
+ }
+
+ case CoA-NAK {
+ update proxy-reply {
+ &Acct-Session-Id += "default:post-proxy-coa-nak"
+ }
+ }
+
+ case Disconnect-ACK {
+ update proxy-reply {
+ &Acct-Session-Id += "default:post-proxy-disconnect-ack"
+ }
+ }
+
+ case Disconnect-NAK {
+ update proxy-reply {
+ &Acct-Session-Id += "default:post-proxy-disconnect-nak"
+ }
+ }
+
+ case {
+ fail
+ }
+ }
+
+ # If there was no response at all
+ Post-Proxy-Type Fail-CoA {
+ ok
+ }
+
+ Post-Proxy-Type Fail-Disconnect {
+ ok
+ }
+
+ detail_test.post-proxy
+ }
+}
+
+#
+# CoA Relay
+#
+listen {
+ type = coa
+ ipaddr = 127.0.0.1
+ port = ${{port-home-coa}}
+ virtual_server = coa
+}
+
+server coa {
+ recv-coa {
+
+ update request {
+ COA-Packet-Type := "%{Packet-Type}"
+ }
+
+ if(&User-Name == "TcpSessionKey-Proxy") {
+ # Proxying CoA
+ update control {
+ &Proxy-To-Originating-Realm := &Called-Station-Id
+ }
+ } else {
+ # Originating CoA
+ detail_coa.accounting
+ }
+ }
+}
+
+server coa-buffered-reader {
+ listen {
+ type = detail
+ filename = "${radacctdir}/detail_coa"
+ load_factor = 90
+ track = yes
+ }
+
+ accounting {
+ switch &User-Name {
+ case "IpAddress" {
+ update {
+ coa:Packet-DST-IP-Address := &NAS-IP-Address
+ coa:Packet-DST-Port:= &Called-Station-Id
+ }
+ }
+ case "IpAddressSingleTunnel" {
+ update {
+ coa:Packet-DST-IP-Address := &NAS-IP-Address
+ }
+ }
+ case "HomePoolCoA" {
+ update {
+ coa:Home-Server-Pool := &Called-Station-Id
+ }
+ }
+ case "TcpSessionKey"{
+ update {
+ coa:Proxy-To-Originating-Realm := &Called-Station-Id
+ }
+ }
+ }
+
+ switch &COA-Packet-Type {
+ case "Disconnect-Request" {
+ update {
+ # Include given attributes
+ &disconnect: += request:[*]
+ &disconnect:Packet-DST-IP-Address := &COA-Packet-DST-IP-Address
+ &disconnect:Packet-DST-Port := &COA-Packet-DST-Port
+ &disconnect:Acct-Session-Id := &COA-Acct-Session-Id
+ &disconnect:Acct-Delay-Time !* ANY
+ }
+ }
+
+ case "CoA-Request" {
+ update {
+ &coa:Acct-Session-Id = "coa-buffered-reader:accounting:coa-request"
+ }
+ }
+ }
+ ok
+ } # accounting
+
+ pre-proxy {
+ update {
+ &proxy-request:Acct-Session-Id += "coa-buffered-reader:pre-proxy"
+ }
+ }
+
+ post-proxy {
+ update {
+ &proxy-reply:Acct-Session-Id += "coa-buffered-reader:post-proxy"
+ }
+ detail_test.post-proxy
+ }
+}
+
+server home-originate-coa-relay {
+
+ pre-proxy {
+ update {
+ &proxy-request:Acct-Session-Id += "home-originate-coa-relay:pre-proxy"
+ }
+ }
+
+ post-proxy {
+ switch &proxy-reply:Packet-Type {
+ case CoA-ACK {
+ update {
+ &proxy-reply:Acct-Session-Id += "home-originate-coa-relay:post-proxy-coa-ack"
+ }
+ }
+
+ case CoA-NAK {
+ update {
+ &proxy-reply:Acct-Session-Id += "home-originate-coa-relay:post-proxy-coa-nak"
+ }
+ }
+
+ case Disconnect-ACK {
+ update {
+ &proxy-reply:Acct-Session-Id += "home-originate-coa-relay:post-proxy-disconnect-ack"
+ }
+ }
+
+ case Disconnect-NAK {
+ update {
+ &proxy-reply:Acct-Session-Id += "home-originate-coa-relay:post-proxy-disconnect-nak"
+ }
+ }
+
+ case {
+ fail
+ }
+ }
+
+ # If there was no response at all
+ Post-Proxy-Type Fail-CoA {
+ ok
+ }
+
+ Post-Proxy-Type Fail-Disconnect {
+ ok
+ }
+
+ detail_test.post-proxy
+ }
+}
+
+home_server coa-nas {
+ type = coa
+ ipaddr = 127.0.0.1
+ port = ${{port-coa}} # A placeholder to be set in test makefile
+ secret = testing123
+
+ coa {
+ irt = 2
+ mrt = 16
+ mrc = 5
+ mrd = 30
+ }
+}
+
+home_server_pool coa-nas {
+ type = fail-over
+ home_server = coa-nas
+ virtual_server = home-originate-coa-relay
+}
+
+home_server coa-nas-tls {
+ type = coa
+ ipaddr = 127.0.0.1
+ port = ${{port-proxy-coa}} # A placeholder to be set in test makefile
+ secret = testing123
+
+ coa {
+ irt = 2
+ mrt = 16
+ mrc = 5
+ mrd = 30
+ }
+}
+
+home_server_pool coa-nas-tls {
+ type = fail-over
+ home_server = coa-nas-tls
+ virtual_server = home-originate-coa-relay
+}
diff --git a/src/tests/radsec/config-proxy/main.conf.template b/src/tests/radsec/config-proxy/main.conf.template
new file mode 100644
index 0000000..aa77835
--- /dev/null
+++ b/src/tests/radsec/config-proxy/main.conf.template
@@ -0,0 +1,207 @@
+server proxy-default {
+
+ listen {
+ type = auth+acct
+ ipaddr = 127.0.0.1
+ port = ${{port-proxy-auth}}
+ }
+
+ authorize {
+ update control {
+ &Proxy-To-Realm := "tls"
+ }
+ }
+
+ authenticate {
+ Auth-Type PAP {
+ pap
+ }
+
+ Auth-Type MS-CHAP {
+ mschap
+ }
+
+ Auth-Type EAP {
+ eap
+ }
+ }
+
+ pre-proxy {
+ update {
+ &Acct-Session-Id += "proxy-default:pre-proxy"
+ }
+ }
+
+ post-proxy {
+ update {
+ &Acct-Session-Id += "proxy-default:post-proxy"
+ }
+ detail_test.recv-coa
+ }
+
+ recv-coa {
+ update {
+ &Acct-Session-Id += "proxy-default:recv-coa"
+ }
+ detail_test.recv-coa
+ }
+
+ send-coa {
+ update {
+ &Acct-Session-Id += "proxy-default:send-coa"
+ }
+ }
+}
+
+server proxy-tls-default {
+
+ listen {
+ type = coa
+ ipaddr = 127.0.0.1
+ port = ${{port-proxy-coa}}
+ }
+
+ recv-coa {
+ update {
+ &control:Home-Server-Pool := coa-nas
+ &request:Acct-Session-Id += "proxy-tls-default:recv-coa"
+ }
+ }
+
+ send-coa {
+ update {
+ &reply:Acct-Session-Id += "proxy-tls-default:send-coa"
+ }
+ }
+}
+
+#
+# Proxy To CoA server
+#
+server proxy-originate-coa-relay {
+ pre-proxy {
+ update {
+ &proxy-request:Acct-Session-Id += "proxy-originate-coa-relay:pre-proxy"
+ }
+ }
+ post-proxy {
+ switch &proxy-reply:Packet-Type {
+ case CoA-ACK {
+ update {
+ &proxy-reply:Acct-Session-Id += "proxy-originate-coa-relay:post-proxy-coa-ack"
+ }
+ }
+
+ case CoA-NAK {
+ update {
+ &proxy-reply:Acct-Session-Id += "proxy-originate-coa-relay:post-proxy-coa-nak"
+ }
+ }
+
+ case Disconnect-ACK {
+ update {
+ &proxy-reply:Acct-Session-Id += "proxy-originate-coa-relay:post-proxy-disconnect-ack"
+ }
+ }
+
+ case Disconnect-NAK {
+ update {
+ &proxy-reply:Acct-Session-Id += "proxy-originate-coa-relay:post-proxy-disconnect-nak"
+ }
+ }
+
+ case {
+ fail
+ }
+ }
+
+ Post-Proxy-Type Fail-CoA {
+ ok
+ }
+
+ Post-Proxy-Type Fail-Disconnect {
+ ok
+ }
+ }
+}
+
+home_server coa-nas {
+ type = coa
+ ipaddr = 127.0.0.1
+ port = ${{port-coa}} # A placeholder to be set in test makefile
+ secret = testing123
+
+ coa {
+ irt = 2
+ mrt = 16
+ mrc = 5
+ mrd = 30
+ }
+}
+
+home_server_pool coa-nas {
+ type = fail-over
+ home_server = coa-nas
+ virtual_server = proxy-originate-coa-relay
+}
+
+
+#
+# Proxy To RADSEC Home server
+#
+server coa_tls {
+ recv-coa {
+ update control {
+ &request:Acct-Session-Id += "coa_tls:recv-coa"
+ &Home-Server-Pool := coa-nas
+ }
+ }
+
+ # When a packet is sent, it is processed through the
+ # send-coa section. This applies to *both* CoA-Request and
+ # Disconnect-Request packets.
+ send-coa {
+ update control {
+ &reply:Acct-Session-Id += "coa_tls:send-coa"
+ }
+ }
+
+ # You can use pre-proxy and post-proxy sections here, too.
+ # They will be processed for sending && receiving proxy packets.
+}
+
+home_server tls {
+ ipaddr = 127.0.0.1
+ port = ${{port-home-auth}} # A placeholder to be set in test makefile
+ type = auth+acct+coa
+ secret = radsec
+ proto = tcp
+ status_check = none
+
+ tls {
+ tls_max_version="1.2"
+ private_key_password = whatever
+ private_key_file = ${certdir}/client.key
+ certificate_file = ${certdir}/client.pem
+ ca_file = ${certdir}/ca.pem
+ random_file = /dev/urandom
+ fragment_size = 8192
+ ca_path = ${cadir}
+ cipher_list = "DEFAULT"
+ }
+
+ recv_coa {
+ virtual_server = coa_tls
+ }
+}
+
+home_server_pool tls {
+ type = fail-over
+ home_server = tls
+ virtual_server = coa_tls
+}
+
+realm tls {
+ auth_pool = tls
+}
+
diff --git a/src/tests/radsec/runtest.sh b/src/tests/radsec/runtest.sh
new file mode 100755
index 0000000..811f6bb
--- /dev/null
+++ b/src/tests/radsec/runtest.sh
@@ -0,0 +1,83 @@
+#!/bin/sh
+#set -x
+
+: ${TYPE=auth}
+: ${TEST_NAME=1.basic-auth}
+: ${PORT=12340}
+: ${SECRET=testing123}
+
+cd $TEST_PATH
+
+BIN_PATH=../../../build/bin/local
+OUTPUT=radclient.log
+
+RES=result-$TEST_NAME.log
+
+clean() {
+ kill $tailcoa $tailhome $tailproxy 2>&1 > /dev/null
+ wait $tailcoa $tailhome $tailproxy 2>&1 > /dev/null # suppress terminated messages
+ echo "" > detail_test
+ rm ./$TEST_NAME.reply.tmp fr-*-$TEST_NAME.log fail ok $RES radclient.log 2>&1 > /dev/null
+}
+
+# Combine a list of several repeated attributes to a single attribute with delimeter:
+# This:
+# Acct-Session-Id = "coa-buffered-reader:accounting:coa-request"
+# Acct-Session-Id = "default:send-coa"
+# Become:
+# Acct-Session-Id = "coa-buffered-reader:accounting:coa-request" "default:send-coa"
+aggregate() {
+ sort -s -t= -k1,1 ./detail_test | awk -F= '
+ prev!=$1 && prev{
+ print prev FS val;
+ prev=val=""}
+ {
+ val=val?val OFS $2:$2;
+ prev=$1
+ }
+ END{
+ if(val){
+ print prev FS val}
+ }' >> $RES
+}
+
+echo "Running test: $TEST_NAME for port: $PORT type: $TYPE"
+
+clean
+
+tail -f fr-coa.log 2> /dev/null > fr-coa-$TEST_NAME.log &
+tailcoa=$(echo $!)
+tail -f fr-home.log 2> /dev/null > fr-home-$TEST_NAME.log &
+tailhome=$(echo $!)
+tail -f fr-proxy.log 2> /dev/null > fr-proxy-$TEST_NAME.log &
+tailproxy=$(echo $!)
+
+$BIN_PATH/radclient -f $TEST_NAME.request -xF -D ./ 127.0.0.1:$PORT $TYPE $SECRET 1> $OUTPUT
+
+# skip comments
+sed '/^\s*#/d' $TEST_NAME.reply > $TEST_NAME.reply.tmp
+
+# wait if needed
+delay=$(grep delay $TEST_NAME.reply.tmp | awk '{print $2}')
+sed '/delay/d' $TEST_NAME.reply.tmp > $TEST_NAME.reply.tmp
+sleep $delay 2>&1 > /dev/null
+
+cat radclient.log > $RES
+aggregate
+
+while read -r line; do
+ if ! grep "$line" $RES >/dev/null 2>&1; then
+ echo "This test failed!" >> fail
+ echo "Testing $TEST_NAME failed. Cannot find $line in $RES." > fail
+ fi
+done < $TEST_NAME.reply.tmp
+
+if [ ! -f fail ]; then echo "This test succeded!" >> ok; fi
+
+mkdir $TEST_NAME.result 2>&1 > /dev/null
+cp ./$TEST_NAME.reply.tmp fr-*-$TEST_NAME.log fail ok \
+ $RES radclient.log detail_test $TEST_NAME.result 2>&1 > /dev/null
+
+clean
+
+test -f $TEST_NAME.result/ok # exit with the status code
diff --git a/src/tests/rbmonkey.c b/src/tests/rbmonkey.c
new file mode 100644
index 0000000..dd58ff0
--- /dev/null
+++ b/src/tests/rbmonkey.c
@@ -0,0 +1,250 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <assert.h>
+
+#include <freeradius-devel/libradius.h>
+
+/*
+ * We need knowlege of the internal structures.
+ * This needs to be kept in lockstep with rbtree.c
+ */
+
+/* RED-BLACK tree description */
+typedef enum {
+ BLACK,
+ RED
+} node_colour_t;
+
+struct rbnode_t {
+ rbnode_t *left; //!< left child
+ rbnode_t *right; //!< right child
+ rbnode_t *parent; //!< Parent
+ node_colour_t colour; //!< Node colour (BLACK, RED)
+ void *data; //!< data stored in node
+};
+
+struct rbtree_t {
+#ifndef NDEBUG
+ uint32_t magic;
+#endif
+ rbnode_t *root;
+ int num_elements;
+ rb_comparator_t compare;
+ rb_free_t free;
+ bool replace;
+#ifdef HAVE_PTHREAD_H
+ bool lock;
+ pthread_mutex_t mutex;
+#endif
+};
+
+/* Storage for the NIL pointer. */
+static rbnode_t *NIL;
+
+static int comp(void const *a, void const *b)
+{
+ if (*(uint32_t const *)a > *(uint32_t const *)b) {
+ return -1;
+ }
+
+ if (*(uint32_t const *)a < *(uint32_t const *)b) {
+ return 1;
+ }
+ return 0;
+}
+
+#if 0
+static int print_cb(UNUSED void *ctx, void *i)
+{
+ fprintf(stderr, "%i\n", *(int*)i);
+ return 0;
+}
+#endif
+
+#define MAXSIZE 1024
+
+static int r = 0;
+static uint32_t rvals[MAXSIZE];
+
+static int store_cb(UNUSED void *ctx, void *i)
+{
+ rvals[r++] = *(int const *)i;
+ return 0;
+}
+
+static uint32_t mask;
+
+static int filter_cb(void *ctx, void *i)
+{
+ if ((*(uint32_t *)i & mask) == (*(uint32_t *)ctx & mask)) {
+ return 2;
+ }
+ return 0;
+}
+
+/*
+ * Returns the count of BLACK nodes from root to child leaves, or a
+ * negative number indicating which RED-BLACK rule was broken.
+ */
+static int rbcount(rbtree_t *t)
+{
+ rbnode_t *n;
+ int count, count_expect;
+
+ count_expect = -1;
+ n = t->root;
+ if (!n || n == NIL) {
+ return 0;
+ }
+ if (n->colour != BLACK) {
+ return -2; /* root not BLACK */
+ }
+ count = 0;
+descend:
+ while (n->left != NIL) {
+ if (n->colour == RED) {
+ if (n->left->colour != BLACK || n->right->colour != BLACK) {
+ return -4; /* Children of RED nodes must be BLACK */
+ }
+ }
+ else {
+ count++;
+ }
+ n = n->left;
+ }
+ if (n->right != NIL) {
+ if (n->colour == RED) {
+ if (n->left->colour != BLACK || n->right->colour != BLACK) {
+ return -4; /* Children of RED nodes must be BLACK */
+ }
+ }
+ else {
+ count++;
+ }
+ n = n->right;
+ }
+ if (n->left != NIL || n->right != NIL) {
+ goto descend;
+ }
+ if (count_expect < 0) {
+ count_expect = count + (n->colour == BLACK);
+ }
+ else {
+ if (count_expect != count + (n->colour == BLACK)) {
+ fprintf(stderr,"Expected %i got %i\n", count_expect, count);
+ return -5; /* All paths must traverse the same number of BLACK nodes. */
+ }
+ }
+ascend:
+ if (n->parent != NIL) return count_expect;
+ while (n->parent->right == n) {
+ n = n->parent;
+ if (!n->parent) return count_expect;
+ if (n->colour == BLACK) {
+ count--;
+ }
+ }
+ if (n->parent->left == n) {
+ if (n->parent->right != NIL) {
+ n = n->parent->right;
+ goto descend;
+ }
+ n = n->parent;
+ if (!n->parent) return count_expect;
+ if (n->colour == BLACK) {
+ count--;
+ }
+ }
+ goto ascend;
+}
+
+#define REPS 10
+
+int main(UNUSED int argc, UNUSED char *argv[])
+{
+ rbtree_t *t;
+ int i, j;
+ uint32_t thresh;
+ int n, rep;
+ uint32_t vals[MAXSIZE];
+ struct timeval now;
+ gettimeofday(&now, NULL);
+
+ /* TODO: make starting seed and repetitions a CLI option */
+ rep = REPS;
+
+again:
+ if (!--rep) return 0;
+
+ thresh = fr_rand();
+ mask = 0xff >> (fr_rand() & 7);
+ thresh &= mask;
+ n = (fr_rand() % MAXSIZE) + 1;
+
+ fprintf(stderr, "filter = %x mask = %x n= %i\n",
+ thresh, mask, n);
+
+ t = rbtree_create(NULL, comp, free, RBTREE_FLAG_LOCK);
+ /* Find out the value of the NIL node */
+ assert(t->root != NULL);
+ assert(t->root->parent == t->root);
+ NIL = t->root;
+
+ for (i = 0; i < n; i++) {
+ int *p;
+ p = malloc(sizeof(*p));
+ *p = fr_rand();
+ vals[i] = *p;
+ rbtree_insert(t, p);
+ }
+
+ i = rbcount(t);
+ fprintf(stderr,"After insert rbcount is %i.\n", i);
+ if (i < 0) { return i; }
+
+ qsort(vals, n, sizeof(int), comp);
+
+ /*
+ * For testing deletebydata instead
+
+ for (i = 0; i < n; i++) {
+ if (filter_cb(&vals[i], &thresh) == 2) {
+ rbtree_deletebydata(t, &vals[i]);
+ }
+ }
+
+ *
+ */
+ (void) rbtree_walk(t, RBTREE_DELETE_ORDER, filter_cb, &thresh);
+ i = rbcount(t);
+ fprintf(stderr,"After delete rbcount is %i.\n", i);
+ if (i < 0) { return i; }
+
+ r = 0;
+ rbtree_walk(t, RBTREE_IN_ORDER, &store_cb, NULL);
+
+ for (j = i = 0; i < n; i++) {
+ if (i && vals[i-1] == vals[i]) continue;
+ if (!filter_cb(&thresh, &vals[i])) {
+ if (vals[i] != rvals[j]) goto bad;
+ j++;
+ }
+ }
+ fprintf(stderr,"matched OK\n");
+ rbtree_free(t);
+ goto again;
+
+bad:
+ for (j = i = 0; i < n; i++) {
+ if (i && vals[i-1] == vals[i]) continue;
+ if (!filter_cb(&thresh, &vals[i])) {
+ fprintf(stderr, "%i: %x %x\n", j, vals[i], rvals[j]);
+ j++;
+ } else {
+ fprintf(stderr, "skipped %x\n", vals[i]);
+ }
+ }
+ return -1;
+}
+
diff --git a/src/tests/rbmonkey.mk b/src/tests/rbmonkey.mk
new file mode 100644
index 0000000..ecf5e06
--- /dev/null
+++ b/src/tests/rbmonkey.mk
@@ -0,0 +1,7 @@
+TARGET := rbmonkey
+
+SOURCES := rbmonkey.c
+
+TGT_PREREQS := libfreeradius-radius.a
+TGT_LDLIBS := $(LIBS)
+TGT_INSTALLDIR :=
diff --git a/src/tests/runtests.sh b/src/tests/runtests.sh
new file mode 100755
index 0000000..cc403c6
--- /dev/null
+++ b/src/tests/runtests.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+: ${BIN_PATH=./}
+: ${PORT=12340}
+: ${HOME_PORT=12350}
+: ${SECRET=testing123}
+
+rm -f verbose.log
+RCODE=0
+
+echo "Running tests:"
+for NAME in $@
+do
+ TOTAL=`grep TESTS $NAME | sed 's/.*TESTS//'`
+
+ #
+ # Each test may have multiple variants.
+ #
+ for NUMBER in `echo $TOTAL`
+ do
+ cp $NAME .request
+ BASE=`echo $NAME | sed 's,.*/,,'`
+
+ #
+ # Add the name of the test, and the variant to the request
+ #
+ echo "Test-Name = \"$BASE\"," >> .request
+ echo 'Test-Number = ' $NUMBER >> .request
+
+ rm ./radclient.log > /dev/null 2>&1
+ $BIN_PATH/radclient -f .request -xF -D ./ 127.0.0.1:$PORT auth $SECRET 1> ./radclient.log
+ if [ "$?" = "0" ]; then
+ echo "${BASE}_${NUMBER} : Success"
+ else
+ echo "${BASE}_${NUMBER} : FAILED"
+ cat ./radclient.log
+ RCODE=1
+ fi
+ done
+done
+
+
+if [ "$RCODE" = "0" ]
+then
+ rm -f radiusd.log radclient.log
+ echo "All tests succeeded"
+else
+ echo "See radclient.log for more details"
+fi
+
+exit $RCODE
diff --git a/src/tests/salt-test-server/.gitignore b/src/tests/salt-test-server/.gitignore
new file mode 100644
index 0000000..e3aa327
--- /dev/null
+++ b/src/tests/salt-test-server/.gitignore
@@ -0,0 +1,8 @@
+# Local files
+*.swp
+.DS_Store
+*.md5
+
+# Salt runtime directories
+tmp
+cache
diff --git a/src/tests/salt-test-server/README b/src/tests/salt-test-server/README
new file mode 100644
index 0000000..8265f46
--- /dev/null
+++ b/src/tests/salt-test-server/README
@@ -0,0 +1,3 @@
+Salt script to build the test VM required for running the ldap, mysql & postgres tests.
+
+See http://docs.saltstack.com/en/latest/index.html
diff --git a/src/tests/salt-test-server/build.sh b/src/tests/salt-test-server/build.sh
new file mode 100755
index 0000000..ad1873b
--- /dev/null
+++ b/src/tests/salt-test-server/build.sh
@@ -0,0 +1 @@
+salt-ssh --config-dir=salt_config -l quiet "test-server" state.highstate
diff --git a/src/tests/salt-test-server/salt/iptable.sls b/src/tests/salt-test-server/salt/iptable.sls
new file mode 100644
index 0000000..7aefdd1
--- /dev/null
+++ b/src/tests/salt-test-server/salt/iptable.sls
@@ -0,0 +1,13 @@
+{% if grains['os'] == 'CentOS' %}
+update_firewall:
+ file.managed:
+ - name: /etc/sysconfig/iptables
+ - source: salt://iptables
+
+reload_iptables:
+ cmd.wait:
+ - cwd: /
+ - name: service iptables reload
+ - watch:
+ - file: /etc/sysconfig/iptables
+{% endif %}
diff --git a/src/tests/salt-test-server/salt/iptables b/src/tests/salt-test-server/salt/iptables
new file mode 100644
index 0000000..2e2d4a2
--- /dev/null
+++ b/src/tests/salt-test-server/salt/iptables
@@ -0,0 +1,15 @@
+# Generated by iptables-save v1.4.7 on Thu Feb 19 13:41:09 2015
+*filter
+:INPUT ACCEPT [0:0]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+-A INPUT -p tcp -m state --state NEW -m tcp --dport 3306 -j ACCEPT
+-A INPUT -p tcp -m state --state NEW -m tcp --dport 5432 -j ACCEPT
+-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
+-A INPUT -p icmp -j ACCEPT
+-A INPUT -i lo -j ACCEPT
+-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
+-A INPUT -j REJECT --reject-with icmp-host-prohibited
+-A FORWARD -j REJECT --reject-with icmp-host-prohibited
+COMMIT
+# Completed on Thu Feb 19 13:41:09 2015
diff --git a/src/tests/salt-test-server/salt/ldap.sls b/src/tests/salt-test-server/salt/ldap.sls
new file mode 100644
index 0000000..006abf8
--- /dev/null
+++ b/src/tests/salt-test-server/salt/ldap.sls
@@ -0,0 +1,41 @@
+{% if grains['os'] == 'Ubuntu' %}
+
+# In Ubuntu 14.10, openldap comes with a broken AppArmor profile (can't connect through socket)
+# Disable AppArmor alltogether
+/etc/init.d/apparmor teardown:
+ cmd.run
+
+update-rc.d -f apparmor remove:
+ cmd.run
+
+{% endif %}
+
+slapd:
+ pkg.installed
+
+ldap-utils:
+ pkg.installed
+
+# Copy ldif file for base structure
+/root/base.ldif:
+ file.managed:
+ - source: salt://ldap/base.ldif
+
+# Copy ldif file for FreeRADIUS schema
+/root/schema_freeradius.ldif:
+ file.managed:
+ - source: salt://ldap/schema_freeradius.ldif
+
+# Add FreeRADIUS schema
+add_fr_schema:
+ cmd.run:
+ - name: "ldapadd -Y EXTERNAL -H ldapi:/// -f /root/schema_freeradius.ldif"
+ - cwd: /root/
+ - unless: "/usr/bin/ldapsearch -Y EXTERNAL -H ldapi:/// -b cn={4}radius,cn=schema,cn=config -s base"
+
+# Create base structure in LDAP
+build_base_structure:
+ cmd.run:
+ - name: "/usr/bin/ldapadd -Y EXTERNAL -H ldapi:/// -f /root/base.ldif"
+ - cwd: /root/
+ - unless: "/usr/bin/ldapsearch -Y EXTERNAL -H ldapi:/// -b dc=example,dc=com -s base"
diff --git a/src/tests/salt-test-server/salt/ldap/base.ldif b/src/tests/salt-test-server/salt/ldap/base.ldif
new file mode 100644
index 0000000..7a7a1eb
--- /dev/null
+++ b/src/tests/salt-test-server/salt/ldap/base.ldif
@@ -0,0 +1,80 @@
+# Database settings
+dn: olcDatabase=mdb,cn=config
+objectClass: olcDatabaseConfig
+objectClass: olcMdbConfig
+olcDatabase: {1}mdb
+olcSuffix: dc=example,dc=com
+olcDbDirectory: /tmp/ldap/db
+olcRootDN: cn=admin,dc=example,dc=com
+olcRootPW: {SSHA}SgCZuAcGQA5HlgKi+g5xwVyI2NhXRFYh
+olcDbIndex: objectClass eq
+olcLastMod: TRUE
+olcDbCheckpoint: 512 30
+olcAccess: to attrs=userPassword by dn="cn=admin,dc=example,dc=com" write by anonymous auth by self write by * none
+olcAccess: to * by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage by dn="cn=admin,cn=config" manage
+olcAccess: to attrs=shadowLastChange by self write by * read
+olcAccess: to dn.base="" by * read
+olcAccess: to * by dn="cn=admin,dc=example,dc=com" write by * read
+
+# Create top-level object in domain
+dn: dc=example,dc=com
+objectClass: top
+objectClass: dcObject
+objectclass: organization
+o: Example Organization
+dc: Example
+description: LDAP Example
+
+dn: ou=people,dc=example,dc=com
+objectClass: organizationalUnit
+ou: people
+
+dn: ou=groups,dc=example,dc=com
+objectClass: organizationalUnit
+ou: groups
+
+# foo, groups, example.com
+dn: cn=foo,ou=groups,dc=example,dc=com
+cn: foo
+objectClass: groupOfNames
+objectClass: top
+member: uid=john,ou=people,dc=example,dc=com
+
+dn: ou=profiles,dc=example,dc=com
+objectClass: organizationalUnit
+ou: profiles
+
+dn: cn=radprofile,ou=profiles,dc=example,dc=com
+objectClass: radiusObjectProfile
+objectClass: radiusprofile
+cn: radprofile
+radiusFramedIPNetmask: 255.255.255.0
+
+dn: cn=profile1,ou=profiles,dc=example,dc=com
+objectClass: radiusObjectProfile
+objectClass: radiusprofile
+cn: profile1
+radiusReplyAttribute: Framed-IP-Netmask := 255.255.0.0
+radiusReplyAttribute: Acct-Interim-Interval := 1800
+radiusRequestAttribute: Service-Type := Framed-User
+radiusControlAttribute: Framed-IP-Address == 1.2.3.4
+radiusControlAttribute: Reply-Message == "Hello world"
+
+dn: uid=john,ou=people,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: posixAccount
+objectClass: shadowAccount
+objectClass: radiusprofile
+uid: john
+sn: Doe
+givenName: John
+cn: John Doe
+displayName: John Doe
+userPassword: {cleartext}password
+uidNumber: 100
+gidNumber: 100
+homeDirectory: /home/john
+radiusIdleTimeout: 3600
+radiusAttribute: reply:Session-Timeout := 7200
+radiusAttribute: control:NAS-IP-Address := 1.2.3.4
+radiusProfileDN: cn=profile1,ou=profiles,dc=example,dc=com
diff --git a/src/tests/salt-test-server/salt/ldap/base2.ldif b/src/tests/salt-test-server/salt/ldap/base2.ldif
new file mode 100644
index 0000000..4ae6b07
--- /dev/null
+++ b/src/tests/salt-test-server/salt/ldap/base2.ldif
@@ -0,0 +1,81 @@
+# Database settings
+dn: olcDatabase=mdb,cn=config
+objectClass: olcDatabaseConfig
+objectClass: olcMdbConfig
+olcDatabase: {1}mdb
+olcSuffix: dc=example,dc=com
+olcDbDirectory: /tmp/ldap2/db
+olcRootDN: cn=admin,dc=example,dc=com
+olcRootPW: {SSHA}SgCZuAcGQA5HlgKi+g5xwVyI2NhXRFYh
+olcDbIndex: objectClass eq
+olcLastMod: TRUE
+olcDbCheckpoint: 512 30
+olcAccess: to attrs=userPassword by dn="cn=admin,dc=example,dc=com" write by anonymous auth by self write by * none
+olcAccess: to * by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage by dn="cn=admin,cn=config" manage
+olcAccess: to attrs=shadowLastChange by self write by * read
+olcAccess: to dn.base="" by * read
+olcAccess: to * by dn="cn=admin,dc=example,dc=com" write by * read
+
+# Create top-level object in domain
+dn: dc=example,dc=com
+objectClass: top
+objectClass: dcObject
+objectclass: organization
+o: Example Organization
+dc: Example
+description: LDAP Example Two
+
+dn: dc=subdept,dc=example,dc=com
+objectClass: organization
+objectClass: dcObject
+o: Sub org
+dc: subdept
+
+dn: ou=people,dc=subdept,dc=example,dc=com
+objectClass: organizationalUnit
+ou: people
+
+dn: ou=groups,dc=subdept,dc=example,dc=com
+objectClass: organizationalUnit
+ou: groups
+
+dn: ou=profiles,dc=subdept,dc=example,dc=com
+objectClass: organizationalUnit
+ou: profiles
+
+dn: cn=radprofile,ou=profiles,dc=subdept,dc=example,dc=com
+objectClass: radiusObjectProfile
+objectClass: radiusprofile
+cn: radprofile
+radiusFramedIPNetmask: 255.255.255.0
+
+dn: uid=fred,ou=people,dc=subdept,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: posixAccount
+objectClass: shadowAccount
+objectClass: radiusprofile
+uid: fred
+sn: Jones
+givenName: Fred
+cn: Fred Jones
+displayName: Fred Jones
+userPassword: password
+uidNumber: 100
+gidNumber: 100
+homeDirectory: /home/fred
+radiusIdleTimeout: 3600
+radiusAttribute: reply.Session-Timeout := 7200
+radiusAttribute: control.NAS-IP-Address := 1.2.3.4
+radiusProfileDN: cn=radprofile,ou=profiles,ou=subdept,dc=example,dc=com
+
+dn: ou=offsite,dc=subdept,dc=example,dc=com
+objectClass: referral
+objectClass: extensibleObject
+ou: offsite
+ref: ldap://127.0.0.1:3890/dc=example,dc=com??sub
+
+dn: ou=bounce1,dc=subdept,dc=example,dc=com
+objectClass: referral
+objectClass: extensibleObject
+ou: bounce1
+ref: ldap://127.0.0.1:3890/ou=bounce2,dc=example,dc=com??sub
diff --git a/src/tests/salt-test-server/salt/ldap/schema_freeradius.ldif b/src/tests/salt-test-server/salt/ldap/schema_freeradius.ldif
new file mode 100644
index 0000000..44d2cb9
--- /dev/null
+++ b/src/tests/salt-test-server/salt/ldap/schema_freeradius.ldif
@@ -0,0 +1,76 @@
+dn: cn=radius,cn=schema,cn=config
+objectClass: olcSchemaConfig
+cn: radius
+olcAttributeTypes: {0}( 1.3.6.1.4.1.11344.4.3.1.1 NAME 'radiusArapFeatures' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {1}( 1.3.6.1.4.1.11344.4.3.1.2 NAME 'radiusArapSecurity' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {2}( 1.3.6.1.4.1.11344.4.3.1.3 NAME 'radiusArapZoneAccess' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {3}( 1.3.6.1.4.1.11344.4.3.1.44 NAME 'radiusAuthType' DESC 'controlItem: Auth-Type' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {4}( 1.3.6.1.4.1.11344.4.3.1.4 NAME 'radiusCallbackId' DESC 'replyItem: Callback-Id' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {5}( 1.3.6.1.4.1.11344.4.3.1.5 NAME 'radiusCallbackNumber' DESC 'replyItem: Callback-Number' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {6}( 1.3.6.1.4.1.11344.4.3.1.6 NAME 'radiusCalledStationId' DESC 'controlItem: Called-Station-Id' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {7}( 1.3.6.1.4.1.11344.4.3.1.7 NAME 'radiusCallingStationId' DESC 'controlItem: Calling-Station-Id' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {8}( 1.3.6.1.4.1.11344.4.3.1.8 NAME 'radiusClass' DESC 'replyItem: Class' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {9}( 1.3.6.1.4.1.11344.4.3.1.45 NAME 'radiusClientIPAddress' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {10}( 1.3.6.1.4.1.11344.4.3.1.9 NAME 'radiusFilterId' DESC 'replyItem: Filter-Id' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {11}( 1.3.6.1.4.1.11344.4.3.1.10 NAME 'radiusFramedAppleTalkLink' DESC 'replyItem: Framed-AppleTalk-Link' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {12}( 1.3.6.1.4.1.11344.4.3.1.11 NAME 'radiusFramedAppleTalkNetwork' DESC 'replyItem: Framed-AppleTalk-Network' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {13}( 1.3.6.1.4.1.11344.4.3.1.12 NAME 'radiusFramedAppleTalkZone' DESC 'replyItem: Framed-AppleTalk-Zone' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {14}( 1.3.6.1.4.1.11344.4.3.1.13 NAME 'radiusFramedCompression' DESC 'replyItem: Framed-Compression' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {15}( 1.3.6.1.4.1.11344.4.3.1.14 NAME 'radiusFramedIPAddress' DESC 'replyItem: Framed-IP-Address' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {16}( 1.3.6.1.4.1.11344.4.3.1.15 NAME 'radiusFramedIPNetmask' DESC 'replyItem: Framed-IP-Netmask' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {17}( 1.3.6.1.4.1.11344.4.3.1.16 NAME 'radiusFramedIPXNetwork' DESC 'replyItem: Framed-IPX-Network' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {18}( 1.3.6.1.4.1.11344.4.3.1.17 NAME 'radiusFramedMTU' DESC' replyItem: Framed-MTU' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {19}( 1.3.6.1.4.1.11344.4.3.1.18 NAME 'radiusFramedProtocol'DESC 'replyItem: Framed-Protocol' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {20}( 1.3.6.1.4.1.11344.4.3.1.19 NAME 'radiusFramedRoute' DESC 'replyItem: Framed-Route' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {21}( 1.3.6.1.4.1.11344.4.3.1.20 NAME 'radiusFramedRouting' DESC 'replyItem: Framed-Routing' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {22}( 1.3.6.1.4.1.11344.4.3.1.46 NAME 'radiusGroupName' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {23}( 1.3.6.1.4.1.11344.4.3.1.47 NAME 'radiusHint' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {24}( 1.3.6.1.4.1.11344.4.3.1.48 NAME 'radiusHuntgroupName' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {25}( 1.3.6.1.4.1.11344.4.3.1.21 NAME 'radiusIdleTimeout' DESC 'replyItem: Idle-Timeout' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {26}( 1.3.6.1.4.1.11344.4.3.1.22 NAME 'radiusLoginIPHost' DESC 'replyItem: Login-IP-Host' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {27}( 1.3.6.1.4.1.11344.4.3.1.23 NAME 'radiusLoginLATGroup' DESC 'replyItem: Login-LAT-Group' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {28}( 1.3.6.1.4.1.11344.4.3.1.24 NAME 'radiusLoginLATNode' DESC 'replyItem: Login-LAT-Node' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {29}( 1.3.6.1.4.1.11344.4.3.1.25 NAME 'radiusLoginLATPort' DESC 'replyItem: Login-LAT-Port' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {30}( 1.3.6.1.4.1.11344.4.3.1.26 NAME 'radiusLoginLATService' DESC 'replyItem: Login-LAT-Service' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {31}( 1.3.6.1.4.1.11344.4.3.1.27 NAME 'radiusLoginService' DESC 'replyItem: Login-Service' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {32}( 1.3.6.1.4.1.11344.4.3.1.28 NAME 'radiusLoginTCPPort' DESC 'replyItem: Login-TCP-Port' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {33}( 1.3.6.1.4.1.11344.4.3.1.29 NAME 'radiusPasswordRetry' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {34}( 1.3.6.1.4.1.11344.4.3.1.30 NAME 'radiusPortLimit' DESC 'replyItem: Port-Limit' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {35}( 1.3.6.1.4.1.11344.4.3.1.49 NAME 'radiusProfileDN' DESC '' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
+olcAttributeTypes: {36}( 1.3.6.1.4.1.11344.4.3.1.31 NAME 'radiusPrompt' DESC ''EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {37}( 1.3.6.1.4.1.11344.4.3.1.50 NAME 'radiusProxyToRealm' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {38}( 1.3.6.1.4.1.11344.4.3.1.51 NAME 'radiusReplicateToRealm' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {39}( 1.3.6.1.4.1.11344.4.3.1.52 NAME 'radiusRealm' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE)
+olcAttributeTypes: {40}( 1.3.6.1.4.1.11344.4.3.1.32 NAME 'radiusServiceType' DESC 'replyItem: Service-Type' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {41}( 1.3.6.1.4.1.11344.4.3.1.33 NAME 'radiusSessionTimeout'DESC 'replyItem: Session-Timeout' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {42}( 1.3.6.1.4.1.11344.4.3.1.34 NAME 'radiusTerminationAction' DESC 'replyItem: Termination-Action' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {43}( 1.3.6.1.4.1.11344.4.3.1.35 NAME 'radiusTunnelAssignmentId' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26)
+olcAttributeTypes: {44}( 1.3.6.1.4.1.11344.4.3.1.36 NAME 'radiusTunnelMediumType' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {45}( 1.3.6.1.4.1.11344.4.3.1.37 NAME 'radiusTunnelPassword' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {46}( 1.3.6.1.4.1.11344.4.3.1.38 NAME 'radiusTunnelPreference' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {47}( 1.3.6.1.4.1.11344.4.3.1.39 NAME 'radiusTunnelPrivateGroupId' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {48}( 1.3.6.1.4.1.11344.4.3.1.40 NAME 'radiusTunnelServerEndpoint' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {49}( 1.3.6.1.4.1.11344.4.3.1.41 NAME 'radiusTunnelType' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {50}( 1.3.6.1.4.1.11344.4.3.1.42 NAME 'radiusVSA' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {51}( 1.3.6.1.4.1.11344.4.3.1.43 NAME 'radiusTunnelClientEndpoint' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {52}( 1.3.6.1.4.1.11344.4.3.1.53 NAME 'radiusSimultaneousUse' DESC 'controlItem: Simultaneous-Use' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
+olcAttributeTypes: {53}( 1.3.6.1.4.1.11344.4.3.1.54 NAME 'radiusLoginTime' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {54}( 1.3.6.1.4.1.11344.4.3.1.55 NAME 'radiusUserCategory' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {55}( 1.3.6.1.4.1.11344.4.3.1.56 NAME 'radiusStripUserName' DESC '' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
+olcAttributeTypes: {56}( 1.3.6.1.4.1.11344.4.3.1.57 NAME 'dialupAccess' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {57}( 1.3.6.1.4.1.11344.4.3.1.58 NAME 'radiusExpiration' DESC 'controlItem: Expiration' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {58}( 1.3.6.1.4.1.11344.4.3.1.59 NAME 'radiusAttribute' DESC 'controlItem: $GENERIC$' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {59}( 1.3.6.1.4.1.11344.4.3.1.61 NAME 'radiusNASIpAddress' DESC '' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {60}( 1.3.6.1.4.1.11344.4.3.1.62 NAME 'radiusReplyMessage' DESC 'replyItem: Reply-Message' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {61}( 1.3.6.1.4.1.11344.4.3.1.63 NAME 'radiusControlAttribute' DESC 'controlItem: $GENERIC$' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {62}( 1.3.6.1.4.1.11344.4.3.1.64 NAME 'radiusReplyAttribute' DESC 'replyItem: $GENERIC$' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: {63}( 1.3.6.1.4.1.11344.4.3.1.65 NAME 'radiusRequestAttribute' DESC 'requestItem: $GENERIC$' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcObjectClasses: {0}( 1.3.6.1.4.1.11344.4.3.2.1 NAME 'radiusprofile' DESC '' SUP top AUXILIARY MAY ( radiusArapFeatures $ radiusArapSecurity $ radiusArapZoneAccess $ radiusAuthType $
+ radiusCallbackId $ radiusCallbackNumber $radiusCalledStationId $ radiusCallingStationId $ radiusClass $ radiusClientIPAddress $ radiusFilterId $ radiusFramedAppleTalkLink $ radiusFramedAppleTalkNetwork $
+ radiusFramedAppleTalkZone $ radiusFramedCompression $ radiusFramedIPAddress $ radiusFramedIPNetmask $ radiusFramedIPXNetwork $ radiusFramedMTU $radiusFramedProtocol $ radiusAttribute $
+ radiusFramedRoute $ radiusFramedRouting $ radiusIdleTimeout $ radiusGroupName $ radiusHint $ radiusHuntgroupName $ radiusLoginIPHost $ radiusLoginLATGroup $ radiusLoginLATNode $ radiusLoginLATPort $
+ radiusLoginLATService $ radiusLoginService $ radiusLoginTCPPort $ radiusLoginTime $ radiusPasswordRetry $ radiusPortLimit $ radiusPrompt $ radiusProxyToRealm $ radiusRealm $ radiusReplicateToRealm $
+ radiusServiceType $ radiusSessionTimeout $ radiusStripUserName $ radiusTerminationAction $ radiusTunnelClientEndpoint $ radiusProfileDN $ radiusSimultaneousUse $ radiusTunnelAssignmentId $
+ radiusTunnelMediumType $ radiusTunnelPassword $ radiusTunnelPreference $ radiusTunnelPrivateGroupId $ radiusTunnelServerEndpoint $ radiusTunnelType $ radiusUserCategory $ radiusVSA $ radiusExpiration $
+ dialupAccess $ radiusNASIpAddress $ radiusReplyMessage $ radiusControlAttribute $ radiusReplyAttribute $ radiusRequestAttribute ) )
+olcObjectClasses: {1}( 1.3.6.1.4.1.11344.4.3.2.2 NAME 'radiusObjectProfile' DESC 'A Container Objectclass to be used for creating radius profile object' SUP top STRUCTURAL MUST cn MAY ( uid $ userPassword $ description ) )
diff --git a/src/tests/salt-test-server/salt/mysql.sls b/src/tests/salt-test-server/salt/mysql.sls
new file mode 100644
index 0000000..df1ea00
--- /dev/null
+++ b/src/tests/salt-test-server/salt/mysql.sls
@@ -0,0 +1,74 @@
+mysql-server:
+ pkg.installed
+
+# On Ubuntu, the default MySQL install only listens on localhost
+/etc/mysql/my.cnf:
+{% if grains['os'] == 'Ubuntu' %}
+ file.sed:
+ - before: 127.0.0.1
+ - after: 0.0.0.0
+ - limit: ^bind-address\s+=
+ - require:
+ - pkg: mysql-server
+{% else %}
+ file.exists
+{% endif %}
+
+mysql_daemon:
+ service:
+{% if grains['os'] == 'CentOS' %}
+ - name: mysqld
+{% elif grains['os'] == 'Ubuntu' or grains['os'] == 'Debian' %}
+ - name: mysql
+{% endif %}
+ - running
+ - enable: True
+ - watch:
+ - file: /etc/mysql/my.cnf
+ - require:
+ - pkg: mysql-server
+
+## FW rules don't work well with CentOS < 7
+# Insert is run each time
+#
+# iptables.insert:
+# - position: 1
+# - table: filter
+# - chain: INPUT
+# - j: ACCEPT # Use 'j' instead of 'jump' because iptables-save outputs 'j' flag.
+# - match: state
+# - connstate: NEW
+# - dport: 3306
+# - proto: tcp
+# - save: True
+
+# Copy DB schema file
+/salt/mysql/schema.sql:
+ file.managed:
+ - source: salt://mysql/schema.sql
+ - makedirs: true
+
+# Copy DB setup script
+/salt/mysql/setup.sql:
+ file.managed:
+ - source: salt://mysql/setup.sql
+ - makedirs: true
+
+# Create DB
+echo "CREATE DATABASE radius" | mysql:
+ cmd.run:
+ - creates: /var/lib/mysql/radius/db.opt
+
+# Create FreeRADIUS schema
+mysql radius < /salt/mysql/schema.sql:
+ cmd.run:
+ - unless: "echo 'desc radacct' | mysql radius"
+ - require:
+ - file: /salt/mysql/schema.sql
+
+# Setup DB access
+mysql radius < /salt/mysql/setup.sql:
+ cmd.run:
+ - unless: "echo \"show grants for 'radius';\" | mysql"
+ - require:
+ - file: /salt/mysql/setup.sql
diff --git a/src/tests/salt-test-server/salt/mysql/schema.sql b/src/tests/salt-test-server/salt/mysql/schema.sql
new file mode 100644
index 0000000..7761a62
--- /dev/null
+++ b/src/tests/salt-test-server/salt/mysql/schema.sql
@@ -0,0 +1,150 @@
+###########################################################################
+# $Id$ #
+# #
+# schema.sql rlm_sql - FreeRADIUS SQL Module #
+# #
+# Database schema for MySQL rlm_sql module #
+# #
+# To load: #
+# mysql -uroot -prootpass radius < schema.sql #
+# #
+# Mike Machado <mike@innercite.com> #
+###########################################################################
+#
+# Table structure for table 'radacct'
+#
+
+CREATE TABLE radacct (
+ radacctid bigint(21) NOT NULL auto_increment,
+ acctsessionid varchar(64) NOT NULL default '',
+ acctuniqueid varchar(32) NOT NULL default '',
+ username varchar(64) NOT NULL default '',
+ groupname varchar(64) NOT NULL default '',
+ realm varchar(64) default '',
+ nasipaddress varchar(15) NOT NULL default '',
+ nasportid varchar(50) default NULL,
+ nasporttype varchar(32) default NULL,
+ acctstarttime datetime NULL default NULL,
+ acctupdatetime datetime NULL default NULL,
+ acctstoptime datetime NULL default NULL,
+ acctinterval int(12) default NULL,
+ acctsessiontime int(12) unsigned default NULL,
+ acctauthentic varchar(32) default NULL,
+ connectinfo_start varchar(50) default NULL,
+ connectinfo_stop varchar(50) default NULL,
+ acctinputoctets bigint(20) default NULL,
+ acctoutputoctets bigint(20) default NULL,
+ calledstationid varchar(50) NOT NULL default '',
+ callingstationid varchar(50) NOT NULL default '',
+ acctterminatecause varchar(32) NOT NULL default '',
+ servicetype varchar(32) default NULL,
+ framedprotocol varchar(32) default NULL,
+ framedipaddress varchar(15) NOT NULL default '',
+ PRIMARY KEY (radacctid),
+ UNIQUE KEY acctuniqueid (acctuniqueid),
+ KEY username (username),
+ KEY framedipaddress (framedipaddress),
+ KEY acctsessionid (acctsessionid),
+ KEY acctsessiontime (acctsessiontime),
+ KEY acctstarttime (acctstarttime),
+ KEY acctinterval (acctinterval),
+ KEY acctstoptime (acctstoptime),
+ KEY nasipaddress (nasipaddress)
+) ENGINE = INNODB;
+
+#
+# Table structure for table 'radcheck'
+#
+
+CREATE TABLE radcheck (
+ id int(11) unsigned NOT NULL auto_increment,
+ username varchar(64) NOT NULL default '',
+ attribute varchar(64) NOT NULL default '',
+ op char(2) NOT NULL DEFAULT '==',
+ value varchar(253) NOT NULL default '',
+ PRIMARY KEY (id),
+ KEY username (username(32))
+);
+
+#
+# Table structure for table 'radgroupcheck'
+#
+
+CREATE TABLE radgroupcheck (
+ id int(11) unsigned NOT NULL auto_increment,
+ groupname varchar(64) NOT NULL default '',
+ attribute varchar(64) NOT NULL default '',
+ op char(2) NOT NULL DEFAULT '==',
+ value varchar(253) NOT NULL default '',
+ PRIMARY KEY (id),
+ KEY groupname (groupname(32))
+);
+
+#
+# Table structure for table 'radgroupreply'
+#
+
+CREATE TABLE radgroupreply (
+ id int(11) unsigned NOT NULL auto_increment,
+ groupname varchar(64) NOT NULL default '',
+ attribute varchar(64) NOT NULL default '',
+ op char(2) NOT NULL DEFAULT '=',
+ value varchar(253) NOT NULL default '',
+ PRIMARY KEY (id),
+ KEY groupname (groupname(32))
+);
+
+#
+# Table structure for table 'radreply'
+#
+
+CREATE TABLE radreply (
+ id int(11) unsigned NOT NULL auto_increment,
+ username varchar(64) NOT NULL default '',
+ attribute varchar(64) NOT NULL default '',
+ op char(2) NOT NULL DEFAULT '=',
+ value varchar(253) NOT NULL default '',
+ PRIMARY KEY (id),
+ KEY username (username(32))
+);
+
+
+#
+# Table structure for table 'radusergroup'
+#
+
+CREATE TABLE radusergroup (
+ username varchar(64) NOT NULL default '',
+ groupname varchar(64) NOT NULL default '',
+ priority int(11) NOT NULL default '1',
+ KEY username (username(32))
+);
+
+#
+# Table structure for table 'radpostauth'
+#
+CREATE TABLE radpostauth (
+ id int(11) NOT NULL auto_increment,
+ username varchar(64) NOT NULL default '',
+ pass varchar(64) NOT NULL default '',
+ reply varchar(32) NOT NULL default '',
+ authdate timestamp NOT NULL,
+ PRIMARY KEY (id)
+) ENGINE = INNODB;
+
+#
+# Table structure for table 'nas'
+#
+CREATE TABLE nas (
+ id int(10) NOT NULL auto_increment,
+ nasname varchar(128) NOT NULL,
+ shortname varchar(32),
+ type varchar(30) DEFAULT 'other',
+ ports int(5),
+ secret varchar(60) DEFAULT 'secret' NOT NULL,
+ server varchar(64),
+ community varchar(50),
+ description varchar(200) DEFAULT 'RADIUS Client',
+ PRIMARY KEY (id),
+ KEY nasname (nasname)
+);
diff --git a/src/tests/salt-test-server/salt/mysql/setup.sql b/src/tests/salt-test-server/salt/mysql/setup.sql
new file mode 100644
index 0000000..3b9ec54
--- /dev/null
+++ b/src/tests/salt-test-server/salt/mysql/setup.sql
@@ -0,0 +1,18 @@
+# -*- text -*-
+##
+## admin.sql -- MySQL commands for creating the RADIUS user.
+##
+## WARNING: You should change 'localhost' and 'radpass'
+## to something else. Also update raddb/sql.conf
+## with the new RADIUS password.
+##
+## $Id$
+
+#
+# Create default administrator for RADIUS
+#
+CREATE USER 'radius';
+SET PASSWORD FOR 'radius' = PASSWORD('radpass');
+
+# Need to read when running RADIUS and delete for cleanup
+GRANT ALL ON radius.* TO 'radius';
diff --git a/src/tests/salt-test-server/salt/ntp.sls b/src/tests/salt-test-server/salt/ntp.sls
new file mode 100644
index 0000000..66434ff
--- /dev/null
+++ b/src/tests/salt-test-server/salt/ntp.sls
@@ -0,0 +1,22 @@
+UTC:
+ timezone.system
+
+ntp_daemon:
+ # Make sure ntp is installed and running
+ pkg:
+{% if grains['os'] == 'CentOS' or grains['os'] == 'Ubuntu' or grains['os'] == 'Debian' %}
+ - name: ntp
+{% elif grains['os'] == 'FreeBSD' %}
+ - name: openntpd
+{% endif %}
+ - installed
+
+# Make sure ntpd is running and enabled (start on boot)
+{% if grains['os'] == 'CentOS' or grains['os'] == 'FreeBSD' %}
+ntpd:
+{% elif grains['os'] == 'Ubuntu' or grains['os'] == 'Debian' %}
+ntp:
+{% endif %}
+ service:
+ - running
+ - enable: True
diff --git a/src/tests/salt-test-server/salt/postgres.sls b/src/tests/salt-test-server/salt/postgres.sls
new file mode 100644
index 0000000..26fc4e6
--- /dev/null
+++ b/src/tests/salt-test-server/salt/postgres.sls
@@ -0,0 +1,71 @@
+postgresql:
+ # Install postgres and make sure it is running and starts on boot
+ pkg:
+ - installed
+ # Only try to start service after DB has been initialized (will fail otherwise)
+ service:
+ - name: postgresql
+ - running
+ - enable: True
+
+postgres_set_interface:
+ file.sed:
+ - name: /etc/postgresql/9.4/main/postgresql.conf
+ - before: ^\#listen_addresses = 'localhost'
+ - after: listen_addresses = '*'
+
+postgres_password_auth:
+ # Add authentication from anywhere
+ file.append:
+ - name: /etc/postgresql/9.4/main/pg_hba.conf
+ - text:
+ - host radius radius 0.0.0.0/0 md5
+
+postgres_restart:
+ # Restart postgres after changes to the config file (reload isn't enough)
+ cmd.wait:
+ - cwd: /
+ - name: service postgresql restart
+ - require:
+ - pkg: postgresql
+ - file: postgres_password_auth
+ - file: postgres_set_interface
+ - watch:
+ - file: /etc/postgresql/9.4/main/postgresql.conf
+ - file: /etc/postgresql/9.4/main/pg_hba.conf
+
+# Copy DB schema file
+/salt/postgres/schema.sql:
+ file.managed:
+ - source: salt://postgres/schema.sql
+ - makedirs: true
+
+# Copy DB setup script
+/salt/postgres/setup.sql:
+ file.managed:
+ - source: salt://postgres/setup.sql
+ - makedirs: true
+
+# Create DB
+create_db:
+ cmd.run:
+ - cwd: /
+ - name: createdb radius
+ - user: postgres
+ - unless: psql --list | grep radius
+
+# Create FreeRADIUS schema
+psql radius < /salt/postgres/schema.sql:
+ cmd.run:
+ - user: postgres
+ - unless: "echo '\\dt public.*' | psql radius | grep radacct;"
+ - require:
+ - file: /salt/postgres/schema.sql
+
+# Setup DB access
+psql radius < /salt/postgres/setup.sql:
+ cmd.run:
+ - user: postgres
+ - unless: "echo '\\du' | psql radius | grep radius"
+ - require:
+ - file: /salt/postgres/setup.sql
diff --git a/src/tests/salt-test-server/salt/postgres/schema.sql b/src/tests/salt-test-server/salt/postgres/schema.sql
new file mode 100644
index 0000000..c94ee9e
--- /dev/null
+++ b/src/tests/salt-test-server/salt/postgres/schema.sql
@@ -0,0 +1,183 @@
+/*
+ * $Id$
+ *
+ * Postgresql schema for FreeRADIUS
+ *
+ * All field lengths need checking as some are still suboptimal. -pnixon 2003-07-13
+ *
+ */
+
+/*
+ * Table structure for table 'radacct'
+ *
+ * Note: Column type bigserial does not exist prior to Postgres 7.2
+ * If you run an older version you need to change this to serial
+ */
+CREATE TABLE radacct (
+ RadAcctId bigserial PRIMARY KEY,
+ AcctSessionId text NOT NULL,
+ AcctUniqueId text NOT NULL UNIQUE,
+ UserName text,
+ GroupName text,
+ Realm text,
+ NASIPAddress inet NOT NULL,
+ NASPortId text,
+ NASPortType text,
+ AcctStartTime timestamp with time zone,
+ AcctUpdateTime timestamp with time zone,
+ AcctStopTime timestamp with time zone,
+ AcctInterval bigint,
+ AcctSessionTime bigint,
+ AcctAuthentic text,
+ ConnectInfo_start text,
+ ConnectInfo_stop text,
+ AcctInputOctets bigint,
+ AcctOutputOctets bigint,
+ CalledStationId text,
+ CallingStationId text,
+ AcctTerminateCause text,
+ ServiceType text,
+ FramedProtocol text,
+ FramedIPAddress inet
+);
+-- This index may be useful..
+-- CREATE UNIQUE INDEX radacct_whoson on radacct (AcctStartTime, nasipaddress);
+
+-- For use by update-, stop- and simul_* queries
+CREATE INDEX radacct_active_session_idx ON radacct (AcctUniqueId) WHERE AcctStopTime IS NULL;
+
+-- Add if you you regularly have to replay packets
+-- CREATE INDEX radacct_session_idx ON radacct (AcctUniqueId);
+
+-- For backwards compatibility
+-- CREATE INDEX radacct_active_user_idx ON radacct (AcctSessionId, UserName, NASIPAddress) WHERE AcctStopTime IS NULL;
+
+-- For use by onoff-
+CREATE INDEX radacct_bulk_close ON radacct (NASIPAddress, AcctStartTime) WHERE AcctStopTime IS NULL;
+
+-- and for common statistic queries:
+CREATE INDEX radacct_start_user_idx ON radacct (AcctStartTime, UserName);
+-- and, optionally
+-- CREATE INDEX radacct_stop_user_idx ON radacct (acctStopTime, UserName);
+
+/*
+ * There was WAAAY too many indexes previously. This combo index
+ * should take care of the most common searches.
+ * I have commented out all the old indexes, but left them in case
+ * someone wants them. I don't recomend anywone use them all at once
+ * as they will slow down your DB too much.
+ * - pnixon 2003-07-13
+ */
+
+/*
+ * create index radacct_UserName on radacct (UserName);
+ * create index radacct_AcctSessionId on radacct (AcctSessionId);
+ * create index radacct_AcctUniqueId on radacct (AcctUniqueId);
+ * create index radacct_FramedIPAddress on radacct (FramedIPAddress);
+ * create index radacct_NASIPAddress on radacct (NASIPAddress);
+ * create index radacct_AcctStartTime on radacct (AcctStartTime);
+ * create index radacct_AcctStopTime on radacct (AcctStopTime);
+*/
+
+
+
+/*
+ * Table structure for table 'radcheck'
+ */
+CREATE TABLE radcheck (
+ id serial PRIMARY KEY,
+ UserName text NOT NULL DEFAULT '',
+ Attribute text NOT NULL DEFAULT '',
+ op VARCHAR(2) NOT NULL DEFAULT '==',
+ Value text NOT NULL DEFAULT ''
+);
+create index radcheck_UserName on radcheck (UserName,Attribute);
+/*
+ * Use this index if you use case insensitive queries
+ */
+-- create index radcheck_UserName_lower on radcheck (lower(UserName),Attribute);
+
+/*
+ * Table structure for table 'radgroupcheck'
+ */
+CREATE TABLE radgroupcheck (
+ id serial PRIMARY KEY,
+ GroupName text NOT NULL DEFAULT '',
+ Attribute text NOT NULL DEFAULT '',
+ op VARCHAR(2) NOT NULL DEFAULT '==',
+ Value text NOT NULL DEFAULT ''
+);
+create index radgroupcheck_GroupName on radgroupcheck (GroupName,Attribute);
+
+/*
+ * Table structure for table 'radgroupreply'
+ */
+CREATE TABLE radgroupreply (
+ id serial PRIMARY KEY,
+ GroupName text NOT NULL DEFAULT '',
+ Attribute text NOT NULL DEFAULT '',
+ op VARCHAR(2) NOT NULL DEFAULT '=',
+ Value text NOT NULL DEFAULT ''
+);
+create index radgroupreply_GroupName on radgroupreply (GroupName,Attribute);
+
+/*
+ * Table structure for table 'radreply'
+ */
+CREATE TABLE radreply (
+ id serial PRIMARY KEY,
+ UserName text NOT NULL DEFAULT '',
+ Attribute text NOT NULL DEFAULT '',
+ op VARCHAR(2) NOT NULL DEFAULT '=',
+ Value text NOT NULL DEFAULT ''
+);
+create index radreply_UserName on radreply (UserName,Attribute);
+/*
+ * Use this index if you use case insensitive queries
+ */
+-- create index radreply_UserName_lower on radreply (lower(UserName),Attribute);
+
+/*
+ * Table structure for table 'radusergroup'
+ */
+CREATE TABLE radusergroup (
+ id serial PRIMARY KEY,
+ UserName text NOT NULL DEFAULT '',
+ GroupName text NOT NULL DEFAULT '',
+ priority integer NOT NULL DEFAULT 0
+);
+create index radusergroup_UserName on radusergroup (UserName);
+/*
+ * Use this index if you use case insensitive queries
+ */
+-- create index radusergroup_UserName_lower on radusergroup (lower(UserName));
+
+--
+-- Table structure for table 'radpostauth'
+--
+
+CREATE TABLE radpostauth (
+ id bigserial PRIMARY KEY,
+ username text NOT NULL,
+ pass text,
+ reply text,
+ CalledStationId text,
+ CallingStationId text,
+ authdate timestamp with time zone NOT NULL default now()
+);
+
+/*
+ * Table structure for table 'nas'
+ */
+CREATE TABLE nas (
+ id serial PRIMARY KEY,
+ nasname text NOT NULL,
+ shortname text NOT NULL,
+ type text NOT NULL DEFAULT 'other',
+ ports integer,
+ secret text NOT NULL,
+ server text,
+ community text,
+ description text
+);
+create index nas_nasname on nas (nasname);
diff --git a/src/tests/salt-test-server/salt/postgres/setup.sql b/src/tests/salt-test-server/salt/postgres/setup.sql
new file mode 100644
index 0000000..6b41aa1
--- /dev/null
+++ b/src/tests/salt-test-server/salt/postgres/setup.sql
@@ -0,0 +1,21 @@
+/*
+ * admin.sql -- PostgreSQL commands for creating the RADIUS user.
+ *
+ * WARNING: You should change 'localhost' and 'radpass'
+ * to something else. Also update raddb/sql.conf
+ * with the new RADIUS password.
+ *
+ * WARNING: This example file is untested. Use at your own risk.
+ * Please send any bug fixes to the mailing list.
+ *
+ * $Id$
+ */
+
+/*
+ * Create default administrator for RADIUS
+ */
+CREATE USER radius WITH PASSWORD 'radpass';
+
+/* radius user needs ti clean tables in test env */
+GRANT ALL ON ALL TABLES IN SCHEMA public TO radius;
+GRANT SELECT, USAGE ON ALL SEQUENCES IN schema public TO radius;
diff --git a/src/tests/salt-test-server/salt/top.sls b/src/tests/salt-test-server/salt/top.sls
new file mode 100644
index 0000000..efba703
--- /dev/null
+++ b/src/tests/salt-test-server/salt/top.sls
@@ -0,0 +1,7 @@
+base:
+ 'test-server':
+ - ntp
+ - mysql
+ - postgres
+ - ldap
+ - iptable
diff --git a/src/tests/salt-test-server/salt_config/master b/src/tests/salt-test-server/salt_config/master
new file mode 100644
index 0000000..257396a
--- /dev/null
+++ b/src/tests/salt-test-server/salt_config/master
@@ -0,0 +1,12 @@
+root_dir: .
+# pki_dir and cachedir are prefixed with root_dir
+pki_dir: /tmp/
+cachedir: /cache/
+file_roots:
+ base:
+ # salt directory in current working directory
+ - salt
+pillar_roots:
+ base:
+ # pillar directory in current working directory
+ - pillar
diff --git a/src/tests/salt-test-server/salt_config/roster b/src/tests/salt-test-server/salt_config/roster
new file mode 100644
index 0000000..8958bd1
--- /dev/null
+++ b/src/tests/salt-test-server/salt_config/roster
@@ -0,0 +1,4 @@
+test-server:
+ host: 192.168.2.132
+ user: root
+ passwd: root
diff --git a/src/tests/sql_nas_table/all.mk b/src/tests/sql_nas_table/all.mk
new file mode 100644
index 0000000..da21501
--- /dev/null
+++ b/src/tests/sql_nas_table/all.mk
@@ -0,0 +1,78 @@
+#
+# Unit tests validating the SQL 'nas' table clients
+#
+
+#
+# Test name
+#
+TEST := test.sql_nas_table
+FILES := $(subst $(DIR)/,,$(wildcard $(DIR)/*.txt))
+
+#
+# If we have rlm_sql_sqlite and sqlite3
+#
+ifneq "$(findstring rlm_sql_sqlite,$(ALL_TGTS))" ""
+SQLITE3 := $(shell which sqlite3)
+endif
+
+ifneq "$(SQLITE3)" ""
+
+#
+# Run the full tests
+#
+$(eval $(call TEST_BOOTSTRAP))
+
+#
+# Config settings
+#
+SQL_NASTABLE_BUILD_DIR := $(BUILD_DIR)/tests/sql_nas_table
+SQL_NASTABLE_RADIUS_LOG := $(SQL_NASTABLE_BUILD_DIR)/radiusd.log
+SQL_NASTABLE_GDB_LOG := $(SQL_NASTABLE_BUILD_DIR)/gdb.log
+SQL_NASTABLE_DB := $(SQL_NASTABLE_BUILD_DIR)/sql_nas_table.db
+
+# Used by src/tests/sql_nas_table/config/radiusd.conf
+export SQL_NASTABLE_DB
+
+#
+# Generic rules to start / stop the radius service.
+#
+include src/tests/radiusd.mk
+$(eval $(call RADIUSD_SERVICE,radiusd,$(OUTPUT)))
+
+.PHONY: sql_nas_table_bootstrap
+sql_nas_table_bootstrap:
+ $(Q)rm -f $(SQL_NASTABLE_DB)
+ $(Q)mkdir -p $(SQL_NASTABLE_BUILD_DIR)
+ $(Q)sqlite3 $(SQL_NASTABLE_DB) < ./raddb/mods-config/sql/main/sqlite/schema.sql
+ $(Q)sqlite3 $(SQL_NASTABLE_DB) < ./src/tests/sql_nas_table/clients.sql
+
+#
+# Run the radclient commands against the radiusd.
+#
+$(OUTPUT)/%: $(DIR)/% | $(TEST).radiusd_kill sql_nas_table_bootstrap $(TEST).radiusd_start
+ $(Q)echo "SQL_NASTABLE-TEST"
+ $(Q)mkdir -p $(dir $@)
+ $(Q)[ -f $(dir $@)/radiusd.pid ] || exit 1
+ $(Q)if ! $(TESTBIN)/radclient $(ARGV) -xf src/tests/sql_nas_table/auth.txt -D share/ 127.0.0.1:$(PORT) auth $(SECRET) 1> $(SQL_NASTABLE_BUILD_DIR)/radclient.log 2>&1; then \
+ echo "FAILED"; \
+ rm -f $(BUILD_DIR)/tests/test.sql_nas_table; \
+ $(MAKE) --no-print-directory test.sql_nas_table.radiusd_kill; \
+ echo ==============================; \
+ tail -10 $(SQL_NASTABLE_BUILD_DIR)/radclient.log; \
+ echo ==============================; \
+ echo "RADIUSD: $(RADIUSD_RUN)"; \
+ echo "SQL_NASTABLE: $(TESTBIN)/radclient $(ARGV) -f $< -xF -d src/tests/sql_nas_table/config -D share/ 127.0.0.1:$(PORT) auth $(SECRET)"; \
+ exit 1; \
+ fi
+
+ $(Q)touch $@
+
+$(TEST):
+ $(Q)$(MAKE) --no-print-directory $@.radiusd_stop
+ @touch $(BUILD_DIR)/tests/$@
+else
+#
+# No sqlite3 command, don't do anything.
+#
+$(TEST):
+endif
diff --git a/src/tests/sql_nas_table/auth.txt b/src/tests/sql_nas_table/auth.txt
new file mode 100644
index 0000000..c1b0a1d
--- /dev/null
+++ b/src/tests/sql_nas_table/auth.txt
@@ -0,0 +1,2 @@
+User-Name = bob
+Cleartext-Password = hello
diff --git a/src/tests/sql_nas_table/clients.sql b/src/tests/sql_nas_table/clients.sql
new file mode 100644
index 0000000..d631b7f
--- /dev/null
+++ b/src/tests/sql_nas_table/clients.sql
@@ -0,0 +1 @@
+INSERT INTO nas (nasname,shortname,type,ports,secret,server,community,description) VALUES ('127.0.0.1', 'test', 'test', '123', 'testing123', 'extra', '', 'RADIUS Client');
diff --git a/src/tests/sql_nas_table/config/radiusd.conf b/src/tests/sql_nas_table/config/radiusd.conf
new file mode 100644
index 0000000..16513bb
--- /dev/null
+++ b/src/tests/sql_nas_table/config/radiusd.conf
@@ -0,0 +1,143 @@
+# -*- text -*-
+#
+# test configuration file. Do not install.
+#
+# $Id$
+#
+
+#
+# Minimal radiusd.conf for testing
+#
+top_srcdir = $ENV{TOP_SRCDIR}
+testdir = $ENV{TESTDIR}
+output = ${top_srcdir}/$ENV{OUTPUT}
+run_dir = ${output}
+raddb = raddb
+pidfile = ${run_dir}/radiusd.pid
+panic_action = "gdb -batch -x src/tests/panic.gdb %e %p > ${run_dir}/gdb.log 2>&1; cat ${run_dir}/gdb.log"
+
+maindir = ${raddb}
+radacctdir = ${run_dir}/radacct
+modconfdir = ${maindir}/mods-config
+certdir = ${maindir}/certs
+cadir = ${maindir}/certs
+test_port = $ENV{TEST_PORT}
+
+client docnet {
+ ipaddr = 192.0.2.1
+ secret = testing123123
+}
+
+# Only for testing!
+# Setting this on a production system is a BAD IDEA.
+security {
+ allow_vulnerable_openssl = yes
+}
+
+policy {
+ files.authorize {
+ if (&User-Name == "bob") {
+ update control {
+ &Password.Cleartext := "hello"
+ }
+ }
+ }
+ $INCLUDE ${maindir}/policy.d/
+}
+
+modules {
+ expr {
+
+ }
+
+ sql {
+ driver = "rlm_sql_sqlite"
+ dialect = "sqlite"
+ sqlite {
+ # Path to the sqlite database
+ filename = "$ENV{SQL_NASTABLE_DB}"
+
+ # How long to wait for write locks on the database to be
+ # released (in ms) before giving up.
+ busy_timeout = 200
+
+ # The bootstrap is handled by src/tests/sql_nas_table/all.mk
+ }
+
+ radius_db = "radius"
+
+ acct_table1 = "radacct"
+ acct_table2 = "radacct"
+ postauth_table = "radpostauth"
+ authcheck_table = "radcheck"
+ groupcheck_table = "radgroupcheck"
+ authreply_table = "radreply"
+ groupreply_table = "radgroupreply"
+ usergroup_table = "radusergroup"
+ read_groups = yes
+ read_profiles = yes
+
+ # Set to 'yes' to read radius clients from the database ('nas' table)
+ # Clients will ONLY be read on server startup.
+ read_clients = yes
+
+ # Table to keep radius client info
+ client_table = "nas"
+
+ # The group attribute specific to this instance of rlm_sql
+ group_attribute = "SQL-Group"
+
+ # Read database-specific queries
+ $INCLUDE ${modconfdir}/${.:name}/main/${dialect}/queries.conf
+ }
+
+ always reject {
+ rcode = reject
+ }
+ always fail {
+ rcode = fail
+ }
+ always ok {
+ rcode = ok
+ }
+ always handled {
+ rcode = handled
+ }
+ always invalid {
+ rcode = invalid
+ }
+ always notfound {
+ rcode = notfound
+ }
+ always noop {
+ rcode = noop
+ }
+ always updated {
+ rcode = updated
+ }
+}
+
+#
+# This virtual server is chosen for processing requests when using:
+#
+# radiusd -Xd src/tests/ -i 127.0.0.1 -p 12340 -n test
+#
+server extra {
+ listen {
+ ipaddr = 127.0.0.1
+ port = ${test_port}
+ type = auth
+ }
+
+ authorize {
+ if (&User-Name == "bob") {
+ accept
+ } else {
+ reject
+ }
+ }
+
+ authenticate {
+
+ }
+}
diff --git a/src/tests/stripped.example.com b/src/tests/stripped.example.com
new file mode 100644
index 0000000..8f4baa3
--- /dev/null
+++ b/src/tests/stripped.example.com
@@ -0,0 +1,5 @@
+#
+# TESTS 1
+#
+User-Name = "bob@stripped.example.com",
+User-Password = "bob"
diff --git a/src/tests/test.example.com b/src/tests/test.example.com
new file mode 100644
index 0000000..56d1960
--- /dev/null
+++ b/src/tests/test.example.com
@@ -0,0 +1,7 @@
+#
+# Tests for clear-text password
+#
+# TESTS 1
+#
+User-Name = "bob@test.example.com"
+User-Password = "bob"
diff --git a/src/tests/tests.gdb b/src/tests/tests.gdb
new file mode 100644
index 0000000..1ff2c91
--- /dev/null
+++ b/src/tests/tests.gdb
@@ -0,0 +1,9 @@
+define hook-stop
+ list
+ info locals
+ info args
+ thread apply all bt full
+end
+handle SIGTERM stop pass noprint
+run
+quit
diff --git a/src/tests/unit/all.mk b/src/tests/unit/all.mk
new file mode 100644
index 0000000..f8cef43
--- /dev/null
+++ b/src/tests/unit/all.mk
@@ -0,0 +1,53 @@
+#
+# Unit tests for individual pieces of functionality.
+#
+
+#
+# The files are put here in order. Later tests need
+# functionality from earlier tests.
+#
+FILES := rfc.txt errors.txt extended.txt lucent.txt wimax.txt \
+ escape.txt condition.txt xlat.txt vendor.txt dhcp.txt ascend.txt \
+ rfc4849.txt eapol_key_msg.txt
+
+#
+# Create the output directory
+#
+.PHONY: $(BUILD_DIR)/tests/unit
+$(BUILD_DIR)/tests/unit:
+ @mkdir -p $@
+
+.PHONY: $(BUILD_DIR)/share
+$(BUILD_DIR)/share:
+ @mkdir -p $@
+
+#
+# We need $INCLUDE in the output file, so we pass 2 parameters to 'echo'
+# No idea how portable that is...
+#
+$(BUILD_DIR)/share/dictionary: $(top_srcdir)/share/dictionary $(top_srcdir)/share/dictionary.dhcp | $(BUILD_DIR)/share
+ @rm -f $@
+ @for x in $^; do \
+ echo '$$INCLUDE ' "$$x" >> $@; \
+ done
+
+#
+# Files in the output dir depend on the unit tests
+#
+$(BUILD_DIR)/tests/unit/%: $(DIR)/% $(BUILD_DIR)/bin/radattr $(TESTBINDIR)/radattr $(BUILD_DIR)/share/dictionary | $(BUILD_DIR)/tests/unit
+ @echo UNIT-TEST $(notdir $@)
+ @if ! $(TESTBIN)/radattr -D $(BUILD_DIR)/share $<; then \
+ echo "$(TESTBIN)/radattr -D $(BUILD_DIR)/share $<"; \
+ exit 1; \
+ fi
+ @touch $@
+
+#
+# Get all of the unit test output files
+#
+TESTS.UNIT_FILES := $(addprefix $(BUILD_DIR)/tests/unit/,$(FILES))
+
+#
+# Depend on the output files, and create the directory first.
+#
+tests.unit: $(TESTS.UNIT_FILES)
diff --git a/src/tests/unit/ascend.txt b/src/tests/unit/ascend.txt
new file mode 100644
index 0000000..4acb223
--- /dev/null
+++ b/src/tests/unit/ascend.txt
@@ -0,0 +1,5 @@
+#
+# Ascend data filters
+#
+encode Ascend-Data-Filter = "ip in drop tcp dstport > 1023"
+data 1a 28 00 00 02 11 f2 22 01 00 01 00 00 00 00 00 00 00 00 00 00 00 06 00 00 00 03 ff 00 03 00 00 00 00 00 00 00 00 00 00
diff --git a/src/tests/unit/condition.txt b/src/tests/unit/condition.txt
new file mode 100644
index 0000000..bbe24d2
--- /dev/null
+++ b/src/tests/unit/condition.txt
@@ -0,0 +1,679 @@
+#
+# 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`
diff --git a/src/tests/unit/dhcp.txt b/src/tests/unit/dhcp.txt
new file mode 100644
index 0000000..db7eae2
--- /dev/null
+++ b/src/tests/unit/dhcp.txt
@@ -0,0 +1,44 @@
+#
+# Test vectors for DHCP attributes
+#
+
+encode-dhcp DHCP-Subnet-Mask = 255.255.0.0
+data 01 04 ff ff 00 00
+
+decode-dhcp -
+data DHCP-Subnet-Mask = 255.255.0.0
+
+#
+# A long one... with a weird DHCP-specific vendor ID.
+#
+decode-dhcp 3501013d0701001ceaadac1e37070103060f2c2e2f3c094d5346545f495054565232011c4c41424f4c54322065746820312f312f30312f30312f31302f312f3209120000197f0d050b4c4142373336304f4c5432
+data DHCP-Message-Type = DHCP-Discover, DHCP-Client-Identifier = 0x01001ceaadac1e, DHCP-Parameter-Request-List = DHCP-Subnet-Mask, DHCP-Parameter-Request-List = DHCP-Router-Address, DHCP-Parameter-Request-List = DHCP-Domain-Name-Server, DHCP-Parameter-Request-List = DHCP-Domain-Name, DHCP-Parameter-Request-List = DHCP-NETBIOS-Name-Servers, DHCP-Parameter-Request-List = DHCP-NETBIOS-Node-Type, DHCP-Parameter-Request-List = DHCP-NETBIOS, DHCP-Vendor-Class-Identifier = 0x4d5346545f49505456, DHCP-Relay-Circuit-Id = 0x4c41424f4c54322065746820312f312f30312f30312f31302f312f32, DHCP-Vendor-Specific-Information = 0x0000197f0d050b4c4142373336304f4c5432
+
+
+encode-dhcp DHCP-Agent-Circuit-Id = 0xabcdef, DHCP-Relay-Remote-Id = 0x010203040506
+data 52 0d 01 03 ab cd ef 02 06 01 02 03 04 05 06
+
+decode-dhcp -
+data DHCP-Relay-Circuit-Id = 0xabcdef, DHCP-Relay-Remote-Id = 0x010203040506
+
+# 35 01 01
+# 3d 06 00 a0 bd 11 22 33
+# 37 05 2b 29 01 03 06
+# 3c 09 76 69 61 73 61 74 31 2e 30
+# 52 31
+# 06 1f 30 30 41 30 42 44 31 31 32 32 33 33 40 73 62 32 2e 72 65 73 2e 76 69 61 73 61 74 2e 63 6f 6d
+# 02 06 00 a0 bc 6c 7d 3a
+# 01 06 00 a0 bc 33 22 11
+# 39 02 05 dc
+# 7d 1e
+# 00 00 0d e9 19
+# 01 06 30 30 30 31 39 46
+# 02 0a 52 4e 56 35 30 34 35 39 34 34
+# 03 03 41 54 41
+# ff 00
+
+decode-dhcp 7d 1e 00 00 0d e9 19 01 06 30 30 30 31 39 46 02 0a 52 4e 56 35 30 34 35 39 34 34 03 03 41 54 41
+data ADSL-Forum-Device-Manufacturer-OUI = 0x303030313946, ADSL-Forum-Device-Serial-Number = "RNV5045944", ADSL-Forum-Device-Product-Class = "ATA"
+
+encode-dhcp -
+data 7d 1e 00 00 0d e9 19 01 06 30 30 30 31 39 46 02 0a 52 4e 56 35 30 34 35 39 34 34 03 03 41 54 41
diff --git a/src/tests/unit/eapol_key_msg.txt b/src/tests/unit/eapol_key_msg.txt
new file mode 100644
index 0000000..c6f83ab
--- /dev/null
+++ b/src/tests/unit/eapol_key_msg.txt
@@ -0,0 +1,14 @@
+#
+# For sending EAPoL key messages in RADIUS.
+#
+encode FreeRADIUS-802.1X-Anonce = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+data f5 29 1a 00 00 00 2c 50 01 aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa
+
+decode -
+data FreeRADIUS-802.1X-Anonce = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+encode FreeRADIUS-802.1X-EAPoL-Key-Msg = 0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+data f5 ff 1a 80 00 00 2c 50 02 bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa f5 08 1a 00 aa aa aa aa
+
+decode -
+data FreeRADIUS-802.1X-EAPoL-Key-Msg = 0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
diff --git a/src/tests/unit/errors.txt b/src/tests/unit/errors.txt
new file mode 100644
index 0000000..801b501
--- /dev/null
+++ b/src/tests/unit/errors.txt
@@ -0,0 +1,17 @@
+#
+# Bad attributes
+#
+decode 01 04 00
+data rad_attr2vp: Insufficient data
+
+decode 01 01 00
+data rad_attr2vp: Insufficient data
+
+encode Attr-26.1.256 = 0x00000001
+data Number '256' out of allowed range in attribute identifier
+
+encode Attr-240.1 = 0x01
+data Standard attributes cannot use OIDs
+
+encode Attr-26.1.1 = 0x01
+data 1a 09 00 00 00 01 01 03 01
diff --git a/src/tests/unit/escape.txt b/src/tests/unit/escape.txt
new file mode 100644
index 0000000..b65e1b7
--- /dev/null
+++ b/src/tests/unit/escape.txt
@@ -0,0 +1,74 @@
+#
+# Like the conditional tests, but tests for escape sequences
+#
+condition "bob" == 0x626f62
+data true
+
+condition 0x == '0x'
+data ERROR offset 2 Empty octet string is invalid
+
+condition 'foo' == 0x
+data ERROR offset 9 Empty octet string is invalid
+
+# \n gets escaped in double quoted strings
+condition "\n" == 0x0a
+data true
+
+# but not in single quoted strings
+condition '\n' == 0x5c6e
+data true
+
+condition '\'' == 0x27
+data true
+
+condition "'" == 0x27
+data true
+
+condition "\"" == 0x22
+data true
+
+condition 0x22 == '"'
+data true
+
+condition '\'' == "'"
+data true
+
+condition '\\' == "\\"
+data true
+
+#
+# The first string is \ + x
+#
+condition '\x' == "x"
+data false
+
+# embedded zeros are OK
+condition "a\000a" == 0x610061
+data true
+
+condition "aa\000" == 0x616100
+data true
+
+condition 'aa\000' == 0x61615c303030
+data true
+
+condition 'aa\000' == "aa\000"
+data false
+
+condition 'a\n' == "a\n"
+data false
+
+condition 0x626f62 == 'bob'
+data true
+
+condition 0x626f62 == "bob"
+data true
+
+condition 0x626f62 == bob
+data true
+
+condition \n == 0x0a
+data ERROR offset 1 Unexpected escape
+
+condition a\n == 0x610a
+data ERROR offset 2 Unexpected escape
diff --git a/src/tests/unit/extended.txt b/src/tests/unit/extended.txt
new file mode 100644
index 0000000..9810b19
--- /dev/null
+++ b/src/tests/unit/extended.txt
@@ -0,0 +1,103 @@
+# Example attributes as used in the "extended attributes" draft.
+raw 241.1 "bob"
+data f1 06 01 62 6f 62
+
+raw 241.2 {1 23 45 }
+data f1 07 02 01 04 23 45
+
+raw 241.2 {1 23 45 } { 2 67 89 }
+data f1 0b 02 01 04 23 45 02 04 67 89
+
+raw 241.2 {1 23 45 } { 3 { 1 ab cd } }
+data f1 0d 02 01 04 23 45 03 06 01 04 ab cd
+
+raw 241.2 {1 23 45 } { 3 { 1 ab cd } {2 "foo" } }
+data f1 12 02 01 04 23 45 03 0b 01 04 ab cd 02 05 66 6f 6f
+
+raw 241.1 {1 { 2 { 3 { 4 { 5 cd ef } } } } }
+data f1 0f 01 01 0c 02 0a 03 08 04 06 05 04 cd ef
+
+raw 241.26.1.4 "test"
+data f1 0c 1a 00 00 00 01 04 74 65 73 74
+
+raw 241.26.1.5 { 3 "test" }
+data f1 0e 1a 00 00 00 01 05 03 06 74 65 73 74
+
+# More examples.
+raw 245.1 "bob"
+data f5 07 01 00 62 6f 62
+
+raw 245.2 {1 23 45 }
+data f5 08 02 00 01 04 23 45
+
+raw 245.2 {1 23 45 } { 2 67 89 }
+data f5 0c 02 00 01 04 23 45 02 04 67 89
+
+raw 245.2 {1 23 45 } { 3 { 1 ab cd } }
+data f5 0e 02 00 01 04 23 45 03 06 01 04 ab cd
+
+raw 245.2 {1 23 45 } { 3 { 1 ab cd } {2 "foo" } }
+data f5 13 02 00 01 04 23 45 03 0b 01 04 ab cd 02 05 66 6f 6f
+
+raw 245.1 {1 { 2 { 3 { 4 { 5 cd ef } } } } }
+data f5 10 01 00 01 0c 02 0a 03 08 04 06 05 04 cd ef
+
+raw 245.26.1.4 "test"
+data f5 0d 1a 00 00 00 00 01 04 74 65 73 74
+
+raw 245.26.1.5 { 3 "test" }
+data f5 0f 1a 00 00 00 00 01 05 03 06 74 65 73 74
+
+raw 245.4 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccccccccccccc
+data f5 ff 04 80 aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa ab bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb f5 13 04 00 cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc
+
+#
+# 256 copies of 'x'
+#
+raw 245.1 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+data f5 ff 01 80 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 f5 09 01 00 78 78 78 78 78
+
+#
+# And it decodes to an attribute with 256 x's
+#
+decode f5 ff 01 80 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 f5 09 01 00 78 78 78 78 79
+data Attr-245.1 = 0x78787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787879
+
+#
+# A VSA which has lots of data
+#
+raw 245.26.1.6 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccc13456789
+data f5 ff 1a 80 00 00 00 01 06 aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa ab bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb f5 17 1a 00 bb bb bb bb bb cc cc cc cc cc cc cc cc cc cc 13 45 67 89
+
+decode f5 ff 1a 80 00 00 00 01 06 aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa ab bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb f5 17 1a 00 bb bb bb bb bb cc cc cc cc cc cc cc cc cc cc 13 45 67 89
+data Attr-245.26.1.6 = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccc13456789
+
+# Same as above, but the first attribute doesn't have
+# the "continuation" bit set.
+decode f5 ff 1a 00 00 00 00 01 06 aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa ab bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb f5 17 1a 00 bb bb bb bb bb cc cc cc cc cc cc cc cc cc cc 13 45 67 89
+data Attr-245.26.1.6 = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, Attr-245.26 = 0xbbbbbbbbbbcccccccccccccccccccc13456789
+
+
+# again, but the second one attr is not an extended attr
+decode f5 ff 1a 80 00 00 00 01 06 aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa ab bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb 01 05 62 6f 62
+data Attr-245 = 0x1a800000000106aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, User-Name = "bob"
+
+# No data means that the attribute is an "invalid attribute"
+decode f5 04 01 00
+data Attr-245 = 0x0100
+
+# No "flags" field means it's an invalid attribute.
+decode f5 03 01
+data Attr-245 = 0x01
+
+decode f5 09 1a 00 00 00 00 01 06
+data Attr-245.26 = 0x0000000106
+
+decode f5 0a 1a 00 00 00 00 01 06 01
+data Attr-245.26.1.6 = 0x01
+
+decode f5 09 1a 80 00 00 00 01 06 f5 05 1a 80 01
+data Attr-245.26.1.6 = 0x01
+
+decode f5 0a 1a 80 00 00 00 01 06 01 f5 05 1a 80 01
+data Attr-245.26.1.6 = 0x0101
diff --git a/src/tests/unit/lucent.txt b/src/tests/unit/lucent.txt
new file mode 100644
index 0000000..e21d03c
--- /dev/null
+++ b/src/tests/unit/lucent.txt
@@ -0,0 +1,11 @@
+encode Lucent-Max-Shared-Users = 1
+data 1a 0d 00 00 12 ee 00 02 07 00 00 00 01
+
+decode -
+data Lucent-Max-Shared-Users = 1
+
+decode 1a 0d 00 00 12 ee ff 02 07 00 00 00 01
+data Attr-26.4846.65282 = 0x00000001
+
+encode -
+data 1a 0d 00 00 12 ee ff 02 07 00 00 00 01
diff --git a/src/tests/unit/rfc.txt b/src/tests/unit/rfc.txt
new file mode 100644
index 0000000..8e95526
--- /dev/null
+++ b/src/tests/unit/rfc.txt
@@ -0,0 +1,204 @@
+# All attribute lengths are implicit, and are calculated automatically
+#
+# Input is of the form:
+#
+# WORD ...
+#
+# The WORD is a keyword which indicates the format of the following text.
+# WORD is one of:
+#
+# raw - read the grammar defined below, and encode an attribute.
+# The grammar supports a trivial way of describing RADIUS
+# attributes, without reference to dictionaries or fancy
+# parsers
+#
+# encode - reads "Attribute-Name = value", encodes it, and prints
+# the result as text.
+# use "-" to encode the output of the last command
+#
+# decode - reads hex, and decodes it "Attribute-Name = value"
+# use "-" to decode the output of the last command
+#
+# data - the expected output of the previous command, in ASCII form.
+# if the actual command output is different, an error message
+# is produced, and the program terminates.
+#
+#
+# The "raw" input satisfies the following grammar:
+#
+# Identifier = 1*DIGIT *( "." 1*DIGIT )
+#
+# HEXCHAR = HEXDIG HEXDIG
+#
+# STRING = DQUOTE *CHAR DQUOTE
+#
+# TLV = "{" 1*DIGIT DATA "}"
+#
+# DATA = 1*HEXCHAR / 1*TLV / STRING
+#
+# LINE = Identifier DATA
+#
+# The "Identifier" is a RADIUS attribute identifier, as given in the draft.
+#
+# e.g. 1 for User-Name
+# 26.9.1 Vendor-Specific, Cisco, Cisco-AVPAir
+# 241.1 Extended Attribute, number 1
+# 241.2.3 Extended Attribute 2, data type TLV, TLV type 3
+# etc.
+#
+# The "DATA" portion is the contents of the RADIUS Attribute.
+#
+# 123456789abcdef hex string
+# 12 34 56 ab with spaces for clarity
+# "hello" Text string
+# { 1 abcdef } TLV, TLV-Type 1, data "abcdef"
+#
+# TLVs can be nested:
+#
+# { tlv-type { tlv-type data } } { 3 { 4 01020304 } }
+#
+# TLVs can be concatencated
+#
+# {tlv-type data } { tlv-type data} { 3 040506 } { 8 aabbcc }
+#
+# The "raw" data is encoded without reference to dictionaries. Any
+# valid string is parsed to a RADIUS attribute. The resulting RADIUS
+# attribute *may not* be correctly formatted to the relevant RADIUS
+# specifications. i.e. you can use this tool to create attribute 1
+# (User-Name), which is encoded as a series of TLVs. That's up to you.
+#
+# The purpose of the "raw" command is to have a simple way of encoding
+# attributes which is independent of any dictionaries or packet processing
+# routines.
+#
+# The output data is the hex version of the encoded attribute.
+#
+
+encode User-Name = "bob"
+data 01 05 62 6f 62
+
+decode -
+data User-Name = "bob"
+
+decode 01 05 62 6f 62
+data User-Name = "bob"
+
+#
+# The Type/Length is OK, but the attribute data is of the wrong size.
+#
+decode 04 04 ab cd
+data Attr-4 = 0xabcd
+
+# Zero-length attributes
+decode 01 02
+data
+
+# don't encode zero-length attributes
+encode User-Name = ""
+data
+
+# except for CUI. Thank you, WiMAX!
+decode 59 02
+data Chargeable-User-Identity = 0x
+
+# Hah! Thought you had it figured out, didn't you?
+encode -
+data 59 02
+
+attribute Framed-IP-Address = 127.0.0.1/32
+data Framed-IP-Address = 127.0.0.1
+
+attribute Framed-IP-Address = 127.0.0.1/323
+data Invalid IPv4 mask length "/323". Should be between 0-32
+
+attribute Framed-IP-Address = 127.0.0.1/30
+data Invalid IPv4 mask length "/30". Only "/32" permitted for non-prefix types
+
+attribute Framed-IP-Address = *
+data Framed-IP-Address = 0.0.0.0
+
+attribute Framed-IP-Address = 127
+data Framed-IP-Address = 0.0.0.127
+
+attribute Framed-IP-Address = 127.0
+data Framed-IP-Address = 127.0.0.0
+
+attribute Framed-IPv6-Prefix = ::1
+data Framed-IPv6-Prefix = ::1/128
+
+attribute Framed-IPv6-Prefix = ::1/200
+data Invalid IPv6 mask length "/200". Should be between 0-128
+
+attribute Framed-IPv6-Prefix = ::1/200
+data Invalid IPv6 mask length "/200". Should be between 0-128
+
+attribute Framed-IPv6-Prefix = 11:22:33:44:55:66:77:88/128
+data Framed-IPv6-Prefix = 11:22:33:44:55:66:77:88/128
+
+attribute Framed-IPv6-Prefix = *
+data Framed-IPv6-Prefix = ::/128
+
+attribute PMIP6-Home-IPv4-HoA = 127/8
+data PMIP6-Home-IPv4-HoA = 127.0.0.0/8
+
+attribute PMIP6-Home-IPv4-HoA = 127/8
+data PMIP6-Home-IPv4-HoA = 127.0.0.0/8
+
+#
+# Octets outside of the mask are OK, but
+# are mashed to zero.
+#
+attribute PMIP6-Home-IPv4-HoA = 127.63/8
+data PMIP6-Home-IPv4-HoA = 127.0.0.0/8
+
+#
+# Unless you give a good mask.
+#
+attribute PMIP6-Home-IPv4-HoA = 127.63/16
+data PMIP6-Home-IPv4-HoA = 127.63.0.0/16
+
+attribute PMIP6-Home-IPv4-HoA = 127.999/16
+data Failed to parse IPv4 address string "127.999/16"
+
+attribute PMIP6-Home-IPv4-HoA = 127.bob/16
+data Failed to parse IPv4 address string "127.bob/16"
+
+attribute PMIP6-Home-IPv4-HoA = 127.63/15
+data PMIP6-Home-IPv4-HoA = 127.62.0.0/15
+
+attribute PMIP6-Home-IPv4-HoA = 127.63.1/24
+data PMIP6-Home-IPv4-HoA = 127.63.1.0/24
+
+attribute PMIP6-Home-IPv4-HoA = 127.63.1.6
+data PMIP6-Home-IPv4-HoA = 127.63.1.6/32
+
+attribute PMIP6-Home-IPv4-HoA = 256/8
+data Failed to parse IPv4 address string "256/8"
+
+attribute PMIP6-Home-IPv4-HoA = bob/8
+data Failed to parse IPv4 address string "bob/8"
+
+#
+# A "concat" attribute, with no data
+#
+decode 89 02
+data PKM-SS-Cert = 0x
+
+#
+# The configuration can use the old names, but they
+# get automatically converted to the new names.
+#
+attribute User-Service-Type = 1
+data Service-Type = Login-User
+
+#
+# Or with weirdly formatted data
+#
+decode 89 03 ff 89 02 89 03 fe
+data PKM-SS-Cert = 0xfffe
+
+$INCLUDE tunnel.txt
+$INCLUDE errors.txt
+$INCLUDE extended.txt
+$INCLUDE lucent.txt
+$INCLUDE wimax.txt
diff --git a/src/tests/unit/rfc4849.txt b/src/tests/unit/rfc4849.txt
new file mode 100644
index 0000000..957a9e9
--- /dev/null
+++ b/src/tests/unit/rfc4849.txt
@@ -0,0 +1,49 @@
+#
+# RFC 4849 NAS-Filter-Rule
+#
+# Individual rules are packed together as with EAP-Message,
+# but the rules are separated by a 0x00 byte.
+#
+encode NAS-Filter-Rule = "hello"
+data 5c 07 68 65 6c 6c 6f
+
+decode -
+data NAS-Filter-Rule = "hello"
+
+
+encode NAS-Filter-Rule = "hello", NAS-Filter-Rule += "bob"
+data 5c 0b 68 65 6c 6c 6f 00 62 6f 62
+
+decode -
+data NAS-Filter-Rule = "hello", NAS-Filter-Rule = "bob"
+
+encode NAS-Filter-Rule = "hello", NAS-Filter-Rule += "bob", NAS-Filter-Rule += "stuff"
+data 5c 11 68 65 6c 6c 6f 00 62 6f 62 00 73 74 75 66 66
+
+decode -
+data NAS-Filter-Rule = "hello", NAS-Filter-Rule = "bob", NAS-Filter-Rule = "stuff"
+
+#
+# And large amounts of data
+#
+
+# 250x and then space (to tell where the 'x' ends
+encode NAS-Filter-Rule = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ", NAS-Filter-Rule += "bob"
+data 5c ff 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 20 00 62 5c 04 6f 62
+
+decode -
+data NAS-Filter-Rule = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ", NAS-Filter-Rule = "bob"
+
+# 251x + ' '
+encode NAS-Filter-Rule = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ", NAS-Filter-Rule += "bob"
+data 5c ff 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 20 00 5c 05 62 6f 62
+
+decode -
+data NAS-Filter-Rule = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ", NAS-Filter-Rule = "bob"
+
+# 252x + ' '
+encode NAS-Filter-Rule = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ", NAS-Filter-Rule += "bob"
+data 5c ff 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 20 5c 06 00 62 6f 62
+
+decode -
+data NAS-Filter-Rule = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ", NAS-Filter-Rule = "bob"
diff --git a/src/tests/unit/tunnel.txt b/src/tests/unit/tunnel.txt
new file mode 100644
index 0000000..da0cb31
--- /dev/null
+++ b/src/tests/unit/tunnel.txt
@@ -0,0 +1,87 @@
+#
+# We can't look at the data here, because the encoded Tunnel-Password has a 2 byte
+# random salt
+#
+encode Tunnel-Password:0 = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxabc"
+decode -
+data Tunnel-Password:0 = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxabc"
+
+encode Tunnel-Password:0 = "0"
+decode -
+data Tunnel-Password:0 = "0"
+
+encode Tunnel-Password:0 = "01"
+decode -
+data Tunnel-Password:0 = "01"
+
+encode Tunnel-Password:0 = "012"
+decode -
+data Tunnel-Password:0 = "012"
+
+encode Tunnel-Password:0 = "0123"
+decode -
+data Tunnel-Password:0 = "0123"
+
+encode Tunnel-Password:0 = "01234"
+decode -
+data Tunnel-Password:0 = "01234"
+
+encode Tunnel-Password:0 = "012345"
+decode -
+data Tunnel-Password:0 = "012345"
+
+encode Tunnel-Password:0 = "0123456"
+decode -
+data Tunnel-Password:0 = "0123456"
+
+encode Tunnel-Password:0 = "01234567"
+decode -
+data Tunnel-Password:0 = "01234567"
+
+encode Tunnel-Password:0 = "012345678"
+decode -
+data Tunnel-Password:0 = "012345678"
+
+encode Tunnel-Password:0 = "0123456789"
+decode -
+data Tunnel-Password:0 = "0123456789"
+
+encode Tunnel-Password:0 = "0123456789a"
+decode -
+data Tunnel-Password:0 = "0123456789a"
+
+encode Tunnel-Password:0 = "0123456789ab"
+decode -
+data Tunnel-Password:0 = "0123456789ab"
+
+encode Tunnel-Password:0 = "0123456789abc"
+decode -
+data Tunnel-Password:0 = "0123456789abc"
+
+encode Tunnel-Password:0 = "0123456789abcd"
+decode -
+data Tunnel-Password:0 = "0123456789abcd"
+
+encode Tunnel-Password:0 = "0123456789abcde"
+decode -
+data Tunnel-Password:0 = "0123456789abcde"
+
+encode Tunnel-Password:0 = "0123456789abcdef"
+decode -
+data Tunnel-Password:0 = "0123456789abcdef"
+
+#
+# We can't look at the data here, because the encoded Tunnel-Password has a 2 byte
+# random salt
+#
+encode Tunnel-Password:0 := "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+decode -
+data Tunnel-Password:0 = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+
+#
+# 1 octet for the tag. 2 octets for salt. One octet for encrypted length.
+# 249 octets left for real data.
+#
+encode Tunnel-Password:0 = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx123456789ab"
+decode -
+data Tunnel-Password:0 = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx123456789"
diff --git a/src/tests/unit/vendor.txt b/src/tests/unit/vendor.txt
new file mode 100644
index 0000000..1325f49
--- /dev/null
+++ b/src/tests/unit/vendor.txt
@@ -0,0 +1,48 @@
+encode SN-VPN-Name = "foo"
+data 1a 0d 00 00 1f e4 00 02 00 07 66 6f 6f
+
+decode 1a 0d 00 00 1f e4 00 02 00 07 66 6f 6f
+data SN-VPN-Name = "foo"
+
+encode USR-Event-Id = 1234
+data 1a 0e 00 00 01 ad 00 00 bf be 00 00 04 d2
+
+decode 1a 0e 00 00 01 ad 00 00 bf be 00 00 04 d2
+data USR-Event-Id = 1234
+
+decode 1a 15 00 00 4e 20 01 0f 6c 69 74 68 69 61 73 70 72 69 6e 67 73
+data Attr-26.20000.1 = 0x6c6974686961737072696e6773
+
+decode 1a 2e 00 00 00 2b 1c 02 01 06 00 00 00 00 3c 20 31 35 35 2e 34 2e 31 32 2e 31 30 30 20 30 30 3a 30 30 3a 30 30 3a 30 30 3a 30 30 3a 30 30
+data 3Com-User-Access-Level = 3Com-Visitor, 3Com-Ip-Host-Addr = "155.4.12.100 00:00:00:00:00:00"
+
+decode 1a 2c 00 00 00 2b 01 06 00 00 00 00 3c 20 31 35 35 2e 34 2e 31 32 2e 31 30 30 20 30 30 3a 30 30 3a 30 30 3a 30 30 3a 30 30 3a 30 30
+data 3Com-User-Access-Level = 3Com-Visitor, 3Com-Ip-Host-Addr = "155.4.12.100 00:00:00:00:00:00"
+
+encode Vendor-Specific = 0xabcdef
+data Must use 'Attr-26 = ...' instead of 'Vendor-Specific = ...'
+
+encode Attr-26 = 0x00000009abcdef
+data 1a 09 00 00 00 09 ab cd ef
+
+attribute Attr-26 = 0x00000009abcdef
+data Attr-26 = 0x00000009abcdef
+attribute Ascend-Data-Filter = 0x01010100010203040a0b0c0d05200600000504d2020200000000000000000000
+data Ascend-Data-Filter = "ip in forward srcip 1.2.3.4/5 dstip 10.11.12.13/32 tcp srcport = 5 dstport = 1234"
+
+encode -
+data 1a 28 00 00 02 11 f2 22 01 01 01 00 01 02 03 04 0a 0b 0c 0d 05 20 06 00 00 05 04 d2 02 02 00 00 00 00 00 00 00 00 00 00
+
+decode 1a2800000211f22201010100010203040a0b0c0d05200600000504d2020200000000000000000000
+data Ascend-Data-Filter = "ip in forward srcip 1.2.3.4/5 dstip 10.11.12.13/32 tcp srcport = 5 dstport = 1234"
+
+# this untagged tunnel encrypted VSA is valid in both access accepts and CoA requests
+encode ERX-LI-Action = off
+decode -
+data ERX-LI-Action = off
+
+packet coa_request
+original null
+encode ERX-LI-Action = off
+decode -
+data ERX-LI-Action = off
diff --git a/src/tests/unit/wimax.txt b/src/tests/unit/wimax.txt
new file mode 100644
index 0000000..0917097
--- /dev/null
+++ b/src/tests/unit/wimax.txt
@@ -0,0 +1,171 @@
+#
+# Test vectors for WiMAX attributes.
+#
+encode WiMAX-Release = "1.0"
+data 1a 0e 00 00 60 b5 01 08 00 01 05 31 2e 30
+
+decode -
+data WiMAX-Release = "1.0"
+
+decode 1a 08 00 00 60 b5 01 02
+data Attr-26 = 0x000060b50102
+
+decode 1a 0a 00 00 60 b5 01 04 00 01
+data Attr-26.24757.1 = 0x01
+
+encode WiMAX-Accounting-Capabilities = 1
+data 1a 0c 00 00 60 b5 01 06 00 02 03 01
+
+decode -
+data WiMAX-Accounting-Capabilities = IP-Session-Based
+
+encode WiMAX-Release = "1.0", WiMAX-Accounting-Capabilities = 1
+data 1a 11 00 00 60 b5 01 0b 00 01 05 31 2e 30 02 03 01
+
+decode -
+data WiMAX-Release = "1.0", WiMAX-Accounting-Capabilities = IP-Session-Based
+
+encode -
+data 1a 11 00 00 60 b5 01 0b 00 01 05 31 2e 30 02 03 01
+
+encode WiMAX-PFDv2-Classifier-Direction = 1
+data 1a 0e 00 00 60 b5 54 08 00 09 05 04 03 01
+
+encode WiMAX-PFDv2-Classifier-Direction = 1, WiMAX-PFDv2-Src-Port = 6809
+data 1a 14 00 00 60 b5 54 0e 00 09 0b 04 03 01 05 06 04 04 1a 99
+
+decode -
+data WiMAX-PFDv2-Classifier-Direction = 1, WiMAX-PFDv2-Src-Port = 6809
+
+decode 1a 11 00 00 60 b5 54 0b 00 09 08 05 06 04 04 1a 99
+data WiMAX-PFDv2-Src-Port = 6809
+
+# 26.24757.89.9.4 has the correct length.
+# 26.24757.89.9.5 has the correct length.
+# 26.24757.89.9.5.4 has the wrong length.
+decode 1a 14 00 00 60 b5 54 0e 00 09 0b 04 03 01 05 06 04 05 1a 99
+data WiMAX-PFDv2-Classifier-Direction = 1, Attr-26.24757.84.9.5 = 0x04051a99
+
+# The 26.24757.1 has the wrong length
+decode 1a 11 00 00 60 b5 01 0a 00 01 05 31 2e 30 02 03 01
+data Attr-26 = 0x000060b5010a000105312e30020301
+
+encode -
+data 1a 11 00 00 60 b5 01 0a 00 01 05 31 2e 30 02 03 01
+
+decode 1a 11 00 00 60 b5 01 0c 00 01 05 31 2e 30 02 03 01
+data Attr-26 = 0x000060b5010c000105312e30020301
+
+encode -
+data 1a 11 00 00 60 b5 01 0c 00 01 05 31 2e 30 02 03 01
+
+# 26.24757.1.1 has the wrong length
+decode 1a 11 00 00 60 b5 01 0b 00 01 04 31 2e 30 02 03 01
+data Attr-26.24757.1 = 0x0104312e30020301
+
+decode 1a 11 00 00 60 b5 01 0b 00 01 06 31 2e 30 02 03 01
+data Attr-26.24757.1 = 0x0106312e30020301
+
+encode -
+data 1a 11 00 00 60 b5 01 0b 00 01 06 31 2e 30 02 03 01
+
+
+# 26.24757.1.2 has the wrong length
+decode 1a 11 00 00 60 b5 01 0b 00 01 05 31 2e 30 02 02 01
+data Attr-26.24757.1 = 0x0105312e30020201
+
+encode -
+data 1a 11 00 00 60 b5 01 0b 00 01 05 31 2e 30 02 02 01
+
+# 26.24757.1.1 has the correct length
+# 26.24757.1.2 has the wrong length
+# This means that 26.24757.1 is invalid, and we create a raw attribute.
+decode 1a 11 00 00 60 b5 01 0b 00 01 05 31 2e 30 02 04 01
+data Attr-26.24757.1 = 0x0105312e30020401
+
+encode -
+data 1a 11 00 00 60 b5 01 0b 00 01 05 31 2e 30 02 04 01
+
+encode WiMAX-PFDv2-Eth-Priority-Range-Low = 55
+data 1a 12 00 00 60 b5 54 0c 00 09 09 09 07 03 05 01 03 37
+
+encode WiMAX-PFDv2-Eth-Priority-Range-Low = 55, WiMAX-PFDv2-Eth-Priority-Range-High = 84
+data 1a 15 00 00 60 b5 54 0f 00 09 0c 09 0a 03 08 01 03 37 02 03 54
+
+decode -
+data WiMAX-PFDv2-Eth-Priority-Range-Low = 55, WiMAX-PFDv2-Eth-Priority-Range-High = 84
+
+# A less efficient encoding of the above data
+decode 1a 17 00 00 60 b5 54 11 00 09 0e 09 0c 03 05 01 03 37 03 05 02 03 54
+data WiMAX-PFDv2-Eth-Priority-Range-Low = 55, WiMAX-PFDv2-Eth-Priority-Range-High = 84
+
+# 26.24757.84.9.9.3.1 has the wrong length
+decode 1a 15 00 00 60 b5 54 0f 00 09 0c 09 0a 03 08 01 04 37 02 03 54
+data Attr-26.24757.84.9.9.3 = 0x010437020354
+
+# 26.24757.84.9.9.3.2 has the wrong length
+decode 1a 15 00 00 60 b5 54 0f 00 09 0c 09 0a 03 08 01 03 37 02 04 54
+data Attr-26.24757.84.9.9.3 = 0x010337020454
+
+# 26.24757.84.9.9.3.2 has the wrong length
+# This means that the SECOND 26.24757.84.9.9.3 is invalid.
+decode 1a 17 00 00 60 b5 54 11 00 09 0e 09 0c 03 05 01 03 37 03 05 02 04 54
+data WiMAX-PFDv2-Eth-Priority-Range-Low = 55, Attr-26.24757.84.9.9.3 = 0x020454
+
+# 26.24757.84.9.9.3.1 has the wrong length
+# This means that 26.24757.84.9.9.3 is invalid.
+decode 1a 17 00 00 60 b5 54 11 00 09 0e 09 0c 03 05 01 02 37 03 05 02 03 54
+data Attr-26.24757.84.9.9.3 = 0x010237, WiMAX-PFDv2-Eth-Priority-Range-High = 84
+
+#
+# Simple test for continued attributes
+#
+decode 1a 0e 00 00 60 b5 01 08 80 01 05 31 2e 30 1a 0c 00 00 60 b5 01 06 00 02 03 00
+data WiMAX-Release = "1.0", WiMAX-Accounting-Capabilities = No-Accounting
+
+#
+# See if encoding multiple attributes works
+#
+encode WiMAX-Packet-Data-Flow-Id := 32, WiMAX-Service-Data-Flow-ID := 32, WiMAX-Service-Profile-ID := 32
+data 1a 17 00 00 60 b5 1c 11 00 01 04 00 20 02 04 00 20 03 06 00 00 00 20
+
+encode WiMAX-Packet-Data-Flow-Id := 33, WiMAX-Service-Data-Flow-ID := 33, WiMAX-Service-Profile-ID := 33
+data 1a 17 00 00 60 b5 1c 11 00 01 04 00 21 02 04 00 21 03 06 00 00 00 21
+
+encode WiMAX-Packet-Data-Flow-Id := 32, WiMAX-Service-Data-Flow-ID := 32, WiMAX-Service-Profile-ID := 32, WiMAX-Packet-Data-Flow-Id := 33, WiMAX-Service-Data-Flow-ID := 33, WiMAX-Service-Profile-ID := 33
+data 1a 25 00 00 60 b5 1c 1f 00 01 04 00 20 02 04 00 20 03 06 00 00 00 20 01 04 00 21 02 04 00 21 03 06 00 00 00 21
+
+encode WiMAX-Packet-Data-Flow-Id := 32, WiMAX-Service-Data-Flow-ID := 32, WiMAX-Service-Profile-ID := 32, WiMAX-Packet-Data-Flow-Id := 33, WiMAX-Service-Data-Flow-ID := 33, WiMAX-Service-Profile-ID := 33, Session-Timeout := 7200
+data 1a 25 00 00 60 b5 1c 1f 00 01 04 00 20 02 04 00 20 03 06 00 00 00 20 01 04 00 21 02 04 00 21 03 06 00 00 00 21 1b 06 00 00 1c 20
+
+encode Acct-Interim-Interval := 3600, WiMAX-Packet-Data-Flow-Id := 32, WiMAX-Service-Data-Flow-ID := 32, WiMAX-Service-Profile-ID := 32, WiMAX-Packet-Data-Flow-Id := 33, WiMAX-Service-Data-Flow-ID := 33, WiMAX-Service-Profile-ID := 33, Session-Timeout := 7200
+data 55 06 00 00 0e 10 1a 25 00 00 60 b5 1c 1f 00 01 04 00 20 02 04 00 20 03 06 00 00 00 20 01 04 00 21 02 04 00 21 03 06 00 00 00 21 1b 06 00 00 1c 20
+
+encode WiMAX-Packet-Data-Flow-Id := 32, WiMAX-Service-Data-Flow-ID := 32, WiMAX-Service-Profile-ID := 32, Session-Timeout := 7200, WiMAX-Packet-Data-Flow-Id := 33, WiMAX-Service-Data-Flow-ID := 33, WiMAX-Service-Profile-ID := 33
+data 1a 17 00 00 60 b5 1c 11 00 01 04 00 20 02 04 00 20 03 06 00 00 00 20 1b 06 00 00 1c 20 1a 17 00 00 60 b5 1c 11 00 01 04 00 21 02 04 00 21 03 06 00 00 00 21
+
+encode WiMAX-Capability = 0x01ff45454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545040301
+data 1a ff 00 00 60 b5 01 f9 80 01 ff 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 1a 15 00 00 60 b5 01 0f 00 45 45 45 45 45 45 45 45 45 04 03 01
+
+decode -
+data WiMAX-Release = "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE", WiMAX-Idle-Mode-Notification-Cap = Supported
+
+#
+# Continuation is set, but there's no continued data.
+decode 1a 0b 00 00 60 b5 31 05 80 00 00
+data Attr-26 = 0x000060b53105800000
+
+encode WiMAX-GMT-Timezone-offset = -1
+data 1a 0d 00 00 60 b5 03 07 00 ff ff ff ff
+
+decode -
+data WiMAX-GMT-Timezone-offset = -1
+
+#
+# It's like a disease which keeps spreading.
+#
+encode Telrad-Reference-QOS-Profile-Name = "garbage"
+data 1a 14 00 00 14 cb 01 0e 00 03 0b 04 09 67 61 72 62 61 67 65
+
+decode -
+data Telrad-Reference-QOS-Profile-Name = "garbage"
diff --git a/src/tests/unit/xlat.txt b/src/tests/unit/xlat.txt
new file mode 100644
index 0000000..5dc4893
--- /dev/null
+++ b/src/tests/unit/xlat.txt
@@ -0,0 +1,142 @@
+#
+# Tests for xlat expansion
+#
+
+xlat %{foo: bar}
+data ERROR offset 2 'Unknown module'
+
+xlat %{test:bar}
+data %{test:bar}
+
+xlat %{1}
+data %{1}
+
+xlat %{33}
+data ERROR offset 2 'Invalid regex reference. Must be in range 0-32'
+
+xlat %{%{foo}:-%{bar}}
+data ERROR offset 4 'Unknown attribute'
+
+xlat %{%{User-Name}:-%{bar}}
+data ERROR offset 18 'Unknown attribute'
+
+xlat %{%{User-Name}:-bar}
+data %{%{User-Name}:-bar}
+
+xlat %{%{test:bar}:-%{User-Name}}
+data %{%{test:bar}:-%{User-Name}}
+
+xlat %{%{test:bar}:-%{%{User-Name}:-bar}}
+data %{%{test:bar}:-%{%{User-Name}:-bar}}
+
+xlat %{Tunnel-Password}
+data %{Tunnel-Password}
+
+xlat %{Tunnel-Password:1}
+data %{Tunnel-Password:1}
+
+xlat %{Tunnel-Password:1[3]}
+data %{Tunnel-Password:1[3]}
+
+xlat %{Tunnel-Password:1[*]}
+data %{Tunnel-Password:1[*]}
+
+xlat %{Tunnel-Password:1[#]}
+data %{Tunnel-Password:1[#]}
+
+xlat %{reply:Tunnel-Password}
+data %{reply:Tunnel-Password}
+
+xlat %{reply:Tunnel-Password:1}
+data %{reply:Tunnel-Password:1}
+
+xlat %{reply:Tunnel-Password:1[3]}
+data %{reply:Tunnel-Password:1[3]}
+
+xlat %{reply:Tunnel-Password:1[*]}
+data %{reply:Tunnel-Password:1[*]}
+
+xlat %{reply:Tunnel-Password:1[#]}
+data %{reply:Tunnel-Password:1[#]}
+
+xlat %{User-Name[3]}
+data %{User-Name[3]}
+
+xlat %{User-Name[*]}
+data %{User-Name[*]}
+
+xlat %{User-Name[#]}
+data %{User-Name[#]}
+
+xlat %{request:User-Name[3]}
+data %{User-Name[3]}
+
+xlat %{request:User-Name[*]}
+data %{User-Name[*]}
+
+xlat %{request:User-Name[#]}
+data %{User-Name[#]}
+
+xlat %{coa:User-Name[#]}
+data %{coa:User-Name[#]}
+
+xlat %{coaX:User-Name[#]}
+data ERROR offset 2 'Unknown module'
+
+xlat %{3GPP-SGSN-Address}
+data %{3GPP-SGSN-Address}
+
+xlat %{%{Operator-Name}:-}
+data %{%{Operator-Name}:-}
+
+xlat %{%{}:-}
+data ERROR offset 4 'Empty expression is invalid'
+
+xlat %{%{}:-foo}
+data ERROR offset 4 'Empty expression is invalid'
+
+xlat %{}
+data ERROR offset 2 'Empty expression is invalid'
+
+xlat %{ }
+data ERROR offset 2 'Invalid attribute name'
+
+xlat %{%{User-Name}:-}
+data %{%{User-Name}:-}
+
+xlat "Hello %S goo"
+data "Hello %S goo"
+
+xlat "%{Foreach-Variable-0}"
+data "%{Foreach-Variable-0}"
+
+#
+# 3GPP stuff, to distinguish "list:3GPP" from
+# "attribute:tag"
+#
+xlat "%{request:3GPP-IMSI}"
+data "%{3GPP-IMSI}"
+
+xlat "%{reply:3GPP-IMSI}"
+data "%{reply:3GPP-IMSI}"
+
+xlat "%{reply:3GPP-IMSI[2]}"
+data "%{reply:3GPP-IMSI[2]}"
+
+xlat /([A-Z0-9\-]*)_%{Calling-Station-Id}/
+data /([A-Z0-9\-]*)_%{Calling-Station-Id}/
+
+xlat %{length:1 + 2
+data ERROR offset 14 'Missing closing brace at end of string'
+
+xlat "%t\tfoo"
+data "%t\tfoo"
+
+xlat "%t\t%{Client-IP-Address}"
+data "%t\t%{Client-IP-Address}"
+
+xlat "foo %{test}"
+data ERROR offset 11 'Missing content in expansion'
+
+xlat "foo %{test:foo}"
+data "foo %{test:foo}"
diff --git a/src/tests/xlat/all.mk b/src/tests/xlat/all.mk
new file mode 100644
index 0000000..190a1ed
--- /dev/null
+++ b/src/tests/xlat/all.mk
@@ -0,0 +1,57 @@
+#
+# Unit tests for dynamic xlat expansions
+#
+
+#
+# The test files are files without extensions.
+# The list is unordered. The order is added in the next step by looking
+# at precursors.
+#
+XLAT_FILES := $(subst $(DIR)/,,$(wildcard $(DIR)/*.txt))
+
+#
+# Create the output directory
+#
+.PHONY: $(BUILD_DIR)/tests/xlat
+$(BUILD_DIR)/tests/xlat:
+ @mkdir -p $@
+
+#
+# Files in the output dir depend on the unit tests
+#
+# src/tests/xlat/FOO input file
+# build/tests/xlat/FOO updated if the test succeeds
+# build/tests/xlat/FOO.log debug output for the test
+#
+# Auto-depend on modules via $(shell grep INCLUDE $(DIR)/radiusd.conf | grep mods-enabled | sed 's/.*}/raddb/'))
+#
+# If the test fails, then look for ERROR in the input. No error
+# means it's unexpected, so we die.
+#
+# Otherwise, check the log file for a parse error which matches the
+# ERROR line in the input.
+#
+$(BUILD_DIR)/tests/xlat/%: $(DIR)/% $(TESTBINDIR)/unittest | $(BUILD_DIR)/tests/xlat build.raddb
+ @echo XLAT-TEST $(notdir $@)
+ @if ! $(TESTBIN)/unittest -D share -d src/tests/xlat/ -i $< -xx -O xlat_only > $@.log 2>&1; then \
+ cat $@.log; \
+ echo "./$(TESTBIN)/unittest -D share -d src/tests/xlat/ -i $< -xx -O xlat_only"; \
+ exit 1; \
+ fi
+ @touch $@
+
+#
+# Get all of the unit test output files
+#
+TESTS.XLAT_FILES := $(addprefix $(BUILD_DIR)/tests/xlat/,$(XLAT_FILES))
+
+#
+# Depend on the output files, and create the directory first.
+#
+tests.xlat: $(TESTS.XLAT_FILES)
+
+$(TESTS.XLAT_FILES): $(TESTS.UNIT_FILES)
+
+.PHONY: clean.tests.xlat
+clean.tests.xlat:
+ @rm -rf $(BUILD_DIR)/tests/xlat/
diff --git a/src/tests/xlat/expr.txt b/src/tests/xlat/expr.txt
new file mode 100644
index 0000000..e2d9f10
--- /dev/null
+++ b/src/tests/xlat/expr.txt
@@ -0,0 +1,20 @@
+xlat %{md5:This is a string\n}
+data 9ac4dbbc3c0ad2429e61d0df5dc28add
+
+xlat %{expr: 1 + 2 + 3 + 4}
+data 10
+
+xlat %{expr: 1 & ~1}
+data 0
+
+xlat %{expr: 2 - -1}
+data 3
+
+xlat %{expr: -1 * 2}
+data -2
+
+xlat %{expr: 1 << 2 | 1}
+data 5
+
+xlat %{expr: 6 + -(1 + 3)}
+data 2
diff --git a/src/tests/xlat/radiusd.conf b/src/tests/xlat/radiusd.conf
new file mode 100644
index 0000000..89e7f24
--- /dev/null
+++ b/src/tests/xlat/radiusd.conf
@@ -0,0 +1,37 @@
+#
+# Minimal radiusd.conf for testing keywords
+#
+
+raddb = raddb
+
+modconfdir = ${raddb}/mods-config
+
+correct_escapes = true
+
+# Only for testing!
+# Setting this on a production system is a BAD IDEA.
+security {
+ allow_vulnerable_openssl = yes
+}
+
+modules {
+ $INCLUDE ${raddb}/mods-enabled/always
+
+ $INCLUDE ${raddb}/mods-enabled/pap
+
+ $INCLUDE ${raddb}/mods-enabled/expr
+}
+
+server default {
+ authorize {
+ update control {
+ Cleartext-Password := 'hello'
+ }
+
+ pap
+ }
+
+ authenticate {
+ pap
+ }
+}