xref: /linux/fs/smb/common/compress/compress.c (revision fab183d632628381b466a41479489541ac0e29a0)
16234f501SNamjae Jeon // SPDX-License-Identifier: GPL-2.0-or-later
26234f501SNamjae Jeon /*
36234f501SNamjae Jeon  * SMB2 compression transform helpers.
46234f501SNamjae Jeon  *
56234f501SNamjae Jeon  * Copyright (C) 2026 Namjae Jeon <linkinjeon@kernel.org>
66234f501SNamjae Jeon  */
76234f501SNamjae Jeon #include <linux/module.h>
86234f501SNamjae Jeon #include <linux/overflow.h>
96234f501SNamjae Jeon #include <linux/string.h>
106234f501SNamjae Jeon #include <linux/unaligned.h>
116234f501SNamjae Jeon 
126234f501SNamjae Jeon #include "compress.h"
136234f501SNamjae Jeon #include "lz77.h"
146234f501SNamjae Jeon 
156234f501SNamjae Jeon #define SMB2_COMPRESSION_CHAINED_HDR_LEN \
166234f501SNamjae Jeon 	offsetof(struct smb2_compression_hdr, CompressionAlgorithm)
176234f501SNamjae Jeon #define SMB2_COMPRESSION_PAYLOAD_BASE_LEN \
186234f501SNamjae Jeon 	(sizeof(struct smb2_compression_payload_hdr) - sizeof(__le32))
196234f501SNamjae Jeon 
206234f501SNamjae Jeon /*
216234f501SNamjae Jeon  * A NONE payload carries bytes verbatim. Keep both cursors and remaining
226234f501SNamjae Jeon  * lengths together so every chained payload handler applies identical bounds
236234f501SNamjae Jeon  * accounting.
246234f501SNamjae Jeon  */
smb_decompress_none(const u8 ** src,u32 * slen,u8 ** dst,u32 * dlen,u32 len)256234f501SNamjae Jeon static int smb_decompress_none(const u8 **src, u32 *slen, u8 **dst, u32 *dlen,
266234f501SNamjae Jeon 			       u32 len)
276234f501SNamjae Jeon {
286234f501SNamjae Jeon 	if (len > *slen || len > *dlen)
296234f501SNamjae Jeon 		return -EINVAL;
306234f501SNamjae Jeon 
316234f501SNamjae Jeon 	memcpy(*dst, *src, len);
326234f501SNamjae Jeon 	*src += len;
336234f501SNamjae Jeon 	*slen -= len;
346234f501SNamjae Jeon 	*dst += len;
356234f501SNamjae Jeon 	*dlen -= len;
366234f501SNamjae Jeon 	return 0;
376234f501SNamjae Jeon }
386234f501SNamjae Jeon 
396234f501SNamjae Jeon /*
406234f501SNamjae Jeon  * Pattern_V1 represents a run of one byte. Its wire payload is always the
416234f501SNamjae Jeon  * fixed-size smb2_compression_pattern_v1 structure.
426234f501SNamjae Jeon  */
smb_decompress_pattern(const u8 ** src,u32 * slen,u8 ** dst,u32 * dlen,u32 len)436234f501SNamjae Jeon static int smb_decompress_pattern(const u8 **src, u32 *slen, u8 **dst,
446234f501SNamjae Jeon 				  u32 *dlen, u32 len)
456234f501SNamjae Jeon {
466234f501SNamjae Jeon 	const struct smb2_compression_pattern_v1 *pattern;
476234f501SNamjae Jeon 	u32 repetitions;
486234f501SNamjae Jeon 
496234f501SNamjae Jeon 	if (len != sizeof(*pattern) || len > *slen)
506234f501SNamjae Jeon 		return -EINVAL;
516234f501SNamjae Jeon 
526234f501SNamjae Jeon 	pattern = (const struct smb2_compression_pattern_v1 *)*src;
536234f501SNamjae Jeon 	repetitions = le32_to_cpu(pattern->Repetitions);
546234f501SNamjae Jeon 	if (repetitions > *dlen)
556234f501SNamjae Jeon 		return -EINVAL;
566234f501SNamjae Jeon 
576234f501SNamjae Jeon 	memset(*dst, pattern->Pattern, repetitions);
586234f501SNamjae Jeon 	*src += len;
596234f501SNamjae Jeon 	*slen -= len;
606234f501SNamjae Jeon 	*dst += repetitions;
616234f501SNamjae Jeon 	*dlen -= repetitions;
626234f501SNamjae Jeon 	return 0;
636234f501SNamjae Jeon }
646234f501SNamjae Jeon 
656234f501SNamjae Jeon /*
666234f501SNamjae Jeon  * LZ77 payload Length includes the four-byte OriginalPayloadSize field.
676234f501SNamjae Jeon  * Consume that field before passing the compressed stream to the raw codec.
686234f501SNamjae Jeon  */
smb_decompress_lz77_payload(const u8 ** src,u32 * slen,u8 ** dst,u32 * dlen,u32 len)696234f501SNamjae Jeon static int smb_decompress_lz77_payload(const u8 **src, u32 *slen, u8 **dst,
706234f501SNamjae Jeon 				       u32 *dlen, u32 len)
716234f501SNamjae Jeon {
726234f501SNamjae Jeon 	u32 orig_size;
736234f501SNamjae Jeon 	int rc;
746234f501SNamjae Jeon 
756234f501SNamjae Jeon 	if (len < sizeof(__le32) || len > *slen)
766234f501SNamjae Jeon 		return -EINVAL;
776234f501SNamjae Jeon 
786234f501SNamjae Jeon 	orig_size = get_unaligned_le32(*src);
796234f501SNamjae Jeon 	if (orig_size > *dlen)
806234f501SNamjae Jeon 		return -EINVAL;
816234f501SNamjae Jeon 
826234f501SNamjae Jeon 	*src += sizeof(__le32);
836234f501SNamjae Jeon 	*slen -= sizeof(__le32);
846234f501SNamjae Jeon 	len -= sizeof(__le32);
856234f501SNamjae Jeon 
866234f501SNamjae Jeon 	rc = smb_lz77_decompress(*src, len, *dst, orig_size);
876234f501SNamjae Jeon 	if (rc)
886234f501SNamjae Jeon 		return rc;
896234f501SNamjae Jeon 
906234f501SNamjae Jeon 	*src += len;
916234f501SNamjae Jeon 	*slen -= len;
926234f501SNamjae Jeon 	*dst += orig_size;
936234f501SNamjae Jeon 	*dlen -= orig_size;
946234f501SNamjae Jeon 	return 0;
956234f501SNamjae Jeon }
966234f501SNamjae Jeon 
smb_decompress_chained(__le16 alg,bool allow_chained,bool allow_pattern,const struct smb2_compression_hdr * hdr,u32 slen,void * dst,u32 dlen)976234f501SNamjae Jeon static int smb_decompress_chained(__le16 alg, bool allow_chained,
98*0710dd08SAnatolii Shumak 				  bool allow_pattern,
996234f501SNamjae Jeon 				  const struct smb2_compression_hdr *hdr,
1006234f501SNamjae Jeon 				  u32 slen, void *dst, u32 dlen)
1016234f501SNamjae Jeon {
1026234f501SNamjae Jeon 	const struct smb2_compression_payload_hdr *payload;
1036234f501SNamjae Jeon 	const u8 *src = (const u8 *)hdr + SMB2_COMPRESSION_CHAINED_HDR_LEN;
1046234f501SNamjae Jeon 	u32 orig_size = le32_to_cpu(hdr->OriginalCompressedSegmentSize);
1056234f501SNamjae Jeon 	u32 remaining = slen - SMB2_COMPRESSION_CHAINED_HDR_LEN;
1066234f501SNamjae Jeon 	u8 *out = dst;
1076234f501SNamjae Jeon 	u32 out_remaining = dlen;
1086234f501SNamjae Jeon 	bool first = true;
1096234f501SNamjae Jeon 	int rc;
1106234f501SNamjae Jeon 
1116234f501SNamjae Jeon 	if (!allow_chained || orig_size != dlen)
1126234f501SNamjae Jeon 		return -EINVAL;
1136234f501SNamjae Jeon 
1146234f501SNamjae Jeon 	/*
1156234f501SNamjae Jeon 	 * The chained transform has an eight-byte top-level header. The next
1166234f501SNamjae Jeon 	 * bytes are a sequence of payload headers whose Length fields account
1176234f501SNamjae Jeon 	 * for payload data, including OriginalPayloadSize where applicable.
1186234f501SNamjae Jeon 	 */
1196234f501SNamjae Jeon 	while (remaining) {
1206234f501SNamjae Jeon 		__le16 payload_alg;
1216234f501SNamjae Jeon 		__le16 flags;
1226234f501SNamjae Jeon 		u32 len;
1236234f501SNamjae Jeon 
1246234f501SNamjae Jeon 		if (remaining < SMB2_COMPRESSION_PAYLOAD_BASE_LEN)
1256234f501SNamjae Jeon 			return -EINVAL;
1266234f501SNamjae Jeon 
1276234f501SNamjae Jeon 		payload = (const struct smb2_compression_payload_hdr *)src;
1286234f501SNamjae Jeon 		payload_alg = payload->CompressionAlgorithm;
1296234f501SNamjae Jeon 		flags = payload->Flags;
1306234f501SNamjae Jeon 		len = le32_to_cpu(payload->Length);
1316234f501SNamjae Jeon 
1326234f501SNamjae Jeon 		/*
1336234f501SNamjae Jeon 		 * CHAINED marks only the first payload. Requiring NONE on every
1346234f501SNamjae Jeon 		 * later payload rejects ambiguous or independently chained data.
1356234f501SNamjae Jeon 		 */
1366234f501SNamjae Jeon 		if ((first && flags != cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED)) ||
1376234f501SNamjae Jeon 		    (!first && flags != cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE)))
1386234f501SNamjae Jeon 			return -EINVAL;
1396234f501SNamjae Jeon 
1406234f501SNamjae Jeon 		src += SMB2_COMPRESSION_PAYLOAD_BASE_LEN;
1416234f501SNamjae Jeon 		remaining -= SMB2_COMPRESSION_PAYLOAD_BASE_LEN;
1426234f501SNamjae Jeon 
1436234f501SNamjae Jeon 		if (payload_alg == SMB3_COMPRESS_NONE) {
1446234f501SNamjae Jeon 			rc = smb_decompress_none(&src, &remaining, &out,
1456234f501SNamjae Jeon 						 &out_remaining, len);
1466234f501SNamjae Jeon 		} else if (payload_alg == SMB3_COMPRESS_PATTERN) {
147*0710dd08SAnatolii Shumak 			if (!allow_pattern)
148*0710dd08SAnatolii Shumak 				return -EINVAL;
1496234f501SNamjae Jeon 			rc = smb_decompress_pattern(&src, &remaining, &out,
1506234f501SNamjae Jeon 						    &out_remaining, len);
1516234f501SNamjae Jeon 		} else if (payload_alg == alg && alg == SMB3_COMPRESS_LZ77) {
1526234f501SNamjae Jeon 			rc = smb_decompress_lz77_payload(&src, &remaining, &out,
1536234f501SNamjae Jeon 							 &out_remaining, len);
1546234f501SNamjae Jeon 		} else {
1556234f501SNamjae Jeon 			return -EINVAL;
1566234f501SNamjae Jeon 		}
1576234f501SNamjae Jeon 		if (rc)
1586234f501SNamjae Jeon 			return rc;
1596234f501SNamjae Jeon 		first = false;
1606234f501SNamjae Jeon 	}
1616234f501SNamjae Jeon 
1626234f501SNamjae Jeon 	return out_remaining ? -EINVAL : 0;
1636234f501SNamjae Jeon }
1646234f501SNamjae Jeon 
smb_decompress_unchained(__le16 alg,const struct smb2_compression_hdr * hdr,u32 slen,void * dst,u32 dlen)1656234f501SNamjae Jeon static int smb_decompress_unchained(__le16 alg,
1666234f501SNamjae Jeon 				    const struct smb2_compression_hdr *hdr,
1676234f501SNamjae Jeon 				    u32 slen, void *dst, u32 dlen)
1686234f501SNamjae Jeon {
1696234f501SNamjae Jeon 	u32 orig_size, offset, comp_size;
1706234f501SNamjae Jeon 
1716234f501SNamjae Jeon 	if (hdr->CompressionAlgorithm != alg ||
1726234f501SNamjae Jeon 	    !smb_compress_alg_valid(hdr->CompressionAlgorithm, false))
1736234f501SNamjae Jeon 		return -EINVAL;
1746234f501SNamjae Jeon 
1756234f501SNamjae Jeon 	orig_size = le32_to_cpu(hdr->OriginalCompressedSegmentSize);
1766234f501SNamjae Jeon 	offset = le32_to_cpu(hdr->Offset);
1776234f501SNamjae Jeon 	if (offset > slen - sizeof(*hdr) || offset > dlen ||
1786234f501SNamjae Jeon 	    orig_size > dlen - offset || orig_size + offset != dlen)
1796234f501SNamjae Jeon 		return -EINVAL;
1806234f501SNamjae Jeon 
1816234f501SNamjae Jeon 	memcpy(dst, (const u8 *)hdr + sizeof(*hdr), offset);
1826234f501SNamjae Jeon 	comp_size = slen - sizeof(*hdr) - offset;
1836234f501SNamjae Jeon 	return smb_lz77_decompress((const u8 *)hdr + sizeof(*hdr) + offset,
1846234f501SNamjae Jeon 				   comp_size, (u8 *)dst + offset, orig_size);
1856234f501SNamjae Jeon }
1866234f501SNamjae Jeon 
1876234f501SNamjae Jeon /**
1886234f501SNamjae Jeon  * smb_compression_decompress() - decode an SMB2 compression transform
1896234f501SNamjae Jeon  * @alg: negotiated general-purpose compression algorithm
1906234f501SNamjae Jeon  * @allow_chained: whether chained transforms were negotiated
191*0710dd08SAnatolii Shumak  * @allow_pattern: whether Pattern_V1 payloads were negotiated
1926234f501SNamjae Jeon  * @src: transform header followed by compressed payload data
1936234f501SNamjae Jeon  * @slen: total number of bytes available at @src
1946234f501SNamjae Jeon  * @dst: output buffer for the reconstructed SMB2 message
1956234f501SNamjae Jeon  * @dlen: exact expected size of the reconstructed SMB2 message
1966234f501SNamjae Jeon  *
1976234f501SNamjae Jeon  * Validate the transform type and negotiated capabilities before dispatching
1986234f501SNamjae Jeon  * to the chained or unchained decoder. The caller supplies the expected output
1996234f501SNamjae Jeon  * size after applying its transport-specific message size limits.
2006234f501SNamjae Jeon  *
2016234f501SNamjae Jeon  * Return: 0 on success, otherwise a negative errno.
2026234f501SNamjae Jeon  */
smb_compression_decompress(__le16 alg,bool allow_chained,bool allow_pattern,const void * src,u32 slen,void * dst,u32 dlen)2036234f501SNamjae Jeon int smb_compression_decompress(__le16 alg, bool allow_chained,
204*0710dd08SAnatolii Shumak 			       bool allow_pattern, const void *src, u32 slen,
205*0710dd08SAnatolii Shumak 			       void *dst, u32 dlen)
2066234f501SNamjae Jeon {
2076234f501SNamjae Jeon 	const struct smb2_compression_hdr *hdr = src;
2086234f501SNamjae Jeon 
2096234f501SNamjae Jeon 	if (!src || !dst || slen < sizeof(*hdr) ||
2106234f501SNamjae Jeon 	    hdr->ProtocolId != SMB2_COMPRESSION_TRANSFORM_ID ||
2116234f501SNamjae Jeon 	    alg == SMB3_COMPRESS_NONE)
2126234f501SNamjae Jeon 		return -EINVAL;
2136234f501SNamjae Jeon 
2146234f501SNamjae Jeon 	if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED))
215*0710dd08SAnatolii Shumak 		return smb_decompress_chained(alg, allow_chained, allow_pattern,
216*0710dd08SAnatolii Shumak 					      hdr, slen, dst, dlen);
2176234f501SNamjae Jeon 
2186234f501SNamjae Jeon 	if (hdr->Flags != cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE))
2196234f501SNamjae Jeon 		return -EINVAL;
2206234f501SNamjae Jeon 
2216234f501SNamjae Jeon 	return smb_decompress_unchained(alg, hdr, slen, dst, dlen);
2226234f501SNamjae Jeon }
2236234f501SNamjae Jeon EXPORT_SYMBOL_GPL(smb_compression_decompress);
2246234f501SNamjae Jeon 
2256234f501SNamjae Jeon struct smb_compression_builder {
2266234f501SNamjae Jeon 	u8 *pos;
2276234f501SNamjae Jeon 	u32 remaining;
2286234f501SNamjae Jeon 	bool first;
2296234f501SNamjae Jeon };
2306234f501SNamjae Jeon 
2316234f501SNamjae Jeon /*
2326234f501SNamjae Jeon  * Reserve one chained payload header and initialize its common fields.
2336234f501SNamjae Jeon  * OriginalPayloadSize is present only for LZNT1/LZ77/LZ77+Huffman payloads.
2346234f501SNamjae Jeon  */
2356234f501SNamjae Jeon static struct smb2_compression_payload_hdr *
smb_compression_add_payload(struct smb_compression_builder * builder,__le16 alg,u32 payload_len,bool orig_size)2366234f501SNamjae Jeon smb_compression_add_payload(struct smb_compression_builder *builder,
2376234f501SNamjae Jeon 			    __le16 alg, u32 payload_len, bool orig_size)
2386234f501SNamjae Jeon {
2396234f501SNamjae Jeon 	struct smb2_compression_payload_hdr *payload;
2406234f501SNamjae Jeon 	u32 hdr_len = SMB2_COMPRESSION_PAYLOAD_BASE_LEN;
2416234f501SNamjae Jeon 	u32 total_len;
2426234f501SNamjae Jeon 
2436234f501SNamjae Jeon 	if (orig_size)
2446234f501SNamjae Jeon 		hdr_len += sizeof(payload->OriginalPayloadSize);
2456234f501SNamjae Jeon 	if (check_add_overflow(hdr_len, payload_len, &total_len) ||
2466234f501SNamjae Jeon 	    total_len > builder->remaining)
2476234f501SNamjae Jeon 		return NULL;
2486234f501SNamjae Jeon 
2496234f501SNamjae Jeon 	payload = (struct smb2_compression_payload_hdr *)builder->pos;
2506234f501SNamjae Jeon 	payload->CompressionAlgorithm = alg;
2516234f501SNamjae Jeon 	payload->Flags = cpu_to_le16(builder->first ?
2526234f501SNamjae Jeon 		SMB2_COMPRESSION_FLAG_CHAINED : SMB2_COMPRESSION_FLAG_NONE);
2536234f501SNamjae Jeon 	payload->Length = cpu_to_le32(payload_len +
2546234f501SNamjae Jeon 		(orig_size ? sizeof(payload->OriginalPayloadSize) : 0));
2556234f501SNamjae Jeon 
2566234f501SNamjae Jeon 	builder->pos += hdr_len;
2576234f501SNamjae Jeon 	builder->remaining -= hdr_len;
2586234f501SNamjae Jeon 	builder->first = false;
2596234f501SNamjae Jeon 	return payload;
2606234f501SNamjae Jeon }
2616234f501SNamjae Jeon 
smb_compression_add_pattern(struct smb_compression_builder * builder,u8 pattern,u32 repetitions)2626234f501SNamjae Jeon static int smb_compression_add_pattern(struct smb_compression_builder *builder,
2636234f501SNamjae Jeon 				       u8 pattern, u32 repetitions)
2646234f501SNamjae Jeon {
2656234f501SNamjae Jeon 	struct smb2_compression_pattern_v1 *payload;
2666234f501SNamjae Jeon 
2676234f501SNamjae Jeon 	if (!smb_compression_add_payload(builder, SMB3_COMPRESS_PATTERN,
2686234f501SNamjae Jeon 					 sizeof(*payload), false))
2696234f501SNamjae Jeon 		return -ENOSPC;
2706234f501SNamjae Jeon 
2716234f501SNamjae Jeon 	payload = (struct smb2_compression_pattern_v1 *)builder->pos;
2726234f501SNamjae Jeon 	payload->Pattern = pattern;
2736234f501SNamjae Jeon 	payload->Reserved1 = 0;
2746234f501SNamjae Jeon 	payload->Reserved2 = 0;
2756234f501SNamjae Jeon 	payload->Repetitions = cpu_to_le32(repetitions);
2766234f501SNamjae Jeon 	builder->pos += sizeof(*payload);
2776234f501SNamjae Jeon 	builder->remaining -= sizeof(*payload);
2786234f501SNamjae Jeon 	return 0;
2796234f501SNamjae Jeon }
2806234f501SNamjae Jeon 
smb_compression_add_none(struct smb_compression_builder * builder,const u8 * src,u32 len)2816234f501SNamjae Jeon static int smb_compression_add_none(struct smb_compression_builder *builder,
2826234f501SNamjae Jeon 				    const u8 *src, u32 len)
2836234f501SNamjae Jeon {
2846234f501SNamjae Jeon 	if (!smb_compression_add_payload(builder, SMB3_COMPRESS_NONE, len, false))
2856234f501SNamjae Jeon 		return -ENOSPC;
2866234f501SNamjae Jeon 
2876234f501SNamjae Jeon 	memcpy(builder->pos, src, len);
2886234f501SNamjae Jeon 	builder->pos += len;
2896234f501SNamjae Jeon 	builder->remaining -= len;
2906234f501SNamjae Jeon 	return 0;
2916234f501SNamjae Jeon }
2926234f501SNamjae Jeon 
smb_compression_add_lz77(struct smb_compression_builder * builder,const u8 * src,u32 len)2936234f501SNamjae Jeon static int smb_compression_add_lz77(struct smb_compression_builder *builder,
2946234f501SNamjae Jeon 				    const u8 *src, u32 len)
2956234f501SNamjae Jeon {
2966234f501SNamjae Jeon 	struct smb2_compression_payload_hdr *payload;
2976234f501SNamjae Jeon 	u32 comp_len;
2986234f501SNamjae Jeon 	int rc;
2996234f501SNamjae Jeon 
3006234f501SNamjae Jeon 	if (builder->remaining <= sizeof(*payload))
3016234f501SNamjae Jeon 		return -ENOSPC;
3026234f501SNamjae Jeon 
3036234f501SNamjae Jeon 	comp_len = builder->remaining - sizeof(*payload);
3046234f501SNamjae Jeon 	payload = smb_compression_add_payload(builder, SMB3_COMPRESS_LZ77,
3056234f501SNamjae Jeon 					      comp_len, true);
3066234f501SNamjae Jeon 	if (!payload)
3076234f501SNamjae Jeon 		return -ENOSPC;
3086234f501SNamjae Jeon 
3096234f501SNamjae Jeon 	rc = smb_lz77_compress(src, len, builder->pos, &comp_len);
3106234f501SNamjae Jeon 	if (rc)
3116234f501SNamjae Jeon 		return rc;
3126234f501SNamjae Jeon 
3136234f501SNamjae Jeon 	payload->Length = cpu_to_le32(comp_len +
3146234f501SNamjae Jeon 				      sizeof(payload->OriginalPayloadSize));
3156234f501SNamjae Jeon 	payload->OriginalPayloadSize = cpu_to_le32(len);
3166234f501SNamjae Jeon 	builder->pos += comp_len;
3176234f501SNamjae Jeon 	builder->remaining -= comp_len;
3186234f501SNamjae Jeon 	return 0;
3196234f501SNamjae Jeon }
3206234f501SNamjae Jeon 
3216234f501SNamjae Jeon /**
3226234f501SNamjae Jeon  * smb_compression_compress_chained() - build a chained SMB2 transform
3236234f501SNamjae Jeon  * @alg: negotiated general-purpose compression algorithm
3246234f501SNamjae Jeon  * @allow_pattern: whether Pattern_V1 was negotiated
3256234f501SNamjae Jeon  * @src: complete uncompressed SMB2 message
3266234f501SNamjae Jeon  * @slen: size of @src
3276234f501SNamjae Jeon  * @dst: output buffer for the transform
3286234f501SNamjae Jeon  * @dlen: input capacity of @dst and output transform size
3296234f501SNamjae Jeon  *
3306234f501SNamjae Jeon  * Following the algorithm in [MS-SMB2] 3.1.4.4, encode sufficiently long
3316234f501SNamjae Jeon  * repeated runs at the front and back as Pattern_V1 payloads. Compress a
3326234f501SNamjae Jeon  * middle region larger than 1 KiB with LZ77; smaller middle regions are
3336234f501SNamjae Jeon  * represented by a chained NONE payload.
3346234f501SNamjae Jeon  *
3356234f501SNamjae Jeon  * This helper does not decide whether the final transform is smaller than the
3366234f501SNamjae Jeon  * original message. The transport caller owns that policy decision.
3376234f501SNamjae Jeon  *
3386234f501SNamjae Jeon  * Return: 0 on success, otherwise a negative errno.
3396234f501SNamjae Jeon  */
smb_compression_compress_chained(__le16 alg,bool allow_pattern,const void * src,u32 slen,void * dst,u32 * dlen)3406234f501SNamjae Jeon int smb_compression_compress_chained(__le16 alg, bool allow_pattern,
3416234f501SNamjae Jeon 				     const void *src, u32 slen,
3426234f501SNamjae Jeon 				     void *dst, u32 *dlen)
3436234f501SNamjae Jeon {
3446234f501SNamjae Jeon 	struct smb2_compression_hdr *hdr = dst;
3456234f501SNamjae Jeon 	struct smb_compression_builder builder;
3466234f501SNamjae Jeon 	const u8 *input = src;
3476234f501SNamjae Jeon 	u32 forward = 0, backward = 0, middle_len;
3486234f501SNamjae Jeon 	int rc;
3496234f501SNamjae Jeon 
3506234f501SNamjae Jeon 	if (!src || !dst || !dlen || alg != SMB3_COMPRESS_LZ77 ||
3516234f501SNamjae Jeon 	    *dlen <= SMB2_COMPRESSION_CHAINED_HDR_LEN || !slen)
3526234f501SNamjae Jeon 		return -EINVAL;
3536234f501SNamjae Jeon 
3546234f501SNamjae Jeon 	hdr->ProtocolId = SMB2_COMPRESSION_TRANSFORM_ID;
3556234f501SNamjae Jeon 	hdr->OriginalCompressedSegmentSize = cpu_to_le32(slen);
3566234f501SNamjae Jeon 	builder.pos = (u8 *)dst + SMB2_COMPRESSION_CHAINED_HDR_LEN;
3576234f501SNamjae Jeon 	builder.remaining = *dlen - SMB2_COMPRESSION_CHAINED_HDR_LEN;
3586234f501SNamjae Jeon 	builder.first = true;
3596234f501SNamjae Jeon 
3606234f501SNamjae Jeon 	if (allow_pattern && slen > 32) {
3616234f501SNamjae Jeon 		for (forward = 1; forward < slen; forward++) {
3626234f501SNamjae Jeon 			if (input[forward] != input[0])
3636234f501SNamjae Jeon 				break;
3646234f501SNamjae Jeon 		}
3656234f501SNamjae Jeon 		if (forward <= 32)
3666234f501SNamjae Jeon 			forward = 0;
3676234f501SNamjae Jeon 
3686234f501SNamjae Jeon 		for (backward = 1; backward < slen - forward; backward++) {
3696234f501SNamjae Jeon 			if (input[slen - backward - 1] != input[slen - 1])
3706234f501SNamjae Jeon 				break;
3716234f501SNamjae Jeon 		}
3726234f501SNamjae Jeon 		if (backward <= 32)
3736234f501SNamjae Jeon 			backward = 0;
3746234f501SNamjae Jeon 	}
3756234f501SNamjae Jeon 
3766234f501SNamjae Jeon 	if (forward) {
3776234f501SNamjae Jeon 		rc = smb_compression_add_pattern(&builder, input[0], forward);
3786234f501SNamjae Jeon 		if (rc)
3796234f501SNamjae Jeon 			return rc;
3806234f501SNamjae Jeon 	}
3816234f501SNamjae Jeon 
3826234f501SNamjae Jeon 	middle_len = slen - forward - backward;
3836234f501SNamjae Jeon 	if (middle_len > 1024)
3846234f501SNamjae Jeon 		rc = smb_compression_add_lz77(&builder, input + forward,
3856234f501SNamjae Jeon 					      middle_len);
3866234f501SNamjae Jeon 	else if (middle_len)
3876234f501SNamjae Jeon 		rc = smb_compression_add_none(&builder,
3886234f501SNamjae Jeon 					      input + forward, middle_len);
3896234f501SNamjae Jeon 	else
3906234f501SNamjae Jeon 		rc = 0;
3916234f501SNamjae Jeon 	if (rc)
3926234f501SNamjae Jeon 		return rc;
3936234f501SNamjae Jeon 
3946234f501SNamjae Jeon 	if (backward) {
3956234f501SNamjae Jeon 		rc = smb_compression_add_pattern(&builder, input[slen - 1],
3966234f501SNamjae Jeon 						 backward);
3976234f501SNamjae Jeon 		if (rc)
3986234f501SNamjae Jeon 			return rc;
3996234f501SNamjae Jeon 	}
4006234f501SNamjae Jeon 
4016234f501SNamjae Jeon 	*dlen = builder.pos - (u8 *)dst;
4026234f501SNamjae Jeon 	return 0;
4036234f501SNamjae Jeon }
4046234f501SNamjae Jeon EXPORT_SYMBOL_GPL(smb_compression_compress_chained);
405