PLUGIN enc
lang: "C++"
version: "1.2.0"
date: "2022-01-30"
author: "Julien BRUGUIER"
maintainer: "Julien BRUGUIER <projet.svm@pappy.tf>"
synopsis: "A basic encoding and hashing plugin."
description:
%{
This plugin provides low-level encoding and hashing instructions.
%}
changelog:
%{
svm-plugin-enc (1.2.0) UNRELEASED; urgency=medium

  * Base64 encoding and decoding fixes.

 -- Julien Bruguier <projet.svm@pappy.tf>  Tue, 10 Mar 2026 20:07:05 +0100

svm-plugin-enc (1.1.1) UNRELEASED; urgency=medium

  * Build for SVM API 2.9.

 -- Julien Bruguier <projet.svm@pappy.tf>  Sun, 01 Mar 2026 22:58:15 +0100

svm-plugin-enc (1.1.0) UNRELEASED; urgency=medium

  * Fix compilation issue

 -- Julien Bruguier <projet.svm@pappy.tf>  Fri, 23 May 2025 10:30:23 +0200

svm-plugin-enc (1.0.0) UNRELEASED; urgency=medium

  * First release of this plugin

 -- Julien Bruguier <projet.svm@pappy.tf>  Fri, 23 May 2025 10:30:23 +0200
%}
example: "Simple example"
%{
.nf
#!===SVMBIN===
LOG
PLUGIN "svmcom.so"
PLUGIN "===PLUGINLIB==="
PROCESS "main"
        CODE "main" INLINE
                :memory STR/s, STR/r
                [ "A test string" ] -> s
                :enc.hex ENCODE @&s -> &r
                :com.message @&r
                :enc.hex DECODE @&r -> &r
                :com.message @&r
                :enc.base64 ENCODE @&r -> &r
                :com.message @&r
                :enc.base64 DECODE @&r -> &r
                :com.message @&r
                :enc.sha1 @&r -> &r
                :enc.hex ENCODE @&r -> &r
                :com.message @&r
        END
END
.fi
%}
example: "A WebSocket handshake response"
%{
.nf
#!===SVMBIN===
LOG
PLUGIN "svmstr.so"
PLUGIN "svmcom.so"
PLUGIN "===PLUGINLIB==="
ARGUMENT STR key
PROCESS "websocket"
        CODE "main" INLINE
                :memory STR/r
                :str.join @&key "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" -> &r
                :enc.sha1 @&r -> &r
                :enc.base64 ENCODE @&r -> &r
                :com.message @&r
        END
        MEMORY key
END
.fi
%}

