mirror of
				https://github.com/tursodatabase/libsql.git
				synced 2025-11-04 10:38:54 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			451 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			451 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
** 2022-11-16
 | 
						|
**
 | 
						|
** The author disclaims copyright to this source code.  In place of
 | 
						|
** a legal notice, here is a blessing:
 | 
						|
**
 | 
						|
**    May you do good and not evil.
 | 
						|
**    May you find forgiveness for yourself and forgive others.
 | 
						|
**    May you share freely, never taking more than you give.
 | 
						|
**
 | 
						|
*************************************************************************
 | 
						|
**
 | 
						|
** This is a utility for converting binary to base85 or vice-versa.
 | 
						|
** It can be built as a standalone program or an SQLite3 extension.
 | 
						|
**
 | 
						|
** Much like base64 representations, base85 can be sent through a
 | 
						|
** sane USASCII channel unmolested. It also plays nicely in CSV or
 | 
						|
** written as TCL brace-enclosed literals or SQL string literals.
 | 
						|
** It is not suited for unmodified use in XML-like documents.
 | 
						|
**
 | 
						|
** The encoding used resembles Ascii85, but was devised by the author
 | 
						|
** (Larry Brasfield) before Mozilla, Adobe, ZMODEM or other Ascii85
 | 
						|
** variant sources existed, in the 1984 timeframe on a VAX mainframe.
 | 
						|
** Further, this is an independent implementation of a base85 system.
 | 
						|
** Hence, the author has rightfully put this into the public domain.
 | 
						|
**
 | 
						|
** Base85 numerals are taken from the set of 7-bit USASCII codes,
 | 
						|
** excluding control characters and Space ! " ' ( ) { | } ~ Del
 | 
						|
** in code order representing digit values 0 to 84 (base 10.)
 | 
						|
**
 | 
						|
** Groups of 4 bytes, interpreted as big-endian 32-bit values,
 | 
						|
** are represented as 5-digit base85 numbers with MS to LS digit
 | 
						|
** order. Groups of 1-3 bytes are represented with 2-4 digits,
 | 
						|
** still big-endian but 8-24 bit values. (Using big-endian yields
 | 
						|
** the simplest transition to byte groups smaller than 4 bytes.
 | 
						|
** These byte groups can also be considered base-256 numbers.)
 | 
						|
** Groups of 0 bytes are represented with 0 digits and vice-versa.
 | 
						|
** No pad characters are used; Encoded base85 numeral sequence
 | 
						|
** (aka "group") length maps 1-to-1 to the decoded binary length.
 | 
						|
**
 | 
						|
** Any character not in the base85 numeral set delimits groups.
 | 
						|
** When base85 is streamed or stored in containers of indefinite
 | 
						|
** size, newline is used to separate it into sub-sequences of no
 | 
						|
** more than 80 digits so that fgets() can be used to read it.
 | 
						|
**
 | 
						|
** Length limitations are not imposed except that the runtime
 | 
						|
** SQLite string or blob length limits are respected. Otherwise,
 | 
						|
** any length binary sequence can be represented and recovered.
 | 
						|
** Base85 sequences can be concatenated by separating them with
 | 
						|
** a non-base85 character; the conversion to binary will then
 | 
						|
** be the concatenation of the represented binary sequences.
 | 
						|
 | 
						|
** The standalone program either converts base85 on stdin to create
 | 
						|
** a binary file or converts a binary file to base85 on stdout.
 | 
						|
** Read or make it blurt its help for invocation details.
 | 
						|
**
 | 
						|
** The SQLite3 extension creates a function, base85(x), which will
 | 
						|
** either convert text base85 to a blob or a blob to text base85
 | 
						|
** and return the result (or throw an error for other types.)
 | 
						|
** Unless built with OMIT_BASE85_CHECKER defined, it also creates a
 | 
						|
** function, is_base85(t), which returns 1 iff the text t contains
 | 
						|
