private static byte[] DecompressForChained(Smb2CompressedPacket packet, Smb2CompressionInfo compressionInfo, Smb2Role role)
        {
            var p = packet as Smb2ChainedCompressedPacket;

            var result = p.Payloads.SelectMany(tuple =>
            {
                var payloadHeader = tuple.Item1;

                switch (payloadHeader.CompressionAlgorithm)
                {
                case CompressionAlgorithm.NONE:
                    {
                        return(tuple.Item2 as byte[]);
                    }
                    break;

                case CompressionAlgorithm.LZNT1:
                case CompressionAlgorithm.LZ77:
                case CompressionAlgorithm.LZ77Huffman:
                    {
                        var decompressor = GetDecompressor(payloadHeader.CompressionAlgorithm);

                        var decompressedData = decompressor.Decompress(tuple.Item2 as byte[]);

                        if (decompressedData.Length != payloadHeader.OriginalPayloadSize)
                        {
                            throw new InvalidOperationException("The length decompressed chained payload is inconsistent with OriginalPayloadSize of payload header.");
                        }

                        return(decompressedData);
                    }
                    break;

                case CompressionAlgorithm.Pattern_V1:
                    {
                        var pattern = (SMB2_COMPRESSION_PATTERN_PAYLOAD_V1)tuple.Item2;

                        return(Enumerable.Repeat(pattern.Pattern, (int)pattern.Repetitions).ToArray());
                    }
                    break;

                default:
                    {
                        throw new InvalidOperationException("Unexpected compression algorithm!");
                    }
                    break;
                }
            });

            return(result.ToArray());
        }
Example #2
0
        /// <summary>
        /// Compress SMB2 packet.
        /// </summary>
        /// <param name="packet">The SMB2 packet.</param>
        /// <param name="compressionInfo">Compression info.</param>
        /// <param name="role">SMB2 role.</param>
        /// <param name="offset">The offset where compression start, default zero.</param>
        /// <returns></returns>
        public static Smb2Packet Compress(Smb2CompressiblePacket packet, Smb2CompressionInfo compressionInfo, Smb2Role role, uint offset = 0)
        {
            var compressionAlgorithm = GetCompressionAlgorithm(packet, compressionInfo, role);

            if (compressionAlgorithm == CompressionAlgorithm.NONE)
            {
                return(packet);
            }

            var packetBytes = packet.ToBytes();

            var compressor = GetCompressor(compressionAlgorithm);

            var compressedPacket = new Smb2CompressedPacket();

            compressedPacket.Header.ProtocolId = Smb2Consts.ProtocolIdInCompressionTransformHeader;
            compressedPacket.Header.OriginalCompressedSegmentSize = (uint)packetBytes.Length;
            compressedPacket.Header.CompressionAlgorithm          = compressionAlgorithm;
            compressedPacket.Header.Reserved  = 0;
            compressedPacket.Header.Offset    = offset;
            compressedPacket.UncompressedData = packetBytes.Take((int)offset).ToArray();
            compressedPacket.CompressedData   = compressor.Compress(packetBytes.Skip((int)offset).ToArray());

            // HACK: fake size
            if (((Smb2SinglePacket)packet).Header.Command == Smb2Command.WRITE)
            {
                ((Smb2WriteRequestPacket)packet).PayLoad.Length       += 0x1000;
                compressedPacket.Header.OriginalCompressedSegmentSize += 0x1000;
            }

            // HACK: force compressed packet to be sent
            return(compressedPacket);

            // var compressedPackectBytes = compressedPacket.ToBytes();

            // Check whether compression shrinks the on-wire packet size
            // if (compressedPackectBytes.Length < packetBytes.Length)
            // {
            //     compressedPacket.OriginalPacket = packet;
            //     return compressedPacket;
            // }
            // else
            // {
            //     return packet;
            // }
        }