includes:
%{
#include <string>
#include <cstdint>
%}
code:
%{
struct Outils
{
	static std::string encode_base64(const std::string& source)
	{
		static const unsigned char base64_caracteres[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
		std::string s;
		const unsigned char *fin_source, *it;

		it = reinterpret_cast<const unsigned char*>(source.c_str());
		fin_source = it + source.size();
		while (fin_source - it >= 3) {
			s += base64_caracteres[it[0] >> 2];
			s += base64_caracteres[((it[0] & 0x03) << 4) | (it[1] >> 4)];
			s += base64_caracteres[((it[1] & 0x0f) << 2) | (it[2] >> 6)];
			s += base64_caracteres[it[2] & 0x3f];
			it += 3;
		}

		if (fin_source - it) {
			s += base64_caracteres[it[0] >> 2];
			if (fin_source - it == 1) {
				s += base64_caracteres[(it[0] & 0x03) << 4];
				s += '=';
			}
			else {
				s += base64_caracteres[((it[0] & 0x03) << 4) | (it[1] >> 4)];
				s += base64_caracteres[(it[1] & 0x0f) << 2];
			}
			s += '=';
		}

		return s;
	}

	static std::string decode_base64(const std::string& source)
	{
		static const int base64_index[256] = { 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 62, 63, 62, 62, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,  0,  0,  0,  0,  0,  0,  0,  0,  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,  0, 0,  0,  0, 63,  0, 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 };
		const unsigned char* p = reinterpret_cast<const unsigned char*>(source.c_str()); 
		int extra = source.size() > 0 && (source.size() % 4 || p[source.size() - 1] == '=');
		const size_t longueur = ((source.size() + 3) / 4 - extra) * 4;
		std::string resultat(longueur / 4 * 3 + extra, '\0');

		for (size_t i = 0, j = 0; i < longueur; i += 4)
		{
			int p0 = base64_index[p[i]];
			int p1 = base64_index[p[i+1]];
			int p2 = base64_index[p[i+2]];
			int p3 = base64_index[p[i+3]];
			int n = p0 << 18 | p1 << 12 | p2 << 6 | p3;
			resultat[j++] = n >> 16;
			resultat[j++] = n >> 8 & 0xFF;
			resultat[j++] = n & 0xFF;
		}
		if (extra)
		{
			int p0 = base64_index[p[longueur]];
			int p1 = base64_index[p[longueur+1]];
			int n = p0 << 18 | p1 << 12;
			resultat[resultat.size() - 1] = n >> 16;

			if (source.size() > longueur + 2 && p[longueur + 2] != '=')
			{
				int p2 = base64_index[p[longueur+2]];
				n |= p2 << 6;
				resultat.push_back(n >> 8 & 0xFF);
			}
		}
		return resultat;
	}

	static std::string encode_hex(const std::string& source)
	{
		std::string resultat;
		for(const auto& c: source)
		{
			unsigned char cc=c;
			unsigned char hc = cc >> 4;
			unsigned char lc = cc & 0xF;
			resultat += Outils::caractere_hex(hc);
			resultat += Outils::caractere_hex(lc);
		}
		return resultat;
	}
	static std::string decode_hex(const std::string& source)
	{
		std::string resultat;
		bool pair = true;
		unsigned char r;
		for(const auto& c: source)
		{
			unsigned char cc=Outils::entier_hex(c);
			if(pair)
			{
				r = cc << 4;
			}
			else
			{
				r += cc;
				resultat += r;
			}
			pair = not pair;
		}
		return resultat;
	}

	static std::string hashe_sha1(const std::string& source)
	{
		Atome hash[5];
		hash[0] = 0x67452301;
		hash[1] = 0xEFCDAB89;
		hash[2] = 0x98BADCFE;
		hash[3] = 0x10325476;
		hash[4] = 0xC3D2E1F0;
		unsigned char bloc[64];
		bool debut_extra = true;
		for(size_t index_bloc = 0 ; ; index_bloc += 64)
		{
			bool fin = false;
			for(size_t index=0 ; index<64 ; ++index)
			{
				if(index_bloc+index<source.size())
				{
					bloc[index] = source[index_bloc+index];
				}
				else
				{
					if(debut_extra)
					{
						bloc[index] = 0x80;
						debut_extra = false;
						if(index<=55) fin = true;
					}
					else
					{
						bloc[index] = 0;
						if(index==0) fin = true;
					}
					if(fin and (index>55))
					{
						bloc[index] = ((8*source.size()) >> (8*(63-index))) bitand 0xFF;
					}
				}
			}
			static const Atome K[] = { 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6 };
			size_t i;
			Atome temporaire;
			Atome W[80];
			Atome A = hash[0];
			Atome B = hash[1];
			Atome C = hash[2];
			Atome D = hash[3];
			Atome E = hash[4];
			for(i=0 ; i<16 ; ++i)
			{
				W[i] = (bloc[i*4] << 24) bitor (bloc[4*i+1] << 16) bitor (bloc[4*i+2] << 8) bitor (bloc[4*i+3]);
			}
			for(i=16 ; i<80 ; ++i)
			{
				W[i] = decalage_circulaire(1,W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16]);
			}
			for(i=0 ; i<20 ; ++i)
			{
				temporaire =  decalage_circulaire(5,A) + ((B & C) | ((~B) & D)) + E + W[i] + K[0];
				E = D;
				D = C;
				C = decalage_circulaire(30,B);
				B = A;
				A = temporaire;
			}
			for(i=20 ; i<40 ; ++i)
			{
				temporaire = decalage_circulaire(5,A) + (B ^ C ^ D) + E + W[i] + K[1];
				E = D;
				D = C;
				C = decalage_circulaire(30,B);
				B = A;
				A = temporaire;
			}
			for(i=40 ; i<60 ; ++i)
			{
				temporaire = decalage_circulaire(5,A) + ((B & C) | (B & D) | (C & D)) + E + W[i] + K[2];
				E = D;
				D = C;
				C = decalage_circulaire(30,B);
				B = A;
				A = temporaire;
			}
			for(i=60 ; i<80 ; ++i)
			{
				temporaire = decalage_circulaire(5,A) + (B ^ C ^ D) + E + W[i] + K[3];
				E = D;
				D = C;
				C = decalage_circulaire(30,B);
				B = A;
				A = temporaire;
			}
			hash[0] += A;
			hash[1] += B;
			hash[2] += C;
			hash[3] += D;
			hash[4] += E;
			if(fin) break;
		}
		unsigned char resultat[20]="";
		for(size_t i=0 ; i<20 ; ++i)
		{
			resultat[i] = hash[i>>2] >> 8*(3-(i bitand 0x03));
		}
		return std::string(reinterpret_cast<char*>(resultat),20);
	}

	private:	
	typedef uint32_t Atome;
	static unsigned char caractere_hex(unsigned char c)
	{
		if(c>=10)
			return 'a'+c-10;
		return '0'+c;
	}

	static unsigned char entier_hex(unsigned char c)
	{
		if(c>='a' and c<='f')
			return 10+c-'a';
		if(c>='A' and c<='F')
			return 10+c-'A';
		if(c>='0' and c<='9')
			return c-'0';
		throw 0;
	}
	template<typename T>
	static T decalage_circulaire(const char decalage, const T entree)
	{
		return (entree << decalage) bitor (entree >> (8*sizeof(T)-decalage));
	}
};
%}

