C strings are simple: they start at a memory address and end at the first zero/null byte (\0). But what if your data contains nulls? If you use standard string types like String or Array[Char], C (and Affix) will assume the data ends at the first null byte. This is fatal for encryption, compression, and binary formats.
To handle this, we must stop thinking in characters and start thinking in bytes.
The Recipe
This is a demonstration of the XOR cipher. Since the data might contain nulls, we handle it as a raw buffer.
use v5.40;
use Affix qw[:all];
use Affix::Build;
# Compile the C library
my $c = Affix::Build->new();
$c->add( \<<~'END', lang => 'c' );
#include <stdlib.h>
#include <string.h>
char * string_crypt(const char * input, int len, const char * key) {
char * output = malloc(len + 1);
if (!output) return NULL;
output[len] = '\0';
int key_len = strlen(key);
for (int i = 0; i < len; i++) {
output[i] = input[i] ^ key[i % key_len];
}
return output;
}
void string_free(void * p) { free(p); }
END
my $lib = $c->link;
# Bind the functions
# Return Pointer[Void] to get the raw address (Pin) without interpretation.
affix $lib, 'string_crypt', [ String, Int, String ] => Pointer [Void];
affix $lib, 'string_free', [ Pointer [Void] ] => Void;
# The Wrapper
sub cipher( $input, $key ) {
my $len = length($input);
# Call C. We get back a Pin (unmanaged pointer).
my $ptr = string_crypt( $input, $len, $key );
return undef if is_null($ptr);
# Copy the raw bytes out of C memory.
# raw() reads exactly $len bytes, regardless of embedded nulls.
my $binary_string = raw( $ptr, $len );
# Clean up the C memory immediately
string_free($ptr);
return $binary_string;
}
# Run it
my $orig = 'hello world!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!';
my $key = 'foobar';
my $enc = cipher( $orig, $key );
my $dec = cipher( $enc, $key );
say "Original: $orig";
say "Decrypted: $dec";
# Verify the binary data size
say "Encrypted length: " . length($enc);
use JSON::PP;
say "Encrypted blob: " . encode_json( [$enc] );
How It Works
-
1. Pointer[Void]
We define the return type as
Pointer[Void]to prevent Affix from automatically attempting to read it as a null-terminated string. We receive a raw Pin. -
2. Copying Raw Bytes with
raw()my $binary = raw( $ptr, $len );raw( $ptr, $bytes )copies exactly$bytesof memory into a Perl string, regardless of embedded nulls. Because the returned string is a plain Perl scalar, it can be passed back to C, hashed, JSON-encoded, or returned as-is without any special handling.
Kitchen Reminders
-
raw()vs. Castingraw( $ptr, $bytes )is the go-to tool when you need a plain Perl string of bytes from C memory. Casting the same address toArray[UInt8]orArray[Char]produces a zero-copy live view (an array reference) into C memory, which is useful for element access but not for string operations.
Comments