** nothing other than base85 numerals and whitespace, or 0 otherwise.
 | 
						|
**
 | 
						|
** To build the extension:
 | 
						|
** Set shell variable SQDIR=<your favorite SQLite checkout directory>
 | 
						|
** and variable OPTS to -DOMIT_BASE85_CHECKER if is_base85() unwanted.
 | 
						|
** *Nix: gcc -O2 -shared -I$SQDIR $OPTS -fPIC -o base85.so base85.c
 | 
						|
** OSX: gcc -O2 -dynamiclib -fPIC -I$SQDIR $OPTS -o base85.dylib base85.c
 | 
						|
** Win32: gcc -O2 -shared -I%SQDIR% %OPTS% -o base85.dll base85.c
 | 
						|
** Win32: cl /Os -I%SQDIR% %OPTS% base85.c -link -dll -out:base85.dll
 | 
						|
**
 | 
						|
** To build the standalone program, define PP symbol BASE85_STANDALONE. Eg.
 | 
						|
** *Nix or OSX: gcc -O2 -DBASE85_STANDALONE base85.c -o base85
 | 
						|
** Win32: gcc -O2 -DBASE85_STANDALONE -o base85.exe base85.c
 | 
						|
** Win32: cl /Os /MD -DBASE85_STANDALONE base85.c
 | 
						|
*/
 | 
						|
 | 
						|
#include <stdio.h>
 | 
						|
#include <memory.h>
 | 
						|
#include <string.h>
 | 
						|
#include <assert.h>
 | 
						|
#ifndef OMIT_BASE85_CHECKER
 | 
						|
# include <ctype.h>
 | 
						|
#endif
 | 
						|
 | 
						|
#ifndef BASE85_STANDALONE
 | 
						|
 | 
						|
# include "sqlite3ext.h"
 | 
						|
 | 
						|
SQLITE_EXTENSION_INIT1;
 | 
						|
 | 
						|
#else
 | 
						|
 | 
						|
# ifdef _WIN32
 | 
						|
#  include <io.h>
 | 
						|
#  include <fcntl.h>
 | 
						|
# else
 | 
						|
#  define setmode(fd,m)
 | 
						|
# endif
 | 
						|
 | 
						|
static char *zHelp =
 | 
						|
  "Usage: base85 <dirFlag> <binFile>\n"
 | 
						|
  " <dirFlag> is either -r to read or -w to write <binFile>,\n"
 | 
						|
  "   content to be converted to/from base85 on stdout/stdin.\n"
 | 
						|
  " <binFile> names a binary file to be rendered or created.\n"
 | 
						|
  "   Or, the name '-' refers to the stdin or stdout stream.\n"
 | 
						|
  ;
 | 
						|
 | 
						|
