예제 #1
0
        /// <summary>
        /// Hashes and writes a null terminator block to the specified writer.
        /// </summary>
        /// <param name="writer">Writer used to output data.</param>
        /// <param name="blockIndex">Block index used to compute HMAC key.</param>
        /// <returns>A task that resolves when output is finished.</returns>
        public async Task WriteTerminatorAsync(DataWriter writer, ulong blockIndex)
        {
            if (writer == null)
            {
                throw new ArgumentNullException(nameof(writer));
            }

            IBuffer nullData  = WindowsRuntimeBuffer.Create(0);
            IBuffer hmacValue = GetMacForBlock(blockIndex, nullData, 0, 0);

            writer.WriteBuffer(hmacValue);
            await writer.StoreAsync();

            writer.WriteUInt32(0);
            await writer.StoreAsync();

            DebugHelper.Trace($"Wrote HMAC terminator block #{blockIndex}.");
            DebugHelper.Trace($"MAC[0]: {hmacValue.GetByte(0)}, MAC[31]: {hmacValue.GetByte(31)}");
        }
        private static void Verify(IBuffer buffer, byte[] source, int offset, int length, int capacity)
        {
            Assert.Equal(length, (int)buffer.Length);
            Assert.Equal(capacity, (int)buffer.Capacity);

            for (uint i = 0; i < length; i++)
            {
                Assert.Equal(source[i + offset], buffer.GetByte(i));
            }
        }
예제 #3
0
        public void Create_Buffer_ReturnsExpected(byte[] source, int offset, int length, int capacity)
        {
            IBuffer buffer = WindowsRuntimeBuffer.Create(source, offset, length, capacity);

            Assert.Equal(capacity, (int)buffer.Capacity);
            Assert.Equal(length, (int)buffer.Length);

            for (uint i = 0; i < length; i++)
            {
                Assert.Equal(source[i + offset], buffer.GetByte(i));
            }

            // The source byte array should be copied.
            if (source.Length > 0)
            {
                source[0] = 45;
                Assert.NotEqual(45, buffer.GetByte(0));
            }
        }
예제 #4
0
        /// <summary>
        /// Generates the HMAC-SHA-256 value for a given block.
        /// The actual hashed value is i || n (block size) || C.
        /// </summary>
        /// <param name="i">The block index.</param>
        /// <param name="cipherText">Encrypted block value.</param>
        /// <param name="offset">Offset into <paramref name="cipherText"/>.</param>
        /// <param name="length">Number of bytes of <paramref name="cipherText"/> to use.</param>
        /// <returns>The HMAC value of the block.</returns>
        public IBuffer GetMacForBlock(ulong i, IBuffer cipherText, uint offset, uint length)
        {
            if (cipherText == null)
            {
                throw new ArgumentNullException(nameof(cipherText));
            }

            if (offset > cipherText.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(offset));
            }

            length = (offset + length > cipherText.Length ? cipherText.Length - offset : length);

            DebugHelper.Assert(length <= int.MaxValue);

            // Construct the HMAC buffer: i || n || C
            byte[] buffer = new byte[8 + 4 + length];

            ByteHelper.GetLittleEndianBytes(i, buffer);
            ByteHelper.GetLittleEndianBytes(length, buffer, 8);
            if (length > 0)
            {
                // Necessary because buffers don't play nice if they're empty...
                cipherText.CopyTo(offset, buffer, 12, (int)length);
            }

            CryptographicHash hash = this.hmacAlgo.CreateHash(GetKeyForBlock(i));

            hash.Append(buffer.AsBuffer());
            IBuffer macValue = hash.GetValueAndReset();

            DebugHelper.Trace($"Generating MAC value for block #{i}, data length {length}");
            if (length > 0)
            {
                DebugHelper.Trace($"data[0]: { buffer[12]}, data[n]: { buffer[buffer.Length - 1]}");
            }
            DebugHelper.Trace($"MAC[0]: {macValue.GetByte(0)}, MAC[31]: {macValue.GetByte(31)}");
            return(macValue);
        }
예제 #5
0
        /// <summary>
        /// Hashes and writes the provided data to the provided stream.
        /// </summary>
        /// <param name="writer">Writer used to output data.</param>
        /// <param name="cipherText">The data block to write.</param>
        /// <param name="offset">Offset into <paramref name="cipherText"/>.</param>
        /// <param name="length">Number of bytes of <paramref name="cipherText"/> to use.</param>
        /// <param name="blockIndex">Block index used to compute HMAC key.</param>
        /// <returns>A task that resolves when output is finished.</returns>
        public async Task WriteCipherBlockAsync(DataWriter writer, IBuffer cipherText, uint offset, uint length, ulong blockIndex)
        {
            if (writer == null)
            {
                throw new ArgumentNullException(nameof(writer));
            }

            if (cipherText == null)
            {
                throw new ArgumentNullException(nameof(cipherText));
            }

            if (cipherText.Length > int.MaxValue)
            {
                throw new ArgumentException("Block size is limited to int.MaxValue", nameof(cipherText));
            }

            if (offset >= cipherText.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(offset));
            }

            length = (offset + length > cipherText.Length ? cipherText.Length - offset : length);

            IBuffer hmacValue = GetMacForBlock(blockIndex, cipherText, offset, length);

            writer.WriteBuffer(hmacValue);
            await writer.StoreAsync();

            writer.WriteUInt32(length);
            await writer.StoreAsync();

            writer.WriteBuffer(cipherText, offset, length);
            await writer.StoreAsync();

            DebugHelper.Trace($"Wrote HMAC block #{blockIndex}; block is {length} bytes.");
            DebugHelper.Trace($"MAC[0]: {hmacValue.GetByte(0)}, MAC[31]: {hmacValue.GetByte(31)}, data[0]: {cipherText.GetByte(offset)}, data[n]: {cipherText.GetByte(offset + length - 1)}");
        }
예제 #6
0
        /// <summary>
        /// Calculate histogram from CanvasBitmap. Currently, only B8G8R8A8UIntNormalized pixel format is supported.
        /// </summary>
        /// <param name="bitmap"></param>
        private void CalculateHistogramFromPixelBuffer()
        {
            for (uint i = 0; i + 3 < ByteBuffer.Length; i += (PixelSkipRate << 2))
            {
                SortPixel(ByteBuffer.GetByte(i), PixelColor.Blue);
                SortPixel(ByteBuffer.GetByte(i + 1), PixelColor.Green);
                SortPixel(ByteBuffer.GetByte(i + 2), PixelColor.Red);
                // pixels[i+3] is Alpha channel in B8G8R8A8UIntNormalized format.
            }

            for (int i = 0; i < Resolution; i++)
            {
                red[i]   = red[i] >> 4;
                green[i] = green[i] >> 4;
                blue[i]  = blue[i] >> 4;
            }

            if (OnHistogramCreated != null)
            {
                OnHistogramCreated(red, green, blue);
            }
            IsRunning = false;
        }
        public static string ConvertToStringHex(IBuffer buffer)
        {
            var str = "";

            for (uint i = 0; i < buffer.Length; i++)
            {
                if (str != "")
                {
                    str += " ";
                }
                str += $"{buffer.GetByte(i):X2}";
            }
            return(str);
        }
예제 #8
0
            private static string AsErrorString(IBuffer buffer, string prefix)
            {
                string str = "";

                for (uint i = 0; i < buffer.Length; i++)
                {
                    if (str != "")
                    {
                        str += " ";
                    }
                    str += $"{buffer.GetByte(i):X2}";
                }
                return(prefix + str);
            }