Example #3
0
        /// <summary>
        /// Decompress the Smb2CompressedPacket.
        /// </summary>
        /// <param name="packet">The compressed packet.</param>
        /// <param name="compressionInfo">Compression info.</param>
        /// <param name="role">SMB2 role.</param>
        /// <returns>Byte array containing the decompressed packet.</returns>
        public static byte[] Decompress(Smb2CompressedPacket packet, Smb2CompressionInfo compressionInfo, Smb2Role role)
        {
            if (packet.Header.CompressionAlgorithm == CompressionAlgorithm.NONE)
            {
                throw new InvalidOperationException("Invalid CompressionAlgorithm in header!");
            }

            if (!compressionInfo.CompressionIds.Any(compressionAlgorithm => compressionAlgorithm == packet.Header.CompressionAlgorithm))
            {
                throw new InvalidOperationException("The CompressionAlgorithm is not supported!");
            }

            var decompressor = GetDecompressor(packet.Header.CompressionAlgorithm);

            var decompressedBytes = decompressor.Decompress(packet.CompressedData);

            var originalPacketBytes = packet.UncompressedData.Concat(decompressedBytes).ToArray();

            return(originalPacketBytes);
        }
        /// <summary>
        /// Decompress the Smb2CompressedPacket.
        /// </summary>
        /// <param name="packet">The compressed packet.</param>
        /// <param name="compressionInfo">Compression info.</param>
        /// <param name="role">SMB2 role.</param>
        /// <returns>Byte array containing the decompressed packet.</returns>
        public static byte[] Decompress(Smb2CompressedPacket packet, Smb2CompressionInfo compressionInfo, Smb2Role role)
        {
            bool isChained = packet.Header.Flags.HasFlag(Compression_Transform_Header_Flags.SMB2_COMPRESSION_FLAG_CHAINED);

            byte[] decompressedData;

            if (isChained)
            {
                decompressedData = DecompressForChained(packet, compressionInfo, role);
            }
            else
            {
                decompressedData = DecompressForNonChained(packet, compressionInfo, role);
            }

            if (decompressedData.Length != packet.Header.OriginalCompressedSegmentSize)
            {
                throw new InvalidOperationException($"The length of decompressed data (0x{decompressedData.Length:X08}) is inconsistent with compression header (0x{packet.Header.OriginalCompressedSegmentSize:X08}).");
            }

            return(decompressedData);
        }
