/*
 *  critical_encode.h
 *
 *  Copyright (c) 2022 Whamcloud
 */

#ifndef _CRITICAL_ENCODE_H
#define _CRITICAL_ENCODE_H

#include <linux/ctype.h>

/* Encoding/decoding routines inspired from yEnc principles.
 * We just take care of a few critical characters:
 * NULL, LF, CR, /, DEL and =.
 * If such a char is found, it is replaced with '=' followed by
 * the char value + 64.
 * All other chars are left untouched.
 * Efficiency of this encoding depends on the occurences of the
 * critical chars, but statistically on binary data it can be much higher
 * than base64 for instance.
 */
static inline int critical_encode(const u8 *src, int len, char *dst)
{
	u8 *p = (u8 *)src, *q = dst;

	while (p - src < len) {
		/* escape NULL, LF, CR, /, DEL and = */
		if (unlikely(*p == 0x0 || *p == 0xA || *p == 0xD ||
			     *p == '/' || *p == 0x7F || *p == '=')) {
			*(q++) = '=';
			*(q++) = *(p++) + 64;
		} else {
			*(q++) = *(p++);
		}
	}

	return (char *)q - dst;
}

/* returns the number of chars encoding would produce */
static inline int critical_chars(const u8 *src, int len)
{
	u8 *p = (u8 *)src;
	int newlen = len;

	while (p - src < len) {
		/* NULL, LF, CR, /, DEL and = cost an additional '=' */
		if (unlikely(*p == 0x0 || *p == 0xA || *p == 0xD ||
			     *p == '/' || *p == 0x7F || *p == '='))
			newlen++;
		p++;
	}

	return newlen;
}

/* decoding routine - returns the number of chars in output */
static inline int critical_decode(const u8 *src, int len, char *dst)
{
	u8 *p = (u8 *)src, *q = dst;

	while (p - src < len) {
		if (unlikely(*p == '=')) {
			*(q++) = *(++p) - 64;
			p++;
		} else {
			*(q++) = *(p++);
		}
	}

	return (char *)q - dst;
}

#define fscrypt_get_encryption_info(inode) \
	(unlikely(!IS_LUSTRE_MOUNT(inode->i_sb)) ? 0 : -EOPNOTSUPP)

static inline int ext4_has_permitted_context(struct inode *parent,
					struct inode *child)
{
	if (unlikely(!IS_LUSTRE_MOUNT(parent->i_sb)))
		return 1;
	return fscrypt_has_permitted_context(parent, child);
}

static inline int ext4_prepare_lookup(struct inode *dir,
                                         struct dentry *dentry,
                                         unsigned int flags)
{
	if (unlikely(!IS_LUSTRE_MOUNT(dir->i_sb)))
		return 0;
        return fscrypt_prepare_lookup(dir, dentry, flags);
}

static inline int ext4_fname_alloc_buffer(const struct inode *inode,
					     u32 max_encrypted_len,
					     struct fscrypt_str *crypto_str)
{
	crypto_str->name = kmalloc(max_encrypted_len + 1, GFP_NOFS);
	if (!crypto_str->name)
		return -ENOMEM;
	crypto_str->len = max_encrypted_len;
	return 0;
}

static inline void ext4_fname_free_buffer(struct fscrypt_str *crypto_str)
{
	if (!crypto_str)
		return;
	kfree(crypto_str->name);
	crypto_str->name = NULL;
}

static inline int ext4_fname_disk_to_usr(struct inode *inode,
					    u32 hash, u32 minor_hash,
					    const struct fscrypt_str *iname,
					    struct fscrypt_str *oname)
{
	int presented_len;

	presented_len = critical_encode(iname->name, iname->len, oname->name);
	if (presented_len > NAME_MAX) {
		/* truncate at NAME_MAX,
		 * or NAME_MAX-1 if name ends with '=' to avoid decoding issue
		 */
		presented_len = NAME_MAX;
		if (oname->name[presented_len - 1] == '=')
			presented_len--;
		oname->len = presented_len;
	}
	oname->name[presented_len] = '\0';

	return 0;
}

static inline int ext4_setup_filename(struct inode *dir,
					 const struct qstr *iname,
					 int lookup,
					 struct ext4_filename *fname)
{
	fname->usr_fname = iname;

	if (lookup && IS_ENCRYPTED(dir) &&
	    unlikely(!IS_LUSTRE_MOUNT(dir->i_sb) &&
		     strnchr(iname->name, iname->len, '='))) {
		/* Only proceed to critical decode if
		 * iname contains escape char '='.
		 */
		int len = iname->len;
		char *buf;

		buf = kmalloc(len, GFP_NOFS);
		if (!buf)
			return -ENOMEM;

		len = critical_decode(iname->name, len, buf);
		fname->disk_name.name = (unsigned char *)buf;
		fname->disk_name.len = len;
		return 0;
	}

	fname->disk_name.name = (unsigned char *) iname->name;
	fname->disk_name.len = iname->len;
	return 0;
}

#endif /* _CRITICAL_ENCODE_H */