예제 #9
0
            public static string ValueAsString(DisplayType displayType, IBuffer buffer, string prefix = "")
            {
                string str = prefix;

                switch (displayType)
                {
                default:
                case DisplayType.Hex:
                    for (uint i = 0; i < buffer.Length; i++)
                    {
                        if (str != "")
                        {
                            str += " ";
                        }
                        str += $"{buffer.GetByte(i):X2}";
                    }
                    break;

                case DisplayType.Percent:
                    if (buffer.Length != 1)
                    {
                        return(AsErrorString(buffer, "Incorrect percent length:"));
                    }
                    var b = buffer.GetByte(0);
                    str += $"{b}%";
                    break;

                case DisplayType.String:
                    var dr     = Windows.Storage.Streams.DataReader.FromBuffer(buffer);
                    var rawstr = dr.ReadString(dr.UnconsumedBufferLength);
                    rawstr = rawstr.Replace("\\", "\\\\");    // escape all back-slashes
                    rawstr = rawstr.Replace("\0", "\\0");     // replace a real NUL with an escaped null.
                    str   += rawstr;
                    break;
                }
                return(str);
            }
예제 #10
0
        public static string GetBits(IBuffer buffer)
        {
            byte[] vals = new byte[buffer.Length];
            for (uint i = 0; i < buffer.Length; i++)
            {
                vals[i] = buffer.GetByte(i);
            }
            string hexStr = BitConverter.ToString(vals);

            string[] hexSplit = hexStr.Split('-');
            string   bits     = string.Empty;

            foreach (var hex in hexSplit)
            {
                ushort longValue = Convert.ToUInt16("0x" + hex, 16);
                bits = bits + Convert.ToString(longValue, 2).PadLeft(8, '0');
            }
            return(bits);
        }
예제 #11
0
        public Rc4RandomGenerator(IBuffer key)
        {
            if (key == null)
            {
                throw new ArgumentNullException("key");
            }

            _state = new byte[256];
            var keyLength = key.Length;

            for (uint w = 0; w < 256; ++w)
            {
                _state[w] = (byte)w;
            }

            unchecked
            {
                byte j        = 0;
                uint keyIndex = 0;

                for (uint w = 0; w < 256; ++w) // Key setup
                {
                    j += (byte)(_state[w] + key.GetByte(keyIndex));

                    var temp = _state[0];
                    _state[0] = _state[j];
                    _state[j] = temp;

                    ++keyIndex;
                    if (keyIndex >= keyLength)
                    {
                        keyIndex = 0;
                    }
                }
            }

            GetRandomBytes(512);
        }
예제 #12
0
        public IBuffer Parse()
        {
            // Skip the first 32 bytes.
            // These are random bytes generated by the writer.
            this.reader.ReadBuffer(32);

            // Create a reusable hash object.
            var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
            var hash   = sha256.CreateHash();

            int            currentIndex = 0;
            List <IBuffer> buffers      = new List <IBuffer>();
            uint           requiredSize = 0;

            // Loop until we're done with the stream.
            while (this.reader.UnconsumedBufferLength > 0)
            {
                // We require at least 40 bytes per block.
                if (this.reader.UnconsumedBufferLength < 40)
                {
                    throw new FormatException("The data is incomplete.");
                }

                // Read the current index, current hash, and expected data size.
                UInt32 bufferIndex = this.reader.ReadUInt32();
                if (bufferIndex != currentIndex++)
                {
                    throw new FormatException(string.Format("The hash check failed. Unexpected buffer index: {0} (should be {1})", bufferIndex, currentIndex - 1));
                }
                IBuffer expectedHash = this.reader.ReadBuffer(32);
                UInt32  bufferSize   = this.reader.ReadUInt32();

                // If this an empty buffer? We're supposedly done.
                if (bufferSize == 0)
                {
                    for (uint i = 0; i < expectedHash.Length; i++)
                    {
                        if (expectedHash.GetByte(i) != 0)
                        {
                            throw new FormatException("The hash check failed. The final buffer had a nonzero hash.");
                        }
                    }

                    break;
                }

                // Make sure we have enough data to keep reading.
                if (this.reader.UnconsumedBufferLength < bufferSize)
                {
                    throw new FormatException("The data is incomplete.");
                }

                // Read the next chunk and check its hash.
                IBuffer data = this.reader.ReadBuffer(bufferSize);
                hash.Append(data);
                IBuffer computedHash = hash.GetValueAndReset();

                for (uint i = 0; i < expectedHash.Length; i++)
                {
                    if (expectedHash.GetByte(i) != computedHash.GetByte(i))
                    {
                        throw new FormatException("The hash check failed. One of the buffers has a bad hash.");
                    }
                }
                buffers.Add(data);
                requiredSize += data.Length;
            }

            // Build a giant buffer from the rest of them.
            uint    offset      = 0;
            IBuffer finalBuffer = (new byte[requiredSize]).AsBuffer();

            foreach (IBuffer buffer in buffers)
            {
                buffer.CopyTo(0, finalBuffer, offset, buffer.Length);
                offset += buffer.Length;
            }

            return(finalBuffer);
        }
예제 #13
0
        /// <summary>
        /// Parse the headers fields.
        /// </summary>
        /// <param name="input">The input stream.</param>
        /// <param name="buffer">The header bytes reader.</param>
        /// <returns>The file headers.</returns>
        private static async Task <FileHeaders> GetHeaders(
            IInputStream input, IBuffer buffer)
        {
            var result = new FileHeaders();

            while (true)
            {
                buffer = await input.ReadAsync(buffer, 3);

                var field = (HeaderFields)buffer.GetByte(0);
                var size  = BitConverter.ToUInt16(buffer.ToArray(1, 2), 0);

                if (size > 0)
                {
                    buffer = await input.ReadAsync(buffer, size);
                }

                switch (field)
                {
                case HeaderFields.EndOfHeader:
                    return(result);

                case HeaderFields.CompressionFlags:
                    result.UseGZip = buffer.GetByte(0) == 1;
                    break;

                case HeaderFields.EncryptionIV:
                    result.EncryptionIV = buffer
                                          .ToArray().AsBuffer();
                    break;

                case HeaderFields.MasterSeed:
                    result.MasterSeed = buffer
                                        .ToArray().AsBuffer();
                    break;

                case HeaderFields.StreamStartBytes:
                    result.StartBytes = buffer
                                        .ToArray().AsBuffer();
                    break;

                case HeaderFields.TransformSeed:
                    result.TransformSeed = buffer
                                           .ToArray().AsBuffer();
                    break;

                case HeaderFields.TransformRounds:
                    result.TransformRounds = BitConverter.ToUInt64(
                        buffer.ToArray(), 0);
                    break;

                case HeaderFields.ProtectedStreamKey:
                    result.ProtectedStreamKey = buffer
                                                .ToArray().AsBuffer();
                    break;

                case HeaderFields.InnerRandomStreamID:
                    result.RandomAlgorithm = (CrsAlgorithm)
                                             BitConverter.ToUInt32(buffer.ToArray(), 0);
                    break;
                }
            }
        }