Example #5
0
            /// <summary>
            /// Run compression test against given input parameters.
            ///     1. Write testData to test file given by treeId and fileId, and compress message based on compressWriteRequest using compressionAlgorithmForTest.
            ///     2. Read out the data just written, and request compressing READ response message based on compressReadRequest.
            ///     3. Check whether the READ response based on readResponseShouldBeCompressed.
            ///     4. Check whether data read out is equal to test data.
            /// </summary>
            /// <param name="client">SMB2 functional client to use.</param>
            /// <param name="treeId">TreeId to use.</param>
            /// <param name="fileId">FileId to use.</param>
            public void Run(Smb2FunctionalClient client, uint treeId, FILEID fileId)
            {
                if (compressionAlgorithmForTest != CompressionAlgorithm.NONE)
                {
                    if (!client.Smb2Client.CompressionInfo.CompressionIds.Any(compressionAlgorithmSupported => compressionAlgorithmSupported == compressionAlgorithmForTest))
                    {
                        // The specified compression algorithm is not supported by SUT.
                        return;
                    }
                }

                BaseTestSite.Log.Add(
                    LogEntryKind.TestStep,
                    "Test will trigger WRITE request with CompressWrite: {0} and preferred compression algorithm: {1}.",
                    compressWriteRequest,
                    compressionAlgorithmForTest
                    );

                // Specify the compression algorithm for write request.
                client.Smb2Client.CompressionInfo.PreferredCompressionAlgorithm = compressionAlgorithmForTest;

                if (compressWriteRequestBufferOnly)
                {
                    client.Smb2Client.CompressionInfo.CompressBufferOnly = true;
                }

                client.Write(treeId, fileId, testData, compressWrite: compressWriteRequest);

                if (compressWriteRequestBufferOnly)
                {
                    client.Smb2Client.CompressionInfo.CompressBufferOnly = false;
                }

                byte[] readOutData = null;

                bool readResponseIsCompressed             = false;
                bool readResponseIsChained                = false;
                Smb2ReadResponsePacket readResponsePacket = null;
                Smb2CompressedPacket   compressedPacket   = null;

                Action <Smb2Packet> Smb2Client_PacketReceived = (Smb2Packet obj) =>
                {
                    if (obj is Smb2ReadResponsePacket)
                    {
                        readResponsePacket       = obj as Smb2ReadResponsePacket;
                        readResponseIsCompressed = readResponsePacket.Compressed;

                        compressedPacket = readResponsePacket.CompressedPacket;

                        if (compressedPacket is Smb2ChainedCompressedPacket)
                        {
                            readResponseIsChained = true;
                        }
                    }
                };

                BaseTestSite.Log.Add(
                    LogEntryKind.TestStep,
                    "Test will trigger READ request with CompressRead: {0} and check whether READ response is compressed: {1}.",
                    compressReadRequest,
                    readResponseShouldBeCompressed
                    );

                client.Smb2Client.PacketReceived += Smb2Client_PacketReceived;
                client.Read(treeId, fileId, 0, (uint)testData.Length, out readOutData, compressRead: compressReadRequest);
                client.Smb2Client.PacketReceived -= Smb2Client_PacketReceived;

                if (compressReadRequest)
                {
                    if (readResponseShouldBeCompressed)
                    {
                        BaseTestSite.Assert.IsTrue(readResponseIsCompressed && compressedPacket != null, "[MS-SMB2] section 3.3.5.12: When SMB2_READFLAG_REQUEST_COMPRESSED is specified in read request, the server MUST compress the message if compression will shrink the message size.");

                        BaseTestSite.Log.Add(LogEntryKind.Debug, "Read response is compressed using {0}.", compressedPacket.Header.CompressionAlgorithm);

                        if (readResponseShouldBeChained)
                        {
                            BaseTestSite.Assert.IsTrue(readResponseIsChained, "The read response should be chained.");
                        }
                        else
                        {
                            BaseTestSite.Assert.IsFalse(readResponseIsChained, "The read response should not be chained.");
                        }
                    }
                    else
                    {
                        BaseTestSite.Assert.IsTrue(!readResponseIsCompressed && compressedPacket == null, "[MS-SMB2] section 3.3.5.12: When SMB2_READFLAG_REQUEST_COMPRESSED is specified in read request, the server MUST NOT compress the message if compression will not shrink the message size.");
                    }
                }
                else
                {
                    BaseTestSite.Log.Add(LogEntryKind.Debug, "SMB2_READFLAG_REQUEST_COMPRESSED is not specified in read request, and read response is compressed: {0}.", readResponseIsCompressed);

                    if (readResponseIsCompressed)
                    {
                        BaseTestSite.Assert.IsTrue(compressedPacket != null, "Compressed packet is received.");
                        BaseTestSite.Log.Add(LogEntryKind.Debug, "Read response is compressed using {0}.", compressedPacket.Header.CompressionAlgorithm);
                    }
                }

                BaseTestSite.Assert.IsTrue(Enumerable.SequenceEqual(testData, readOutData), "The read out content MUST be the same with that is written.");
            }
