summaryrefslogtreecommitdiff
path: root/fs/smb/client/compress.h
blob: c5691c1db066df0f9405575977c993d6bfbad7c2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * Copyright (C) 2024, SUSE LLC
 *
 * Authors: Enzo Matsumiya <ematsumiya@suse.de>
 *
 * This file implements I/O compression support for SMB2 messages (SMB 3.1.1 only).
 * See compress/ for implementation details of each algorithm.
 *
 * References:
 * MS-SMB2 "3.1.4.4 Compressing the Message" - for compression details
 * MS-SMB2 "3.1.5.3 Decompressing the Chained Message" - for decompression details
 * MS-XCA - for details of the supported algorithms
 */
#ifndef _SMB_COMPRESS_H
#define _SMB_COMPRESS_H

#include <linux/uio.h>
#include <linux/kernel.h>
#include "../common/smb2pdu.h"
#include "cifsglob.h"

/* sizeof(smb2_compression_hdr) - sizeof(OriginalPayloadSize) */
#define SMB_COMPRESS_HDR_LEN		16
/* sizeof(smb2_compression_payload_hdr) - sizeof(OriginalPayloadSize) */
#define SMB_COMPRESS_PAYLOAD_HDR_LEN	8
#define SMB_COMPRESS_MIN_LEN		PAGE_SIZE
/* follows Windows implementation (as per MS-SMB2) */
#define SMB_COMPRESS_PAYLOAD_MIN_LEN	1024
#define SMB_COMPRESS_PATTERN_MIN_LEN	64
#define SMB_DECOMPRESS_MAX_LEN(_srv) \
        (256 + SMB_COMPRESS_HDR_LEN + \
         max_t(size_t, (_srv)->maxBuf, max_t(size_t, (_srv)->max_read, (_srv)->max_write)))

struct smb_compress_ctx {
	struct TCP_Server_Info *server;
	struct work_struct work;
	struct mid_q_entry *mid;

	void *buf; /* compressed data */
	void *data; /* uncompressed data */
	size_t len;
};

#ifdef CONFIG_CIFS_COMPRESSION
int smb_compress(void *buf, const void *data, size_t *len, bool chained);
int smb_decompress(const void *src, size_t src_len, void *dst, size_t *dst_len);

/**
 * smb_compress_alg_valid() - Validate a compression algorithm.
 * @alg: Compression algorithm to check.
 * @valid_none: Conditional check whether NONE algorithm should be
 *		considered valid or not.
 *
 * If @alg is SMB3_COMPRESS_NONE, this function returns @valid_none.
 *
 * Note that 'NONE' (0) compressor type is considered invalid in protocol
 * negotiation, as it's never requested to/returned from the server.
 *
 * Return: true if @alg is valid/supported, false otherwise.
 */
static __always_inline int smb_compress_alg_valid(__le16 alg, bool valid_none)
{
	if (alg == SMB3_COMPRESS_NONE)
		return valid_none;

	if (alg == SMB3_COMPRESS_LZ77 || alg == SMB3_COMPRESS_PATTERN)
		return true;

	return false;
}

static __always_inline bool has_compress_hdr(void *buf)
{
	struct smb2_compression_hdr *hdr = buf;

	return (hdr->ProtocolId == SMB2_COMPRESSION_TRANSFORM_ID);
}

/**
 * should_compress() - Determines if a request (write) or the response to a
 *		       request (read) should be compressed.
 * @tcon: tcon of the request is being sent to
 * @buf: buffer with an SMB2 READ/WRITE request
 *
 * Return: true iff:
 * - compression was successfully negotiated with server
 * - server has enabled compression for the share
 * - it's a read or write request
 * - if write, request length is >= SMB_COMPRESS_MIN_LEN
 *
 * Return false otherwise.
 */
static __always_inline bool should_compress(const struct cifs_tcon *tcon, const void *buf)
{
	const struct smb2_hdr *shdr = buf;

	if (!tcon || !tcon->ses || !tcon->ses->server)
		return false;

	if (!tcon->ses->server->compression.enabled)
		return false;

	if (!(tcon->share_flags & SMB2_SHAREFLAG_COMPRESS_DATA))
		return false;

	if (shdr->Command == SMB2_WRITE) {
		const struct smb2_write_req *req = buf;

		return (req->Length >= SMB_COMPRESS_MIN_LEN);
	}

	return (shdr->Command == SMB2_READ);
}

static __always_inline size_t decompressed_size(const void *buf)
{
	const struct smb2_compression_hdr *hdr = buf;
	size_t size = le32_to_cpu(hdr->OriginalCompressedSegmentSize);

	if (hdr->Flags == SMB2_COMPRESSION_FLAG_CHAINED)
		size += le32_to_cpu(hdr->Offset);

	return size;
}
#else /* CONFIG_CIFS_COMPRESSION */
#define smb_compress(arg1, arg2, arg3, arg4)	(-EOPNOTSUPP)
#define smb_decompress(arg1, arg2, arg3, arg4)	(-EOPNOTSUPP)
#define smb_compress_alg_valid(arg1, arg2)	(-EOPNOTSUPP)
#define should_compress(arg1, arg2)		(false)
#define decompress_size(arg1)			(0)
#endif /* !CONFIG_CIFS_COMPRESSION */
#endif /* _SMB_COMPRESS_H */