This repository has been archived by the owner on Nov 29, 2018. It is now read-only.
/
Ascii85.cs
168 lines (149 loc) · 4.68 KB
/
Ascii85.cs
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
using System;
using System.IO;
using System.Text;
namespace Logos.Utility
{
/// <summary>
/// Converts between binary data and an Ascii85-encoded string.
/// </summary>
/// <remarks>See <a href="http://en.wikipedia.org/wiki/Ascii85">Ascii85 at Wikipedia</a>.</remarks>
public static class Ascii85
{
/// <summary>
/// Encodes the specified byte array in Ascii85.
/// </summary>
/// <param name="bytes">The bytes to encode.</param>
/// <returns>An Ascii85-encoded string representing the input byte array.</returns>
public static string Encode(byte[] bytes)
{
if (bytes == null)
throw new ArgumentNullException("bytes");
// preallocate a StringBuilder with enough room to store the encoded bytes
StringBuilder sb = new StringBuilder(bytes.Length * 5 / 4);
// walk the bytes
int count = 0;
uint value = 0;
foreach (byte b in bytes)
{
// build a 32-bit value from the bytes
value |= ((uint) b) << (24 - (count * 8));
count++;
// every 32 bits, convert the previous 4 bytes into 5 Ascii85 characters
if (count == 4)
{
if (value == 0)
sb.Append('z');
else
EncodeValue(sb, value, 0);
count = 0;
value = 0;
}
}
// encode any remaining bytes (that weren't a multiple of 4)
if (count > 0)
EncodeValue(sb, value, 4 - count);
return sb.ToString();
}
/// <summary>
/// Decodes the specified Ascii85 string into the corresponding byte array.
/// </summary>
/// <param name="encoded">The Ascii85 string.</param>
/// <returns>The decoded byte array.</returns>
public static byte[] Decode(string encoded)
{
if (encoded == null)
throw new ArgumentNullException("encoded");
// preallocate a memory stream with enough capacity to hold the decoded data
using (MemoryStream stream = new MemoryStream(encoded.Length * 4 / 5))
{
// walk the input string
int count = 0;
uint value = 0;
foreach (char ch in encoded)
{
if (ch == 'z' && count == 0)
{
// handle "z" block specially
DecodeValue(stream, value, 0);
}
else if (ch < c_firstCharacter || ch > c_lastCharacter)
{
throw new FormatException("Invalid character '{0}' in Ascii85 block.".FormatInvariant(ch));
}
else
{
// build a 32-bit value from the input characters
try
{
checked { value += (uint) (s_powersOf85[count] * (ch - c_firstCharacter)); }
}
catch (OverflowException ex)
{
throw new FormatException("The current group of characters decodes to a value greater than UInt32.MaxValue.", ex);
}
count++;
// every five characters, convert the characters into the equivalent byte array
if (count == 5)
{
DecodeValue(stream, value, 0);
count = 0;
value = 0;
}
}
}
if (count == 1)
{
throw new FormatException("The final Ascii85 block must contain more than one character.");
}
else if (count > 1)
{
// decode any remaining characters
for (int padding = count; padding < 5; padding++)
{
try
{
checked { value += 84 * s_powersOf85[padding]; }
}
catch (OverflowException ex)
{
throw new FormatException("The current group of characters decodes to a value greater than UInt32.MaxValue.", ex);
}
}
DecodeValue(stream, value, 5 - count);
}
return stream.ToArray();
}
}
// Writes the Ascii85 characters for a 32-bit value to a StringBuilder.
private static void EncodeValue(StringBuilder sb, uint value, int paddingBytes)
{
char[] encoded = new char[5];
for (int index = 4; index >= 0; index--)
{
encoded[index] = (char) ((value % 85) + c_firstCharacter);
value /= 85;
}
if (paddingBytes != 0)
Array.Resize(ref encoded, 5 - paddingBytes);
sb.Append(encoded);
}
// Writes the bytes of a 32-bit value to a stream.
private static void DecodeValue(Stream stream, uint value, int paddingChars)
{
stream.WriteByte((byte) (value >> 24));
if (paddingChars == 3)
return;
stream.WriteByte((byte) ((value >> 16) & 0xFF));
if (paddingChars == 2)
return;
stream.WriteByte(((byte) ((value >> 8) & 0xFF)));
if (paddingChars == 1)
return;
stream.WriteByte((byte) (value & 0xFF));
}
// the first and last characters used in the Ascii85 encoding character set
const char c_firstCharacter = '!';
const char c_lastCharacter = 'u';
static readonly uint[] s_powersOf85 = new uint[] { 85u * 85u * 85u * 85u, 85u * 85u * 85u, 85u * 85u, 85u, 1 };
}
}