Skip to content

Commit e11961d

Browse files
committed
Added d64 encoding
1 parent 07cfcb9 commit e11961d

2 files changed

Lines changed: 151 additions & 0 deletions

File tree

src/bd2k/util/d64.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Copyright (c) 2014 Dominic Tarr
2+
# Copyright (c) 2015 Hannes Schmidt
3+
#
4+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software
5+
# and associated documentation files (the "Software"), to deal in the Software without
6+
# restriction, including without limitation the rights to use, copy, modify, merge, publish,
7+
# distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
8+
# Software is furnished to do so, subject to the following conditions:
9+
#
10+
# The above copyright notice and this permission notice shall be included in all copies or
11+
# substantial portions of the Software.
12+
#
13+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
14+
# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
16+
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18+
19+
# Ported from JS found at https://github.com/dominictarr/d64
20+
21+
22+
23+
class D64( object ):
24+
def __init__( self, special_chars ):
25+
super( D64, self ).__init__( )
26+
self.chars = bytearray( sorted(
27+
'PYFGCRLAOEUIDHTNSQJKXBMWVZpyfgcrlaoeuidhtnsqjkxbmwvz1234567890' + special_chars ) )
28+
self.codeToIndex = bytearray( 128 )
29+
for i in xrange( 64 ):
30+
code = self.chars[ i ]
31+
self.codeToIndex[ code ] = i
32+
33+
def encode( self, data ):
34+
"""
35+
>>> encode = standard.encode
36+
>>> encode('')
37+
''
38+
>>> encode('\\x00')
39+
'..'
40+
>>> encode('\\x00\\x01')
41+
'..3'
42+
>>> encode('\\x00\\x01\\x02')
43+
'..31'
44+
>>> encode('\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07')
45+
'..31.kF40VR'
46+
"""
47+
l = len( data )
48+
s = bytearray( (l * 4 + 2) / 3 )
49+
hang = 0
50+
j = 0
51+
chars = self.chars
52+
for i in xrange( l ):
53+
v = ord( data[ i ] )
54+
r = i % 3
55+
if r == 0:
56+
s[ j ] = chars[ v >> 2 ]
57+
j += 1
58+
hang = (v & 3) << 4
59+
elif r == 1:
60+
s[ j ] = chars[ hang | v >> 4 ]
61+
j += 1
62+
hang = (v & 0xf) << 2
63+
elif r == 2:
64+
s[ j ] = chars[ hang | v >> 6 ]
65+
j += 1
66+
s[ j ] = chars[ v & 0x3f ]
67+
j += 1
68+
hang = 0
69+
else:
70+
assert False
71+
if l % 3:
72+
s[ j ] = chars[ hang ]
73+
74+
return str( s )
75+
76+
def decode( self, s ):
77+
"""
78+
>>> decode = standard.decode
79+
>>> decode('')
80+
''
81+
>>> decode('..')
82+
'\\x00'
83+
>>> decode('..3')
84+
'\\x00\\x01'
85+
>>> decode('..31')
86+
'\\x00\\x01\\x02'
87+
>>> decode('..31.kF40VR')
88+
'\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07'
89+
"""
90+
l = len( s )
91+
j = 0
92+
b = bytearray( l * 3 / 4 )
93+
hang = 0
94+
codeToIndex = self.codeToIndex
95+
96+
for i in xrange( l ):
97+
v = codeToIndex[ ord( s[ i ] ) ]
98+
r = i % 4
99+
if r == 0:
100+
hang = v << 2
101+
elif r == 1:
102+
b[ j ] = hang | v >> 4
103+
j += 1
104+
hang = (v << 4) & 0xFF
105+
elif r == 2:
106+
b[ j ] = hang | v >> 2
107+
j += 1
108+
hang = (v << 6) & 0xFF
109+
elif r == 3:
110+
b[ j ] = hang | v
111+
j += 1
112+
else:
113+
assert False
114+
return str( b )
115+
116+
117+
standard = D64( '._' )

src/bd2k/util/test/test_d64.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright (c) 2014 Dominic Tarr
2+
# Copyright (c) 2015 Hannes Schmidt
3+
#
4+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software
5+
# and associated documentation files (the "Software"), to deal in the Software without
6+
# restriction, including without limitation the rights to use, copy, modify, merge, publish,
7+
# distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
8+
# Software is furnished to do so, subject to the following conditions:
9+
#
10+
# The above copyright notice and this permission notice shall be included in all copies or
11+
# substantial portions of the Software.
12+
#
13+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
14+
# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
16+
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18+
19+
# Ported from JS found at https://github.com/dominictarr/d64
20+
21+
from __future__ import absolute_import
22+
from unittest import TestCase
23+
from bd2k.util.d64 import standard as d64
24+
import os
25+
26+
27+
class TestD64( TestCase ):
28+
def test( self ):
29+
data = [ (os.urandom( i ), i) for i in xrange( 1000 ) ]
30+
encoded_data = [ (d64.encode( d ), i) for d, i in data ]
31+
decoded_data = [ (d64.decode( s ), i) for s, i in encoded_data ]
32+
self.assertEqual( data, decoded_data )
33+
# Ensure that lexicographical sort is consistent between data and encoded data
34+
self.assertEqual( zip( *sorted( data ) )[ 1 ], zip( *sorted( encoded_data ) )[ 1 ] )

0 commit comments

Comments
 (0)