Example #6
0
            /// <summary>
            /// Run compression test against given input parameters.
            ///     1. Write testData to test file given by treeId and fileId, and compress message based on compressWriteRequest using compressionAlgorithmForTest.
            ///     2. Read out the data just written, and request compressing READ response message based on compressReadRequest.
            ///     3. Check whether the READ response based on readResponseShouldBeCompressed.
            ///     4. Check whether data read out is equal to test data.
            /// </summary>
            /// <param name="client">SMB2 functional client to use.</param>
            /// <param name="variant">Compression test variant.</param>
            /// <param name="treeId">TreeId to use.</param>
            /// <param name="fileId">FileId to use.</param>
            /// <param name="isLargeFile">Whether is large file.</param>
            public void Run(Smb2FunctionalClient client, CompressionTestVariant variant, uint treeId, FILEID fileId, bool isLargeFile = false)
            {
                if (compressionAlgorithmForTest != CompressionAlgorithm.NONE)
                {
                    if (!client.Smb2Client.CompressionInfo.CompressionIds.Any(compressionAlgorithmSupported => compressionAlgorithmSupported == compressionAlgorithmForTest))
                    {
                        // The specified compression algorithm is not supported by SUT.
                        return;
                    }
                }

                BaseTestSite.Log.Add(
                    LogEntryKind.TestStep,
                    "Test will trigger WRITE request with CompressWrite: {0} and preferred compression algorithm: {1}.",
                    compressWriteRequest,
                    compressionAlgorithmForTest
                    );

                // Specify the compression algorithm for write request.
                client.Smb2Client.CompressionInfo.PreferredCompressionAlgorithm = compressionAlgorithmForTest;

                if (compressWriteRequestBufferOnly)
                {
                    client.Smb2Client.CompressionInfo.CompressBufferOnly = true;
                }

                if (isLargeFile)
                {
                    // Write several times for existing testData for large data write and read tests
                    // Base testData:
                    // Length of LZ77 and LZ77 Huffman testData is 300 bytes.
                    // Length of LZNT1 testData is 142 bytes.
                    // Length of Pattern_V1 testData is 256 bytes.
                    // Length of Compressible data testData is 2048 bytes.
                    // We will change testData to 1 MB, and write 100 times to generate a 100 MB file
                    int   writeRequestCount = 100;
                    ulong offset            = 0;

                    // According to real experience with copying large file to shared folder, the client will request 1 MB packet per request.
                    // Change testData to 512 bytes * 2048 = 1 megabytes, so we can test large file
                    if (compressionAlgorithmForTest == CompressionAlgorithm.LZ77Huffman)
                    {
                        // LZ77Huffman match length needs to be less than 65538, so we use 512*128=65536.
                        var test512Bytes = Enumerable.Repeat(testData, 2).SelectMany(a => a).Take(512).ToArray();
                        testData = Enumerable.Repeat(test512Bytes, 128).SelectMany(b => b).ToArray();
                    }
                    else if (variant == CompressionTestVariant.ChainedCompressibleWritePatternV1AtFront)
                    {
                        // For ChainedCompressibleWritePatternV1AtFront, we need to prepend some data which can be compressed with PatternV1.
                        var test256Bytes = commonCompressibleData.Take(256).ToArray();
                        var newCommonCompressibleData = Enumerable.Repeat(test256Bytes, 4 * 1024 - 1).SelectMany(b => b).ToArray();
                        testData = GenerateByteArray(exampleTestData[CompressionAlgorithm.Pattern_V1], newCommonCompressibleData);
                    }
                    else if (variant == CompressionTestVariant.ChainedCompressibleRead || variant == CompressionTestVariant.ChainedCompressibleWritePatternV1AtEnd)
                    {
                        // For ChainedCompressibleRead or ChainedCompressibleWritePatternV1AtEnd, we need to append some data which can be compressed with PatternV1.
                        var test256Bytes = commonCompressibleData.Take(256).ToArray();
                        var newCommonCompressibleData = Enumerable.Repeat(test256Bytes, 4 * 1024 - 1).SelectMany(b => b).ToArray();
                        testData = GenerateByteArray(newCommonCompressibleData, exampleTestData[CompressionAlgorithm.Pattern_V1]);
                    }
                    else if (variant == CompressionTestVariant.ChainedCompressibleWritePatternV1AtFrontAndEnd)
                    {
                        // For ChainedCompressibleWritePatternV1AtFrontAndEnd, we need to add start and end with PatternV1.
                        var test256Bytes = commonCompressibleData.Take(256).ToArray();
                        var newCommonCompressibleData = Enumerable.Repeat(test256Bytes, 4 * 1024 - 2).SelectMany(b => b).ToArray();
                        testData = GenerateByteArray(exampleTestData[CompressionAlgorithm.Pattern_V1], newCommonCompressibleData, exampleTestData[CompressionAlgorithm.Pattern_V1]);
                    }
                    else
                    {
                        // For other cases, testData with a length of 1 MB will be used.
                        var test512Bytes = Enumerable.Repeat(testData, 4).SelectMany(a => a).Take(512).ToArray();
                        testData = Enumerable.Repeat(test512Bytes, 2048).SelectMany(b => b).ToArray();
                    }
                    int requestBytes = testData.Length;

                    for (int time = 0; time < writeRequestCount; time++)
                    {
                        client.Write(treeId, fileId, testData, offset, compressWrite: compressWriteRequest);
                        offset += (uint)requestBytes;
                    }
                }
                else
                {
                    client.Write(treeId, fileId, testData, compressWrite: compressWriteRequest);
                }

                if (compressWriteRequestBufferOnly)
                {
                    client.Smb2Client.CompressionInfo.CompressBufferOnly = false;
                }

                byte[] readOutData = null;

                bool readResponseIsCompressed             = false;
                bool readResponseIsChained                = false;
                Smb2ReadResponsePacket readResponsePacket = null;
                Smb2CompressedPacket   compressedPacket   = null;

                Action <Smb2Packet> Smb2Client_PacketReceived = (Smb2Packet obj) =>
                {
                    if (obj is Smb2ReadResponsePacket)
                    {
                        readResponsePacket       = obj as Smb2ReadResponsePacket;
                        readResponseIsCompressed = readResponsePacket.Compressed;

                        compressedPacket = readResponsePacket.CompressedPacket;

                        if (compressedPacket is Smb2ChainedCompressedPacket)
                        {
                            readResponseIsChained = true;
                        }
                    }
                };

                BaseTestSite.Log.Add(
                    LogEntryKind.TestStep,
                    "Test will trigger READ request with CompressRead: {0} and check whether READ response is compressed: {1}.",
                    compressReadRequest,
                    readResponseShouldBeCompressed
                    );

                client.Smb2Client.PacketReceived += Smb2Client_PacketReceived;

                if (isLargeFile)
                {
                    // Read several times for exist testData for large data write and read tests
                    int   requestBytes     = testData.Length;
                    int   readRequestCount = 100;
                    ulong offset           = 0;

                    for (int time = 0; time < readRequestCount; time++)
                    {
                        client.Read(treeId, fileId, (uint)offset, (uint)requestBytes, out readOutData, compressRead: compressReadRequest);
                        BaseTestSite.Assert.IsTrue(Enumerable.SequenceEqual(testData, readOutData), $"Request times:{time + 1}, packet offset: {offset}, byteSize:{requestBytes}, the read out content MUST be the same with that is written.");
                        offset += (uint)requestBytes;
                    }
                }
                else
                {
                    client.Read(treeId, fileId, 0, (uint)testData.Length, out readOutData, compressRead: compressReadRequest);
                    BaseTestSite.Assert.IsTrue(Enumerable.SequenceEqual(testData, readOutData), "The read out content MUST be the same with that is written.");
                }

                client.Smb2Client.PacketReceived -= Smb2Client_PacketReceived;

                if (compressReadRequest)
                {
                    if (readResponseShouldBeCompressed)
                    {
                        BaseTestSite.Assert.IsTrue(readResponseIsCompressed && compressedPacket != null, "[MS-SMB2] section 3.3.5.12: When SMB2_READFLAG_REQUEST_COMPRESSED is specified in read request, the server MUST compress the message if compression will shrink the message size.");

                        BaseTestSite.Log.Add(LogEntryKind.Debug, "Read response is compressed using {0}.", compressedPacket.Header.CompressionAlgorithm);

                        if (readResponseShouldBeChained)
                        {
                            BaseTestSite.Assert.IsTrue(readResponseIsChained, "The read response should be chained.");
                        }
                        else
                        {
                            BaseTestSite.Assert.IsFalse(readResponseIsChained, "The read response should not be chained.");
                        }
                    }
                    else
                    {
                        BaseTestSite.Assert.IsTrue(!readResponseIsCompressed && compressedPacket == null, "[MS-SMB2] section 3.3.5.12: When SMB2_READFLAG_REQUEST_COMPRESSED is specified in read request, the server MUST NOT compress the message if compression will not shrink the message size.");
                    }
                }
                else
                {
                    BaseTestSite.Log.Add(LogEntryKind.Debug, "SMB2_READFLAG_REQUEST_COMPRESSED is not specified in read request, and read response is compressed: {0}.", readResponseIsCompressed);

                    if (readResponseIsCompressed)
                    {
                        BaseTestSite.Assert.IsTrue(compressedPacket != null, "Compressed packet is received.");
                        BaseTestSite.Log.Add(LogEntryKind.Debug, "Read response is compressed using {0}.", compressedPacket.Header.CompressionAlgorithm);
                    }
                }
            }