charm-crypto-framework 0.61.1__cp313-cp313-macosx_10_13_universal2.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. charm/__init__.py +5 -0
  2. charm/adapters/__init__.py +0 -0
  3. charm/adapters/abenc_adapt_hybrid.py +90 -0
  4. charm/adapters/dabenc_adapt_hybrid.py +145 -0
  5. charm/adapters/ibenc_adapt_hybrid.py +72 -0
  6. charm/adapters/ibenc_adapt_identityhash.py +80 -0
  7. charm/adapters/kpabenc_adapt_hybrid.py +91 -0
  8. charm/adapters/pkenc_adapt_bchk05.py +121 -0
  9. charm/adapters/pkenc_adapt_chk04.py +91 -0
  10. charm/adapters/pkenc_adapt_hybrid.py +98 -0
  11. charm/adapters/pksig_adapt_naor01.py +89 -0
  12. charm/config.py +7 -0
  13. charm/core/__init__.py +0 -0
  14. charm/core/benchmark/benchmark_util.c +353 -0
  15. charm/core/benchmark/benchmark_util.h +61 -0
  16. charm/core/benchmark/benchmarkmodule.c +476 -0
  17. charm/core/benchmark/benchmarkmodule.h +162 -0
  18. charm/core/benchmark.cpython-313-darwin.so +0 -0
  19. charm/core/crypto/AES/AES.c +1464 -0
  20. charm/core/crypto/AES.cpython-313-darwin.so +0 -0
  21. charm/core/crypto/DES/DES.c +113 -0
  22. charm/core/crypto/DES.cpython-313-darwin.so +0 -0
  23. charm/core/crypto/DES3/DES3.c +26 -0
  24. charm/core/crypto/DES3.cpython-313-darwin.so +0 -0
  25. charm/core/crypto/__init__.py +0 -0
  26. charm/core/crypto/cryptobase/XOR.c +80 -0
  27. charm/core/crypto/cryptobase/_counter.c +496 -0
  28. charm/core/crypto/cryptobase/_counter.h +54 -0
  29. charm/core/crypto/cryptobase/block_template.c +900 -0
  30. charm/core/crypto/cryptobase/block_template.h +69 -0
  31. charm/core/crypto/cryptobase/cryptobasemodule.c +220 -0
  32. charm/core/crypto/cryptobase/libtom/tomcrypt.h +90 -0
  33. charm/core/crypto/cryptobase/libtom/tomcrypt_argchk.h +44 -0
  34. charm/core/crypto/cryptobase/libtom/tomcrypt_cfg.h +186 -0
  35. charm/core/crypto/cryptobase/libtom/tomcrypt_cipher.h +941 -0
  36. charm/core/crypto/cryptobase/libtom/tomcrypt_custom.h +556 -0
  37. charm/core/crypto/cryptobase/libtom/tomcrypt_des.c +1912 -0
  38. charm/core/crypto/cryptobase/libtom/tomcrypt_hash.h +407 -0
  39. charm/core/crypto/cryptobase/libtom/tomcrypt_mac.h +496 -0
  40. charm/core/crypto/cryptobase/libtom/tomcrypt_macros.h +435 -0
  41. charm/core/crypto/cryptobase/libtom/tomcrypt_math.h +534 -0
  42. charm/core/crypto/cryptobase/libtom/tomcrypt_misc.h +103 -0
  43. charm/core/crypto/cryptobase/libtom/tomcrypt_pk.h +653 -0
  44. charm/core/crypto/cryptobase/libtom/tomcrypt_pkcs.h +90 -0
  45. charm/core/crypto/cryptobase/libtom/tomcrypt_prng.h +199 -0
  46. charm/core/crypto/cryptobase/stream_template.c +271 -0
  47. charm/core/crypto/cryptobase/strxor.c +229 -0
  48. charm/core/crypto/cryptobase.cpython-313-darwin.so +0 -0
  49. charm/core/engine/__init__.py +5 -0
  50. charm/core/engine/protocol.py +293 -0
  51. charm/core/engine/util.py +174 -0
  52. charm/core/math/__init__.py +0 -0
  53. charm/core/math/elliptic_curve/ecmodule.c +1986 -0
  54. charm/core/math/elliptic_curve/ecmodule.h +230 -0
  55. charm/core/math/elliptic_curve.cpython-313-darwin.so +0 -0
  56. charm/core/math/elliptic_curve.pyi +63 -0
  57. charm/core/math/integer/integermodule.c +2539 -0
  58. charm/core/math/integer/integermodule.h +145 -0
  59. charm/core/math/integer.cpython-313-darwin.so +0 -0
  60. charm/core/math/integer.pyi +76 -0
  61. charm/core/math/pairing/miracl/miracl_config.h +37 -0
  62. charm/core/math/pairing/miracl/miracl_interface.h +118 -0
  63. charm/core/math/pairing/miracl/miracl_interface2.h +126 -0
  64. charm/core/math/pairing/miracl/pairingmodule2.c +2094 -0
  65. charm/core/math/pairing/miracl/pairingmodule2.h +307 -0
  66. charm/core/math/pairing/pairingmodule.c +2230 -0
  67. charm/core/math/pairing/pairingmodule.h +241 -0
  68. charm/core/math/pairing/relic/pairingmodule3.c +1853 -0
  69. charm/core/math/pairing/relic/pairingmodule3.h +233 -0
  70. charm/core/math/pairing/relic/relic_interface.c +1337 -0
  71. charm/core/math/pairing/relic/relic_interface.h +217 -0
  72. charm/core/math/pairing/relic/test_relic.c +171 -0
  73. charm/core/math/pairing.cpython-313-darwin.so +0 -0
  74. charm/core/math/pairing.pyi +69 -0
  75. charm/core/utilities/base64.c +248 -0
  76. charm/core/utilities/base64.h +15 -0
  77. charm/schemes/__init__.py +0 -0
  78. charm/schemes/abenc/__init__.py +0 -0
  79. charm/schemes/abenc/abenc_accountability_jyjxgd20.py +647 -0
  80. charm/schemes/abenc/abenc_bsw07.py +146 -0
  81. charm/schemes/abenc/abenc_ca_cpabe_ar17.py +684 -0
  82. charm/schemes/abenc/abenc_dacmacs_yj14.py +298 -0
  83. charm/schemes/abenc/abenc_lsw08.py +159 -0
  84. charm/schemes/abenc/abenc_maabe_rw15.py +236 -0
  85. charm/schemes/abenc/abenc_maabe_yj14.py +297 -0
  86. charm/schemes/abenc/abenc_tbpre_lww14.py +309 -0
  87. charm/schemes/abenc/abenc_unmcpabe_yahk14.py +223 -0
  88. charm/schemes/abenc/abenc_waters09.py +144 -0
  89. charm/schemes/abenc/abenc_yct14.py +208 -0
  90. charm/schemes/abenc/abenc_yllc15.py +178 -0
  91. charm/schemes/abenc/ac17.py +248 -0
  92. charm/schemes/abenc/bsw07.py +141 -0
  93. charm/schemes/abenc/cgw15.py +277 -0
  94. charm/schemes/abenc/dabe_aw11.py +204 -0
  95. charm/schemes/abenc/dfa_fe12.py +144 -0
  96. charm/schemes/abenc/pk_hve08.py +179 -0
  97. charm/schemes/abenc/waters11.py +143 -0
  98. charm/schemes/aggrsign_MuSig.py +150 -0
  99. charm/schemes/aggrsign_bls.py +267 -0
  100. charm/schemes/blindsig_ps16.py +654 -0
  101. charm/schemes/chamhash_adm05.py +113 -0
  102. charm/schemes/chamhash_rsa_hw09.py +100 -0
  103. charm/schemes/commit/__init__.py +0 -0
  104. charm/schemes/commit/commit_gs08.py +77 -0
  105. charm/schemes/commit/commit_pedersen92.py +53 -0
  106. charm/schemes/encap_bchk05.py +62 -0
  107. charm/schemes/grpsig/__init__.py +0 -0
  108. charm/schemes/grpsig/groupsig_bgls04.py +114 -0
  109. charm/schemes/grpsig/groupsig_bgls04_var.py +115 -0
  110. charm/schemes/hibenc/__init__.py +0 -0
  111. charm/schemes/hibenc/hibenc_bb04.py +105 -0
  112. charm/schemes/hibenc/hibenc_lew11.py +193 -0
  113. charm/schemes/ibenc/__init__.py +0 -0
  114. charm/schemes/ibenc/clpkc_rp03.py +119 -0
  115. charm/schemes/ibenc/ibenc_CW13_z.py +168 -0
  116. charm/schemes/ibenc/ibenc_bb03.py +94 -0
  117. charm/schemes/ibenc/ibenc_bf01.py +121 -0
  118. charm/schemes/ibenc/ibenc_ckrs09.py +120 -0
  119. charm/schemes/ibenc/ibenc_cllww12_z.py +172 -0
  120. charm/schemes/ibenc/ibenc_lsw08.py +120 -0
  121. charm/schemes/ibenc/ibenc_sw05.py +238 -0
  122. charm/schemes/ibenc/ibenc_waters05.py +144 -0
  123. charm/schemes/ibenc/ibenc_waters05_z.py +164 -0
  124. charm/schemes/ibenc/ibenc_waters09.py +107 -0
  125. charm/schemes/ibenc/ibenc_waters09_z.py +147 -0
  126. charm/schemes/joye_scheme.py +106 -0
  127. charm/schemes/lem_scheme.py +207 -0
  128. charm/schemes/pk_fre_ccv11.py +107 -0
  129. charm/schemes/pk_vrf.py +127 -0
  130. charm/schemes/pkenc/__init__.py +0 -0
  131. charm/schemes/pkenc/pkenc_cs98.py +108 -0
  132. charm/schemes/pkenc/pkenc_elgamal85.py +122 -0
  133. charm/schemes/pkenc/pkenc_gm82.py +98 -0
  134. charm/schemes/pkenc/pkenc_paillier99.py +118 -0
  135. charm/schemes/pkenc/pkenc_rabin.py +254 -0
  136. charm/schemes/pkenc/pkenc_rsa.py +186 -0
  137. charm/schemes/pksig/__init__.py +0 -0
  138. charm/schemes/pksig/pksig_CW13_z.py +135 -0
  139. charm/schemes/pksig/pksig_bls04.py +87 -0
  140. charm/schemes/pksig/pksig_boyen.py +156 -0
  141. charm/schemes/pksig/pksig_chch.py +97 -0
  142. charm/schemes/pksig/pksig_chp.py +70 -0
  143. charm/schemes/pksig/pksig_cl03.py +150 -0
  144. charm/schemes/pksig/pksig_cl04.py +87 -0
  145. charm/schemes/pksig/pksig_cllww12_z.py +142 -0
  146. charm/schemes/pksig/pksig_cyh.py +132 -0
  147. charm/schemes/pksig/pksig_dsa.py +76 -0
  148. charm/schemes/pksig/pksig_ecdsa.py +71 -0
  149. charm/schemes/pksig/pksig_hess.py +104 -0
  150. charm/schemes/pksig/pksig_hw.py +110 -0
  151. charm/schemes/pksig/pksig_lamport.py +63 -0
  152. charm/schemes/pksig/pksig_ps01.py +135 -0
  153. charm/schemes/pksig/pksig_ps02.py +124 -0
  154. charm/schemes/pksig/pksig_ps03.py +119 -0
  155. charm/schemes/pksig/pksig_rsa_hw09.py +206 -0
  156. charm/schemes/pksig/pksig_schnorr91.py +77 -0
  157. charm/schemes/pksig/pksig_waters.py +115 -0
  158. charm/schemes/pksig/pksig_waters05.py +121 -0
  159. charm/schemes/pksig/pksig_waters09.py +121 -0
  160. charm/schemes/pre_mg07.py +150 -0
  161. charm/schemes/prenc/pre_afgh06.py +126 -0
  162. charm/schemes/prenc/pre_bbs98.py +123 -0
  163. charm/schemes/prenc/pre_nal16.py +216 -0
  164. charm/schemes/protocol_a01.py +272 -0
  165. charm/schemes/protocol_ao00.py +215 -0
  166. charm/schemes/protocol_cns07.py +274 -0
  167. charm/schemes/protocol_schnorr91.py +125 -0
  168. charm/schemes/sigma1.py +64 -0
  169. charm/schemes/sigma2.py +129 -0
  170. charm/schemes/sigma3.py +126 -0
  171. charm/schemes/threshold/__init__.py +59 -0
  172. charm/schemes/threshold/dkls23_dkg.py +556 -0
  173. charm/schemes/threshold/dkls23_presign.py +1089 -0
  174. charm/schemes/threshold/dkls23_sign.py +761 -0
  175. charm/schemes/threshold/xrpl_wallet.py +967 -0
  176. charm/test/__init__.py +0 -0
  177. charm/test/adapters/__init__.py +0 -0
  178. charm/test/adapters/abenc_adapt_hybrid_test.py +29 -0
  179. charm/test/adapters/dabenc_adapt_hybrid_test.py +56 -0
  180. charm/test/adapters/ibenc_adapt_hybrid_test.py +36 -0
  181. charm/test/adapters/ibenc_adapt_identityhash_test.py +32 -0
  182. charm/test/adapters/kpabenc_adapt_hybrid_test.py +30 -0
  183. charm/test/benchmark/abenc_yllc15_bench.py +92 -0
  184. charm/test/benchmark/benchmark_test.py +148 -0
  185. charm/test/benchmark_threshold.py +260 -0
  186. charm/test/conftest.py +38 -0
  187. charm/test/fuzz/__init__.py +1 -0
  188. charm/test/fuzz/conftest.py +5 -0
  189. charm/test/fuzz/fuzz_policy_parser.py +76 -0
  190. charm/test/fuzz/fuzz_serialization.py +83 -0
  191. charm/test/schemes/__init__.py +0 -0
  192. charm/test/schemes/abenc/__init__.py +0 -0
  193. charm/test/schemes/abenc/abenc_bsw07_test.py +39 -0
  194. charm/test/schemes/abenc/abenc_dacmacs_yj14_test.py +16 -0
  195. charm/test/schemes/abenc/abenc_lsw08_test.py +33 -0
  196. charm/test/schemes/abenc/abenc_maabe_yj14_test.py +16 -0
  197. charm/test/schemes/abenc/abenc_tbpre_lww14_test.py +16 -0
  198. charm/test/schemes/abenc/abenc_waters09_test.py +38 -0
  199. charm/test/schemes/abenc/abenc_yllc15_test.py +74 -0
  200. charm/test/schemes/chamhash_adm05_test.py +31 -0
  201. charm/test/schemes/chamhash_rsa_hw09_test.py +29 -0
  202. charm/test/schemes/commit/__init__.py +0 -0
  203. charm/test/schemes/commit/commit_gs08_test.py +24 -0
  204. charm/test/schemes/commit/commit_pedersen92_test.py +26 -0
  205. charm/test/schemes/dabe_aw11_test.py +45 -0
  206. charm/test/schemes/encap_bchk05_test.py +21 -0
  207. charm/test/schemes/grpsig/__init__.py +0 -0
  208. charm/test/schemes/grpsig/groupsig_bgls04_test.py +35 -0
  209. charm/test/schemes/grpsig/groupsig_bgls04_var_test.py +39 -0
  210. charm/test/schemes/hibenc/__init__.py +0 -0
  211. charm/test/schemes/hibenc/hibenc_bb04_test.py +28 -0
  212. charm/test/schemes/ibenc/__init__.py +0 -0
  213. charm/test/schemes/ibenc/ibenc_bb03_test.py +26 -0
  214. charm/test/schemes/ibenc/ibenc_bf01_test.py +24 -0
  215. charm/test/schemes/ibenc/ibenc_ckrs09_test.py +25 -0
  216. charm/test/schemes/ibenc/ibenc_lsw08_test.py +31 -0
  217. charm/test/schemes/ibenc/ibenc_sw05_test.py +32 -0
  218. charm/test/schemes/ibenc/ibenc_waters05_test.py +31 -0
  219. charm/test/schemes/ibenc/ibenc_waters09_test.py +27 -0
  220. charm/test/schemes/pk_vrf_test.py +29 -0
  221. charm/test/schemes/pkenc/__init__.py +0 -0
  222. charm/test/schemes/pkenc_test.py +255 -0
  223. charm/test/schemes/pksig/__init__.py +0 -0
  224. charm/test/schemes/pksig_test.py +376 -0
  225. charm/test/schemes/rsa_alg_test.py +340 -0
  226. charm/test/schemes/threshold_test.py +1792 -0
  227. charm/test/serialize/__init__.py +0 -0
  228. charm/test/serialize/serialize_test.py +40 -0
  229. charm/test/toolbox/__init__.py +0 -0
  230. charm/test/toolbox/conversion_test.py +30 -0
  231. charm/test/toolbox/ecgroup_test.py +53 -0
  232. charm/test/toolbox/integer_arithmetic_test.py +441 -0
  233. charm/test/toolbox/paddingschemes_test.py +238 -0
  234. charm/test/toolbox/policy_parser_stress_test.py +969 -0
  235. charm/test/toolbox/secretshare_test.py +28 -0
  236. charm/test/toolbox/symcrypto_test.py +108 -0
  237. charm/test/toolbox/test_policy_expression.py +16 -0
  238. charm/test/vectors/__init__.py +1 -0
  239. charm/test/vectors/test_bls_vectors.py +289 -0
  240. charm/test/vectors/test_pedersen_vectors.py +315 -0
  241. charm/test/vectors/test_schnorr_vectors.py +368 -0
  242. charm/test/zkp_compiler/__init__.py +9 -0
  243. charm/test/zkp_compiler/benchmark_zkp.py +258 -0
  244. charm/test/zkp_compiler/test_and_proof.py +240 -0
  245. charm/test/zkp_compiler/test_batch_verify.py +248 -0
  246. charm/test/zkp_compiler/test_dleq_proof.py +264 -0
  247. charm/test/zkp_compiler/test_or_proof.py +231 -0
  248. charm/test/zkp_compiler/test_proof_serialization.py +121 -0
  249. charm/test/zkp_compiler/test_range_proof.py +241 -0
  250. charm/test/zkp_compiler/test_representation_proof.py +325 -0
  251. charm/test/zkp_compiler/test_schnorr_proof.py +221 -0
  252. charm/test/zkp_compiler/test_thread_safety.py +169 -0
  253. charm/test/zkp_compiler/test_zkp_parser.py +139 -0
  254. charm/toolbox/ABEnc.py +26 -0
  255. charm/toolbox/ABEncMultiAuth.py +66 -0
  256. charm/toolbox/ABEnumeric.py +800 -0
  257. charm/toolbox/Commit.py +24 -0
  258. charm/toolbox/DFA.py +89 -0
  259. charm/toolbox/FSA.py +1254 -0
  260. charm/toolbox/Hash.py +39 -0
  261. charm/toolbox/IBEnc.py +62 -0
  262. charm/toolbox/IBSig.py +64 -0
  263. charm/toolbox/PKEnc.py +66 -0
  264. charm/toolbox/PKSig.py +56 -0
  265. charm/toolbox/PREnc.py +32 -0
  266. charm/toolbox/ZKProof.py +289 -0
  267. charm/toolbox/__init__.py +0 -0
  268. charm/toolbox/bitstring.py +49 -0
  269. charm/toolbox/broadcast.py +220 -0
  270. charm/toolbox/conversion.py +100 -0
  271. charm/toolbox/eccurve.py +149 -0
  272. charm/toolbox/ecgroup.py +143 -0
  273. charm/toolbox/enum.py +60 -0
  274. charm/toolbox/hash_module.py +91 -0
  275. charm/toolbox/integergroup.py +323 -0
  276. charm/toolbox/iterate.py +22 -0
  277. charm/toolbox/matrixops.py +76 -0
  278. charm/toolbox/mpc_utils.py +296 -0
  279. charm/toolbox/msp.py +175 -0
  280. charm/toolbox/mta.py +985 -0
  281. charm/toolbox/node.py +120 -0
  282. charm/toolbox/ot/__init__.py +22 -0
  283. charm/toolbox/ot/base_ot.py +374 -0
  284. charm/toolbox/ot/dpf.py +642 -0
  285. charm/toolbox/ot/mpfss.py +228 -0
  286. charm/toolbox/ot/ot_extension.py +589 -0
  287. charm/toolbox/ot/silent_ot.py +378 -0
  288. charm/toolbox/paddingschemes.py +423 -0
  289. charm/toolbox/paddingschemes_test.py +238 -0
  290. charm/toolbox/pairingcurves.py +85 -0
  291. charm/toolbox/pairinggroup.py +186 -0
  292. charm/toolbox/policy_expression_spec.py +70 -0
  293. charm/toolbox/policytree.py +189 -0
  294. charm/toolbox/reCompiler.py +346 -0
  295. charm/toolbox/redundancyschemes.py +65 -0
  296. charm/toolbox/schemebase.py +188 -0
  297. charm/toolbox/secretshare.py +104 -0
  298. charm/toolbox/secretutil.py +174 -0
  299. charm/toolbox/securerandom.py +73 -0
  300. charm/toolbox/sigmaprotocol.py +46 -0
  301. charm/toolbox/specialprimes.py +45 -0
  302. charm/toolbox/symcrypto.py +279 -0
  303. charm/toolbox/threshold_sharing.py +553 -0
  304. charm/toolbox/xmlserialize.py +94 -0
  305. charm/toolbox/zknode.py +105 -0
  306. charm/zkp_compiler/__init__.py +89 -0
  307. charm/zkp_compiler/and_proof.py +460 -0
  308. charm/zkp_compiler/batch_verify.py +324 -0
  309. charm/zkp_compiler/dleq_proof.py +423 -0
  310. charm/zkp_compiler/or_proof.py +305 -0
  311. charm/zkp_compiler/range_proof.py +417 -0
  312. charm/zkp_compiler/representation_proof.py +466 -0
  313. charm/zkp_compiler/schnorr_proof.py +273 -0
  314. charm/zkp_compiler/thread_safe.py +150 -0
  315. charm/zkp_compiler/zk_demo.py +489 -0
  316. charm/zkp_compiler/zkp_factory.py +330 -0
  317. charm/zkp_compiler/zkp_generator.py +370 -0
  318. charm/zkp_compiler/zkparser.py +269 -0
  319. charm_crypto_framework-0.61.1.dist-info/METADATA +337 -0
  320. charm_crypto_framework-0.61.1.dist-info/RECORD +323 -0
  321. charm_crypto_framework-0.61.1.dist-info/WHEEL +5 -0
  322. charm_crypto_framework-0.61.1.dist-info/licenses/LICENSE.txt +165 -0
  323. charm_crypto_framework-0.61.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,969 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ Comprehensive stress test for the ABE policy parser.
