public ConvertStatus ConvertData(ReadOnlySpan <char> inputData, int inputIndex, int inputCount, Span <byte> outputData, int outputIndex, int outputCount, bool flush, out int inputUsed, out int outputUsed) { int inputEnd = inputIndex + inputCount; int outputEnd = outputIndex + outputCount; inputUsed = outputUsed = 0; while (true) { switch (_currentState) { case State.Reset: Reset(); goto case State.BeginReadingPrefix; case State.BeginReadingPrefix: if ((_flags & SettingsFlags.FlagHasPrefixes) == 0) { goto case State.BeginReading; } _currentState = State.ReadingPrefix; goto case State.ReadingPrefix; case State.ReadingPrefix: { int matchLength = AffixMatcher.TryMatchAffix(inputData, inputIndex, inputEnd, flush, _decodingPrefixes); if (matchLength < 0) // need more data.. { return(ConvertStatus.InputRequired); } if ((matchLength == 0) && ((_flags & SettingsFlags.FlagRequirePrefix) != 0)) { throw new FormatException(Resources.DecoderPrefixRequired); } inputIndex += matchLength; inputUsed += matchLength; goto case State.BeginReading; } case State.BeginReading: _decodedBits = _decodedCount = 0; _currentState = State.Reading; goto case State.Reading; case State.Reading: while ((_decodedCount < 8) && (inputIndex < inputEnd)) { char inputChar = inputData[inputIndex++]; if (inputChar >= 128) { if ((_flags & SettingsFlags.FlagIgnoreInvalidCharacters) == 0) { throw new FormatException(Resources.DecoderInvalidCharacter); } // ignoring invalid characters.. inputUsed++; continue; } do { // check the character against the postfix characters, this happens before we // try to decode (in case the postfix overlaps the alphabet -or- does not) if ((_decodingAffixTable[inputChar] & 0x02) != 0) { int matchLength = AffixMatcher.TryMatchAffix(inputData, inputIndex - 1, inputEnd, flush, _decodingPostfixes); // incomplete match, need more data... if (matchLength < 0) { return(ConvertStatus.InputRequired); } if (matchLength == 0) { break; } // any match must use all of the remaining input.. if (matchLength != inputEnd - inputIndex + 1) { break; } // have a match and it's at what appears to be the end of the input, but // we can't actually accept it unless we're flushing.. if (!flush) { return(ConvertStatus.InputRequired); } // this is a good match, adjust the indices and move to flushing.. inputIndex += matchLength - 1; inputUsed += matchLength; _postfixRead = true; goto case State.Flushing; } } while (false); // inputChar is always 127 or less here (due to check above) byte decodedValue = _decodingTable[inputChar]; if ((decodedValue & SettingsCharacterTypes.CharTypeMask) == SettingsCharacterTypes.CharAlphabet) { _decodedBits = (_decodedBits << 4) | (uint)(decodedValue & 0x0F); _decodedCount++; } else if (decodedValue != SettingsCharacterTypes.CharSpecialIgnored) { // all non-ignored characters are treated as invalid.. if ((_flags & SettingsFlags.FlagIgnoreInvalidCharacters) == 0) { throw new FormatException(Resources.DecoderInvalidCharacter); } } inputUsed++; } if (_decodedCount == 8) { goto case State.BeginWriting; } if (flush) { goto case State.Flushing; } return(ConvertStatus.InputRequired); case State.BeginWriting: _currentState = State.Writing; goto case State.Writing; case State.Writing: while ((_decodedCount < 12) && (outputIndex < outputEnd)) { outputData[outputIndex++] = (byte)((_decodedBits & 0xFF000000) >> 24); outputUsed++; _decodedBits <<= 8; _decodedCount++; } if (_decodedCount == 12) { goto case State.BeginReading; } return(ConvertStatus.OutputRequired); case State.Flushing: if ((_decodedCount & 0x01) != 0) { // an odd number of input characters is invalid.. if ((_flags & SettingsFlags.FlagIgnoreInvalidFinalQuantum) == 0) { throw new FormatException(Resources.DecoderTruncatedQuantum); } _decodedBits >>= 4; _decodedCount--; } if (_decodedCount == 0) { goto case State.Finished; } _decodedBits <<= (int)(8 * (4u - (_decodedCount >> 1))); _decodedCount = 12u - (_decodedCount >> 1); goto case State.BeginWriting; case State.Finished: if (((_flags & SettingsFlags.FlagRequirePostfix) != 0) && (!_postfixRead)) { throw new FormatException(Resources.DecoderPostfixRequired); } _currentState = State.Reset; return(ConvertStatus.Complete); case State.ReturnToPreviousState: System.Diagnostics.Debug.Assert(_previousState != State.ReturnToPreviousState); _currentState = _previousState; _previousState = State.ReturnToPreviousState; continue; } throw new InvalidOperationException("Unreachable code, reached."); } }
public ConvertStatus ConvertData(ReadOnlySpan <char> inputData, int inputIndex, int inputCount, Span <byte> outputData, int outputIndex, int outputCount, bool flush, out int inputUsed, out int outputUsed) { int inputEnd = inputIndex + inputCount; int outputEnd = outputIndex + outputCount; inputUsed = outputUsed = 0; while (true) { switch (_currentState) { case State.Reset: Reset(); goto case State.BeginReadingPrefix; case State.BeginReadingPrefix: if ((_flags & SettingsFlags.FlagHasPrefixes) == 0) { goto case State.BeginReading; } _currentState = State.ReadingPrefix; goto case State.ReadingPrefix; case State.ReadingPrefix: { int matchLength = AffixMatcher.TryMatchAffix(inputData, inputIndex, inputEnd, flush, _decodingPrefixes); if (matchLength < 0) // need more data.. { return(ConvertStatus.InputRequired); } if ((matchLength == 0) && ((_flags & SettingsFlags.FlagRequirePrefix) != 0)) { throw new FormatException(Resources.DecoderPrefixRequired); } inputIndex += matchLength; inputUsed += matchLength; goto case State.BeginReading; } case State.BeginReading: _decodedBits = _decodedCount = 0; _currentState = State.Reading; goto case State.Reading; case State.ContinueReading: _currentState = State.Reading; goto case State.Reading; case State.Reading: while ((_decodedCount < 8) && (inputIndex < inputEnd)) { char inputChar = inputData[inputIndex++]; if (inputChar >= 128) { if ((_flags & SettingsFlags.FlagIgnoreInvalidCharacters) == 0) { throw new FormatException(Resources.DecoderInvalidCharacter); } // ignoring invalid characters.. inputUsed++; continue; } do { // check the character against the postfix characters, this happens before we // try to decode (in case the postfix overlaps the alphabet -or- does not) if ((_decodingAffixTable[inputChar] & 0x02) != 0) { int matchLength = AffixMatcher.TryMatchAffix(inputData, inputIndex - 1, inputEnd, flush, _decodingPostfixes); // incomplete match, need more data... if (matchLength < 0) { return(ConvertStatus.InputRequired); } if (matchLength == 0) { break; } // any match must use all of the remaining input.. if (matchLength != inputEnd - inputIndex + 1) { break; } // have a match and it's at what appears to be the end of the input, but // we can't actually accept it unless we're flushing.. if (!flush) { return(ConvertStatus.InputRequired); } // this is a good match, adjust the indices and move to flushing.. inputIndex += matchLength - 1; inputUsed += matchLength; _postfixRead = true; goto case State.Flushing; } } while (false); // inputChar is always 127 or less here (due to check above) byte decodedValue = _decodingTable[inputChar]; if ((decodedValue & SettingsCharacterTypes.CharTypeMask) == SettingsCharacterTypes.CharAlphabet) { _decodedBits = (_decodedBits << 5) | (uint)(decodedValue & 0x1F); _decodedCount++; } else if (decodedValue != SettingsCharacterTypes.CharSpecialIgnored) { // padding characters are handled in the reading padding state.. if (decodedValue == SettingsCharacterTypes.CharSpecialPadding) { goto case State.BeginReadingPadding; } // all other characters are considered invalid.. if ((_flags & SettingsFlags.FlagIgnoreInvalidCharacters) == 0) { throw new FormatException(Resources.DecoderInvalidCharacter); } } inputUsed++; } if (_decodedCount == 8) { goto case State.BeginWriting; } if (flush) { goto case State.Flushing; } return(ConvertStatus.InputRequired); case State.BeginWriting: _currentState = State.Writing; goto case State.Writing; case State.Writing: while ((_decodedCount < 13) && (outputIndex < outputEnd)) { outputData[outputIndex++] = (byte)((_decodedBits & 0xFF00000000ul) >> 32); outputUsed++; _decodedBits <<= 8; _decodedCount++; } if (_decodedCount == 13) { goto case State.BeginReading; } return(ConvertStatus.OutputRequired); case State.BeginReadingPadding: // determine the amount of padding that we should expect if this is the // final quantum of the input.. _paddingRead = 1; _paddingRequired = Base32Codec.PadInfo[PadInfoDecoding, _decodedCount]; if ((_paddingRequired == 0) && ((_flags & SettingsFlags.FlagIgnoreInvalidPadding) == 0)) { throw new FormatException(Resources.DecoderInvalidPadding); } // reading state doesn't "use" the first padding character, so we have to.. inputUsed++; _currentState = State.ReadingPadding; goto case State.ReadingPadding; case State.ReadingPadding: while (inputIndex < inputEnd) { char inputChar = inputData[inputIndex++]; if (inputChar >= 128) { // character is invalid, if we're ignoring invalid characters then we can just keep // going otherwise, now is the time to fail.. if ((_flags & SettingsFlags.FlagIgnoreInvalidCharacters) == 0) { throw new FormatException(Resources.DecoderInvalidCharacter); } inputUsed++; continue; } do { // check the input character against the affix table, if it is the start of // a possible postfix, try to match it.. if ((_decodingAffixTable[inputChar] & 0x02) != 0) { // match the input data against the postfixes.. int matchLength = AffixMatcher.TryMatchAffix(inputData, inputIndex - 1, inputEnd, flush, _decodingPostfixes); if (matchLength < 0) { return(ConvertStatus.InputRequired); // need more data to determine } if (matchLength == 0) { break; // definative non-match } // any match must use all of the remaining input.. if (matchLength != inputEnd - inputIndex + 1) { break; } // have a match and it's at what appears to be the end of the input, but // we can't actually accept it unless we're flushing.. if (!flush) { return(ConvertStatus.InputRequired); } // this is a good match, adjust the indices and move to flushing.. inputIndex += matchLength - 1; inputUsed += matchLength; _postfixRead = true; goto case State.Flushing; } } while (false); // inputChar is always 127 or less here (due to check above) byte decodedValue = _decodingTable[inputChar]; if (decodedValue == SettingsCharacterTypes.CharSpecialPadding) { _paddingRead++; if (_paddingRead > _paddingRequired) { if ((_flags & SettingsFlags.FlagIgnoreInvalidPadding) == 0) { throw new FormatException(Resources.DecoderInvalidPadding); } // reset _paddingRead so that we don't overflow it.. _paddingRead = (byte)(_paddingRequired + 1); } inputUsed++; continue; } else if ((decodedValue & SettingsCharacterTypes.CharTypeMask) == SettingsCharacterTypes.CharAlphabet) { // all of the padding that we've read is invalid... if ((_flags & SettingsFlags.FlagIgnoreInvalidPadding) == 0) { throw new FormatException(Resources.DecoderInvalidPadding); } // go back to the reading state.. _paddingRead = 0; inputIndex--; goto case State.ContinueReading; } else if ((decodedValue == SettingsCharacterTypes.CharSpecialIgnored) || ((_flags & SettingsFlags.FlagIgnoreInvalidCharacters) != 0)) { // always ignored character, or invalid character that we are ignoring... inputUsed++; continue; } // anything else is an invalid character.. throw new FormatException(Resources.DecoderInvalidCharacter); } // we only reach this point if we've run out of input.. if (flush) { goto case State.Flushing; } return(ConvertStatus.InputRequired); case State.Flushing: { bool invalidQuantum = (PadInfo[PadInfoUnusedBits, _decodedCount] >= 5); if (invalidQuantum) { // an incorrect number of characters in the last quantum.. if ((_flags & SettingsFlags.FlagIgnoreInvalidFinalQuantum) == 0) { throw new FormatException(Resources.DecoderTruncatedQuantum); } // if we require padding, then this is an error state, because there is no correct // padding for an invalid last quantum.. if ((_flags & SettingsFlags.FlagRequirePadding) != 0) { throw new FormatException(Resources.DecoderInvalidPadding); } // remove characters from the quantum to make it "valid" and adjust the decoded bits // appropriately.. var toRemove = PadInfo[PadInfoRemove, _decodedCount]; _decodedCount -= toRemove; _decodedBits >>= (toRemove * 5); } if ((_flags & SettingsFlags.FlagRequirePadding) != 0) { // valid padding is required, there are few problems here. First is that // we may have an invalid quantum, which means any padding is invalid var requiredPadding = PadInfo[PadInfoDecoding, _decodedCount]; if (_paddingRead < requiredPadding) { throw new FormatException(Resources.DecoderPaddingRequired); } else if ((_paddingRead > requiredPadding) && ((_flags & SettingsFlags.FlagIgnoreInvalidPadding) == 0)) { throw new FormatException(Resources.DecoderInvalidPadding); } } } if (_decodedCount == 0) { goto case State.Finished; } if ((_flags & SettingsFlags.FlagRequireTrailingZeroBits) != 0) { // ensure that the unused trailing bits are all zero, this can detect truncation // in some cases .. uint unusedBits = (1u << PadInfo[PadInfoUnusedBits, _decodedCount]) - 1u; if ((_decodedBits & unusedBits) != 0) { throw new FormatException(Resources.DecoderUnusedBitNonZero); } } // adjust decodedBits and decodedCount so that we can flush the final quantum.. _decodedBits <<= (int)(40u - (5u * _decodedCount)); _decodedCount = 13u - ((5u * _decodedCount) >> 3); goto case State.BeginWriting; case State.Finished: if (((_flags & SettingsFlags.FlagRequirePostfix) != 0) && (!_postfixRead)) { throw new FormatException(Resources.DecoderPostfixRequired); } _currentState = State.Reset; return(ConvertStatus.Complete); case State.ReturnToPreviousState: System.Diagnostics.Debug.Assert(_previousState != State.ReturnToPreviousState); _currentState = _previousState; _previousState = State.ReturnToPreviousState; continue; } throw new InvalidOperationException("Unreachable code, reached."); } }
public ConvertStatus ConvertData(ReadOnlySpan <char> inputData, int inputIndex, int inputCount, Span <byte> outputData, int outputIndex, int outputCount, bool flush, out int inputUsed, out int outputUsed) { int inputEnd = inputIndex + inputCount; int outputEnd = outputIndex + outputCount; inputUsed = outputUsed = 0; while (true) { switch (_currentState) { case State.Reset: Reset(); goto case State.BeginReadingPrefix; case State.BeginReadingPrefix: if ((_flags & SettingsFlags.FlagHasPrefixes) == 0) { goto case State.BeginReading; } _currentState = State.ReadingPrefix; goto case State.ReadingPrefix; case State.ReadingPrefix: { int matchLength = AffixMatcher.TryMatchAffix(inputData, inputIndex, inputEnd, flush, _decodingPrefixes); if (matchLength < 0) // need more data.. { return(ConvertStatus.InputRequired); } if ((matchLength == 0) && ((_flags & SettingsFlags.FlagRequirePrefix) != 0)) { throw new FormatException(Resources.DecoderPrefixRequired); } inputIndex += matchLength; inputUsed += matchLength; goto case State.BeginReading; } case State.BeginReading: _decodedBits = _decodedCount = 0; _currentState = State.Reading; goto case State.Reading; case State.Reading: while ((_decodedCount < 5) && (inputIndex < inputEnd)) { char inputChar = inputData[inputIndex++]; if (inputChar >= 128) { if ((_flags & SettingsFlags.FlagIgnoreInvalidCharacters) == 0) { throw new FormatException(Resources.DecoderInvalidCharacter); } // ignoring invalid characters.. inputUsed++; continue; } do { // check the character against the postfix characters, this happens before we // try to decode (in case the postfix overlaps the alphabet -or- does not) if ((_decodingAffixTable[inputChar] & 0x02) != 0) { int matchLength = AffixMatcher.TryMatchAffix(inputData, inputIndex - 1, inputEnd, flush, _decodingPostfixes); // incomplete match, need more data... if (matchLength < 0) { return(ConvertStatus.InputRequired); } if (matchLength == 0) { break; } // any match must use all of the remaining input.. if (matchLength != inputEnd - inputIndex + 1) { break; } // have a match and it's at what appears to be the end of the input, but // we can't actually accept it unless we're flushing.. if (!flush) { return(ConvertStatus.InputRequired); } // this is a good match, adjust the indices and move to flushing.. inputIndex += matchLength - 1; inputUsed += matchLength; _postfixRead = true; goto case State.Flushing; } } while (false); // inputChar is always 127 or less here (due to check above) byte decodedValue = _decodingTable[inputChar]; if ((decodedValue & SettingsCharacterTypes.CharTypeMask) == SettingsCharacterTypes.CharAlphabet) { _decodedBits = (_decodedBits * 85) + decodedValue; _decodedCount++; } else if ((decodedValue >= SettingsCharacterTypes.CharSpecialZ) && (_decodedCount == 0)) { if (decodedValue == SettingsCharacterTypes.CharSpecialY) { _decodedBits = 0x20202020ul; } // special characters are abbreviations for _decodedCount = 5; } else if (decodedValue != SettingsCharacterTypes.CharSpecialIgnored) { // all non-ignored characters are treated as invalid.. if ((_flags & SettingsFlags.FlagIgnoreInvalidCharacters) == 0) { throw new FormatException(Resources.DecoderInvalidCharacter); } } inputUsed++; } if (_decodedCount == 5) { goto case State.BeginWriting; } if (flush) { goto case State.Flushing; } return(ConvertStatus.InputRequired); case State.BeginWriting: if (_decodedBits > uint.MaxValue) { // 5 digits of base85 can exceed the maximum uint value, if it does, the encoding // is bad (but we can ignore if configured to do so) if ((_flags & SettingsFlags.FlagIgnoreOverflow) == 0) { throw new FormatException(Resources.DecoderOverflow); } // ignoring overflow, discard any overflow bits .. anything we do here is going to // produce the "wrong" value (since we have no way of knowing what the correct value // is supposed to be) _decodedBits &= 0xFFFFFFFF; } _currentState = State.Writing; goto case State.Writing; case State.Writing: while ((_decodedCount < 9) && (outputIndex < outputEnd)) { outputData[outputIndex++] = (byte)((_decodedBits & 0xFF000000ul) >> 24); outputUsed++; _decodedBits <<= 8; _decodedCount++; } if (_decodedCount == 9) { goto case State.BeginReading; } return(ConvertStatus.OutputRequired); case State.Flushing: if (_decodedCount == 0) { goto case State.Finished; } if (((_flags & SettingsFlags.FlagRequireCompleteFinalQuantum) != 0) || (_decodedCount == 1)) { // configured to require a full quantum even at the end, if we're not // ignoring invalid quantums then we're in an error state.. or the quantum // is actually invalid (decodedCount == 1) if ((_flags & SettingsFlags.FlagIgnoreInvalidFinalQuantum) == 0) { throw new FormatException(Resources.DecoderTruncatedQuantum); } // nothing more to write.. goto case State.Finished; } { int paddingCount = 5 - (int)_decodedCount; for (int padCount = 0; padCount < paddingCount; padCount++) { _decodedBits = (_decodedBits * 85) + 84; } // adjust decodedCount to write the correct number of bytes.. _decodedCount = 5u + (uint)paddingCount; } goto case State.BeginWriting; case State.Finished: if (((_flags & SettingsFlags.FlagRequirePostfix) != 0) && (!_postfixRead)) { throw new FormatException(Resources.DecoderPostfixRequired); } _currentState = State.Reset; return(ConvertStatus.Complete); case State.ReturnToPreviousState: System.Diagnostics.Debug.Assert(_previousState != State.ReturnToPreviousState); _currentState = _previousState; _previousState = State.ReturnToPreviousState; continue; } throw new InvalidOperationException("Unreachable code, reached."); } }