azrmooenc 1.0.0__tar.gz
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.
- azrmooenc-1.0.0/PKG-INFO +73 -0
- azrmooenc-1.0.0/README.md +52 -0
- azrmooenc-1.0.0/azrmooenc.egg-info/PKG-INFO +73 -0
- azrmooenc-1.0.0/azrmooenc.egg-info/SOURCES.txt +7 -0
- azrmooenc-1.0.0/azrmooenc.egg-info/dependency_links.txt +1 -0
- azrmooenc-1.0.0/azrmooenc.egg-info/top_level.txt +1 -0
- azrmooenc-1.0.0/azrmooenc.py +460 -0
- azrmooenc-1.0.0/pyproject.toml +31 -0
- azrmooenc-1.0.0/setup.cfg +4 -0
azrmooenc-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: azrmooenc
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A completely original reversible encoding library using Moo tokens
|
|
5
|
+
Author: azr
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://pypi.org/project/azrmooenc/
|
|
8
|
+
Keywords: encoding,moo,azrmoo,reversible-encoding
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Topic :: Utilities
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# AzrMoo Enc v1
|
|
23
|
+
|
|
24
|
+
A completely original reversible encoding library using Moo tokens.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install azrmooenc
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import azrmooenc
|
|
36
|
+
|
|
37
|
+
# Encode/decode strings
|
|
38
|
+
encoded = azrmooenc.encode("Hello, World!")
|
|
39
|
+
decoded = azrmooenc.decode(encoded)
|
|
40
|
+
|
|
41
|
+
# Encode/decode bytes
|
|
42
|
+
encoded = azrmooenc.encode_bytes(b"binary data")
|
|
43
|
+
decoded = azrmooenc.decode_bytes(encoded)
|
|
44
|
+
|
|
45
|
+
# With custom key
|
|
46
|
+
encoded = azrmooenc.encode("Secret", key="mykey")
|
|
47
|
+
decoded = azrmooenc.decode(encoded, key="mykey")
|
|
48
|
+
|
|
49
|
+
# File support
|
|
50
|
+
azrmooenc.encode_file("photo.jpg", "photo.jpg.azr")
|
|
51
|
+
azrmooenc.decode_file("photo.jpg.azr", "photo.jpg")
|
|
52
|
+
|
|
53
|
+
# Generate lookup tables
|
|
54
|
+
enc_table, dec_table = azrmooenc.generate_table("AZR")
|
|
55
|
+
|
|
56
|
+
# Version
|
|
57
|
+
print(azrmooenc.version())
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## API
|
|
61
|
+
|
|
62
|
+
- `encode(text, key="AZR")` - Encode string to AzrMoo format
|
|
63
|
+
- `decode(text, key="AZR")` - Decode AzrMoo string back to text
|
|
64
|
+
- `encode_bytes(data, key="AZR")` - Encode bytes to AzrMoo format
|
|
65
|
+
- `decode_bytes(data, key="AZR")` - Decode AzrMoo bytes back to original
|
|
66
|
+
- `encode_file(input_path, output_path, key="AZR")` - Encode a file
|
|
67
|
+
- `decode_file(input_path, output_path, key="AZR")` - Decode a file
|
|
68
|
+
- `generate_table(key="AZR")` - Generate encoding/decoding lookup tables
|
|
69
|
+
- `version()` - Return version string
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# AzrMoo Enc v1
|
|
2
|
+
|
|
3
|
+
A completely original reversible encoding library using Moo tokens.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install azrmooenc
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
import azrmooenc
|
|
15
|
+
|
|
16
|
+
# Encode/decode strings
|
|
17
|
+
encoded = azrmooenc.encode("Hello, World!")
|
|
18
|
+
decoded = azrmooenc.decode(encoded)
|
|
19
|
+
|
|
20
|
+
# Encode/decode bytes
|
|
21
|
+
encoded = azrmooenc.encode_bytes(b"binary data")
|
|
22
|
+
decoded = azrmooenc.decode_bytes(encoded)
|
|
23
|
+
|
|
24
|
+
# With custom key
|
|
25
|
+
encoded = azrmooenc.encode("Secret", key="mykey")
|
|
26
|
+
decoded = azrmooenc.decode(encoded, key="mykey")
|
|
27
|
+
|
|
28
|
+
# File support
|
|
29
|
+
azrmooenc.encode_file("photo.jpg", "photo.jpg.azr")
|
|
30
|
+
azrmooenc.decode_file("photo.jpg.azr", "photo.jpg")
|
|
31
|
+
|
|
32
|
+
# Generate lookup tables
|
|
33
|
+
enc_table, dec_table = azrmooenc.generate_table("AZR")
|
|
34
|
+
|
|
35
|
+
# Version
|
|
36
|
+
print(azrmooenc.version())
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## API
|
|
40
|
+
|
|
41
|
+
- `encode(text, key="AZR")` - Encode string to AzrMoo format
|
|
42
|
+
- `decode(text, key="AZR")` - Decode AzrMoo string back to text
|
|
43
|
+
- `encode_bytes(data, key="AZR")` - Encode bytes to AzrMoo format
|
|
44
|
+
- `decode_bytes(data, key="AZR")` - Decode AzrMoo bytes back to original
|
|
45
|
+
- `encode_file(input_path, output_path, key="AZR")` - Encode a file
|
|
46
|
+
- `decode_file(input_path, output_path, key="AZR")` - Decode a file
|
|
47
|
+
- `generate_table(key="AZR")` - Generate encoding/decoding lookup tables
|
|
48
|
+
- `version()` - Return version string
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
|
|
52
|
+
MIT
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: azrmooenc
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A completely original reversible encoding library using Moo tokens
|
|
5
|
+
Author: azr
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://pypi.org/project/azrmooenc/
|
|
8
|
+
Keywords: encoding,moo,azrmoo,reversible-encoding
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Topic :: Utilities
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# AzrMoo Enc v1
|
|
23
|
+
|
|
24
|
+
A completely original reversible encoding library using Moo tokens.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install azrmooenc
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import azrmooenc
|
|
36
|
+
|
|
37
|
+
# Encode/decode strings
|
|
38
|
+
encoded = azrmooenc.encode("Hello, World!")
|
|
39
|
+
decoded = azrmooenc.decode(encoded)
|
|
40
|
+
|
|
41
|
+
# Encode/decode bytes
|
|
42
|
+
encoded = azrmooenc.encode_bytes(b"binary data")
|
|
43
|
+
decoded = azrmooenc.decode_bytes(encoded)
|
|
44
|
+
|
|
45
|
+
# With custom key
|
|
46
|
+
encoded = azrmooenc.encode("Secret", key="mykey")
|
|
47
|
+
decoded = azrmooenc.decode(encoded, key="mykey")
|
|
48
|
+
|
|
49
|
+
# File support
|
|
50
|
+
azrmooenc.encode_file("photo.jpg", "photo.jpg.azr")
|
|
51
|
+
azrmooenc.decode_file("photo.jpg.azr", "photo.jpg")
|
|
52
|
+
|
|
53
|
+
# Generate lookup tables
|
|
54
|
+
enc_table, dec_table = azrmooenc.generate_table("AZR")
|
|
55
|
+
|
|
56
|
+
# Version
|
|
57
|
+
print(azrmooenc.version())
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## API
|
|
61
|
+
|
|
62
|
+
- `encode(text, key="AZR")` - Encode string to AzrMoo format
|
|
63
|
+
- `decode(text, key="AZR")` - Decode AzrMoo string back to text
|
|
64
|
+
- `encode_bytes(data, key="AZR")` - Encode bytes to AzrMoo format
|
|
65
|
+
- `decode_bytes(data, key="AZR")` - Decode AzrMoo bytes back to original
|
|
66
|
+
- `encode_file(input_path, output_path, key="AZR")` - Encode a file
|
|
67
|
+
- `decode_file(input_path, output_path, key="AZR")` - Decode a file
|
|
68
|
+
- `generate_table(key="AZR")` - Generate encoding/decoding lookup tables
|
|
69
|
+
- `version()` - Return version string
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
azrmooenc
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
|
|
2
|
+
"""
|
|
3
|
+
AzrMoo Enc v1 - A completely original reversible encoding library.
|
|
4
|
+
|
|
5
|
+
This library implements a custom encoding scheme that transforms binary data
|
|
6
|
+
into a string of 'Moo' tokens using a unique alphabet and algorithm.
|
|
7
|
+
This is NOT encryption; it is a reversible encoding format similar in purpose
|
|
8
|
+
to Base64 but with a completely original design.
|
|
9
|
+
|
|
10
|
+
Package: azrmooenc
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import random
|
|
14
|
+
from typing import Dict, List, Tuple
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ─── Custom Exceptions ────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AzrMooError(Exception):
|
|
21
|
+
"""Base exception for all AzrMoo encoding errors."""
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class InvalidHeaderError(AzrMooError):
|
|
26
|
+
"""Raised when the encoded data has an invalid or missing header."""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class InvalidTokenError(AzrMooError):
|
|
31
|
+
"""Raised when an unrecognized Moo token is encountered."""
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class InvalidChecksumError(AzrMooError):
|
|
36
|
+
"""Raised when checksum validation fails, indicating data corruption."""
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class DecodeError(AzrMooError):
|
|
41
|
+
"""Raised when decoding fails for any reason."""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ─── Constants ────────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
HEADER = "AZRMOOENCV1:"
|
|
48
|
+
DELIMITER = "~"
|
|
49
|
+
CHECKSUM_SIZE = 2
|
|
50
|
+
|
|
51
|
+
_BASE_TOKENS: List[str] = []
|
|
52
|
+
for _i in range(256):
|
|
53
|
+
_chars = []
|
|
54
|
+
for _bit in range(7, -1, -1):
|
|
55
|
+
_chars.append('m' if (_i >> _bit) & 1 else 'o')
|
|
56
|
+
_BASE_TOKENS.append(''.join(_chars))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ─── Internal Helpers ─────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _key_to_seed(key: str) -> int:
|
|
63
|
+
"""Convert a string key to a deterministic 31-bit integer seed."""
|
|
64
|
+
seed = 0
|
|
65
|
+
for c in key.encode('utf-8'):
|
|
66
|
+
seed = ((seed << 5) - seed + c) & 0x7FFFFFFF
|
|
67
|
+
return seed
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _shuffle(items: List[str], key: str) -> List[str]:
|
|
71
|
+
"""Deterministically shuffle a list using Fisher-Yates and a key-derived seed."""
|
|
72
|
+
rng = random.Random(_key_to_seed(key))
|
|
73
|
+
result = items.copy()
|
|
74
|
+
for i in range(len(result) - 1, 0, -1):
|
|
75
|
+
j = rng.randint(0, i)
|
|
76
|
+
result[i], result[j] = result[j], result[i]
|
|
77
|
+
return result
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ─── Public API ────────────────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def version() -> str:
|
|
84
|
+
"""Return the library version string."""
|
|
85
|
+
return "AzrMoo Enc v1.0.0"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def generate_table(key: str = "AZR") -> Tuple[Dict[int, str], Dict[str, int]]:
|
|
89
|
+
"""Generate encoding and decoding lookup tables.
|
|
90
|
+
|
|
91
|
+
The key deterministically shuffles the Moo alphabet so different
|
|
92
|
+
keys produce different mappings.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
key: The key to seed alphabet generation (default: "AZR").
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
(encode_table, decode_table) where:
|
|
99
|
+
- encode_table maps byte value (0-255) -> Moo token string
|
|
100
|
+
- decode_table maps Moo token string -> byte value (0-255)
|
|
101
|
+
"""
|
|
102
|
+
tokens = _shuffle(_BASE_TOKENS, key)
|
|
103
|
+
enc: Dict[int, str] = {i: tokens[i] for i in range(256)}
|
|
104
|
+
dec: Dict[str, int] = {tokens[i]: i for i in range(256)}
|
|
105
|
+
return enc, dec
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _transform(data: bytes, key: str, forward: bool) -> bytes:
|
|
109
|
+
"""Apply or reverse the byte transformation.
|
|
110
|
+
|
|
111
|
+
The transformation combines XOR with key bytes, bit rotation, and
|
|
112
|
+
position-dependent addition modulo 256 to scramble byte values.
|
|
113
|
+
"""
|
|
114
|
+
k = key.encode('utf-8')
|
|
115
|
+
kl = len(k)
|
|
116
|
+
out = bytearray(len(data))
|
|
117
|
+
for i, b in enumerate(data):
|
|
118
|
+
if forward:
|
|
119
|
+
x = b ^ k[i % kl]
|
|
120
|
+
s = i % 8
|
|
121
|
+
x = ((x << s) | (x >> (8 - s))) & 0xFF
|
|
122
|
+
x = (x + i * 19 + 7) & 0xFF
|
|
123
|
+
else:
|
|
124
|
+
x = (b - i * 19 - 7) & 0xFF
|
|
125
|
+
s = i % 8
|
|
126
|
+
x = ((x >> s) | (x << (8 - s))) & 0xFF
|
|
127
|
+
x ^= k[i % kl]
|
|
128
|
+
out[i] = x
|
|
129
|
+
return bytes(out)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _checksum(data: bytes) -> bytes:
|
|
133
|
+
"""Compute a 2-byte checksum: (sum mod 256, xor of all bytes)."""
|
|
134
|
+
total = 0
|
|
135
|
+
xor_val = 0
|
|
136
|
+
for b in data:
|
|
137
|
+
total = (total + b) & 0xFF
|
|
138
|
+
xor_val ^= b
|
|
139
|
+
return bytes([total, xor_val])
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def encode_bytes(data: bytes, key: str = "AZR") -> str:
|
|
143
|
+
"""Encode bytes to an AzrMoo Encoded string.
|
|
144
|
+
|
|
145
|
+
The input bytes are transformed, checksummed, and converted to
|
|
146
|
+
Moo tokens joined by the delimiter, prefixed with the format header.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
data: The bytes to encode.
|
|
150
|
+
key: The encoding key (default: "AZR").
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
An AzrMoo-encoded string.
|
|
154
|
+
"""
|
|
155
|
+
ck = _checksum(data)
|
|
156
|
+
payload = data + ck
|
|
157
|
+
transformed = _transform(payload, key, forward=True)
|
|
158
|
+
enc, _ = generate_table(key)
|
|
159
|
+
tokens = [enc[b] for b in transformed]
|
|
160
|
+
return HEADER + DELIMITER.join(tokens)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def decode_bytes(encoded: str, key: str = "AZR") -> bytes:
|
|
164
|
+
"""Decode an AzrMoo Encoded string back to bytes.
|
|
165
|
+
|
|
166
|
+
Verifies the header, decodes tokens, reverses the transformation,
|
|
167
|
+
and validates the checksum.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
encoded: The AzrMoo-encoded string.
|
|
171
|
+
key: The decoding key (default: "AZR").
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
The original decoded bytes.
|
|
175
|
+
|
|
176
|
+
Raises:
|
|
177
|
+
InvalidHeaderError: If the header is missing or invalid.
|
|
178
|
+
InvalidTokenError: If an unrecognized token is encountered.
|
|
179
|
+
InvalidChecksumError: If the checksum does not match.
|
|
180
|
+
DecodeError: If decoding fails for any other reason.
|
|
181
|
+
"""
|
|
182
|
+
if not encoded.startswith(HEADER):
|
|
183
|
+
raise InvalidHeaderError(
|
|
184
|
+
f"Missing or invalid header: expected '{HEADER}'"
|
|
185
|
+
)
|
|
186
|
+
body = encoded[len(HEADER):]
|
|
187
|
+
if not body:
|
|
188
|
+
raise DecodeError("No encoded data found after header")
|
|
189
|
+
|
|
190
|
+
token_list = body.split(DELIMITER)
|
|
191
|
+
_, dec = generate_table(key)
|
|
192
|
+
|
|
193
|
+
decoded = bytearray()
|
|
194
|
+
for t in token_list:
|
|
195
|
+
if t not in dec:
|
|
196
|
+
raise InvalidTokenError(f"Unrecognized token: '{t}'")
|
|
197
|
+
decoded.append(dec[t])
|
|
198
|
+
|
|
199
|
+
raw = _transform(bytes(decoded), key, forward=False)
|
|
200
|
+
if len(raw) < CHECKSUM_SIZE:
|
|
201
|
+
raise DecodeError("Encoded data is too short")
|
|
202
|
+
data = raw[:-CHECKSUM_SIZE]
|
|
203
|
+
ck = raw[-CHECKSUM_SIZE:]
|
|
204
|
+
expected = _checksum(data)
|
|
205
|
+
if ck != expected:
|
|
206
|
+
raise InvalidChecksumError(
|
|
207
|
+
f"Checksum mismatch: expected {expected.hex()}, got {ck.hex()}"
|
|
208
|
+
)
|
|
209
|
+
return bytes(data)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def encode(text: str, key: str = "AZR") -> str:
|
|
213
|
+
"""Encode a string to an AzrMoo Encoded string.
|
|
214
|
+
|
|
215
|
+
The input string is UTF-8 encoded, then encoded with encode_bytes.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
text: The string to encode.
|
|
219
|
+
key: The encoding key (default: "AZR").
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
An AzrMoo-encoded string.
|
|
223
|
+
"""
|
|
224
|
+
return encode_bytes(text.encode('utf-8'), key)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def decode(text: str, key: str = "AZR") -> str:
|
|
228
|
+
"""Decode an AzrMoo Encoded string back to a string.
|
|
229
|
+
|
|
230
|
+
Decodes with decode_bytes then UTF-8 decodes the result.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
text: The AzrMoo-encoded string.
|
|
234
|
+
key: The decoding key (default: "AZR").
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
The original decoded string.
|
|
238
|
+
|
|
239
|
+
Raises:
|
|
240
|
+
DecodeError: If the decoded bytes are not valid UTF-8.
|
|
241
|
+
"""
|
|
242
|
+
data = decode_bytes(text, key)
|
|
243
|
+
try:
|
|
244
|
+
return data.decode('utf-8')
|
|
245
|
+
except UnicodeDecodeError as e:
|
|
246
|
+
raise DecodeError(f"Decoded data is not valid UTF-8: {e}")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def encode_file(input_path: str, output_path: str, key: str = "AZR") -> None:
|
|
250
|
+
"""Encode a file to AzrMoo format.
|
|
251
|
+
|
|
252
|
+
Reads the input file as binary, encodes it, and writes the
|
|
253
|
+
encoded string to the output path.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
input_path: Path to the input file (any file type).
|
|
257
|
+
output_path: Path for the encoded output file.
|
|
258
|
+
key: The encoding key (default: "AZR").
|
|
259
|
+
"""
|
|
260
|
+
with open(input_path, 'rb') as f:
|
|
261
|
+
data = f.read()
|
|
262
|
+
encoded = encode_bytes(data, key)
|
|
263
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
264
|
+
f.write(encoded)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def decode_file(input_path: str, output_path: str, key: str = "AZR") -> None:
|
|
268
|
+
"""Decode an AzrMoo-encoded file back to its original format.
|
|
269
|
+
|
|
270
|
+
Reads the encoded file, decodes it, and writes the original bytes
|
|
271
|
+
to the output path.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
input_path: Path to the AzrMoo-encoded file.
|
|
275
|
+
output_path: Path for the decoded output file.
|
|
276
|
+
key: The decoding key (default: "AZR").
|
|
277
|
+
"""
|
|
278
|
+
with open(input_path, 'r', encoding='utf-8') as f:
|
|
279
|
+
encoded = f.read()
|
|
280
|
+
data = decode_bytes(encoded, key)
|
|
281
|
+
with open(output_path, 'wb') as f:
|
|
282
|
+
f.write(data)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
# ─── Self-Test ─────────────────────────────────────────────────────────────────
|
|
286
|
+
|
|
287
|
+
if __name__ == "__main__":
|
|
288
|
+
import os
|
|
289
|
+
import sys
|
|
290
|
+
import tempfile
|
|
291
|
+
import random as _random
|
|
292
|
+
|
|
293
|
+
_random.seed(42)
|
|
294
|
+
|
|
295
|
+
class _T:
|
|
296
|
+
def __init__(self):
|
|
297
|
+
self.passed = 0
|
|
298
|
+
self.failed = 0
|
|
299
|
+
|
|
300
|
+
def check(self, name: str, condition: bool) -> None:
|
|
301
|
+
if condition:
|
|
302
|
+
self.passed += 1
|
|
303
|
+
print(f" \u2713 {name}")
|
|
304
|
+
else:
|
|
305
|
+
self.failed += 1
|
|
306
|
+
print(f" \u2717 {name}")
|
|
307
|
+
|
|
308
|
+
tt = _T()
|
|
309
|
+
|
|
310
|
+
print("AzrMoo Enc v1 Self-Test")
|
|
311
|
+
print("=" * 40)
|
|
312
|
+
|
|
313
|
+
# 1: Basic string roundtrip
|
|
314
|
+
original = "Hello, AzrMoo!"
|
|
315
|
+
encoded = encode(original)
|
|
316
|
+
decoded = decode(encoded)
|
|
317
|
+
tt.check("Basic string roundtrip", original == decoded)
|
|
318
|
+
|
|
319
|
+
# 2: Unicode (Arabic)
|
|
320
|
+
original = "\u0645\u0631\u062d\u0628\u0627 \u0628\u0627\u0644\u0639\u0627\u0644\u0645"
|
|
321
|
+
encoded = encode(original)
|
|
322
|
+
decoded = decode(encoded)
|
|
323
|
+
tt.check("Arabic text roundtrip", original == decoded)
|
|
324
|
+
|
|
325
|
+
# 3: Unicode (Emoji)
|
|
326
|
+
original = "\U0001f680\U00002728\U0001f31f\U0001f389"
|
|
327
|
+
encoded = encode(original)
|
|
328
|
+
decoded = decode(encoded)
|
|
329
|
+
tt.check("Emoji roundtrip", original == decoded)
|
|
330
|
+
|
|
331
|
+
# 4: English text with punctuation
|
|
332
|
+
original = "The quick brown fox jumps over the lazy dog. 1234567890!@#$%^&*()"
|
|
333
|
+
encoded = encode(original)
|
|
334
|
+
decoded = decode(encoded)
|
|
335
|
+
tt.check("English text roundtrip", original == decoded)
|
|
336
|
+
|
|
337
|
+
# 5: Empty string
|
|
338
|
+
original = ""
|
|
339
|
+
encoded = encode(original)
|
|
340
|
+
decoded = decode(encoded)
|
|
341
|
+
tt.check("Empty string roundtrip", original == decoded)
|
|
342
|
+
|
|
343
|
+
# 6: encode_bytes / decode_bytes with random bytes (various lengths)
|
|
344
|
+
test_bytes = _random.randbytes
|
|
345
|
+
for length in [0, 1, 10, 100, 1000]:
|
|
346
|
+
data = test_bytes(length)
|
|
347
|
+
encoded = encode_bytes(data)
|
|
348
|
+
decoded = decode_bytes(encoded)
|
|
349
|
+
tt.check(f"Random bytes roundtrip (len={length})", data == decoded)
|
|
350
|
+
|
|
351
|
+
# 7: Different keys produce different output
|
|
352
|
+
data = b"Hello World"
|
|
353
|
+
e1 = encode_bytes(data, "AZR")
|
|
354
|
+
e2 = encode_bytes(data, "KEY")
|
|
355
|
+
tt.check("Different keys produce different output", e1 != e2)
|
|
356
|
+
|
|
357
|
+
# 8: Same key produces same output
|
|
358
|
+
e3 = encode_bytes(data, "AZR")
|
|
359
|
+
tt.check("Same key produces same output", e1 == e3)
|
|
360
|
+
|
|
361
|
+
# 9: Checksum catches corruption
|
|
362
|
+
encoded = encode("Test data")
|
|
363
|
+
mid = len(encoded) // 2
|
|
364
|
+
corrupted = encoded[:mid] + "X" + encoded[mid + 1:]
|
|
365
|
+
try:
|
|
366
|
+
decode(corrupted)
|
|
367
|
+
tt.check("Checksum catches corruption", False)
|
|
368
|
+
except (InvalidChecksumError, InvalidTokenError, DecodeError):
|
|
369
|
+
tt.check("Checksum catches corruption", True)
|
|
370
|
+
|
|
371
|
+
# 10: Invalid header raises InvalidHeaderError
|
|
372
|
+
try:
|
|
373
|
+
decode_bytes("INVALIDHEADER:moo~oom")
|
|
374
|
+
tt.check("Invalid header raises InvalidHeaderError", False)
|
|
375
|
+
except InvalidHeaderError:
|
|
376
|
+
tt.check("Invalid header raises InvalidHeaderError", True)
|
|
377
|
+
|
|
378
|
+
# 11: File roundtrip (text)
|
|
379
|
+
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
|
|
380
|
+
f.write("File encoding test content \U0001f4c4")
|
|
381
|
+
inpath_txt = f.name
|
|
382
|
+
outpath_txt = inpath_txt + ".azr"
|
|
383
|
+
decpath_txt = inpath_txt + ".dec"
|
|
384
|
+
try:
|
|
385
|
+
encode_file(inpath_txt, outpath_txt)
|
|
386
|
+
decode_file(outpath_txt, decpath_txt)
|
|
387
|
+
with open(inpath_txt, 'rb') as f:
|
|
388
|
+
orig_data = f.read()
|
|
389
|
+
with open(decpath_txt, 'rb') as f:
|
|
390
|
+
dec_data = f.read()
|
|
391
|
+
tt.check("File roundtrip (text)", orig_data == dec_data)
|
|
392
|
+
finally:
|
|
393
|
+
for p in [inpath_txt, outpath_txt, decpath_txt]:
|
|
394
|
+
if os.path.exists(p):
|
|
395
|
+
os.unlink(p)
|
|
396
|
+
|
|
397
|
+
# 12: File roundtrip (binary)
|
|
398
|
+
with tempfile.NamedTemporaryFile(delete=False) as f:
|
|
399
|
+
bin_data = test_bytes(500)
|
|
400
|
+
f.write(bin_data)
|
|
401
|
+
inpath_bin = f.name
|
|
402
|
+
outpath_bin = inpath_bin + ".azr"
|
|
403
|
+
decpath_bin = inpath_bin + ".dec"
|
|
404
|
+
try:
|
|
405
|
+
encode_file(inpath_bin, outpath_bin)
|
|
406
|
+
decode_file(outpath_bin, decpath_bin)
|
|
407
|
+
with open(decpath_bin, 'rb') as f:
|
|
408
|
+
result = f.read()
|
|
409
|
+
tt.check("File roundtrip (binary)", bin_data == result)
|
|
410
|
+
finally:
|
|
411
|
+
for p in [inpath_bin, outpath_bin, decpath_bin]:
|
|
412
|
+
if os.path.exists(p):
|
|
413
|
+
os.unlink(p)
|
|
414
|
+
|
|
415
|
+
# 13: version() returns non-empty string
|
|
416
|
+
v = version()
|
|
417
|
+
tt.check("version() returns non-empty string", isinstance(v, str) and len(v) > 0)
|
|
418
|
+
|
|
419
|
+
# 14: generate_table() produces valid tables
|
|
420
|
+
enc, dec = generate_table()
|
|
421
|
+
tt.check("encode table has 256 entries", len(enc) == 256)
|
|
422
|
+
tt.check("decode table has 256 entries", len(dec) == 256)
|
|
423
|
+
for i in range(256):
|
|
424
|
+
tt.check(f"generate_table roundtrip at {i}", dec[enc[i]] == i)
|
|
425
|
+
|
|
426
|
+
# 15: Different key generates different table
|
|
427
|
+
enc1, _ = generate_table("AZR")
|
|
428
|
+
enc2, _ = generate_table("DIFFERENT")
|
|
429
|
+
vals1 = list(enc1.values())
|
|
430
|
+
vals2 = list(enc2.values())
|
|
431
|
+
tt.check("Different keys produce different tables", vals1 != vals2)
|
|
432
|
+
|
|
433
|
+
# 16: Large data roundtrip
|
|
434
|
+
large_data = b"AzrMoo" * 10000
|
|
435
|
+
encoded = encode_bytes(large_data)
|
|
436
|
+
decoded = decode_bytes(encoded)
|
|
437
|
+
tt.check("Large data roundtrip (60KB)", large_data == decoded)
|
|
438
|
+
|
|
439
|
+
# 17: Key-dependent encoding (different key == different output for same text)
|
|
440
|
+
text = "Secret message"
|
|
441
|
+
enc_a = encode(text, "key1")
|
|
442
|
+
enc_b = encode(text, "key2")
|
|
443
|
+
tt.check("Key changes encoded output", enc_a != enc_b)
|
|
444
|
+
|
|
445
|
+
# 18: Decode with wrong key fails (checksum)
|
|
446
|
+
enc_c = encode(text, "correct-key")
|
|
447
|
+
try:
|
|
448
|
+
decode(enc_c, "wrong-key")
|
|
449
|
+
tt.check("Wrong key fails decoding", False)
|
|
450
|
+
except (InvalidChecksumError, DecodeError, InvalidTokenError):
|
|
451
|
+
tt.check("Wrong key fails decoding", True)
|
|
452
|
+
|
|
453
|
+
print(f"\n{'=' * 40}")
|
|
454
|
+
total = tt.passed + tt.failed
|
|
455
|
+
print(f"Results: {tt.passed} passed, {tt.failed} failed out of {total}")
|
|
456
|
+
if tt.failed:
|
|
457
|
+
print("SOME TESTS FAILED!")
|
|
458
|
+
sys.exit(1)
|
|
459
|
+
else:
|
|
460
|
+
print("All tests passed!")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "azrmooenc"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "A completely original reversible encoding library using Moo tokens"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [{name = "azr"}]
|
|
13
|
+
keywords = ["encoding", "moo", "azrmoo", "reversible-encoding"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.9",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
|
+
"Topic :: Utilities",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[tool.setuptools]
|
|
28
|
+
py-modules = ["azrmooenc"]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://pypi.org/project/azrmooenc/"
|