예제 #14
0
        public static YFirmwareFile imm_Parse(string path, byte[] data)
        {
            int ofs;

            if (data[0] != 'B' || data[1] != 'Y' || data[2] != 'N' || data[3] != 0)
            {
                throw new YAPI_Exception(YAPI.INVALID_ARGUMENT, "Not a firmware file");
            }
            ofs = 4;
            int rev = data[ofs] + (data[ofs + 1] << 8);

            ofs += 2;
            string serial = imm_getString(data, ofs, YAPI.YOCTO_SERIAL_LEN);

            ofs += YAPI.YOCTO_SERIAL_LEN;
            string pictype = imm_getString(data, ofs, 20);

            ofs += 20;
            string product = imm_getString(data, ofs, YAPI.YOCTO_PRODUCTNAME_LEN);

            ofs += YAPI.YOCTO_PRODUCTNAME_LEN;
            string firmware = imm_getString(data, ofs, YAPI.YOCTO_FIRMWARE_LEN);

            ofs += YAPI.YOCTO_FIRMWARE_LEN;
            if (serial.Length >= YAPI.YOCTO_SERIAL_LEN)
            {
                throw new YAPI_Exception(YAPI.INVALID_ARGUMENT, "Bad serial_buf");
            }
            if (product.Length >= YAPI.YOCTO_PRODUCTNAME_LEN)
            {
                throw new YAPI_Exception(YAPI.INVALID_ARGUMENT, "Bad product name");
            }
            if (firmware.Length >= YAPI.YOCTO_FIRMWARE_LEN)
            {
                throw new YAPI_Exception(YAPI.INVALID_ARGUMENT, "Bad firmware revision");
            }

            int    ROM_nb_zone    = 0;
            int    FLA_nb_zone    = 0;
            int    ROM_total_size = 0;
            int    FLA_total_size = 0;
            string prog_version   = "";
            int    zone_ofs;

            switch (rev)
            {
            case BYN_REV_V4:
                zone_ofs    = BYN_HEAD_SIZE_V4;
                ROM_nb_zone = imm_getInt(data, ofs);
                ofs        += 4;
                int datasize = imm_getInt(data, ofs);
                ofs += 4;
                if (ROM_nb_zone > MAX_ROM_ZONES_PER_FILES)
                {
                    throw new YAPI_Exception(YAPI.INVALID_ARGUMENT, "Too many zones");
                }
                if (datasize != data.Length - BYN_HEAD_SIZE_V4)
                {
                    throw new YAPI_Exception(YAPI.INVALID_ARGUMENT, "Incorrect file size");
                }
                break;

            case BYN_REV_V5:
                zone_ofs     = BYN_HEAD_SIZE_V5;
                prog_version = imm_checkProgField(data, ofs, YAPI.YOCTO_FIRMWARE_LEN);
                ofs         += YAPI.YOCTO_FIRMWARE_LEN;
                ofs         += 2; //skip pad
                ROM_nb_zone  = imm_getInt(data, ofs);
                ofs         += 4;
                datasize     = imm_getInt(data, ofs);
                ofs         += 4;
                if (ROM_nb_zone > MAX_ROM_ZONES_PER_FILES)
                {
                    throw new YAPI_Exception(YAPI.INVALID_ARGUMENT, "Too many zones");
                }
                if (datasize != data.Length - BYN_HEAD_SIZE_V5)
                {
                    throw new YAPI_Exception(YAPI.INVALID_ARGUMENT, "Incorrect file size");
                }
                break;

            case BYN_REV_V6:
                zone_ofs = BYN_HEAD_SIZE_V6;
                int     size          = data.Length - BYN_MD5_OFS_V6;
                var     alg           = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Md5);
                IBuffer buff          = data.AsBuffer(BYN_MD5_OFS_V6, size);
                IBuffer messageDigest = alg.HashData(buff);
                for (uint i = 0; i < 16; i++)
                {
                    if (data[ofs + i] != messageDigest.GetByte(i))
                    {
                        throw new YAPI_Exception(YAPI.INVALID_ARGUMENT, "Invalid checksum");
                    }
                }

                ofs           += 16;
                prog_version   = imm_checkProgField(data, ofs, YAPI.YOCTO_FIRMWARE_LEN);
                ofs           += YAPI.YOCTO_FIRMWARE_LEN;
                ROM_nb_zone    = data[ofs++];
                FLA_nb_zone    = data[ofs++];
                ROM_total_size = imm_getInt(data, ofs);
                ofs           += 4;
                FLA_total_size = imm_getInt(data, ofs);
                ofs           += 4;
                if (ROM_nb_zone > MAX_ROM_ZONES_PER_FILES)
                {
                    throw new YAPI_Exception(YAPI.INVALID_ARGUMENT, "Too many ROM zones");
                }
                if (FLA_nb_zone > MAX_FLASH_ZONES_PER_FILES)
                {
                    throw new YAPI_Exception(YAPI.INVALID_ARGUMENT, "Too many FLASH zones");
                }
                break;

            default:
                throw new YAPI_Exception(YAPI.INVALID_ARGUMENT, "unknown BYN file revision");
            }
            return(new YFirmwareFile(path, serial, pictype, product, firmware, prog_version, ROM_nb_zone, FLA_nb_zone, ROM_total_size, FLA_total_size, data, zone_ofs));
        }
예제 #15
0
        /// <summary>
        /// Attempts to load KDBX header data from the provided data stream.
        /// </summary>
        /// <remarks>
        /// On success, the HeaderData property of this KdbxReader will be populated.
        /// On failure, it will be null.
        /// </remarks>
        /// <param name="stream">A stream representing a KDBX document (or header).</param>
        /// <param name="token">A token allowing the operation to be cancelled.</param>
        /// <returns>A Task representing the result of the read operation.</returns>
        public async Task <ReaderResult> ReadHeaderAsync(IRandomAccessStream stream, CancellationToken token)
        {
            HeaderData = null;

            using (DataReader reader = GetReaderForStream(stream))
            {
                ReaderResult result = await ValidateSignature(reader);

                if (result != ReaderResult.Success)
                {
                    reader.DetachStream();
                    return(result);
                }

                result = await ValidateVersion(reader);

                if (result != ReaderResult.Success)
                {
                    reader.DetachStream();
                    return(result);
                }

                // If we get this far, we've passed basic sanity checks.
                // Construct a HeaderData entity and start reading fields.
                KdbxHeaderData headerData = new KdbxHeaderData(KdbxHeaderData.Mode.Read);

                bool gotEndOfHeader = false;
                while (!gotEndOfHeader)
                {
                    if (token.IsCancellationRequested)
                    {
                        return(new ReaderResult(KdbxParserCode.OperationCancelled));
                    }

                    try
                    {
                        OuterHeaderField field = await ReadOuterHeaderField(reader, headerData);

                        if (field == OuterHeaderField.EndOfHeader)
                        {
                            gotEndOfHeader = true;
                        }

                        this.headerInitializationMap[field] = true;
                    }
                    catch (KdbxParseException e)
                    {
                        reader.DetachStream();
                        return(e.Error);
                    }
                }

                // Ensure all headers are initialized
                bool gotAllHeaders = this.headerInitializationMap.All(
                    kvp => this.headerInitializationMap[kvp.Key]
                    );

                if (!gotAllHeaders)
                {
                    reader.DetachStream();
                    return(new ReaderResult(KdbxParserCode.HeaderMissing));
                }

                // Hash entire header
                var sha256                  = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
                CryptographicHash hash      = sha256.CreateHash();
                ulong             streamPos = stream.Position;
                stream.Seek(0);
                await reader.LoadAsync((uint)streamPos);

                headerData.FullHeader = reader.ReadBuffer((uint)streamPos);

                hash.Append(headerData.FullHeader);
                IBuffer plainHeaderHash = hash.GetValueAndReset();

                if (this.parameters.UseXmlHeaderAuthentication)
                {
                    // The header was parsed successfully - finish creating the object and return success
                    headerData.HeaderHash = CryptographicBuffer.EncodeToBase64String(plainHeaderHash);
                    headerData.Size       = streamPos;
                }

                if (this.parameters.UseInlineHeaderAuthentication)
                {
                    // In KDBX4, the header hash is written directly after the header fields.
                    // After the unencrypted hash, an HMAC-SHA-256 hash of the header is written.
                    await reader.LoadAsync(32);

                    IBuffer existingPlainHash = reader.ReadBuffer(32);

                    // Validate plaintext hash
                    if (plainHeaderHash.Length != existingPlainHash.Length)
                    {
                        return(new ReaderResult(KdbxParserCode.BadHeaderHash));
                    }

                    for (uint i = 0; i < plainHeaderHash.Length; i++)
                    {
                        if (plainHeaderHash.GetByte(i) != existingPlainHash.GetByte(i))
                        {
                            return(new ReaderResult(KdbxParserCode.BadHeaderHash));
                        }
                    }

                    DebugHelper.Trace("Validated plaintext KDBX4 header hash");
                }

                if (this.parameters.Version <= KdbxVersion.Three && headerData.KdfParameters == null)
                {
                    // Need to manually populate KDF parameters for older versions
                    headerData.KdfParameters = new AesParameters(
                        headerData.TransformRounds,
                        headerData.TransformSeed
                        );
                }

                HeaderData = headerData;
                reader.DetachStream();
                return(ReaderResult.Success);
            }
        }
