using Disco.Data.Repository; using Disco.Models.Repository; using Disco.Services.Documents; using System; using System.Text; namespace Disco.Services { public static class DocumentUniqueIdentifierExtensions { public static DocumentUniqueIdentifier CreateUniqueIdentifier(this DocumentTemplate Template, DiscoDataContext Database, IAttachmentTarget Target, User Creator, DateTime Timestamp, int PageIndex) { return DocumentUniqueIdentifier.Create(Database, Template, Target, Creator, Timestamp, PageIndex); } public static byte[] BinaryEncode(this DocumentUniqueIdentifier Identifier, string Data) { return BinaryEncode(Data); } public static byte[] BinaryEncode(string Data) { if (Data == null) throw new ArgumentNullException(nameof(Data)); byte[] result; if (Data.Length == 0) { // Zero-length Alpha Encode (1 byte) return new byte[] { 0x40 }; } // Try Numeric Encode if (TryBinaryNumericEncode(Data, out result)) { return result; } // Try CASES21 ST/DF Key Encode if (TryBinaryC21Encode(Data, out result)) { return result; } // Try Alpha Encode if (TryBinaryAlphaEncode(Data, out result)) { return result; } // Use UTF8 Encoding return BinaryUTF8Encode(Data); } public static string BinaryDecode(this DocumentUniqueIdentifier Identifier, byte[] Data, int Offset, out int NewOffset) { return BinaryDecode(Data, Offset, out NewOffset); } public static string BinaryDecode(byte[] Data, int Offset, out int NewOffset) { int encoding = Data[Offset] >> 6; switch (encoding) { case 0: // 00 - numeric encoding return BinaryNumericDecode(Data, Offset, out NewOffset); case 1: // 01 - alpha encoding return BinaryAlphaDecode(Data, Offset, out NewOffset); case 2: // 10 - C21 encoding return BinaryC21Decode(Data, Offset, out NewOffset); case 3: // 11 - UTF8 encoding return BinaryUTF8Decode(Data, Offset, out NewOffset); default: throw new InvalidOperationException(); } } public static bool TryBinaryNumericEncode(string Data, out byte[] Result) { // byte[0] = XXZZ YYYY // byte[1] = YYYY YYYY // byte[2] = YYYY YYYY // byte[3] = YYYY YYYY // X = 00 = numeric encoded // Z = number of leading zeros // Y = number component < 0x0FFFFFFF (268,435,455) uint number; if (uint.TryParse(Data, out number) && number <= 0x0FFFFFFF) { Result = new byte[4]; int leadingZeros = 0; for (int i = 0; i < Data.Length; i++) { if (Data[leadingZeros] != '0') break; leadingZeros++; } if (leadingZeros <= 3) { Result[0] = (byte)((byte)(leadingZeros << 4) | (number >> 24)); Result[1] = (byte)(number >> 16); Result[2] = (byte)(number >> 8); Result[3] = (byte)(number); return true; } } Result = null; return false; } public static string BinaryNumericDecode(byte[] Data, int Offset, out int NewOffset) { int leadingZeros = (Data[Offset] & 0x30) >> 4; int number = ((Data[Offset] & 0x0F) << 24) | (Data[Offset + 1] << 16) | (Data[Offset + 2] << 8) | (Data[Offset + 3]); NewOffset = Offset + 4; if (leadingZeros == 0) { return number.ToString(); } else { var builder = new StringBuilder(12); // 12 = max number length builder.Append('0', leadingZeros); builder.Append(number); return builder.ToString(); } } private const string AlphaEncodeMap = @"ABCDEFGHIJKLMNOPQRSTUVWXYZ-.01\"; public static bool TryBinaryAlphaEncode(string Data, out byte[] Result) { // byte[0] = XXYY YYYY // X = 01 = alpha encoded // Y = data length <= 63 // byte[1.] = AAAA ABBB // byte[2.] = BBCD DDDD // A = first character index // B = second character index // C = not used // D = third character index if (Data.Length == 0) { // Zero-length Alpha Encode (1 byte) Result = new byte[] { 0x40 }; return true; } if (Data.Length > 0 && Data.Length <= 63) { Data = Data.ToUpperInvariant(); var position = 1; var requiredBytes = ((Data.Length / 3) * 2); switch (Data.Length % 3) { case 2: requiredBytes += 3; break; case 1: requiredBytes += 2; break; default: requiredBytes++; break; } Result = new byte[requiredBytes]; Result[0] = (byte)(0x40 | Data.Length); for (int i = 0; i < Data.Length; i++) { var charIndex = AlphaEncodeMap.IndexOf(Data[i]); if (charIndex == -1) { Result = null; return false; } switch (i % 3) { case 0: Result[position] = (byte)(charIndex << 3); break; case 1: Result[position] = (byte)(Result[position] | (charIndex >> 2)); Result[++position] = (byte)(charIndex << 6); break; case 2: Result[position] = (byte)(Result[position] | charIndex); position++; break; } } return true; } Result = null; return false; } public static string BinaryAlphaDecode(byte[] Data, int Offset, out int NewOffset) { var length = Data[Offset++] & 0x3F; return BinaryAlphaDecode(Data, Offset, length, out NewOffset); } private static string BinaryAlphaDecode(byte[] Data, int Offset, int Length, out int NewOffset) { var builder = new StringBuilder(Length); for (int i = 0; i < Length; i++) { switch (i % 3) { case 0: builder.Append(AlphaEncodeMap[Data[Offset++] >> 3]); break; case 1: builder.Append(AlphaEncodeMap[((Data[Offset - 1] & 0x7) << 2) | ((Data[Offset++] >> 6) & 0x3)]); break; case 2: builder.Append(AlphaEncodeMap[Data[Offset - 1] & 0x1F]); break; } } NewOffset = Offset; return builder.ToString(); } public static bool TryBinaryC21Encode(string Data, out byte[] Result) { // byte[0] = XXYY YYYY // byte[1] = YYYY YYYY // X = 10 = C21 encoded // Y = number component // byte[2] = AAAA ABBB // byte[3] = BBCC CCCD // A,B,C = character component in // alpha encoded format short number; byte[] chars; if (Data.Length == 7 && short.TryParse(Data.Substring(3), out number) && number <= 9999 && TryBinaryAlphaEncode(Data.Substring(0, 3), out chars)) { Result = new byte[4]; Result[0] = (byte)(0x80 | (number >> 8)); Result[1] = (byte)number; Result[2] = chars[1]; Result[3] = chars[2]; return true; } Result = null; return false; } public static string BinaryC21Decode(byte[] Data, int Offset, out int NewOffset) { var number = ((Data[Offset++] & 0x3F) << 8) | (Data[Offset++]); var chars = BinaryAlphaDecode(Data, Offset, 3, out NewOffset); return $"{chars}{number:0000}"; } public static byte[] BinaryUTF8Encode(string Data) { // byte[0] = XXYY YYYY // X = 11 = UTF8 encoded // Y = data length <= 63 // byte[.] = AAAA AAAA // A = UTF8 encoded string if (Data.Length == 0) { // Zero-length Alpha Encode (1 byte) return new byte[] { 0xC0 }; } if (Data.Length <= 63) { var utf8Bytes = Encoding.UTF8.GetBytes(Data); if (utf8Bytes.Length <= 63) { var result = new byte[1 + utf8Bytes.Length]; result[0] = (byte)(0xC0 | utf8Bytes.Length); utf8Bytes.CopyTo(result, 1); return result; } } throw new ArgumentException("Unable to encode the data. The input data is to long."); } public static string BinaryUTF8Decode(byte[] Data, int Offset, out int NewOffset) { var length = Data[Offset] & 0x3F; NewOffset = Offset + length + 1; return Encoding.UTF8.GetString(Data, Offset + 1, length); } } }