static void sayHelp(){
 | 
						|
  printf("%s", zHelp);
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
#ifndef U8_TYPEDEF
 | 
						|
typedef unsigned char u8;
 | 
						|
#define U8_TYPEDEF
 | 
						|
#endif
 | 
						|
 | 
						|
/* Classify c according to interval within USASCII set w.r.t. base85
 | 
						|
 * Values of 1 and 3 are base85 numerals. Values of 0, 2, or 4 are not.
 | 
						|
 */
 | 
						|
#define B85_CLASS( c ) (((c)>='#')+((c)>'&')+((c)>='*')+((c)>'z'))
 | 
						|
 | 
						|
/* Provide digitValue to b85Numeral offset as a function of above class. */
 | 
						|
static u8 b85_cOffset[] = { 0, '#', 0, '*'-4, 0 };
 | 
						|
#define B85_DNOS( c ) b85_cOffset[B85_CLASS(c)]
 | 
						|
 | 
						|
/* Say whether c is a base85 numeral. */
 | 
						|
#define IS_B85( c ) (B85_CLASS(c) & 1)
 | 
						|
 | 
						|
#if 0 /* Not used, */
 | 
						|
static u8 base85DigitValue( char c ){
 | 
						|
  u8 dv = (u8)(c - '#');
 | 
						|
  if( dv>87 ) return 0xff;
 | 
						|
  return (dv > 3)? dv-3 : dv;
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
/* Width of base64 lines. Should be an integer multiple of 5. */
 | 
						|
#define B85_DARK_MAX 80
 | 
						|
 | 
						|
 | 
						|
static char * skipNonB85( char *s, int nc ){
 | 
						|
  char c;
 | 
						|
  while( nc-- > 0 && (c = *s) && !IS_B85(c) ) ++s;
 | 
						|
  return s;
 | 
						|
}
 | 
						|
 | 
						|
/* Convert small integer, known to be in 0..84 inclusive, to base85 numeral.
 | 
						|
 * Do not use the macro form with argument expression having a side-effect.*/
 | 
						|
#if 0
 | 
						|
static char base85Numeral( u8 b ){
 | 
						|
  return (b < 4)? (char)(b + '#') : (char)(b - 4 + '*');
 | 
						|
}
 | 
						|
#else
 | 
						|
# define base85Numeral( dn )\
 | 
						|
  ((char)(((dn) < 4)? (char)((dn) + '#') : (char)((dn) - 4 + '*')))
 | 
						|
#endif
 | 
						|
 | 
						|
static char *putcs(char *pc, char *s){
 | 
						|
  char c;
 | 
						|
  while( (c = *s++)!=0 ) *pc++ = c;
 | 
						|
  return pc;
 | 
						|
}
 | 
						|
 | 
						|
/* Encode a byte buffer into base85 text. If pSep!=0, it's a C string
 | 
						|
** to be appended to encoded groups to limit their length to B85_DARK_MAX
 | 
						|
** or to terminate the last group (to aid concatenation.)
 | 
						|
*/
 | 
						|
static char* toBase85( u8 *pIn, int nbIn, char *pOut, char *pSep ){
 | 
						|
  int nCol = 0;
 | 
						|
  while( nbIn >= 4 ){
 | 
						|
    int nco = 5;
 | 
						|
    unsigned long qbv = (((unsigned long)pIn[0])<<24) |
 | 
						|
                        (pIn[1]<<16) | (pIn[2]<<8) | pIn[3];
 | 
						|
    while( nco > 0 ){
 | 
						|
      unsigned nqv = (unsigned)(qbv/85UL);
 | 
						|
      unsigned char dv = qbv - 85UL*nqv;
 | 
						|
      qbv = nqv;
 | 
						|
      pOut[--nco] = base85Numeral(dv);
 | 
						|
    }
 | 
						|
    nbIn -= 4;
 | 
						|
    pIn += 4;
 | 
						|
    pOut += 5;
 | 
						|
    if( pSep && (nCol += 5)>=B85_DARK_MAX ){
 | 
						|
      pOut = putcs(pOut, pSep);
 | 
						|
      nCol = 0;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  if( nbIn > 0 ){
 | 
						|
    int nco = nbIn + 1;
 | 
						|
    unsigned long qv = *pIn++;
 | 
						|
    int nbe = 1;
 | 
						|
    while( nbe++ < nbIn ){
 | 
						|
      qv = (qv<<8) | *pIn++;
 | 
						|
    }
 | 
						|
    nCol += nco;
 | 
						|
    while( nco > 0 ){
 | 
						|
      u8 dv = (u8)(qv % 85);
 | 
						|
      qv /= 85;
 | 
						|
      pOut[--nco] = base85Numeral(dv);
 | 
						|
    }
 | 
						|
    pOut += (nbIn+1);
 | 
						|
  }
 | 
						|
  if( pSep && nCol>0 ) pOut = putcs(pOut, pSep);
 | 
						|
  *pOut = 0;
 | 
						|
  return pOut;
 | 
						|
}
 | 
						|
 | 
						|
/* Decode base85 text into a byte buffer. */
 | 
						|
static u8* fromBase85( char *pIn, int ncIn, u8 *pOut ){
 | 
						|
  if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn;
 | 
						|
  while( ncIn>0 ){
 | 
						|
    static signed char nboi[] = { 0, 0, 1, 2, 3, 4 };
 | 
						|
    char *pUse = skipNonB85(pIn, ncIn);
 | 
						|
    unsigned long qv = 0L;
 | 
						|
    int nti, nbo;
 | 
						|
    ncIn -= (pUse - pIn);
 | 
						|
    pIn = pUse;
 | 
						|
    nti = (ncIn>5)? 5 : ncIn;
 | 
						|
    nbo = nboi[nti];
 | 
						|
    if( nbo==0 ) break;
 | 
						|
    while( nti>0 ){
 | 
						|
      char c = *pIn++;
 | 
						|
      u8 cdo = B85_DNOS(c);
 | 
						|
      --ncIn;
 | 
						|
      if( cdo==0 ) break;
 | 
						|
      qv = 85 * qv + (c - cdo);
 | 
						|
      --nti;
 | 
						|
    }
 | 
						|
    nbo -= nti; /* Adjust for early (non-digit) end of group. */
 | 
						|
    switch( nbo ){
 | 
						|
    case 4:
 | 
						|
      *pOut++ = (qv >> 24)&0xff;
 | 
						|
    case 3:
 | 
						|
      *pOut++ = (qv >> 16)&0xff;
 | 
						|
    case 2:
 | 
						|
      *pOut++ = (qv >> 8)&0xff;
 | 
						|
    case 1:
 | 
						|
      *pOut++ = qv&0xff;
 | 
						|
    case 0:
 | 
						|
      break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return pOut;
 | 
						|
}
 | 
						|
 | 
						|
#ifndef OMIT_BASE85_CHECKER
 | 
						|
/* Say whether input char sequence is all (base85 and/or whitespace).*/
 | 
						|
static int allBase85( char *p, int len ){
 | 
						|
  char c;
 | 
						|
  while( len-- > 0 && (c = *p++) != 0 ){
 | 
						|
    if( !IS_B85(c) && !isspace(c) ) return 0;
 | 
						|
  }
 | 
						|
  return 1;
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
#ifndef BASE85_STANDALONE
 | 
						|
 | 
						|
# ifndef OMIT_BASE85_CHECKER
 | 
						|
/* This function does the work for the SQLite is_base85(t) UDF. */
 | 
						|
static void is_base85(sqlite3_context *context, int na, sqlite3_value *av[]){
 | 
						|
  assert(na==1);
 | 
						|
  switch( sqlite3_value_type(av[0]) ){
 | 
						|
  case SQLITE_TEXT:
 | 
						|
    {
 | 
						|
      int rv = allBase85( (char *)sqlite3_value_text(av[0]),
 | 
						|
                          sqlite3_value_bytes(av[0]) );
 | 
						|
      sqlite3_result_int(context, rv);
 | 
						|
    }
 | 
						|
    break;
 | 
						|
  case SQLITE_NULL:
 | 
						|
    sqlite3_result_null(context);
 | 
						|
    break;
 | 
						|
  default:
 | 
						|
    sqlite3_result_error(context, "is_base85 accepts only text or NULL", -1);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
}
 | 
						|
# endif
 | 
						|
 | 
						|
/* This function does the work for the SQLite base85(x) UDF. */
 | 
						|
static void base85(sqlite3_context *context, int na, sqlite3_value *av[]){
 | 
						|
  int nb, nc, nv = sqlite3_value_bytes(av[0]);
 | 
						|
  int nvMax = sqlite3_limit(sqlite3_context_db_handle(context),
 | 
						|
                            SQLITE_LIMIT_LENGTH, -1);
 | 
						|
  char *cBuf;
 | 
						|
  u8 *bBuf;
 | 
						|
  assert(na==1);
 | 
						|
  switch( sqlite3_value_type(av[0]) ){
 | 
						|
  case SQLITE_BLOB:
 | 
						|
    nb = nv;
 | 
						|
    /*    ulongs    tail   newlines  tailenc+nul*/
 | 
						|
    nc = 5*(nv/4) + nv%4 + nv/64+1 + 2;
 | 
						|
    if( nvMax < nc ){
 | 
						|
      sqlite3_result_error(context, "blob expanded to base85 too big", -1);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    bBuf = (u8*)sqlite3_value_blob(av[0]);
 | 
						|
    if( !bBuf ){
 | 
						|
      if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){
 | 
						|
        goto memFail;
 | 
						|
      }
 | 
						|
      sqlite3_result_text(context,"",-1,SQLITE_STATIC);
 | 
						|
      break;
 | 
						|
    }
 | 
						|
    cBuf = sqlite3_malloc(nc);
 | 
						|
    if( !cBuf ) goto memFail;
 | 
						|
    nc = (int)(toBase85(bBuf, nb, cBuf, "\n") - cBuf);
 | 
						|
    sqlite3_result_text(context, cBuf, nc, sqlite3_free);
 | 
						|
    break;
 | 
						|
  case SQLITE_TEXT:
 | 
						|
    nc = nv;
 | 
						|
    nb = 4*(nv/5) + nv%5; /* may overestimate */
 | 
						|
    if( nvMax < nb ){
 | 
						|
      sqlite3_result_error(context, "blob from base85 may be too big", -1);
 | 
						|
      return;
 | 
						|
    }else if( nb<1 ){
 | 
						|
      nb = 1;
 | 
						|
    }
 | 
						|
    cBuf = (char *)sqlite3_value_text(av[0]);
 | 
						|
    if( !cBuf ){
 | 
						|
      if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){
 | 
						|
        goto memFail;
 | 
						|
      }
 | 
						|
      sqlite3_result_zeroblob(context, 0);
 | 
						|
      break;
 | 
						|
    }
 | 
						|
    bBuf = sqlite3_malloc(nb);
 | 
						|
    if( !bBuf ) goto memFail;
 | 
						|
    nb = (int)(fromBase85(cBuf, nc, bBuf) - bBuf);
 | 
						|
    sqlite3_result_blob(context, bBuf, nb, sqlite3_free);
 | 
						|
    break;
 | 
						|
  default:
 | 
						|
    sqlite3_result_error(context, "base85 accepts only blob or text.", -1);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  return;
 | 
						|
 memFail:
 | 
						|
  sqlite3_result_error(context, "base85 OOM", -1);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
** Establish linkage to running SQLite library.
 | 
						|
*/
 | 
						|
#ifndef SQLITE_SHELL_EXTFUNCS
 | 
						|
#ifdef _WIN32
 | 
						|
__declspec(dllexport)
 | 
						|
#endif
 | 
						|
int sqlite3_base_init
 | 
						|
#else
 | 
						|
static int sqlite3_base85_init
 | 
						|
#endif
 | 
						|
(sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi){
 | 
						|
  SQLITE_EXTENSION_INIT2(pApi);
 | 
						|
  (void)pzErr;
 | 
						|
# ifndef OMIT_BASE85_CHECKER
 | 
						|
  {
 | 
						|
    int rc = sqlite3_create_function
 | 
						|
      (db, "is_base85", 1,
 | 
						|
       SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_UTF8,
 | 
						|
       0, is_base85, 0, 0);
 | 
						|
    if( rc!=SQLITE_OK ) return rc;
 | 
						|
  }
 | 
						|
# endif
 | 
						|
  return sqlite3_create_function
 | 
						|
    (db, "base85", 1,
 | 
						|
     SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_DIRECTONLY|SQLITE_UTF8,
 | 
						|
     0, base85, 0, 0);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
** Define some macros to allow this extension to be built into the shell
 | 
						|
** conveniently, in conjunction with use of SQLITE_SHELL_EXTFUNCS. This
 | 
						|
** allows shell.c, as distributed, to have this extension built in.
 | 
						|
*/
 | 
						|
# define BASE85_INIT(db) sqlite3_base85_init(db, 0, 0)
 | 
						|
# define BASE85_EXPOSE(db, pzErr) /* Not needed, ..._init() does this. */
 | 
						|
 | 
						|
#else /* standalone program */
 | 
						|
 | 
						|
int main(int na, char *av[]){
 | 
						|
  int cin;
 | 
						|
  int rc = 0;
 | 
						|
  u8 bBuf[4*(B85_DARK_MAX/5)];
 | 
						|
  char cBuf[5*(sizeof(bBuf)/4)+2];
 | 
						|
  size_t nio;
 | 
						|
# ifndef OMIT_BASE85_CHECKER
 | 
						|
  int b85Clean = 1;
 | 
						|
# endif
 | 
						|
  char rw;
 | 
						|
  FILE *fb = 0, *foc = 0;
 | 
						|
  char fmode[3] = "xb";
 | 
						|
  if( na < 3 || av[1][0]!='-' || (rw = av[1][1])==0 || (rw!='r' && rw!='w') ){
 | 
						|
    sayHelp();
 | 
						|
    return 0;
 | 
						|
  }
 | 
						|
  fmode[0] = rw;
 | 
						|
  if( av[2][0]=='-' && av[2][1]==0 ){
 | 
						|
    switch( rw ){
 | 
						|
    case 'r':
 | 
						|
      fb = stdin;
 | 
						|
      setmode(fileno(stdin), O_BINARY);
 | 
						|
      break;
 | 
						|
    case 'w':
 | 
						|
      fb = stdout;
 | 
						|
      setmode(fileno(stdout), O_BINARY);
 | 
						|
      break;
 | 
						|
    }
 | 
						|
  }else{
 | 
						|
    fb = fopen(av[2], fmode);
 | 
						|
    foc = fb;
 | 
						|
  }
 | 
						|
  if( !fb ){
 | 
						|
    fprintf(stderr, "Cannot open %s for %c\n", av[2], rw);
 | 
						|
    rc = 1;
 | 
						|
  }else{
 | 
						|
    switch( rw ){
 | 
						|
    case 'r':
 | 
						|
      while( (nio = fread( bBuf, 1, sizeof(bBuf), fb))>0 ){
 | 
						|
        toBase85( bBuf, (int)nio, cBuf, 0 );
 | 
						|
        fprintf(stdout, "%s\n", cBuf);
 | 
						|
      }
 | 
						|
      break;
 | 
						|
    case 'w':
 | 
						|
      while( 0 != fgets(cBuf, sizeof(cBuf), stdin) ){
 | 
						|
        int nc = strlen(cBuf);
 | 
						|
        size_t nbo = fromBase85( cBuf, nc, bBuf ) - bBuf;
 | 
						|
        if( 1 != fwrite(bBuf, nbo, 1, fb) ) rc = 1;
 | 
						|
# ifndef OMIT_BASE85_CHECKER
 | 
						|
        b85Clean &= allBase85( cBuf, nc );
 | 
						|
# endif
 | 
						|
      }
 | 
						|
      break;
 | 
						|
    default:
 | 
						|
      sayHelp();
 | 
						|
      rc = 1;
 | 
						|
    }
 | 
						|
    if( foc ) fclose(foc);
 | 
						|
  }
 | 
						|
# ifndef OMIT_BASE85_CHECKER
 | 
						|
  if( !b85Clean ){
 | 
						|
    fprintf(stderr, "Base85 input had non-base85 dark or control content.\n");
 | 
						|
  }
 | 
						|
# endif
 | 
						|
  return rc;
 | 
						|
}
 | 
						|
 | 
						|
#endif
 |