예제 #16
0
        /// <summary>
        /// Asynchronously attempts to unlock the document file.
        /// </summary>
        /// <remarks>
        /// Algorithm is as of this writing (11/5/2012):
        /// 0. Use UTF8 encoding with no BOM.
        /// 1. Read header.
        /// 2. Compute SHA256 hash of header.
        /// 3. Decrypt the rest of the viewModel using header parameters.
        ///     Relies on:
        ///         a. MasterSeed.Length == 32
        ///             Write masterseed to stream
        ///         b. GenerateKey32(_transformSeed, KeyEncryptionRounds)
        ///             Create raw32 (CreateRawCompositeKey32)
        ///                 Concatenate all data and Sha256
        ///             TransformKey(raw32, _transformSeed, numRounds)
        ///                 Init Rijndael:
        ///                     128 bit (16 byte) blocks
        ///                     ECB mode
        ///                     k = _transformSeed
        ///                     For numRounds:
        ///                         Transform in place raw32[0:15]
        ///                         Transform in place raw32[16:31]
        ///         c. Write 32 bytes of Key32 to stream
        ///         d. aesKey = Sha256 the stream
        ///         e. DecryptStream with aesKey and _encryptionIV
        /// 4. Verify the first 32 bytes of the decrypted viewModel match up with
        ///     "StreamStartBytes" from the header.
        /// 5. Read from the decrypted viewModel as a "HashedBlockStream"
        ///
        /// File format at the time of this writing (11/5/2012):
        ///
        /// 4 bytes: SIG1
        /// 4 bytes: SIG2
        /// Failure to match these constants results in a parse Result.
        ///
        /// 4 bytes: File version
        ///
        /// Header fields:
        /// 1 byte: Field ID
        /// 2 bytes: Field size (n)
        /// n bytes: Data
        /// </remarks>
        /// <param name="stream">An IRandomAccessStream containing the document to unlock (including the header).</param>
        /// <param name="rawKey">The aggregate raw key to use for decrypting the database.</param>
        /// <param name="token">A token allowing the parse to be cancelled.</param>
        /// <returns>A Task representing the result of the descryiption operation.</returns>
        public async Task <KdbxDecryptionResult> DecryptFile(IRandomAccessStream stream, IBuffer rawKey, CancellationToken token)
        {
            if (HeaderData == null)
            {
                throw new InvalidOperationException("Cannot decrypt database before ReadHeader has been called.");
            }

            // Init a SHA256 hash buffer and append the master seed to it
            HashAlgorithmProvider sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
            CryptographicHash     hash   = sha256.CreateHash();

            hash.Append(HeaderData.MasterSeed);

            this.rawKey = rawKey;
            this.logger.LogEvent("KdbxReader.GotRawKey", EventVerbosity.Verbose);

            // Transform the key (this can take a while)
            IBuffer transformedKey;

            try
            {
                IKdfEngine kdf = HeaderData.KdfParameters.CreateEngine();

                LoggingFields fields = new LoggingFields();
                fields.AddString("KdfEngine", kdf.GetType().Name);
                this.logger.LogEvent("KdbxReader.StartingKeyTransform", fields, EventVerbosity.Info);

                transformedKey = await HeaderData.KdfParameters.CreateEngine().TransformKeyAsync(rawKey, token)
                                 .ConfigureAwait(false);

                if (transformedKey == null)
                {
                    throw new OperationCanceledException();
                }

                this.logger.LogEvent("KdbxReader.KeyTransformSucceeded", EventVerbosity.Info);
            }
            catch (OperationCanceledException)
            {
                return(new KdbxDecryptionResult(new ReaderResult(KdbxParserCode.OperationCancelled)));
            }

            // In KDBX4, after the header is an HMAC-SHA-256 value computed over the header
            // allowing validation of header integrity.
            IBuffer          hmacKey     = HmacBlockHandler.DeriveHmacKey(transformedKey, HeaderData.MasterSeed);
            HmacBlockHandler hmacHandler = new HmacBlockHandler(hmacKey);

            IBuffer expectedMac = null;

            if (this.parameters.UseInlineHeaderAuthentication)
            {
                var algorithm = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha256);
                CryptographicHash hmacHash = algorithm.CreateHash(hmacHandler.GetKeyForBlock(UInt64.MaxValue));

                DebugHelper.Assert(HeaderData.FullHeader != null);
                hmacHash.Append(HeaderData.FullHeader);

                expectedMac = hmacHash.GetValueAndReset();
            }

            // Hash transformed k (with the master seed) to get final cipher k
            hash.Append(transformedKey);
            IBuffer cipherKey = hash.GetValueAndReset();

            this.logger.LogEvent("KdbxReader.GotFinalCipherKey", EventVerbosity.Info);

            // Decrypt the document starting from the end of the header
            ulong headerLength = HeaderData.FullHeader.Length;

            if (this.parameters.UseInlineHeaderAuthentication)
            {
                // KDBX4 has a hash at the end of the header
                headerLength += 32;
            }

            stream.Seek(headerLength);
            if (expectedMac != null)
            {
                using (DataReader macReader = GetReaderForStream(stream))
                {
                    await macReader.LoadAsync(expectedMac.Length);

                    IBuffer actualMac = macReader.ReadBuffer(expectedMac.Length);

                    for (uint i = 0; i < expectedMac.Length; i++)
                    {
                        if (expectedMac.GetByte(i) != actualMac.GetByte(i))
                        {
                            this.logger.LogEvent("KdbxReader.HmacFailure", EventVerbosity.Critical);
                            return(new KdbxDecryptionResult(new ReaderResult(KdbxParserCode.CouldNotDecrypt)));
                        }
                    }

                    macReader.DetachStream();
                }
            }

            IBuffer cipherText;

            try
            {
                cipherText = await GetCipherText(stream, hmacHandler);
            }
            catch (FormatException ex)
            {
                this.logger.LogEvent("KdbxReader.DataIntegrityFailure", ex.ToLoggingFields(), EventVerbosity.Critical);
                return(new KdbxDecryptionResult(new ReaderResult(KdbxParserCode.DataIntegrityProblem, ex)));
            }

            IBuffer decryptedFile = DecryptDatabaseData(cipherText, cipherKey);

            if (decryptedFile == null)
            {
                this.logger.LogEvent("KdbxReader.DecryptionFailure", EventVerbosity.Critical);
                return(new KdbxDecryptionResult(new ReaderResult(KdbxParserCode.CouldNotDecrypt)));
            }

            this.logger.LogEvent("KdbxReader.DecryptionSucceeded", EventVerbosity.Info);

            // Verify first 32 bytes of the clear data; if StreamStartBytes wasn't set
            // (e.g. due to KDBX4), nothing happens here.
            for (uint i = 0; i < (HeaderData.StreamStartBytes?.Length ?? 0); i++)
            {
                byte actualByte   = decryptedFile.GetByte(i);
                byte expectedByte = HeaderData.StreamStartBytes.GetByte(i);

                if (actualByte != expectedByte)
                {
                    this.logger.LogEvent("KdbxReader.PlaintextValidationFailure", EventVerbosity.Critical);
                    return(new KdbxDecryptionResult(new ReaderResult(KdbxParserCode.FirstBytesMismatch)));
                }
            }

            this.logger.LogEvent("KdbxReader.PlaintextValidationSucceeded", EventVerbosity.Verbose);

            IBuffer plainText = await UnhashAndInflate(decryptedFile);

            if (plainText == null)
            {
                return(new KdbxDecryptionResult(new ReaderResult(KdbxParserCode.CouldNotInflate)));
            }

            // Update HeaderData with info from the inner header, if relevant
            if (this.parameters.UseInnerHeader)
            {
                using (IRandomAccessStream plainTextStream = plainText.AsStream().AsRandomAccessStream())
                {
                    using (DataReader reader = GetReaderForStream(plainTextStream))
                    {
                        ReaderResult innerHeaderResult = await ReadInnerHeader(reader, HeaderData);

                        if (innerHeaderResult != ReaderResult.Success)
                        {
                            LoggingFields fields = new LoggingFields();
                            fields.AddInt32("Code", (int)innerHeaderResult.Code);
                            this.logger.LogEvent("KdbxReader.InnerHeaderReadFailure", fields, EventVerbosity.Critical);
                            return(new KdbxDecryptionResult(innerHeaderResult));
                        }

                        // Update plainText to point to the remainder of the buffer
                        uint bytesRemaining = plainText.Length - (uint)plainTextStream.Position;
                        await reader.LoadAsync(bytesRemaining);

                        plainText = reader.ReadBuffer(bytesRemaining);
                    }
                }
            }

            XDocument finalTree = null;

            try
            {
                finalTree = XDocument.Load(plainText.AsStream());
            }
            catch (XmlException)
            {
                return(null);
            }

            if (finalTree == null)
            {
                return(new KdbxDecryptionResult(new ReaderResult(KdbxParserCode.MalformedXml)));
            }

            try
            {
                KdbxDocument parsedDocument = await Task.Run(() => new KdbxDocument(finalTree.Root, HeaderData.ProtectedBinaries, HeaderData.GenerateRng(), this.parameters));

                // Validate the final parsed header hash before returning
                if (this.parameters.UseXmlHeaderAuthentication &&
                    !String.IsNullOrEmpty(parsedDocument.Metadata.HeaderHash) &&
                    parsedDocument.Metadata.HeaderHash != HeaderData.HeaderHash)
                {
                    return(new KdbxDecryptionResult(new ReaderResult(KdbxParserCode.BadHeaderHash)));
                }

                return(new KdbxDecryptionResult(this.parameters, parsedDocument, this.rawKey));
            }
            catch (KdbxParseException e)
            {
                return(new KdbxDecryptionResult(e.Error));
            }
        }
