import ceylon.buffer { Buffer, ByteBuffer, CharacterBuffer } import ceylon.buffer.codec { IncrementalCodec, ByteToByteCodec, CharacterToByteCodec, ErrorStrategy, PieceConvert, strict, DecodeException, ignore, resetStrategy=reset } abstract class Base32PieceEncoderState() of b32EncodeFirst | b32EncodeSecond | b32EncodeThird | b32EncodeFourth | b32EncodeFifth {} object b32EncodeFirst extends Base32PieceEncoderState() {} object b32EncodeSecond extends Base32PieceEncoderState() {} object b32EncodeThird extends Base32PieceEncoderState() {} object b32EncodeFourth extends Base32PieceEncoderState() {} object b32EncodeFifth extends Base32PieceEncoderState() {} abstract class Base32PieceDecoderState() of b32DecodeFirst | b32DecodeSecond | b32DecodeThird | b32DecodeFourth | b32DecodeFifth | b32DecodeSixth | b32DecodeSeventh | b32DecodeEighth {} object b32DecodeFirst extends Base32PieceDecoderState() {} object b32DecodeSecond extends Base32PieceDecoderState() {} object b32DecodeThird extends Base32PieceDecoderState() {} object b32DecodeFourth extends Base32PieceDecoderState() {} object b32DecodeFifth extends Base32PieceDecoderState() {} object b32DecodeSixth extends Base32PieceDecoderState() {} object b32DecodeSeventh extends Base32PieceDecoderState() {} object b32DecodeEighth extends Base32PieceDecoderState() {} shared sealed abstract class Base32<ToMutable, ToImmutable, ToSingle>(toMutableOfSize) satisfies IncrementalCodec<ToMutable,ToImmutable,ToSingle,ByteBuffer,List<Byte>,Byte> given ToMutable satisfies Buffer<ToSingle> given ToImmutable satisfies {ToSingle*} given ToSingle satisfies Object { ToMutable(Integer) toMutableOfSize; shared formal ToSingle[] encodeTable; shared formal Integer decodeToIndex(ToSingle input); shared formal Byte[] decodeTable; "The padding character, used where required to terminate discrete blocks of encoded data so they may be concatenated without making the seperation point ambiguous." shared formal ToSingle pad; shared actual Integer averageEncodeSize(Integer inputSize) => ceiling(inputSize, 5.0) * 8; shared actual Integer maximumEncodeSize(Integer inputSize) => averageEncodeSize(inputSize); shared actual Integer averageDecodeSize(Integer inputSize) => ceiling(inputSize * 5, 8.0); shared actual Integer maximumDecodeSize(Integer inputSize) => averageDecodeSize(inputSize); shared actual PieceConvert<ToSingle,Byte> pieceEncoder(ErrorStrategy error) => object satisfies PieceConvert<ToSingle,Byte> { ToMutable output = toMutableOfSize(8); variable Base32PieceEncoderState state = b32EncodeFirst; variable Byte? remainder = null; void reset() { state = b32EncodeFirst; remainder = null; } ToSingle byteToChar(Byte byte) { // Not using ErrorStrategy / EncodeException here since if this // doesn't succeed the implementation is wrong. All input bytes are // valid. "5 bit split is too large. Internal error." assert (byte.signed < 32); "5 bit split is negative. Internal error." assert (byte.signed >= 0); "Base32 table is invalid. Internal error." assert (exists char = encodeTable[byte.signed]); return char; } shared actual {ToSingle*} more(Byte input) { output.clear(); // Five byte repeating quantum, producing 8 characters of 5-bits each switch (state) case (b32EncodeFirst) { // [in 01234567] -> [char 01234][rem 567] remainder = input.and($111.byte); output.put(byteToChar(input.rightLogicalShift(3))); state = b32EncodeSecond; } case (b32EncodeSecond) { // [rem 567][in 01234567] -> [char [rem 567]01][char 23456][rem 7] assert (exists rem = remainder); remainder = input.and($1.byte); output.put(byteToChar { rem.leftLogicalShift(2).or(input.rightLogicalShift(6)); }); output.put(byteToChar { input.rightLogicalShift(1).and($11111.byte); }); state = b32EncodeThird; } case (b32EncodeThird) { // [rem 7][in 01234567] -> [char [rem 7]0123][rem 4567] assert (exists rem = remainder); remainder = input.and($1111.byte); output.put(byteToChar { rem.leftLogicalShift(4).or(input.rightLogicalShift(4)); }); state = b32EncodeFourth; } case (b32EncodeFourth) { // [rem 4567][in 01234567] -> [char [rem 4567]0][char 12345][rem 67] assert (exists rem = remainder); remainder = input.and($11.byte); output.put(byteToChar { rem.leftLogicalShift(1).or(input.rightLogicalShift(7)); }); output.put(byteToChar { input.rightLogicalShift(2).and($11111.byte); }); state = b32EncodeFifth; } case (b32EncodeFifth) { // [rem 67][in 01234567] -> [char [rem 67]012][char 34567] assert (exists rem = remainder); output.put(byteToChar { rem.leftLogicalShift(3).or(input.rightLogicalShift(5)); }); output.put(byteToChar { input.and($11111.byte); }); reset(); } output.flip(); return output; } shared actual {ToSingle*} done() { output.clear(); switch (state) case (b32EncodeFirst) { // At quantum boundary, nothing to return } case (b32EncodeSecond) { // [rem 567] -> [char [rem 567][pad 00]][char [pad 00000]] pad*5 assert (exists rem = remainder); output.put(byteToChar(rem.leftLogicalShift(2))); output.put(pad); output.put(pad); output.put(pad); output.put(pad); output.put(pad); output.put(pad); } case (b32EncodeThird) { // [rem 7] -> [char [rem 7][pad 0000]] pad pad pad pad assert (exists rem = remainder); output.put(byteToChar(rem.leftLogicalShift(4))); output.put(pad); output.put(pad); output.put(pad); output.put(pad); } case (b32EncodeFourth) { // [rem 4567] -> [char [rem 4567][pad 0]][char [pad 00000]] pad pad assert (exists rem = remainder); output.put(byteToChar(rem.leftLogicalShift(1))); output.put(pad); output.put(pad); output.put(pad); } case (b32EncodeFifth) { assert (exists rem = remainder); // [rem 67] -> [char [rem 67][pad 000]][char [pad 00000]] output.put(byteToChar(rem.leftLogicalShift(3))); output.put(pad); } reset(); output.flip(); return output; } }; shared actual PieceConvert<Byte,ToSingle> pieceDecoder(ErrorStrategy error) => object satisfies PieceConvert<Byte,ToSingle> { ByteBuffer output = ByteBuffer.ofSize(1); variable Base32PieceDecoderState state = b32DecodeFirst; variable Boolean padSeen = false; variable Byte? remainder = null; void reset() { state = b32DecodeFirst; padSeen = false; remainder = null; } shared actual {Byte*} more(ToSingle input) { output.clear(); void handleState(nextState, handleInputByte, handlePad = null) { variable Base32PieceDecoderState nextState; Anything(Byte) handleInputByte; Anything()? handlePad; if (input == pad) { if (exists handlePad) { padSeen = true; if (exists rem = remainder, rem != 0.byte) { handlePad(); } } else { switch (error) case (strict) { throw DecodeException { "Pad element ``input`` is not allowed here"; }; } case (ignore) { } case (resetStrategy) { reset(); nextState = state; } } } else if (padSeen) { switch (error) case (strict) { throw DecodeException { "Non-pad character ``input`` is not allowed here"; }; } case (ignore) { } case (resetStrategy) { reset(); nextState = state; } } else { Integer inputIndex = decodeToIndex(input); if (exists inByte = decodeTable[inputIndex], inByte != 255.byte) { handleInputByte(inByte); } else { switch (error) case (strict) { throw DecodeException("``input`` is not a base32 character"); } case (ignore) { } case (resetStrategy) { reset(); nextState = state; } } } state = nextState; } // 8 character (of 5-bits each) repeating quantum, producing 5 bytes switch (state) case (b32DecodeFirst) { handleState { nextState = b32DecodeSecond; // [rem 01234] (insufficent to make a byte) handleInputByte = (b) => remainder = b; }; } case (b32DecodeSecond) { assert (exists rem = remainder); handleState { nextState = b32DecodeThird; // [rem 01234][in 01234] -> [out [rem 01234][in 012]][rem 34] handleInputByte = (b) { remainder = b.and($11.byte); output.put { rem.leftLogicalShift(3).or(b.rightLogicalShift(2)); }; }; handlePad = () { // [rem 01234][pad 000] -> [out [[rem 01234][pad 000]] remainder = 0.byte; output.put(rem.leftLogicalShift(3)); }; }; } case (b32DecodeThird) { assert (exists rem = remainder); handleState { nextState = b32DecodeFourth; // [rem 34][in 01234] -> [rem [rem 34][in 01234]] (insufficent) handleInputByte = (b) { remainder = rem.leftLogicalShift(5).or(b); }; handlePad = () { // [rem 34][pad 000000] -> [out [rem 34][pad 000000]] remainder = 0.byte; output.put(rem.leftLogicalShift(6)); }; }; } case (b32DecodeFourth) { assert (exists rem = remainder); handleState { nextState = b32DecodeFifth; // [rem 3401234][in 01234] -> [out [rem 3401234][in 0]][rem 1234] handleInputByte = (b) { remainder = b.and($1111.byte); output.put { rem.leftLogicalShift(1).or(b.rightLogicalShift(4)); }; }; handlePad = () { // [rem 3401234][pad 0] -> [out [rem 3401234][pad 0]] remainder = 0.byte; output.put(rem.leftLogicalShift(1)); }; }; } case (b32DecodeFifth) { assert (exists rem = remainder); handleState { nextState = b32DecodeSixth; // [rem 1234][in 01234] -> [out [rem 1234][in 0123]][rem 4] handleInputByte = (b) { remainder = b.and($1.byte); output.put { rem.leftLogicalShift(4).or(b.rightLogicalShift(1)); }; }; handlePad = () { // [rem 1234][pad 0000] -> [out [rem 1234][pad 0000]] remainder = 0.byte; output.put(rem.leftLogicalShift(4)); }; }; } case (b32DecodeSixth) { assert (exists rem = remainder); handleState { nextState = b32DecodeSeventh; // [rem 4][in 01234] -> [rem [rem 4][in 01234]] (insufficent) handleInputByte = (b) { remainder = rem.leftLogicalShift(5).or(b); }; handlePad = () { // [rem 4][pad 0000000] -> [rem [rem 4][pad 0000000]] remainder = 0.byte; output.put(rem.leftLogicalShift(7)); }; }; } case (b32DecodeSeventh) { assert (exists rem = remainder); handleState { nextState = b32DecodeEighth; // [rem 401234][in 01234] -> [out [rem 401234][in 01]][rem 234] handleInputByte = (b) { remainder = b.and($111.byte); output.put { rem.leftLogicalShift(2).or(b.rightLogicalShift(3)); }; }; handlePad = () { // [rem 401234][pad 00] -> [out [rem 401234][pad 00]] remainder = 0.byte; output.put(rem.leftLogicalShift(2)); }; }; } case (b32DecodeEighth) { assert (exists rem = remainder); handleState { nextState = b32DecodeFirst; // [rem 234][in 01234] -> [out [rem 234][in 01234]] handleInputByte = (b) { remainder = 0.byte; output.put { rem.leftLogicalShift(5).or(b); }; }; handlePad = () { // [rem 234][pad 00000] -> [out [rem 234][pad 00000]] remainder = 0.byte; output.put(rem.leftLogicalShift(5)); }; }; reset(); } output.flip(); return output; } shared actual {Byte*} done() { output.clear(); // Pad termination is optional if (!padSeen) { switch (state) case (b32DecodeFirst) { // At quantum boundary, nothing to return } case (b32DecodeSecond) { if (exists rem = remainder, rem != 0.byte) { output.put(rem.leftLogicalShift(3)); } } case (b32DecodeThird) { if (exists rem = remainder, rem != 0.byte) { output.put(rem.leftLogicalShift(6)); } } case (b32DecodeFourth) { if (exists rem = remainder, rem != 0.byte) { output.put(rem.leftLogicalShift(1)); } } case (b32DecodeFifth) { if (exists rem = remainder, rem != 0.byte) { output.put(rem.leftLogicalShift(4)); } } case (b32DecodeSixth) { if (exists rem = remainder, rem != 0.byte) { output.put(rem.leftLogicalShift(7)); } } case (b32DecodeSeventh) { if (exists rem = remainder, rem != 0.byte) { output.put(rem.leftLogicalShift(2)); } } case (b32DecodeEighth) { if (exists rem = remainder, rem != 0.byte) { output.put(rem.leftLogicalShift(5)); } } } reset(); output.flip(); return output; } }; shared actual Integer decodeBid({ToSingle*} sample) { if (sample.every((s) => s==pad || s in encodeTable)) { return 50; } else { return 0; } } } shared abstract class Base32String() extends Base32<CharacterBuffer,String,Character>(CharacterBuffer.ofSize) satisfies CharacterToByteCodec { shared actual Character pad = '='; } shared abstract class Base32Byte() extends Base32<ByteBuffer,List<Byte>,Byte>(ByteBuffer.ofSize) satisfies ByteToByteCodec { shared actual Byte pad = '='.integer.byte; } Character[] standardBase32CharTable = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7' ]; shared object base32StringStandard extends Base32String() { shared actual Character[] encodeTable = standardBase32CharTable; shared actual Integer decodeToIndex(Character input) => input.integer; shared actual Byte[] decodeTable = toDecodeTable(encodeTable, decodeToIndex); shared actual [String+] aliases = ["base32", "base-32", "base_32"]; shared actual Integer encodeBid({Byte*} sample) => 10; } Byte[] standardBase32ByteTable = standardBase32CharTable*.integer*.byte.sequence(); shared object base32ByteStandard extends Base32Byte() { shared actual Byte[] encodeTable = standardBase32ByteTable; shared actual Integer decodeToIndex(Byte input) => input.unsigned; shared actual Byte[] decodeTable = toDecodeTable(encodeTable, decodeToIndex); shared actual [String+] aliases = ["base32", "base-32", "base-32"]; shared actual Integer encodeBid({Byte*} sample) => 10; } Character[] hexBase32CharTable = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V' ]; shared object base32StringHex extends Base32String() { shared actual Character[] encodeTable = hexBase32CharTable; shared actual Integer decodeToIndex(Character input) => input.integer; shared actual Byte[] decodeTable = toDecodeTable(encodeTable, decodeToIndex); shared actual [String+] aliases = ["base32hex", "base-32-hex", "base_32_hex"]; shared actual Integer encodeBid({Byte*} sample) => 10; } Byte[] hexBase32ByteTable = hexBase32CharTable*.integer*.byte.sequence(); shared object base32ByteHex extends Base32Byte() { shared actual Byte[] encodeTable = hexBase32ByteTable; shared actual Integer decodeToIndex(Byte input) => input.unsigned; shared actual Byte[] decodeTable = toDecodeTable(encodeTable, decodeToIndex); shared actual [String+] aliases = ["base32hex", "base-32-hex", "base_32_hex"]; shared actual Integer encodeBid({Byte*} sample) => 10; }