4
+
5
+ This script tests the PolicyParser and MSP classes for expressiveness,
6
+ correctness, and robustness. It can be run independently to verify
7
+ the policy parser functionality.
8
+
9
+ Usage:
10
+ python -m charm.test.toolbox.policy_parser_stress_test
11
+
12
+ # Or with pytest:
13
+ pytest charm/test/toolbox/policy_parser_stress_test.py -v
14
+ """
15
+
16
+ import sys
17
+ import time
18
+ import random
19
+ import string
20
+ import unittest
21
+ from typing import List, Tuple, Optional
22
+
23
+ from charm.toolbox.policytree import PolicyParser
24
+ from charm.toolbox.node import OpType, BinNode
25
+ from charm.toolbox.msp import MSP
26
+ from charm.toolbox.pairinggroup import PairingGroup
27
+
28
+
29
+ class PolicyParserStressTest(unittest.TestCase):
30
+ """Comprehensive stress tests for the ABE policy parser."""
31
+
32
+ @classmethod
33
+ def setUpClass(cls):
34
+ cls.parser = PolicyParser()
35
+ cls.group = PairingGroup('SS512')
36
+ cls.msp = MSP(cls.group)
37
+
38
+ # =========================================================================
39
+ # Basic Parsing Tests
40
+ # =========================================================================
41
+
42
+ def test_single_attribute(self):
43
+ """Test parsing single attributes."""
44
+ # Note: underscore followed by digits is treated as duplicate index
45
+ # e.g., ATTR_123 becomes ATTR with index 123
46
+ test_cases = [
47
+ ('A', 'A'),
48
+ ('attribute', 'ATTRIBUTE'),
49
+ ('role-admin', 'ROLE-ADMIN'),
50
+ ('user.name', 'USER.NAME'),
51
+ ]
52
+ for attr, expected in test_cases:
53
+ tree = self.parser.parse(attr)
54
+ self.assertEqual(tree.getNodeType(), OpType.ATTR)
55
+ self.assertEqual(tree.getAttribute(), expected)
56
+
57
+ def test_attribute_with_index(self):
58
+ """Test attributes with numeric index suffix (used for duplicates)."""
59
+ # ATTR_123 is parsed as attribute ATTR with index 123
60
+ tree = self.parser.parse('ATTR_123')
61
+ self.assertEqual(tree.getNodeType(), OpType.ATTR)
62
+ self.assertEqual(tree.getAttribute(), 'ATTR')
63
+ self.assertEqual(tree.index, 123)
64
+
65
+ def test_basic_and(self):
66
+ """Test basic AND operations."""
67
+ for op in ['and', 'AND']:
68
+ tree = self.parser.parse(f'A {op} B')
69
+ self.assertEqual(tree.getNodeType(), OpType.AND)
70
+
71
+ def test_basic_or(self):
72
+ """Test basic OR operations."""
73
+ for op in ['or', 'OR']:
74
+ tree = self.parser.parse(f'A {op} B')
75
+ self.assertEqual(tree.getNodeType(), OpType.OR)
76
+
77
+ def test_nested_expressions(self):
78
+ """Test nested policy expressions."""
79
+ test_cases = [
80
+ ('(A and B) or C', OpType.OR),
81
+ ('A and (B or C)', OpType.AND),
82
+ ('((A and B) or C) and D', OpType.AND),
83
+ ('(A or B) and (C or D)', OpType.AND),
84
+ ]
85
+ for policy, expected_root_type in test_cases:
86
+ tree = self.parser.parse(policy)
87
+ self.assertEqual(tree.getNodeType(), expected_root_type,
88
+ f"Failed for policy: {policy}")
89
+
90
+ def test_negated_attributes(self):
91
+ """Test negated attribute parsing."""
92
+ tree = self.parser.parse('!A and B')
93
+ left = tree.getLeft()
94
+ self.assertTrue(left.negated)
95
+ self.assertEqual(left.getAttribute(), '!A')
96
+
97
+ # =========================================================================
98
+ # Stress Tests
99
+ # =========================================================================
100
+
101
+ def test_deep_nesting(self):
102
+ """Test deeply nested expressions (20 levels)."""
103
+ policy = 'A'
104
+ for i in range(20):
105
+ policy = f'({policy} and B{i})'
106
+ tree = self.parser.parse(policy)
107
+ self.assertIsNotNone(tree)
108
+
109
+ def test_many_attributes(self):
110
+ """Test policy with 100 attributes."""
111
+ attrs = ' and '.join([f'ATTR{i}' for i in range(100)])
112
+ tree = self.parser.parse(attrs)
113
+ self.assertIsNotNone(tree)
114
+
115
+ # Verify all attributes are present
116
+ attr_list = self.msp.getAttributeList(tree)
117
+ self.assertEqual(len(attr_list), 100)
118
+
119
+ def test_wide_or_tree(self):
120
+ """Test wide OR tree with 50 branches."""
121
+ attrs = ' or '.join([f'ATTR{i}' for i in range(50)])
122
+ tree = self.parser.parse(attrs)
123
+ self.assertIsNotNone(tree)
124
+
125
+ def test_balanced_tree(self):
126
+ """Test balanced binary tree structure."""
127
+ # Create: ((A and B) or (C and D)) and ((E and F) or (G and H))
128
+ policy = '((A and B) or (C and D)) and ((E and F) or (G and H))'
129
+ tree = self.parser.parse(policy)
130
+ self.assertEqual(tree.getNodeType(), OpType.AND)
131
+
132
+ def test_random_policies(self):
133
+ """Generate and parse 100 random valid policies."""
134
+ for _ in range(100):
135
+ policy = self._generate_random_policy(depth=5, max_attrs=10)
136
+ try:
137
+ tree = self.parser.parse(policy)
138
+ self.assertIsNotNone(tree)
139
+ except Exception as e:
140
+ self.fail(f"Failed to parse random policy: {policy}\nError: {e}")
141
+
142
+ # =========================================================================
143
+ # Policy Satisfaction Tests
144
+ # =========================================================================
145
+
146
+ def test_prune_and_policy(self):
147
+ """Test policy satisfaction for AND policies."""
148
+ tree = self.parser.parse('A and B and C')
149
+
150
+ # All attributes present - should satisfy
151
+ result = self.parser.prune(tree, ['A', 'B', 'C'])
152
+ self.assertIsNotNone(result)
153
+ self.assertNotEqual(result, False)
154
+
155
+ # Missing one attribute - should not satisfy
156
+ result = self.parser.prune(tree, ['A', 'B'])
157
+ self.assertFalse(result)
158
+
159
+ def test_prune_or_policy(self):
160
+ """Test policy satisfaction for OR policies."""
161
+ tree = self.parser.parse('A or B or C')
162
+
163
+ # Any single attribute should satisfy
164
+ for attr in ['A', 'B', 'C']:
165
+ result = self.parser.prune(tree, [attr])
166
+ self.assertIsNotNone(result)
167
+ self.assertNotEqual(result, False)
168
+
169
+ # No attributes - should not satisfy
170
+ result = self.parser.prune(tree, [])
171
+ self.assertFalse(result)
172
+
173
+ def test_prune_complex_policy(self):
174
+ """Test policy satisfaction for complex policies."""
175
+ tree = self.parser.parse('(A and B) or (C and D)')
176
+
177
+ # Left branch satisfied
178
+ result = self.parser.prune(tree, ['A', 'B'])
179
+ self.assertIsNotNone(result)
180
+
181
+ # Right branch satisfied
182
+ result = self.parser.prune(tree, ['C', 'D'])
183
+ self.assertIsNotNone(result)
184
+
185
+ # Neither branch satisfied
186
+ result = self.parser.prune(tree, ['A', 'C'])
187
+ self.assertFalse(result)
188
+
189
+ # =========================================================================
190
+ # MSP Conversion Tests
191
+ # =========================================================================
192
+
193
+ def test_msp_simple_and(self):
194
+ """Test MSP conversion for AND policy."""
195
+ tree = self.msp.createPolicy('A and B')
196
+ matrix = self.msp.convert_policy_to_msp(tree)
197
+
198
+ self.assertIn('A', matrix)
199
+ self.assertIn('B', matrix)
200
+ # AND gate: first child gets [1, 1], second gets [0, -1]
201
+ self.assertEqual(matrix['A'], [1, 1])
202
+ self.assertEqual(matrix['B'], [0, -1])
203
+
204
+ def test_msp_simple_or(self):
205
+ """Test MSP conversion for OR policy."""
206
+ tree = self.msp.createPolicy('A or B')
207
+ matrix = self.msp.convert_policy_to_msp(tree)
208
+
209
+ self.assertIn('A', matrix)
210
+ self.assertIn('B', matrix)
211
+ # OR gate: both children get same vector
212
+ self.assertEqual(matrix['A'], [1])
213
+ self.assertEqual(matrix['B'], [1])
214
+
215
+ def test_msp_complex_policy(self):
216
+ """Test MSP conversion for complex policy."""
217
+ policy = '((A and B) or C) and D'
218
+ tree = self.msp.createPolicy(policy)
219
+ matrix = self.msp.convert_policy_to_msp(tree)
220
+
221
+ # All attributes should be in the matrix
222
+ for attr in ['A', 'B', 'C', 'D']:
223
+ self.assertIn(attr, matrix)
224
+
225
+ def test_msp_coefficient_recovery(self):
226
+ """Test coefficient recovery from MSP."""
227
+ tree = self.msp.createPolicy('A and B')
228
+ coeffs = self.msp.getCoefficients(tree)
229
+
230
+ self.assertIn('A', coeffs)
231
+ self.assertIn('B', coeffs)
232
+
233
+ # =========================================================================
234
+ # Duplicate Attribute Tests
235
+ # =========================================================================
236
+
237
+ def test_duplicate_attributes(self):
238
+ """Test handling of duplicate attributes."""
239
+ tree = self.parser.parse('A and B and A')
240
+
241
+ _dictCount = {}
242
+ self.parser.findDuplicates(tree, _dictCount)
243
+
244
+ self.assertEqual(_dictCount['A'], 2)
245
+ self.assertEqual(_dictCount['B'], 1)
246
+
247
+ def test_duplicate_labeling(self):
248
+ """Test that duplicate attributes get unique labels."""
249
+ tree = self.msp.createPolicy('A and B and A')
250
+ attr_list = self.msp.getAttributeList(tree)
251
+
252
+ # Should have 3 attributes with unique labels
253
+ self.assertEqual(len(attr_list), 3)
254
+ # Check that duplicates are labeled (A_0, A_1)
255
+ a_attrs = [a for a in attr_list if a.startswith('A')]
256
+ self.assertEqual(len(a_attrs), 2)
257
+
258
+ # =========================================================================
259
+ # Special Character Tests
260
+ # =========================================================================
261
+
262
+ def test_special_characters_in_attributes(self):
263
+ """Test attributes with special characters."""
264
+ # Note: underscore followed by non-digits works, but underscore + digits
265
+ # is treated as duplicate index notation (e.g., ATTR_0, ATTR_1)
266
+ special_attrs = [
267
+ 'attr-name', # hyphen
268
+ 'attr.name', # dot
269
+ 'attr@domain', # at sign
270
+ 'attr#123', # hash
271
+ 'attr$var', # dollar
272
+ 'role/admin', # slash
273
+ ]
274
+ for attr in special_attrs:
275
+ try:
276
+ tree = self.parser.parse(attr)
277
+ self.assertEqual(tree.getNodeType(), OpType.ATTR)
278
+ except Exception as e:
279
+ self.fail(f"Failed to parse attribute: {attr}\nError: {e}")
280
+
281
+ def test_underscore_limitation(self):
282
+ """Test that underscore + non-digits fails (known limitation)."""
283
+ # This is a known limitation: attr_name fails because the parser
284
+ # expects digits after underscore for duplicate indexing
285
+ with self.assertRaises(Exception):
286
+ self.parser.parse('attr_name')
287
+
288
+ # =========================================================================
289
+ # Performance Tests
290
+ # =========================================================================
291
+
292
+ def test_parsing_performance(self):
293
+ """Test parsing performance with 1000 iterations."""
294
+ policy = '(A and B) or (C and D) or (E and F)'
295
+
296
+ start = time.time()
297
+ for _ in range(1000):
298
+ self.parser.parse(policy)
299
+ elapsed = time.time() - start
300
+
301
+ # Should complete in under 5 seconds
302
+ self.assertLess(elapsed, 5.0,
303
+ f"Parsing 1000 policies took {elapsed:.2f}s (expected < 5s)")
304
+
305
+ def test_msp_conversion_performance(self):
306
+ """Test MSP conversion performance."""
307
+ policy = ' and '.join([f'ATTR{i}' for i in range(20)])
308
+ tree = self.msp.createPolicy(policy)
309
+
310
+ start = time.time()
311
+ for _ in range(100):
312
+ self.msp.convert_policy_to_msp(tree)
313
+ elapsed = time.time() - start
314
+
315
+ # Should complete in under 2 seconds
316
+ self.assertLess(elapsed, 2.0,
317
+ f"MSP conversion took {elapsed:.2f}s (expected < 2s)")
318
+
319
+ # =========================================================================
320
+ # Helper Methods
321
+ # =========================================================================
322
+
323
+ def _generate_random_policy(self, depth: int, max_attrs: int) -> str:
324
+ """Generate a random valid policy expression."""
325
+ if depth <= 0 or random.random() < 0.3:
326
+ # Generate leaf node (attribute)
327
+ return f'ATTR{random.randint(0, max_attrs)}'
328
+
329
+ # Generate internal node
330
+ left = self._generate_random_policy(depth - 1, max_attrs)
331
+ right = self._generate_random_policy(depth - 1, max_attrs)
332
+ op = random.choice(['and', 'or'])
333
+ return f'({left} {op} {right})'
334
+
335
+
336
+ class PolicyParserEdgeCaseTest(unittest.TestCase):
337
+ """Edge case tests for the policy parser."""
338
+
339
+ @classmethod
340
+ def setUpClass(cls):
341
+ cls.parser = PolicyParser()
342
+
343
+ def test_case_insensitive_operators(self):
344
+ """Test that AND/OR operators are case-insensitive."""
345
+ # These should all parse correctly
346
+ for policy in ['A AND B', 'A and B', 'A OR B', 'A or B']:
347
+ tree = self.parser.parse(policy)
348
+ self.assertIsNotNone(tree)
349
+
350
+ def test_whitespace_handling(self):
351
+ """Test handling of extra whitespace."""
352
+ policies = [
353
+ 'A and B', # extra spaces
354
+ ' A and B ', # leading/trailing spaces
355
+ 'A and B', # mixed spacing
356
+ ]
357
+ for policy in policies:
358
+ tree = self.parser.parse(policy)
359
+ self.assertIsNotNone(tree)
360
+
361
+ def test_parentheses_variations(self):
362
+ """Test various parentheses patterns."""
363
+ policies = [
364
+ '(A)',
365
+ '((A))',
366
+ '(A and B)',
367
+ '((A and B))',
368
+ '(A) and (B)',
369
+ ]
370
+ for policy in policies:
371
+ tree = self.parser.parse(policy)
372
+ self.assertIsNotNone(tree)
373
+
374
+
375
+ class NumericAttributeTest(unittest.TestCase):
376
+ """Tests for numeric attribute support using bag of bits encoding."""
377
+
378
+ @classmethod
379
+ def setUpClass(cls):
380
+ cls.parser = PolicyParser()
381
+ # Import here to avoid circular imports
382
+ from charm.toolbox.ABEnumeric import NumericAttributeHelper
383
+ cls.helper = NumericAttributeHelper(num_bits=8)
384
+
385
+ def test_greater_than(self):
386
+ """Test attr > value comparison."""
387
+ expanded = self.helper.expand_policy('age > 10')
388
+ tree = self.parser.parse(expanded)
389
+
390
+ # age=15 should satisfy age > 10
391
+ user_attrs = self.helper.user_attributes({'age': 15})
392
+ result = self.parser.prune(tree, user_attrs)
393
+ self.assertTrue(result)
394
+
395
+ # age=10 should NOT satisfy age > 10
396
+ user_attrs = self.helper.user_attributes({'age': 10})
397
+ result = self.parser.prune(tree, user_attrs)
398
+ self.assertFalse(result)
399
+
400
+ # age=5 should NOT satisfy age > 10
401
+ user_attrs = self.helper.user_attributes({'age': 5})
402
+ result = self.parser.prune(tree, user_attrs)
403
+ self.assertFalse(result)
404
+
405
+ def test_greater_than_or_equal(self):
406
+ """Test attr >= value comparison."""
407
+ expanded = self.helper.expand_policy('age >= 18')
408
+ tree = self.parser.parse(expanded)
409
+
410
+ # age=25 should satisfy
411
+ user_attrs = self.helper.user_attributes({'age': 25})
412
+ self.assertTrue(self.parser.prune(tree, user_attrs))
413
+
414
+ # age=18 should satisfy (boundary)
415
+ user_attrs = self.helper.user_attributes({'age': 18})
416
+ self.assertTrue(self.parser.prune(tree, user_attrs))
417
+
418
+ # age=17 should NOT satisfy
419
+ user_attrs = self.helper.user_attributes({'age': 17})
420
+ self.assertFalse(self.parser.prune(tree, user_attrs))
421
+
422
+ def test_less_than(self):
423
+ """Test attr < value comparison."""
424
+ expanded = self.helper.expand_policy('age < 18')
425
+ tree = self.parser.parse(expanded)
426
+
427
+ # age=17 should satisfy
428
+ user_attrs = self.helper.user_attributes({'age': 17})
429
+ self.assertTrue(self.parser.prune(tree, user_attrs))
430
+
431
+ # age=18 should NOT satisfy
432
+ user_attrs = self.helper.user_attributes({'age': 18})
433
+ self.assertFalse(self.parser.prune(tree, user_attrs))
434
+
435
+ # age=25 should NOT satisfy
436
+ user_attrs = self.helper.user_attributes({'age': 25})
437
+ self.assertFalse(self.parser.prune(tree, user_attrs))
438
+
439
+ def test_less_than_or_equal(self):
440
+ """Test attr <= value comparison."""
441
+ expanded = self.helper.expand_policy('level <= 5')
442
+ tree = self.parser.parse(expanded)
443
+
444
+ # level=3 should satisfy
445
+ user_attrs = self.helper.user_attributes({'level': 3})
446
+ self.assertTrue(self.parser.prune(tree, user_attrs))
447
+
448
+ # level=5 should satisfy (boundary)
449
+ user_attrs = self.helper.user_attributes({'level': 5})
450
+ self.assertTrue(self.parser.prune(tree, user_attrs))
451
+
452
+ # level=6 should NOT satisfy
453
+ user_attrs = self.helper.user_attributes({'level': 6})
454
+ self.assertFalse(self.parser.prune(tree, user_attrs))
455
+
456
+ def test_equality(self):
457
+ """Test attr == value comparison."""
458
+ expanded = self.helper.expand_policy('level == 5')
459
+ tree = self.parser.parse(expanded)
460
+
461
+ # level=5 should satisfy
462
+ user_attrs = self.helper.user_attributes({'level': 5})
463
+ self.assertTrue(self.parser.prune(tree, user_attrs))
464
+
465
+ # level=4 should NOT satisfy
466
+ user_attrs = self.helper.user_attributes({'level': 4})
467
+ self.assertFalse(self.parser.prune(tree, user_attrs))
468
+
469
+ # level=6 should NOT satisfy
470
+ user_attrs = self.helper.user_attributes({'level': 6})
471
+ self.assertFalse(self.parser.prune(tree, user_attrs))
472
+
473
+ def test_compound_numeric_policy(self):
474
+ """Test combined numeric comparisons."""
475
+ expanded = self.helper.expand_policy('age >= 18 and level > 5')
476
+ tree = self.parser.parse(expanded)
477
+
478
+ # age=25, level=10 should satisfy both
479
+ user_attrs = self.helper.user_attributes({'age': 25, 'level': 10})
480
+ self.assertTrue(self.parser.prune(tree, user_attrs))
481
+
482
+ # age=25, level=3 should fail (level > 5 fails)
483
+ user_attrs = self.helper.user_attributes({'age': 25, 'level': 3})
484
+ self.assertFalse(self.parser.prune(tree, user_attrs))
485
+
486
+ # age=15, level=10 should fail (age >= 18 fails)
487
+ user_attrs = self.helper.user_attributes({'age': 15, 'level': 10})
488
+ self.assertFalse(self.parser.prune(tree, user_attrs))
489
+
490
+ def test_mixed_numeric_and_string_policy(self):
491
+ """Test policies mixing numeric comparisons and string attributes."""
492
+ expanded = self.helper.expand_policy('(age >= 21 or admin) and level > 0')
493
+ tree = self.parser.parse(expanded)
494
+
495
+ # age=25, level=1 should satisfy (age >= 21 satisfies first clause)
496
+ user_attrs = self.helper.user_attributes({'age': 25, 'level': 1})
497
+ self.assertTrue(self.parser.prune(tree, user_attrs))
498
+
499
+ # role=admin, level=1 should satisfy (admin satisfies first clause)
500
+ user_attrs = self.helper.user_attributes({'level': 1, 'role': 'ADMIN'})
501
+ result = self.parser.prune(tree, user_attrs)
502
+ self.assertTrue(result)
503
+
504
+ def test_bit_encoding_correctness(self):
505
+ """Test that bit encoding is correct for various values."""
506
+ from charm.toolbox.ABEnumeric import int_to_bits
507
+
508
+ # Test specific values
509
+ self.assertEqual(int_to_bits(0, 8), [0, 0, 0, 0, 0, 0, 0, 0])
510
+ self.assertEqual(int_to_bits(1, 8), [1, 0, 0, 0, 0, 0, 0, 0])
511
+ self.assertEqual(int_to_bits(5, 8), [1, 0, 1, 0, 0, 0, 0, 0]) # 101
512
+ self.assertEqual(int_to_bits(255, 8), [1, 1, 1, 1, 1, 1, 1, 1])
513
+
514
+ def test_boundary_values(self):
515
+ """Test boundary conditions for numeric comparisons."""
516
+ # Test at boundary of 8-bit range
517
+ expanded = self.helper.expand_policy('val >= 255')
518
+ tree = self.parser.parse(expanded)
519
+
520
+ # val=255 should satisfy (exactly equal)
521
+ user_attrs = self.helper.user_attributes({'val': 255})
522
+ self.assertTrue(self.parser.prune(tree, user_attrs))
523
+
524
+ # val=254 should NOT satisfy
525
+ user_attrs = self.helper.user_attributes({'val': 254})
526
+ self.assertFalse(self.parser.prune(tree, user_attrs))
527
+
528
+ def test_zero_comparisons(self):
529
+ """Test comparisons with zero."""
530
+ # age > 0
531
+ expanded = self.helper.expand_policy('age > 0')
532
+ tree = self.parser.parse(expanded)
533
+
534
+ user_attrs = self.helper.user_attributes({'age': 1})
535
+ self.assertTrue(self.parser.prune(tree, user_attrs))
536
+
537
+ user_attrs = self.helper.user_attributes({'age': 0})
538
+ self.assertFalse(self.parser.prune(tree, user_attrs))
539
+
540
+
541
+ class NumericAttributeEdgeCaseTest(unittest.TestCase):
542
+ """Tests for edge cases in numeric attribute handling."""
543
+
544
+ def setUp(self):
545
+ from charm.toolbox.ABEnumeric import (
546
+ NumericAttributeHelper, preprocess_numeric_policy,
547
+ expand_numeric_comparison, int_to_bits, validate_num_bits,
548
+ validate_attribute_name, BitOverflowError, InvalidBitWidthError,
549
+ InvalidOperatorError, AttributeNameConflictError
550
+ )
551
+ self.helper = NumericAttributeHelper(num_bits=8)
552
+ self.strict_helper = NumericAttributeHelper(num_bits=8, strict=True)
553
+ self.preprocess = preprocess_numeric_policy
554
+ self.expand = expand_numeric_comparison
555
+ self.int_to_bits = int_to_bits
556
+ self.validate_num_bits = validate_num_bits
557
+ self.validate_attribute_name = validate_attribute_name
558
+ self.BitOverflowError = BitOverflowError
559
+ self.InvalidBitWidthError = InvalidBitWidthError
560
+ self.InvalidOperatorError = InvalidOperatorError
561
+ self.AttributeNameConflictError = AttributeNameConflictError
562
+
563
+ # --- Bit Overflow Tests ---
564
+ def test_bit_overflow_in_expand(self):
565
+ """Test that values exceeding bit width raise BitOverflowError."""
566
+ with self.assertRaises(self.BitOverflowError):
567
+ self.expand('age', '==', 256, num_bits=8) # Max for 8-bit is 255
568
+
569
+ def test_bit_overflow_in_user_attributes(self):
570
+ """Test that user_attributes raises error for overflow values."""
571
+ with self.assertRaises(self.BitOverflowError):
572
+ self.helper.user_attributes({'age': 256})
573
+
574
+ def test_bit_overflow_error_in_int_to_bits(self):
575
+ """Test that int_to_bits raises BitOverflowError on overflow."""
576
+ with self.assertRaises(self.BitOverflowError):
577
+ self.int_to_bits(256, 8)
578
+
579
+ def test_boundary_value_at_max(self):
580
+ """Test value exactly at maximum (255 for 8-bit)."""
581
+ # Should work without error
582
+ expanded = self.expand('age', '==', 255, num_bits=8)
583
+ self.assertIsNotNone(expanded)
584
+
585
+ def test_boundary_value_just_over_max(self):
586
+ """Test value just over maximum."""
587
+ with self.assertRaises(self.BitOverflowError):
588
+ self.expand('age', '==', 256, num_bits=8)
589
+
590
+ # --- Negative Value Tests ---
591
+ def test_negative_value_in_expand(self):
592
+ """Test that negative values raise ValueError."""
593
+ with self.assertRaises(ValueError):
594
+ self.expand('age', '>=', -1, num_bits=8)
595
+
596
+ def test_negative_value_in_user_attributes(self):
597
+ """Test that user_attributes raises error for negative values."""
598
+ with self.assertRaises(ValueError):
599
+ self.helper.user_attributes({'age': -5})
600
+
601
+ def test_negative_value_message(self):
602
+ """Test that error message mentions negative values."""
603
+ try:
604
+ self.expand('age', '>=', -10, num_bits=8)
605
+ except ValueError as e:
606
+ self.assertIn('Negative', str(e))
607
+
608
+ # --- Invalid Operator Tests ---
609
+ def test_invalid_operator_exclamation_equal(self):
610
+ """Test that != operator is rejected."""
611
+ with self.assertRaises(self.InvalidOperatorError):
612
+ self.expand('age', '!=', 21, num_bits=8)
613
+
614
+ def test_invalid_operator_not_equal_diamond(self):
615
+ """Test that <> operator is rejected."""
616
+ with self.assertRaises(self.InvalidOperatorError):
617
+ self.expand('age', '<>', 21, num_bits=8)
618
+
619
+ def test_invalid_operator_tilde(self):
620
+ """Test that arbitrary operators are rejected."""
621
+ with self.assertRaises(self.InvalidOperatorError):
622
+ self.expand('age', '~', 21, num_bits=8)
623
+
624
+ def test_valid_operators_all_work(self):
625
+ """Test that all supported operators work."""
626
+ for op in ['==', '>', '>=', '<', '<=']:
627
+ result = self.expand('age', op, 10, num_bits=8)
628
+ self.assertIsNotNone(result)
629
+
630
+ # --- Invalid Bit Width Tests ---
631
+ def test_zero_bit_width(self):
632
+ """Test that num_bits=0 raises InvalidBitWidthError."""
633
+ with self.assertRaises(self.InvalidBitWidthError):
634
+ self.validate_num_bits(0)
635
+
636
+ def test_negative_bit_width(self):
637
+ """Test that negative num_bits raises InvalidBitWidthError."""
638
+ with self.assertRaises(self.InvalidBitWidthError):
639
+ self.validate_num_bits(-1)
640
+
641
+ def test_excessive_bit_width(self):
642
+ """Test that num_bits > 64 raises InvalidBitWidthError."""
643
+ with self.assertRaises(self.InvalidBitWidthError):
644
+ self.validate_num_bits(65)
645
+
646
+ def test_non_integer_bit_width(self):
647
+ """Test that non-integer num_bits raises InvalidBitWidthError."""
648
+ with self.assertRaises(self.InvalidBitWidthError):
649
+ self.validate_num_bits(8.5)
650
+
651
+ def test_string_bit_width(self):
652
+ """Test that string num_bits raises InvalidBitWidthError."""
653
+ with self.assertRaises(self.InvalidBitWidthError):
654
+ self.validate_num_bits("8")
655
+
656
+ # --- Attribute Name Conflict Tests ---
657
+ def test_attribute_name_with_encoding_pattern(self):
658
+ """Test that attribute names with #b# pattern are rejected."""
659
+ with self.assertRaises(self.AttributeNameConflictError):
660
+ self.validate_attribute_name('age#b0#1')
661
+
662
+ def test_attribute_name_with_partial_pattern(self):
663
+ """Test attribute names with partial encoding pattern."""
664
+ with self.assertRaises(self.AttributeNameConflictError):
665
+ self.validate_attribute_name('test#b5#value')
666
+
667
+ def test_valid_attribute_names(self):
668
+ """Test that normal attribute names are accepted."""
669
+ # These should not raise
670
+ self.validate_attribute_name('age')
671
+ self.validate_attribute_name('AGE')
672
+ self.validate_attribute_name('user_level')
673
+ self.validate_attribute_name('clearance123')
674
+
675
+ # --- Empty/Malformed Policy Tests ---
676
+ def test_none_policy(self):
677
+ """Test that None policy raises ValueError."""
678
+ with self.assertRaises(ValueError):
679
+ self.preprocess(None, num_bits=8)
680
+
681
+ def test_empty_policy(self):
682
+ """Test that empty policy returns empty string."""
683
+ result = self.preprocess('', num_bits=8)
684
+ self.assertEqual(result, '')
685
+
686
+ def test_whitespace_only_policy(self):
687
+ """Test that whitespace-only policy returns empty string."""
688
+ result = self.preprocess(' ', num_bits=8)
689
+ self.assertEqual(result, '')
690
+
691
+ def test_policy_without_numeric(self):
692
+ """Test that policy without numeric comparisons is unchanged."""
693
+ policy = 'admin and manager'
694
+ result = self.preprocess(policy, num_bits=8)
695
+ self.assertEqual(result, policy)
696
+
697
+ # --- Regex Edge Cases ---
698
+ def test_extra_spaces_around_operator(self):
699
+ """Test numeric comparison with extra spaces."""
700
+ policy = 'age >= 21'
701
+ result = self.preprocess(policy, num_bits=8)
702
+ self.assertIn('#b', result)
703
+ self.assertNotIn('>=', result)
704
+
705
+ def test_no_spaces_around_operator(self):
706
+ """Test numeric comparison without spaces."""
707
+ policy = 'age>=21'
708
+ result = self.preprocess(policy, num_bits=8)
709
+ self.assertIn('#b', result)
710
+
711
+ def test_mixed_spacing(self):
712
+ """Test numeric comparison with mixed spacing."""
713
+ policy = 'age>= 21 and level <5'
714
+ result = self.preprocess(policy, num_bits=8)
715
+ self.assertIn('#b', result)
716
+ self.assertNotIn('>=', result)
717
+ self.assertNotIn('<', result)
718
+
719
+ def test_multiple_parentheses(self):
720
+ """Test policy with multiple levels of parentheses."""
721
+ policy = '((age >= 21) and (level > 5)) or admin'
722
+ result = self.preprocess(policy, num_bits=8)
723
+ self.assertIn('admin', result)
724
+ self.assertIn('#b', result)
725
+
726
+ def test_attr_name_all_caps(self):
727
+ """Test attribute name in all caps."""
728
+ policy = 'AGE >= 21'
729
+ result = self.preprocess(policy, num_bits=8)
730
+ self.assertIn('AGE#b', result)
731
+
732
+ def test_attr_name_mixed_case(self):
733
+ """Test attribute name in mixed case."""
734
+ policy = 'Age >= 21'
735
+ result = self.preprocess(policy, num_bits=8)
736
+ self.assertIn('Age#b', result)
737
+
738
+ # --- Strict Mode Tests ---
739
+ def test_strict_mode_raises_on_overflow(self):
740
+ """Test that strict mode raises exceptions."""
741
+ from charm.toolbox.ABEnumeric import preprocess_numeric_policy
742
+ with self.assertRaises(self.BitOverflowError):
743
+ preprocess_numeric_policy('age >= 256', num_bits=8, strict=True)
744
+
745
+ def test_non_strict_mode_continues_on_error(self):
746
+ """Test that non-strict mode leaves problematic expression unchanged."""
747
+ import warnings
748
+ with warnings.catch_warnings(record=True) as w:
749
+ warnings.simplefilter("always")
750
+ result = self.preprocess('age >= 256', num_bits=8, strict=False)
751
+ # Should keep original expression and warn
752
+ self.assertIn('age >= 256', result)
753
+ self.assertGreater(len(w), 0)
754
+
755
+ def test_strict_helper_raises_on_overflow(self):
756
+ """Test that helper in strict mode raises exceptions."""
757
+ with self.assertRaises((self.BitOverflowError, ValueError)):
758
+ self.strict_helper.expand_policy('age >= 256')
759
+
760
+ # --- Zero Comparison Tests ---
761
+ def test_greater_than_zero(self):
762
+ """Test attr > 0 works correctly."""
763
+ result = self.expand('age', '>', 0, num_bits=8)
764
+ self.assertIsNotNone(result)
765
+
766
+ def test_greater_equal_zero_is_tautology(self):
767
+ """Test attr >= 0 returns None (always true for non-negative)."""
768
+ result = self.expand('age', '>=', 0, num_bits=8)
769
+ # >= 0 is always true for non-negative, encode_greater_than_or_equal returns None
770
+ self.assertIsNone(result)
771
+
772
+ def test_less_than_zero_is_contradiction(self):
773
+ """Test attr < 0 handling."""
774
+ result = self.expand('age', '<', 0, num_bits=8)
775
+ # < 0 is always false for non-negative, returns None
776
+ self.assertIsNone(result)
777
+
778
+ def test_equal_zero(self):
779
+ """Test attr == 0 correctly encodes all zeros."""
780
+ result = self.expand('age', '==', 0, num_bits=8)
781
+ self.assertIsNotNone(result)
782
+ # Should have all #0 (zero bits)
783
+ self.assertIn('#0', result)
784
+
785
+ # --- Max Value Property Test ---
786
+ def test_helper_max_value_property(self):
787
+ """Test that NumericAttributeHelper exposes max_value correctly."""
788
+ from charm.toolbox.ABEnumeric import NumericAttributeHelper
789
+ self.assertEqual(self.helper.max_value, 255) # 2^8 - 1 = 255
790
+ helper16 = NumericAttributeHelper(num_bits=16)
791
+ self.assertEqual(helper16.max_value, 65535) # 2^16 - 1
792
+
793
+
794
+ class NumericNegationTest(unittest.TestCase):
795
+ """Tests for negation of numeric comparisons."""
796
+
797
+ def setUp(self):
798
+ from charm.toolbox.ABEnumeric import (
799
+ NumericAttributeHelper, negate_comparison, negate_comparison_to_policy,
800
+ InvalidOperatorError
801
+ )
802
+ from charm.toolbox.policytree import PolicyParser
803
+ self.helper = NumericAttributeHelper(num_bits=8)
804
+ self.negate = negate_comparison
805
+ self.negate_to_policy = negate_comparison_to_policy
806
+ self.parser = PolicyParser()
807
+ self.InvalidOperatorError = InvalidOperatorError
808
+
809
+ # --- Basic Negation Tests ---
810
+ def test_negate_greater_equal(self):
811
+ """Test NOT (age >= 21) becomes age < 21."""
812
+ result = self.negate('age', '>=', 21)
813
+ self.assertEqual(result, ('age', '<', 21))
814
+
815
+ def test_negate_greater_than(self):
816
+ """Test NOT (age > 21) becomes age <= 21."""
817
+ result = self.negate('age', '>', 21)
818
+ self.assertEqual(result, ('age', '<=', 21))
819
+
820
+ def test_negate_less_equal(self):
821
+ """Test NOT (age <= 21) becomes age > 21."""
822
+ result = self.negate('age', '<=', 21)
823
+ self.assertEqual(result, ('age', '>', 21))
824
+
825
+ def test_negate_less_than(self):
826
+ """Test NOT (age < 21) becomes age >= 21."""
827
+ result = self.negate('age', '<', 21)
828
+ self.assertEqual(result, ('age', '>=', 21))
829
+
830
+ def test_negate_equality(self):
831
+ """Test NOT (age == 21) becomes (age < 21) OR (age > 21)."""
832
+ result = self.negate('age', '==', 21)
833
+ self.assertEqual(result, (('age', '<', 21), ('age', '>', 21)))
834
+
835
+ # --- Negation to Policy String Tests ---
836
+ def test_negate_to_policy_simple(self):
837
+ """Test negate_comparison_to_policy for simple operators."""
838
+ self.assertEqual(self.negate_to_policy('age', '>=', 21), 'age < 21')
839
+ self.assertEqual(self.negate_to_policy('age', '>', 21), 'age <= 21')
840
+ self.assertEqual(self.negate_to_policy('age', '<=', 21), 'age > 21')
841
+ self.assertEqual(self.negate_to_policy('age', '<', 21), 'age >= 21')
842
+
843
+ def test_negate_to_policy_equality(self):
844
+ """Test negate_comparison_to_policy for equality."""
845
+ result = self.negate_to_policy('age', '==', 21)
846
+ self.assertEqual(result, '(age < 21) or (age > 21)')
847
+
848
+ # --- Invalid Operator Tests ---
849
+ def test_negate_invalid_operator(self):
850
+ """Test that negating invalid operators raises error."""
851
+ with self.assertRaises(self.InvalidOperatorError):
852
+ self.negate('age', '!=', 21)
853
+
854
+ # --- Helper Method Tests ---
855
+ def test_helper_negate_comparison(self):
856
+ """Test NumericAttributeHelper.negate_comparison method."""
857
+ result = self.helper.negate_comparison('age', '>=', 21)
858
+ self.assertEqual(result, ('age', '<', 21))
859
+
860
+ def test_helper_expand_negated_policy_simple(self):
861
+ """Test expand_negated_policy for simple operators."""
862
+ # NOT (age >= 21) should expand to bit encoding of age < 21
863
+ result = self.helper.expand_negated_policy('age', '>=', 21)
864
+ self.assertIsNotNone(result)
865
+ self.assertIn('#b', result)
866
+
867
+ def test_helper_expand_negated_policy_equality(self):
868
+ """Test expand_negated_policy for equality."""
869
+ # NOT (age == 21) should expand to (age < 21) OR (age > 21)
870
+ result = self.helper.expand_negated_policy('age', '==', 21)
871
+ self.assertIsNotNone(result)
872
+ self.assertIn(' or ', result)
873
+ self.assertIn('#b', result)
874
+
875
+ # --- End-to-End Negation Tests ---
876
+ def test_negated_policy_satisfaction(self):
877
+ """Test that negated policies work correctly end-to-end."""
878
+ # Original: age >= 21 (user with age 20 should NOT satisfy)
879
+ # Negated: age < 21 (user with age 20 SHOULD satisfy)
880
+
881
+ negated_policy = self.negate_to_policy('age', '>=', 21)
882
+ expanded = self.helper.expand_policy(negated_policy)
883
+ tree = self.parser.parse(expanded)
884
+
885
+ # User with age 20 should satisfy "age < 21"
886
+ user_attrs = self.helper.user_attributes({'age': 20})
887
+ self.assertTrue(self.parser.prune(tree, user_attrs))
888
+
889
+ # User with age 21 should NOT satisfy "age < 21"
890
+ user_attrs = self.helper.user_attributes({'age': 21})
891
+ self.assertFalse(self.parser.prune(tree, user_attrs))
892
+
893
+ # User with age 25 should NOT satisfy "age < 21"
894
+ user_attrs = self.helper.user_attributes({'age': 25})
895
+ self.assertFalse(self.parser.prune(tree, user_attrs))
896
+
897
+ def test_negated_equality_satisfaction(self):
898
+ """Test that negated equality works correctly end-to-end."""
899
+ # NOT (age == 21) means age != 21, i.e., (age < 21) OR (age > 21)
900
+
901
+ negated_policy = self.negate_to_policy('age', '==', 21)
902
+ expanded = self.helper.expand_policy(negated_policy)
903
+ tree = self.parser.parse(expanded)
904
+
905
+ # User with age 20 should satisfy "age != 21"
906
+ user_attrs = self.helper.user_attributes({'age': 20})
907
+ self.assertTrue(self.parser.prune(tree, user_attrs))
908
+
909
+ # User with age 21 should NOT satisfy "age != 21"
910
+ user_attrs = self.helper.user_attributes({'age': 21})
911
+ self.assertFalse(self.parser.prune(tree, user_attrs))
912
+
913
+ # User with age 22 should satisfy "age != 21"
914
+ user_attrs = self.helper.user_attributes({'age': 22})
915
+ self.assertTrue(self.parser.prune(tree, user_attrs))
916
+
917
+ # --- Boundary Value Tests ---
918
+ def test_negate_at_zero(self):
919
+ """Test negation at zero boundary."""
920
+ # NOT (age >= 0) = age < 0 (always false for non-negative)
921
+ result = self.negate('age', '>=', 0)
922
+ self.assertEqual(result, ('age', '<', 0))
923
+
924
+ # NOT (age > 0) = age <= 0 (only true for 0)
925
+ result = self.negate('age', '>', 0)
926
+ self.assertEqual(result, ('age', '<=', 0))
927
+
928
+ def test_negate_at_max(self):
929
+ """Test negation at max value boundary."""
930
+ # NOT (age <= 255) = age > 255 (always false for 8-bit)
931
+ result = self.negate('age', '<=', 255)
932
+ self.assertEqual(result, ('age', '>', 255))
933
+
934
+
935
+ def run_stress_test():
936
+ """Run the stress test suite and print results."""
937
+ print("=" * 70)
938
+ print("ABE Policy Parser Stress Test")
939
+ print("=" * 70)
940
+ print()
941
+
942
+ # Run tests
943
+ loader = unittest.TestLoader()
944
+ suite = unittest.TestSuite()
945
+
946
+ suite.addTests(loader.loadTestsFromTestCase(PolicyParserStressTest))
947
+ suite.addTests(loader.loadTestsFromTestCase(PolicyParserEdgeCaseTest))
948
+ suite.addTests(loader.loadTestsFromTestCase(NumericAttributeTest))
949
+ suite.addTests(loader.loadTestsFromTestCase(NumericAttributeEdgeCaseTest))
950
+ suite.addTests(loader.loadTestsFromTestCase(NumericNegationTest))
951
+
952
+ runner = unittest.TextTestRunner(verbosity=2)
953
+ result = runner.run(suite)
954
+
955
+ print()
956
+ print("=" * 70)
957
+ if result.wasSuccessful():
958
+ print("ALL TESTS PASSED")
959
+ else:
960
+ print(f"FAILURES: {len(result.failures)}, ERRORS: {len(result.errors)}")
961
+ print("=" * 70)
962
+
963
+ return result.wasSuccessful()
964
+
965
+
966
+ if __name__ == '__main__':
967
+ success = run_stress_test()
968
+ sys.exit(0 if success else 1)
969
+