예제 #17
0
        private async Task <HTTPRequest> ProcessStream(IInputStream stream)
        {
            Dictionary <string, string> _httpHeaders   = null;
            Dictionary <string, string> _urlParameters = null;

            byte[]        data          = new byte[BufferSize];
            StringBuilder requestString = new StringBuilder();
            uint          dataRead      = BufferSize;

            IBuffer buffer = data.AsBuffer();

            string hValue = "";
            string hKey   = "";

            try
            {
                // binary data buffer index
                uint bfndx = 0;

                // Incoming message may be larger than the buffer size.
                while (dataRead == BufferSize)
                {
                    await stream.ReadAsync(buffer, BufferSize, InputStreamOptions.Partial);

                    requestString.Append(Encoding.UTF8.GetString(data, 0, data.Length));
                    dataRead = buffer.Length;

                    // read buffer index
                    uint ndx = 0;
                    do
                    {
                        switch (ParserState)
                        {
                        case HttpParserState.METHOD:
                            if (data[ndx] != ' ')
                            {
                                HTTPRequest.Method += (char)buffer.GetByte(ndx++);
                            }
                            else
                            {
                                ndx++;
                                ParserState = HttpParserState.URL;
                            }
                            break;

                        case HttpParserState.URL:
                            if (data[ndx] == '?')
                            {
                                ndx++;
                                hKey = "";
                                HTTPRequest.Execute = true;
                                _urlParameters      = new Dictionary <string, string>();
                                ParserState         = HttpParserState.URLPARM;
                            }
                            else if (data[ndx] != ' ')
                            {
                                HTTPRequest.URL += (char)buffer.GetByte(ndx++);
                            }
                            else
                            {
                                ndx++;
                                HTTPRequest.URL = WebUtility.UrlDecode(HTTPRequest.URL);
                                ParserState     = HttpParserState.VERSION;
                            }
                            break;

                        case HttpParserState.URLPARM:
                            if (data[ndx] == '=')
                            {
                                ndx++;
                                hValue      = "";
                                ParserState = HttpParserState.URLVALUE;
                            }
                            else if (data[ndx] == ' ')
                            {
                                ndx++;

                                HTTPRequest.URL = WebUtility.UrlDecode(HTTPRequest.URL);
                                ParserState     = HttpParserState.VERSION;
                            }
                            else
                            {
                                hKey += (char)buffer.GetByte(ndx++);
                            }
                            break;

                        case HttpParserState.URLVALUE:
                            if (data[ndx] == '&')
                            {
                                ndx++;
                                hKey   = WebUtility.UrlDecode(hKey);
                                hValue = WebUtility.UrlDecode(hValue);
                                _urlParameters[hKey] = _urlParameters.ContainsKey(hKey) ? _urlParameters[hKey] + ", " + hValue : hValue;
                                hKey        = "";
                                ParserState = HttpParserState.URLPARM;
                            }
                            else if (data[ndx] == ' ')
                            {
                                ndx++;
                                hKey   = WebUtility.UrlDecode(hKey);
                                hValue = WebUtility.UrlDecode(hValue);
                                _urlParameters[hKey] = _urlParameters.ContainsKey(hKey) ? _urlParameters[hKey] + ", " + hValue : hValue;
                                HTTPRequest.URL      = WebUtility.UrlDecode(HTTPRequest.URL);
                                ParserState          = HttpParserState.VERSION;
                            }
                            else
                            {
                                hValue += (char)buffer.GetByte(ndx++);
                            }
                            break;

                        case HttpParserState.VERSION:
                            if (data[ndx] == '\r')
                            {
                                ndx++;
                            }
                            else if (data[ndx] != '\n')
                            {
                                HTTPRequest.Version += (char)buffer.GetByte(ndx++);
                            }
                            else
                            {
                                ndx++;
                                hKey         = "";
                                _httpHeaders = new Dictionary <string, string>();
                                ParserState  = HttpParserState.HEADERKEY;
                            }
                            break;

                        case HttpParserState.HEADERKEY:
                            if (data[ndx] == '\r')
                            {
                                ndx++;
                            }
                            else if (data[ndx] == '\n')
                            {
                                ndx++;
                                if (_httpHeaders.ContainsKey("Content-Length"))
                                {
                                    HTTPRequest.BodySize = Convert.ToInt32(_httpHeaders["Content-Length"]);
                                    ParserState          = HttpParserState.BODY;
                                }
                                else
                                {
                                    ParserState = HttpParserState.OK;
                                }
                            }
                            else if (data[ndx] == ':')
                            {
                                ndx++;
                            }
                            else if (data[ndx] != ' ')
                            {
                                hKey += (char)buffer.GetByte(ndx++);
                            }
                            else
                            {
                                ndx++;
                                hValue      = "";
                                ParserState = HttpParserState.HEADERVALUE;
                            }
                            break;

                        case HttpParserState.HEADERVALUE:
                            if (data[ndx] == '\r')
                            {
                                ndx++;
                            }
                            else if (data[ndx] != '\n')
                            {
                                hValue += (char)buffer.GetByte(ndx++);
                            }
                            else
                            {
                                ndx++;
                                _httpHeaders.Add(hKey, hValue);
                                hKey        = "";
                                ParserState = HttpParserState.HEADERKEY;
                            }
                            break;

                        case HttpParserState.BODY:
                            // Append to request BodyData
                            this.HTTPRequest.BodyContent = Encoding.UTF8.GetString(data, 0, this.HTTPRequest.BodySize);
                            bfndx += dataRead - ndx;
                            ndx    = dataRead;
                            if (this.HTTPRequest.BodySize <= bfndx)
                            {
                                ParserState = HttpParserState.OK;
                            }
                            break;
                            //default:
                            //   ndx++;
                            //   break;
                        }
                    }while (ndx < dataRead);
                }
                ;

                // Print out the received message to the console.
                Debug.WriteLine("You received the following message : \n" + requestString);
                if (_httpHeaders != null)
                {
                    HTTPRequest.Headers = _httpHeaders.AsEnumerable();
                }
                if (_urlParameters != null)
                {
                    HTTPRequest.URLParametes = _urlParameters.AsEnumerable();
                }

                return(HTTPRequest);
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.ToString());
            }

            return(null);
        }
