Skip to content
This repository
branch: master
Bradley Grainger February 18, 2012
file 169 lines (149 sloc) 4.796 kb
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 };
}
}
Something went wrong with that request. Please try again.