DEFINE

INTERRUPTION enc.bad_encoding
help: "Interruption raised when a decoding from an invalid string is performed."

INSTRUCTION enc.hex [ 'ENCODE' 'DECODE' ] STR -> STR
%{
	std::string mode = ARGV_KEYWORD(0);
	SVM_String valeur_brute = ARGV_VALUE(1,string);
	std::string valeur(valeur_brute.string,valeur_brute.size);
	if(mode=="ENCODE")
	{
		std::string resultat = Outils::encode_hex(valeur);
		return ::svm_value_string_new__buffer(svm,resultat.c_str(),resultat.size());
	}
	else
	{
		try
		{
			std::string resultat = Outils::decode_hex(valeur);
			return ::svm_value_string_new__buffer(svm,resultat.c_str(),resultat.size());
		}
		catch(...)
		{
			ERROR_EXTERNAL(enc,bad_encoding,"Non-hex string");
			return nullptr;
		}
	}
%}
help:
%{
Encode or decode a string in hexadecimal format.
.P
The encoding produces a string with low case characters.
The decoding can decode a string with mixed case characters, and raises the interruption !enc.bad_encoding if the input string contains characters outside 0-9, a-f and A-F.
%}

INSTRUCTION enc.base64 [ 'ENCODE' 'DECODE' ] STR -> STR
%{
	std::string mode = ARGV_KEYWORD(0);
	SVM_String valeur_brute = ARGV_VALUE(1,string);
	std::string valeur(valeur_brute.string,valeur_brute.size);
	if(mode=="ENCODE")
	{
		std::string resultat = Outils::encode_base64(valeur);
		return ::svm_value_string_new__buffer(svm,resultat.c_str(),resultat.size());
	}
	else
	{
		try
		{
			std::string resultat = Outils::decode_base64(valeur);
			return ::svm_value_string_new__buffer(svm,resultat.c_str(),resultat.size());
		}
		catch(...)
		{
			ERROR_EXTERNAL(enc,bad_encoding,"Non-base64 string");
			return nullptr;
		}
	}
%}
help:
%{
Encode or decode a string in base 64 format.
.TP
The decoding raises the interruption !enc.bad_encoding if the input string contains characters outside base 64 allowed characters.
%}

INSTRUCTION enc.sha1 STR -> STR
%{
	SVM_String valeur_brute = ARGV_VALUE(0,string);
	std::string valeur(valeur_brute.string,valeur_brute.size);
	std::string resultat = Outils::hashe_sha1(valeur);
	return ::svm_value_string_new__buffer(svm,resultat.c_str(),resultat.size());
%}
help:
%{
Compute the raw SHA-1 hash of the input string.
%}