예제 #18
0
        /// <summary>
        /// Attempts to read an HMAC-authenticated cipher block from a given data source.
        /// Returns null on EOS.
        /// </summary>
        /// <param name="reader">Reader used to access data.</param>
        /// <param name="blockIndex">The index of this block.</param>
        /// <returns>A buffer of read data.</returns>
        public async Task <IBuffer> ReadCipherBlockAsync(DataReader reader, ulong blockIndex)
        {
            if (reader == null)
            {
                throw new ArgumentNullException(nameof(reader));
            }

            uint loadedBytes = await reader.LoadAsync(32);

            if (loadedBytes == 0)
            {
                return(null);
            }

            if (loadedBytes < 32)
            {
                throw new FormatException("Couldn't load 32 bytes for HMAC value");
            }

            IBuffer actualHmacValue = reader.ReadBuffer(32);

            loadedBytes = await reader.LoadAsync(4);

            if (loadedBytes < 4)
            {
                throw new FormatException("Couldn't load 4 bytes for block size");
            }

            int blockSize = reader.ReadInt32();

            if (blockSize == 0)
            {
                return(null);
            }
            else if (blockSize < 0)
            {
                throw new FormatException($"Block size must be a positive int32, got {blockSize}");
            }

            loadedBytes = await reader.LoadAsync((uint)blockSize);

            if (loadedBytes < blockSize)
            {
                throw new FormatException($"Expected to load {blockSize} bytes for block, only got {loadedBytes}");
            }

            IBuffer cipherText        = reader.ReadBuffer(loadedBytes);
            IBuffer expectedHmacValue = GetMacForBlock(blockIndex, cipherText);

            // Validate HMAC
            if (expectedHmacValue.Length != actualHmacValue.Length)
            {
                throw new FormatException("Expected and actual HMAC values had different lengths");
            }

            DebugHelper.Trace($"Read HMAC block #{blockIndex}; block is {loadedBytes} bytes.");
            DebugHelper.Trace($"MAC[0]: {actualHmacValue.GetByte(0)}, MAC[31]: {actualHmacValue.GetByte(31)}, data[0]: {cipherText.GetByte(0)}, data[n]: {cipherText.GetByte(loadedBytes - 1)}");

            for (uint i = 0; i < expectedHmacValue.Length; i++)
            {
                if (expectedHmacValue.GetByte(i) != actualHmacValue.GetByte(i))
                {
                    throw new FormatException($"HMAC values differed at index {i}");
                }
            }

            // Ciphertext was validated
            return(cipherText);
        }
예제 #19
0
        public static async Task <ExportResult> ExportSvgAsync(
            ExportStyle style,
            InstalledFont selectedFont,
            FontVariant selectedVariant,
            Character selectedChar,
            CanvasTextLayoutAnalysis analysis,
            CanvasTypography typography)
        {
            try
            {
                string name = GetFileName(selectedFont, selectedVariant, selectedChar, "svg");
                if (await PickFileAsync(name, "SVG", new[] { ".svg" }) is StorageFile file)
                {
                    CachedFileManager.DeferUpdates(file);

                    CanvasDevice device    = Utils.CanvasDevice;
                    Color        textColor = style == ExportStyle.Black ? Colors.Black : Colors.White;


                    // If COLR format (e.g. Segoe UI Emoji), we have special export path.
                    if (style == ExportStyle.ColorGlyph && analysis.HasColorGlyphs && !analysis.GlyphFormats.Contains(GlyphImageFormat.Svg))
                    {
                        NativeInterop interop = Utils.GetInterop();
                        List <string> paths   = new List <string>();
                        Rect          bounds  = Rect.Empty;

                        foreach (var thing in analysis.Indicies)
                        {
                            var path = interop.GetPathDatas(selectedVariant.FontFace, thing.ToArray()).First();
                            paths.Add(path.Path);

                            if (!path.Bounds.IsEmpty)
                            {
                                var left   = Math.Min(bounds.Left, path.Bounds.Left);
                                var top    = Math.Min(bounds.Top, path.Bounds.Top);
                                var right  = Math.Max(bounds.Right, path.Bounds.Right);
                                var bottom = Math.Max(bounds.Bottom, path.Bounds.Bottom);
                                bounds = new Rect(
                                    left,
                                    top,
                                    right - left,
                                    bottom - top);
                            }
                        }

                        using (CanvasSvgDocument document = Utils.GenerateSvgDocument(device, bounds, paths, analysis.Colors, invertBounds: false))
                        {
                            await Utils.WriteSvgAsync(document, file);
                        }

                        return(new ExportResult(true, file));
                    }



                    var data = GetGeometry(1024, selectedVariant, selectedChar, analysis, typography);
                    async Task SaveMonochromeAsync()
                    {
                        using CanvasSvgDocument document = Utils.GenerateSvgDocument(device, data.Bounds, data.Path, textColor);
                        await Utils.WriteSvgAsync(document, file);
                    }

                    // If the font uses SVG glyphs, we can extract the raw SVG from the font file
                    if (analysis.GlyphFormats.Contains(GlyphImageFormat.Svg))
                    {
                        string  str = null;
                        IBuffer b   = GetGlyphBuffer(selectedVariant.FontFace, selectedChar.UnicodeIndex, GlyphImageFormat.Svg);
                        if (b.Length > 2 && b.GetByte(0) == 31 && b.GetByte(1) == 139)
                        {
                            using var stream = b.AsStream();
                            using var gzip   = new GZipStream(stream, CompressionMode.Decompress);
                            using var reader = new StreamReader(gzip);
                            str = reader.ReadToEnd();
                        }
                        else
                        {
                            using var dataReader       = DataReader.FromBuffer(b);
                            dataReader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
                            str = dataReader.ReadString(b.Length);
                        }

                        if (str.StartsWith("<?xml"))
                        {
                            str = str.Remove(0, str.IndexOf(">") + 1);
                        }

                        str = str.TrimStart();

                        try
                        {
                            using (CanvasSvgDocument document = CanvasSvgDocument.LoadFromXml(Utils.CanvasDevice, str))
                            {
                                // We need to transform the SVG to fit within the default document bounds, as characters
                                // are based *above* the base origin of (0,0) as (0,0) is the Baseline (bottom left) position for a character,
                                // so by default a will appear out of bounds of the default SVG viewport (towards top left).

                                //if (!document.Root.IsAttributeSpecified("viewBox")) // Specified viewbox requires baseline transform?
                                {
                                    // We'll regroup all the elements inside a "g" / group tag,
                                    // and apply a transform to the "g" tag to try and put in
                                    // in the correct place. There's probably a more accurate way
                                    // to do this by directly setting the root viewBox, if anyone
                                    // can find the correct calculation...

                                    List <ICanvasSvgElement> elements = new List <ICanvasSvgElement>();

                                    double minTop    = 0;
                                    double minLeft   = double.MaxValue;
                                    double maxWidth  = double.MinValue;
                                    double maxHeight = double.MinValue;

                                    void ProcessChildren(CanvasSvgNamedElement root)
                                    {
                                        CanvasSvgNamedElement ele = root.FirstChild as CanvasSvgNamedElement;

                                        while (true)
                                        {
                                            CanvasSvgNamedElement next = root.GetNextSibling(ele) as CanvasSvgNamedElement;
                                            if (ele.Tag == "g")
                                            {
                                                ProcessChildren(ele);
                                            }
                                            else if (ele.Tag == "path")
                                            {
                                                // Create a XAML geometry to try and find the bounds of each character
                                                // Probably more efficient to do in Win2D, but far less code to do with XAML.
                                                Geometry gm = XamlBindingHelper.ConvertValue(typeof(Geometry), ele.GetStringAttribute("d")) as Geometry;
                                                minTop    = Math.Min(minTop, gm.Bounds.Top);
                                                minLeft   = Math.Min(minLeft, gm.Bounds.Left);
                                                maxWidth  = Math.Max(maxWidth, gm.Bounds.Width);
                                                maxHeight = Math.Max(maxHeight, gm.Bounds.Height);
                                            }
                                            ele = next;
                                            if (ele == null)
                                            {
                                                break;
                                            }
                                        }
                                    }

                                    ProcessChildren(document.Root);

                                    double top  = minTop < 0 ? minTop : 0;
                                    double left = minLeft;
                                    document.Root.SetRectangleAttribute("viewBox", new Rect(left, top, data.Bounds.Width, data.Bounds.Height));
                                }

                                await Utils.WriteSvgAsync(document, file);
                            }
                        }
                        catch
                        {
                            // Certain fonts seem to have their SVG glyphs encoded with... I don't even know what encoding.
                            // for example: https://github.com/adobe-fonts/emojione-color
                            // In these cases, fallback to monochrome black
                            await SaveMonochromeAsync();
                        }
                    }
                    else
                    {
                        await SaveMonochromeAsync();
                    }

                    await CachedFileManager.CompleteUpdatesAsync(file);

                    return(new ExportResult(true, file));
                }
            }
            catch (Exception ex)
            {
                await SimpleIoc.Default.GetInstance <IDialogService>()
                .ShowMessageBox(ex.Message, Localization.Get("SaveImageError"));
            }

            return(new ExportResult(false, null));
        }
        private static ValueParserResult ConvertHelper(DataReader dr, string decodeCommands)
        {
            var  str        = "";
            var  vps        = ValueParserSplit.ParseLine(decodeCommands);
            var  valueList  = new BCBasic.BCValueList();
            bool isOptional = false;

            for (int i = 0; i < vps.Count; i++)
            {
                var stritem = "";

                var command                = vps[i];
                var readcmd                = command.ByteFormatPrimary;
                var readindicator          = readcmd[0];
                var displayFormat          = command.DisplayFormatPrimary;
                var displayFormatSecondary = command.Get(1, 1);

                var name = command.NamePrimary;
                if (string.IsNullOrEmpty(name))
                {
                    name = $"param{i}";
                }
                var units = command.UnitsPrimary;

                var    resultState = ResultState.IsDouble; // the most common result
                double dvalue      = double.NaN;
                try
                {
                    switch (readindicator)
                    {
                    case 'F':     // FLOAT
                        if (dr.UnconsumedBufferLength == 0)
                        {
                            if (isOptional)
                            {
                                dvalue  = double.NaN;
                                stritem = "";
                                break;
                            }
                            else
                            {
                                return(ValueParserResult.CreateError(decodeCommands, $"Missing data with {readcmd} field {i+1}"));
                            }
                        }
                        switch (readcmd)
                        {
                        case "F32":
                        {
                            dvalue = dr.ReadSingle();
                            switch (displayFormat)
                            {
                            case "":
                            case "FIXED":
                                displayFormat = (displayFormatSecondary == "") ? "N3" : displayFormatSecondary;
                                break;

                            case "DEC":
                                displayFormat = (displayFormatSecondary == "") ? "N0" : displayFormatSecondary;
                                break;

                            case "HEX":
                                return(ValueParserResult.CreateError(decodeCommands, $"Float displayFormat unrecognized; should be FIXED {displayFormat}"));
                            }
                            stritem = dvalue.ToString(displayFormat);             // e.g. N3 for 3 digits
                        }
                        break;

                        case "F64":
                        {
                            dvalue = dr.ReadDouble();
                            switch (displayFormat)
                            {
                            case "":
                            case "FIXED":
                                displayFormat = (displayFormatSecondary == "") ? "N3" : displayFormatSecondary;
                                break;

                            case "DEC":
                                displayFormat = (displayFormatSecondary == "") ? "N0" : displayFormatSecondary;
                                break;

                            case "HEX":
                                return(ValueParserResult.CreateError(decodeCommands, $"Float displayFormat unrecognized; should be FIXED {displayFormat}"));
                            }
                            stritem = dvalue.ToString(displayFormat);             // e.g. N3 for 3 digits
                        }
                        break;

                        default:
                            return(ValueParserResult.CreateError(decodeCommands, $"Float command unrecognized; should be F32 or F64 not {readcmd}"));
                        }
                        break;

                    case 'I':
                        if (dr.UnconsumedBufferLength == 0)
                        {
                            if (isOptional)
                            {
                                dvalue  = double.NaN;
                                stritem = "";
                                break;
                            }
                            else
                            {
                                return(ValueParserResult.CreateError(decodeCommands, $"Missing data with {readcmd} field {i + 1}"));
                            }
                        }
                        switch (readcmd)
                        {
                        case "I8":
                        case "I16":
                        case "I24":
                        case "I32":
                        {
                            string floatFormat = "F2";
                            string intFormat   = "X6";
                            switch (readcmd)
                            {
                            case "I8":
                            {
                                var value = (sbyte)dr.ReadByte();
                                dvalue = (double)value;
                            }
                            break;

                            case "I16":
                            {
                                var value = dr.ReadInt16();
                                dvalue = (double)value;
                            }
                            break;

                            case "I24":
                            {
                                var b0    = dr.ReadByte();
                                var b1    = dr.ReadByte();
                                var b2    = dr.ReadByte();
                                var msb   = (sbyte)(dr.ByteOrder == ByteOrder.BigEndian ? b0 : b2);
                                var lsb   = dr.ByteOrder == ByteOrder.BigEndian ? b2 : b0;
                                int value = (int)(msb << 16) + (b1 << 8) + (lsb);
                                dvalue = (double)value;
                            }
                            break;

                            case "I32":
                            {
                                var value = dr.ReadInt32();
                                dvalue    = (double)value;
                                intFormat = "X8";
                            }
                            break;
                            }
                            string calculateCommand = command.Get(0, 1);             // e.g. for I24^100_/ for TI 1350 barometer values
                            if (!string.IsNullOrEmpty(calculateCommand))
                            {
                                dvalue = ValueCalculate.Calculate(calculateCommand, dvalue).D;
                                if (double.IsNaN(dvalue))
                                {
                                    return(ValueParserResult.CreateError(decodeCommands, $"Calculation failed for {calculateCommand} in {readcmd}"));
                                }
                                else
                                {
                                    // Everything worked and got a value
                                    stritem = DoubleToString(dvalue, displayFormat, displayFormatSecondary);
                                    if (stritem == null)
                                    {
                                        return(ValueParserResult.CreateError(decodeCommands, $"Integer display format command unrecognized; should be FIXED or HEX or DEC not {displayFormat} in {readcmd}"));
                                    }
                                }
                            }
                            else
                            {
                                if (displayFormat == "")
                                {
                                    displayFormat = "HEX";
                                }
                                stritem = DoubleToString(dvalue, displayFormat, displayFormatSecondary, floatFormat, intFormat);
                                if (stritem == null)
                                {
                                    return(ValueParserResult.CreateError(decodeCommands, $"Integer display format command unrecognized; should be FIXED or HEX or DEC not {displayFormat} in {readcmd}"));
                                }
                            }
                        }
                        break;

                        default:
                            return(ValueParserResult.CreateError(decodeCommands, $"Integer command unrecognized; should be I8/16/24/32 not {readcmd}"));
                        }
                        break;

                    case 'O':
                        switch (readcmd)
                        {
                        case "OEB":
                            resultState  = ResultState.NoResult;
                            dr.ByteOrder = ByteOrder.LittleEndian;
                            break;

                        case "OEL":
                            resultState  = ResultState.NoResult;
                            dr.ByteOrder = ByteOrder.LittleEndian;
                            break;

                        case "OOPT":
                            isOptional = true;
                            break;

                        default:
                            return(ValueParserResult.CreateError(decodeCommands, $"Optional command unrecognized; should be OEL or OEB not {readcmd}"));
                        }
                        break;

                    case 'Q':
                        if (dr.UnconsumedBufferLength == 0)
                        {
                            if (isOptional)
                            {
                                dvalue  = double.NaN;
                                stritem = "";
                                break;
                            }
                            else
                            {
                                return(ValueParserResult.CreateError(decodeCommands, $"Missing data with {readcmd} field {i + 1}"));
                            }
                        }
                        // e.g. Q12Q4.Fixed
                        {
                            var subtypes = readcmd.Split(new char[] { 'Q' });
                            if (subtypes.Length != 3)     // Actually 2, but first is blank
                            {
                                return(ValueParserResult.CreateError(decodeCommands, $"Q command unrecognized; wrong number of Qs {readcmd}"));
                            }
                            stritem = FixedQuotientHelper(dr, subtypes[1], subtypes[2], displayFormat, out dvalue);
                            //NOTE: fail on failure
                        }
                        break;

                    case 'U':
                        if (dr.UnconsumedBufferLength == 0)
                        {
                            if (isOptional)
                            {
                                dvalue  = double.NaN;
                                stritem = "";
                                break;
                            }
                            else
                            {
                                return(ValueParserResult.CreateError(decodeCommands, $"Missing data with {readcmd} field {i + 1}"));
                            }
                        }
                        switch (readcmd)
                        {
                        case "U8":
                        case "U16":
                        case "U24":
                        case "U32":
                            string xfmt = "X2";
                            switch (readcmd)
                            {
                            case "U8":
                            {
                                var value = dr.ReadByte();
                                dvalue = (double)value;
                                xfmt   = "X2";
                            }
                            break;

                            case "U16":
                            {
                                var value = dr.ReadUInt16();
                                dvalue = (double)value;
                                xfmt   = "X4";
                            }
                            break;

                            case "U24":
                            {
                                var b0    = dr.ReadByte();
                                var b1    = dr.ReadByte();
                                var b2    = dr.ReadByte();
                                var msb   = (byte)(dr.ByteOrder == ByteOrder.BigEndian ? b0 : b2);
                                var lsb   = dr.ByteOrder == ByteOrder.BigEndian ? b2 : b0;
                                int value = (int)(msb << 16) + (b1 << 8) + (lsb);
                                dvalue = (double)value;
                            }
                            break;

                            case "U32":
                            {
                                var value = dr.ReadUInt32();
                                dvalue = (double)value;
                                xfmt   = "X8";
                            }
                            break;
                            }
                            string calculateCommand = command.Get(0, 1);         // e.g. for I24^100_/ for TI 1350 barometer values
                            if (!string.IsNullOrEmpty(calculateCommand))
                            {
                                dvalue = ValueCalculate.Calculate(calculateCommand, dvalue).D;
                                if (double.IsNaN(dvalue))
                                {
                                    return(ValueParserResult.CreateError(decodeCommands, $"Calculation failed for {calculateCommand} in {readcmd}"));
                                }
                                else
                                {
                                    stritem = DoubleToString(dvalue, displayFormat, displayFormatSecondary);
                                    if (stritem == null)
                                    {
                                        return(ValueParserResult.CreateError(decodeCommands, $"Integer display format command unrecognized; should be FIXED or HEX or DEC not {displayFormat} in {readcmd}"));
                                    }
                                }
                            }
                            else
                            {
                                if (displayFormat == "")
                                {
                                    displayFormat = "HEX";
                                }
                                stritem = DoubleToString(dvalue, displayFormat, displayFormatSecondary, "F2", xfmt);
                                if (stritem == null)
                                {
                                    return(ValueParserResult.CreateError(decodeCommands, $"Integer display format command unrecognized;\nshould be FIXED or HEX or DEC not {displayFormat} in {readcmd}"));
                                }
                            }
                            break;

                        default:
                            return(ValueParserResult.CreateError(decodeCommands, $"UInteger command unrecognized;\nshould be U8/U16/U24/U32 not {readcmd}"));
                        }
                        break;

                    case '/':
                        // e.g. /U8/I16|Fixed
                        if (dr.UnconsumedBufferLength == 0)
                        {
                            if (isOptional)
                            {
                                dvalue  = double.NaN;
                                stritem = "";
                                break;
                            }
                            else
                            {
                                return(ValueParserResult.CreateError(decodeCommands, $"Missing data with {readcmd} field {i + 1}"));
                            }
                        }
                        {
                            var subtypes = readcmd.Split(new char[] { '/' });
                            if (subtypes.Length != 3)     // Actually 2, but first is blank
                            {
                                return(ValueParserResult.CreateError(decodeCommands, $"/ command unrecognized; wrong number of slashes {readcmd}"));
                            }
                            stritem = FixedFractionHelper(dr, subtypes[1], subtypes[2], displayFormat, out dvalue);
                            // NOTE: fail on failure
                        }
                        break;

                    default:
                        if (readcmd != readcmd.ToUpper())
                        {
                            System.Diagnostics.Debug.WriteLine("ERROR: readcmd {readcmd} but should be uppercase");
                        }
                        switch (readcmd.ToUpper())     //NOTE: should be all-uppercase; any lowercase is wrong
                        {
                        case "STRING":
                        {
                            try
                            {
                                stritem = dr.ReadString(dr.UnconsumedBufferLength);
                                switch (displayFormat)
                                {
                                case "":
                                case "ASCII":
                                    stritem = EscapeString(stritem, displayFormatSecondary);
                                    break;

                                case "Eddystone":
                                    stritem = BluetoothDefinitionLanguage.Eddystone.EddystoneUrlToString(stritem);
                                    break;

                                default:
                                    return(ValueParserResult.CreateError(decodeCommands, $"Unknown string format type {displayFormat}"));
                                }
                            }
                            catch (Exception)
                            {
                                // The string can't be read. Let's try reading as a buffer instead.
                                IBuffer buffer = dr.ReadBuffer(dr.UnconsumedBufferLength);
                                for (uint ii = 0; ii < buffer.Length; ii++)
                                {
                                    if (ii != 0)
                                    {
                                        stritem += " ";
                                    }
                                    stritem += buffer.GetByte(ii).ToString("X2");
                                }
                            }
                            resultState = ResultState.IsString;
                        }
                        break;

                        case "BYTES":
                        {
                            IBuffer buffer = dr.ReadBuffer(dr.UnconsumedBufferLength);
                            for (uint ii = 0; ii < buffer.Length; ii++)
                            {
                                if (ii != 0)
                                {
                                    stritem += " ";
                                }
                                stritem += buffer.GetByte(ii).ToString("X2");
                            }
                            resultState = ResultState.IsString;
                        }
                        break;

                        default:
                            return(ValueParserResult.CreateError(decodeCommands, $"Other command unrecognized; should be String or Bytes {readcmd}"));
                        }
                        break;
                    }
                }
                catch (Exception e)
                {
                    stritem = $"EXCEPTION reading data {e} index {i} command {command} len {dr.UnconsumedBufferLength}";
                    return(ValueParserResult.CreateError(str + stritem, stritem));
                }
                switch (resultState)
                {
                case ResultState.IsDouble:
                    valueList.SetProperty(name, new BCBasic.BCValue(dvalue));
                    break;

                case ResultState.IsString:
                    valueList.SetProperty(name, new BCBasic.BCValue(stritem));
                    break;
                }

                if (str != "")
                {
                    str += " ";
                }
                str += stritem;

                if (dr.UnconsumedBufferLength <= 0)
                {
                    break;
                }
            }
            return(ValueParserResult.CreateOk(str, valueList));
        }