]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Speed up base85() conversions and sync w/trunk.
authorlarrybr <larrybr@noemail.net>
Thu, 24 Nov 2022 20:11:34 +0000 (20:11 +0000)
committerlarrybr <larrybr@noemail.net>
Thu, 24 Nov 2022 20:11:34 +0000 (20:11 +0000)
FossilOrigin-Name: 17b823500a2ed135c1f40aa7f4d87ba5b2eab35c0abd9e0856041cf0f510cbee

1  2 
Makefile.in
ext/misc/base64.c
ext/misc/base85.c
manifest
manifest.uuid
src/shell.c.in

diff --cc Makefile.in
Simple merge
index 7bb7683d1fe734dd6b077ebe5d00617d61961864,0000000000000000000000000000000000000000..ec2fa70f1be31b1798567b9e6a7773350fda14b0
mode 100755,000000..100755
--- /dev/null
@@@ -1,266 -1,0 +1,269 @@@
 +/*
 +** 2022-11-18
 +**
 +** 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 SQLite extension for converting in either direction
 +** between a (binary) blob and base64 text. Base64 can transit a
 +** sane USASCII channel unmolested. It also plays nicely in CSV or
 +** written as TCL brace-enclosed literals or SQL string literals,
 +** and can be used unmodified in XML-like documents.
 +**
 +** This is an independent implementation of conversions specified in
 +** RFC 4648, done on the above date by the author (Larry Brasfield)
 +** who thereby has the right to put this into the public domain.
 +**
 +** The conversions meet RFC 4648 requirements, provided that this
 +** C source specifies that line-feeds are included in the encoded
 +** data to limit visible line lengths to 72 characters and to
 +** terminate any encoded blob having non-zero length.
 +**
 +** 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.
 +** Generated base64 sequences, with their line-feeds included,
 +** can be concatenated; the result converted back to binary will
 +** be the concatenation of the represented binary sequences.
 +**
 +** This SQLite3 extension creates a function, base64(x), which
 +** either: converts text x containing base64 to a returned blob;
 +** or converts a blob x to returned text containing base64. An
 +** error will be thrown for other input argument types.
 +**
 +** This code relies on UTF-8 encoding only with respect to the
 +** meaning of the first 128 (7-bit) codes matching that of USASCII.
 +** It will fail miserably if somehow made to try to convert EBCDIC.
 +** Because it is table-driven, it could be enhanced to handle that,
 +** but the world and SQLite have moved on from that anachronism.
 +**
 +** To build the extension:
 +** Set shell variable SQDIR=<your favorite SQLite checkout directory>
 +** *Nix: gcc -O2 -shared -I$SQDIR -fPIC -o base64.so base64.c
 +** OSX: gcc -O2 -dynamiclib -fPIC -I$SQDIR -o base64.dylib base64.c
 +** Win32: gcc -O2 -shared -I%SQDIR% -o base64.dll base64.c
 +** Win32: cl /Os -I%SQDIR% base64.c -link -dll -out:base64.dll
 +*/
 +
 +#include <assert.h>
 +
 +#ifndef SQLITE_SHELL_EXTFUNCS /* Guard for #include as built-in extension. */
 +#include "sqlite3ext.h"
 +#endif
 +
 +SQLITE_EXTENSION_INIT1;
 +
 +#define PC 0x80 /* pad character */
 +#define WS 0x81 /* whitespace */
 +#define ND 0x82 /* Not above or digit-value */
 +#define PAD_CHAR '='
 +
++#ifndef UBYTE_TYPEDEF
 +typedef unsigned char ubyte;
++# define UBYTE_TYPEDEF
++#endif
 +
 +static const ubyte b64DigitValues[128] = {
 +  /*                             HT LF VT  FF CR       */
 +    ND,ND,ND,ND, ND,ND,ND,ND, ND,WS,WS,WS, WS,WS,ND,ND,
 +  /*                                                US */
 +    ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND,
 +  /*sp                                  +            / */
 +    WS,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,62, ND,ND,ND,63,
 +  /* 0  1            5            9            =       */
 +    52,53,54,55, 56,57,58,59, 60,61,ND,ND, ND,PC,ND,ND,
 +  /*    A                                            O */
 +    ND, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
 +  /* P                               Z                 */
 +    15,16,17,18, 19,20,21,22, 23,24,25,ND, ND,ND,ND,ND,
 +  /*    a                                            o */
 +    ND,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
 +  /* p                               z                 */
 +    41,42,43,44, 45,46,47,48, 49,50,51,ND, ND,ND,ND,ND
 +};
 +
 +static const char b64Numerals[64+1]
 += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 +
 +#define BX_DV_PROTO(c) ((((ubyte)(c))<0x80)? b64DigitValues[c] : 0x80)
 +#define IS_BX_DIGIT(bdp) (((ubyte)(bdp))<0x80)
 +#define IS_BX_WS(bdp) ((bdp)==WS)
 +#define IS_BX_PAD(bdp) ((bdp)==PC)
 +#define BX_NUMERAL(dv) (b64Numerals[(ubyte)(dv)])
 +/* Width of base64 lines. Should be an integer multiple of 4. */
 +#define B64_DARK_MAX 72
 +
 +/* Encode a byte buffer into base64 text with linefeeds appended to limit
 +** encoded group lengths to B64_DARK_MAX or to terminate the last group.
 +*/
 +static char* toBase64( ubyte *pIn, int nbIn, char *pOut ){
 +  int nCol = 0;
 +  while( nbIn >= 3 ){
 +    /* Do the bit-shuffle, exploiting unsigned input to avoid masking. */
 +    pOut[0] = BX_NUMERAL(pIn[0]>>2);
 +    pOut[1] = BX_NUMERAL(((pIn[0]<<4)|(pIn[1]>>4))&0x3f);
 +    pOut[2] = BX_NUMERAL(((pIn[1]&0xf)<<2)|(pIn[2]>>6));
 +    pOut[3] = BX_NUMERAL(pIn[2]&0x3f);
 +    pOut += 4;
 +    nbIn -= 3;
 +    pIn += 3;
 +    if( (nCol += 4)>=B64_DARK_MAX || nbIn<=0 ){
 +      *pOut++ = '\n';
 +      nCol = 0;
 +    }
 +  }
 +  if( nbIn > 0 ){
 +    signed char nco = nbIn+1;
 +    int nbe;
 +    unsigned long qv = *pIn++;
 +    for( nbe=1; nbe<3; ++nbe ){
 +      qv <<= 8;
 +      if( nbe<nbIn ) qv |= *pIn++;
 +    }
 +    for( nbe=3; nbe>=0; --nbe ){
 +      char ce = (nbe<nco)? BX_NUMERAL((ubyte)(qv & 0x3f)) : PAD_CHAR;
 +      qv >>= 6;
 +      pOut[nbe] = ce;
 +    }
 +    pOut += 4;
 +    *pOut++ = '\n';
 +  }
 +  *pOut = 0;
 +  return pOut;
 +}
 +
 +/* Skip over text which is not base64 numeral(s). */
 +static char * skipNonB64( char *s ){
 +  char c;
 +  while( (c = *s) && !IS_BX_DIGIT(BX_DV_PROTO(c)) ) ++s;
 +  return s;
 +}
 +
 +/* Decode base64 text into a byte buffer. */
 +static ubyte* fromBase64( char *pIn, int ncIn, ubyte *pOut ){
 +  if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn;
 +  while( ncIn>0 && *pIn!=PAD_CHAR ){
 +    static signed char nboi[] = { 0, 0, 1, 2, 3 };
 +    char *pUse = skipNonB64(pIn);
 +    unsigned long qv = 0L;
 +    int nti, nbo, nac;
 +    ncIn -= (pUse - pIn);
 +    pIn = pUse;
 +    nti = (ncIn>4)? 4 : ncIn;
 +    ncIn -= nti;
 +    nbo = nboi[nti];
 +    if( nbo==0 ) break;
 +    for( nac=0; nac<4; ++nac ){
 +      char c = (nac<nti)? *pIn++ : b64Numerals[0];
 +      ubyte bdp = BX_DV_PROTO(c);
 +      switch( bdp ){
 +      case ND:
 +        /*  Treat dark non-digits as pad, but they terminate decode too. */
 +        ncIn = 0;
 +        /* fall thru */
 +      case WS:
 +        /* Treat whitespace as pad and terminate this group.*/
 +        nti = nac;
 +        /* fall thru */
 +      case PC:
 +        bdp = 0;
 +        --nbo;
 +         /* fall thru */
 +      default: /* bdp is the digit value. */
 +        qv = qv<<6 | bdp;
 +        break;
 +      }
 +    }
 +    switch( nbo ){
 +    case 3:
 +      pOut[2] = (qv) & 0xff;
 +    case 2:
 +      pOut[1] = (qv>>8) & 0xff;
 +    case 1:
 +      pOut[0] = (qv>>16) & 0xff;
 +    }
 +    pOut += nbo;
 +  }
 +  return pOut;
 +}
 +
 +/* This function does the work for the SQLite base64(x) UDF. */
 +static void base64(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;
 +  ubyte *bBuf;
 +  assert(na==1); 
 +  switch( sqlite3_value_type(av[0]) ){
 +  case SQLITE_BLOB:
 +    nb = nv;
 +    nc = 4*(nv+2/3); /* quads needed */
 +    nc += (nc+(B64_DARK_MAX-1))/B64_DARK_MAX + 1; /* LFs and a 0-terminator */
 +    if( nvMax < nc ){
 +      sqlite3_result_error(context, "blob expanded to base64 too big", -1);
 +      return;
 +    }
 +    cBuf = sqlite3_malloc(nc);
 +    if( !cBuf ) goto memFail;
 +    bBuf = (ubyte*)sqlite3_value_blob(av[0]);
 +    nc = (int)(toBase64(bBuf, nb, cBuf) - cBuf);
 +    sqlite3_result_text(context, cBuf, nc, sqlite3_free);
 +    break;
 +  case SQLITE_TEXT:
 +    nc = nv;
 +    nb = 3*((nv+3)/4); /* may overestimate due to LF and padding */
 +    if( nvMax < nb ){
 +      sqlite3_result_error(context, "blob from base64 may be too big", -1);
 +      return;
 +    }else if( nb<1 ){
 +      nb = 1;
 +    }
 +    bBuf = sqlite3_malloc(nb);
 +    if( !bBuf ) goto memFail;
 +    cBuf = (char *)sqlite3_value_text(av[0]);
 +    nb = (int)(fromBase64(cBuf, nc, bBuf) - bBuf);
 +    sqlite3_result_blob(context, bBuf, nb, sqlite3_free);
 +    break;
 +  default:
 +    sqlite3_result_error(context, "base64 accepts only blob or text", -1);
 +    return;
 +  }
 +  return;
 + memFail:
 +  sqlite3_result_error(context, "base64 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_base64_init
 +#endif
 +(sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi){
 +  SQLITE_EXTENSION_INIT2(pApi);
 +  (void)pzErr;
 +  return sqlite3_create_function
 +    (db, "base64", 1,
 +     SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_DIRECTONLY|SQLITE_UTF8,
 +     0, base64, 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 BASE64_INIT(db) sqlite3_base64_init(db, 0, 0)
 +#define BASE64_EXPOSE(db, pzErr) /* Not needed, ..._init() does this. */
index c82677f90c160e2ba588df4a1d0c002b569046a9,0000000000000000000000000000000000000000..32ae94906c5dbb88d9149a2bbb3b36271361224e
mode 100644,000000..100644
--- /dev/null
@@@ -1,402 -1,0 +1,431 @@@
- static unsigned char b85_cOffset[] = { 0, '#', 0, '*'-4, 0 };
 +/*
 +** 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
 +
 +#ifndef SQLITE_SHELL_EXTFUNCS /* Guard for #include as built-in extension. */
 +# include "sqlite3ext.h"
 +#endif
 +
 +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 UBYTE_TYPEDEF
++typedef unsigned char ubyte;
++# define UBYTE_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 unsigned char base85DigitValue( char c ){
-   unsigned char dv = (unsigned char)(c - '#');
++static ubyte 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 char base85Numeral( unsigned char b ){
++static ubyte base85DigitValue( char c ){
++  ubyte dv = (ubyte)(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 ){
 +  char c;
 +  while( (c = *s) && !IS_B85(c) ) ++s;
 +  return s;
 +}
 +
 +/* Convert small integer, known to be in 0..84 inclusive, to base85 numeral.*/
- static char* toBase85( unsigned char *pIn, int nbIn, char *pOut, char *pSep ){
++static char base85Numeral( ubyte b ){
 +  return (b < 4)? (char)(b + '#') : (char)(b - 4 + '*');
 +}
 +
++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.)
 +*/
-   *pOut = 0;
-   while( nbIn > 0 ){
-     static signed char ncio[] = { 0, 2, 3, 4, 5 };
-     int nbi = (nbIn > 4)? 4 : nbIn;
-     unsigned long qv = 0L;
-     int nbe = 0;
-     signed char nco;
-     while( nbe++ < nbi ){
++static char* toBase85( ubyte *pIn, int nbIn, char *pOut, char *pSep ){
 +  int nCol = 0;
-     nco = ncio[nbi];
-     nbIn -= nbi;
++  while( nbIn >= 4 ){
++    int nco = 5;
++    unsigned long qbv = (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++;
 +    }
-       unsigned char dv = (unsigned char)(qv % 85);
++    nCol += nco;
 +    while( nco > 0 ){
-     pOut += ncio[nbi];
-     if( pSep && ((nCol += ncio[nbi])>=B85_DARK_MAX || nbIn<=0) ){
-       char *p = pSep;
-       while( *p ) *pOut++ = *p++;
-       nCol = 0;
-     }
-     *pOut = 0;
++      ubyte dv = (ubyte)(qv % 85);
 +      qv /= 85;
 +      pOut[--nco] = base85Numeral(dv);
 +    }
- static unsigned char* fromBase85( char *pIn, int ncIn, unsigned char *pOut ){
++    pOut += (nbIn+1);
 +  }
++  if( pSep && nCol>0 ) pOut = putcs(pOut, pSep);
++  *pOut = 0;
 +  return pOut;
 +}
 +
 +/* Decode base85 text into a byte buffer. */
-     if( ncIn==0 ) break;
++static ubyte* fromBase85( char *pIn, int ncIn, ubyte *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);
 +    unsigned long qv = 0L;
 +    int nti, nbo;
 +    ncIn -= (pUse - pIn);
-       unsigned char cdo = B85_DNOS(c);
 +    pIn = pUse;
 +    nti = (ncIn>5)? 5 : ncIn;
 +    nbo = nboi[nti];
++    if( nbo==0 ) break;
 +    while( nti>0 ){
 +      char c = *pIn++;
-       qv = 85 * qv + c - cdo;
++      ubyte cdo = B85_DNOS(c);
 +      --ncIn;
 +      if( cdo==0 ) break;
-     nbo -= nti;
-     while( nbo-- > 0 ){
-       *pOut++ = (qv >> (8*nbo))&0xff;
++      qv = 85 * qv + (c - cdo);
 +      --nti;
 +    }
-   unsigned char *bBuf;
++    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;
-     bBuf = (unsigned char*)sqlite3_value_blob(av[0]);
++  ubyte *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;
 +    }
 +    cBuf = sqlite3_malloc(nc);
 +    if( !cBuf ) goto memFail;
-   unsigned char bBuf[4*(B85_DARK_MAX/5)];
++    bBuf = (ubyte*)sqlite3_value_blob(av[0]);
 +    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;
 +    }
 +    bBuf = sqlite3_malloc(nb);
 +    if( !bBuf ) goto memFail;
 +    cBuf = (char *)sqlite3_value_text(av[0]);
 +    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;
++  ubyte 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
diff --cc manifest
index 56f984f662e6d7b3ff95e46509316d43ec55038f,b3aa3b73dae0e3862dfb9e4fe3b28ed38d4702d5..10cb5bf04ee8e453f4c00a2bd56aa12468941b13
+++ b/manifest
@@@ -1,9 -1,9 +1,9 @@@
- C Speed\sup\sbase64\sconversions,\sand\sadd\stest\swith\smore\sdata\sfor\sthe\sbaseNN\sconversion\sto\sgrind.
- D 2022-11-24T02:59:33.748
 -C Fix\sa\stest\scase\sin\sfts3expr4.test\sto\saccount\sfor\sdifferent\slocales.
 -D 2022-11-24T17:58:55.503
++C Speed\sup\sbase85()\sconversions\sand\ssync\sw/trunk.
++D 2022-11-24T20:11:34.749
  F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
  F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
  F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
- F Makefile.in 4a16a05d210813cf8448b3143c80f73335a08a79728569de49d6984442b3f4cf
 -F Makefile.in 9a1228fd6406cc607958ccf1386a9a8f74f8255a7e98bc9c0a2792b56bd3a309
++F Makefile.in c223963d7b0828f26cb62ea3e0f583d26839b7d3ef0d1cca87f35c4b222ff01b
  F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
  F Makefile.msc e7a564ceec71f0d9666031d5638cf0d4f88b050b44e8df5d32125137cd259ac0
  F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e
@@@ -289,9 -289,6 +289,9 @@@ F ext/misc/README.md d6dd0fe1d8af770402
  F ext/misc/amatch.c e3ad5532799cee9a97647f483f67f43b38796b84b5a8c60594fe782a4338f358
  F ext/misc/anycollseq.c 5ffdfde9829eeac52219136ad6aa7cd9a4edb3b15f4f2532de52f4a22525eddb
  F ext/misc/appendvfs.c 9642c7a194a2a25dca7ad3e36af24a0a46d7702168c4ad7e59c9f9b0e16a3824
- F ext/misc/base64.c 6333194e5c2e85b0748116ad4004bf3e070347cc09984aaa8d462fb3fc0566b6 x
- F ext/misc/base85.c 9005549904fc06ec2f3ff96970709f92f76e2d9ec2b785553ac32908ddc1baa0
++F ext/misc/base64.c 8b200527ea933294d9a77e051e15d37e0c78f0a5f2ed1be50cb95fd41936c5ac x
++F ext/misc/base85.c b082f8dbfb823b479d19df1fa0f753e356d82ae51c3b6fc774252d4fd6877ee8
 +F ext/misc/basexx.c 678dcc83894f78c26fd3662b322886777cc26bf2b40809236cd2abdad532a33c
  F ext/misc/blobio.c a867c4c4617f6ec223a307ebfe0eabb45e0992f74dd47722b96f3e631c0edb2a
  F ext/misc/btreeinfo.c d28ce349b40054eaa9473e835837bad7a71deec33ba13e39f963d50933bfa0f9
  F ext/misc/carray.c b752f46411e4e47e34dce6f0c88bc8e51bb821ba9e49bfcd882506451c928f69
@@@ -645,7 -642,7 +645,7 @@@ F src/random.c 606b00941a1d7dd09c381d32
  F src/resolve.c efea4e5fbecfd6d0a9071b0be0d952620991673391b6ffaaf4c277b0bb674633
  F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
  F src/select.c 4c48373abb4e67129c36bc15d1f5a99a0dfd9534afeb539a2169a09ae91ccec9
- F src/shell.c.in 6bb8e3b54f079e5373199e2fb0ebe8acf5cd4dcac4bbfe2467b1cddbf15ced80
 -F src/shell.c.in 09cb15d7421c475f2d308f6a4312d8d690916ea5cb62ea1618f2f4ce5703af35
++F src/shell.c.in be0463b4a48b5d7c87651867cd6a4b6f453e1a152481419f7936f0bee9a81c30
  F src/sqlite.h.in 100fc660c2f19961b8ed8437b9d53d687de2f8eb2b96437ec6da216adcb643ca
  F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
  F src/sqlite3ext.h c4b9fa7a7e2bcdf850cfeb4b8a91d5ec47b7a00033bc996fd2ee96cbf2741f5f
@@@ -2063,8 -2059,8 +2063,8 @@@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a9
  F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
  F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
  F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
- P 03819e9368fd9f78f351147a1dc865743f9634893e43a9d1e3d7cbaf4c966069
- R f94c977ea1969bc60efbcf2158a80ba4
 -P 18e89a436daa18a8c972caf06b298da43c97a6ea3e2e5229dccb6920c27bfdb9
 -R e3089d3062e617db155d3fe3a44f9c32
 -U dan
 -Z c38105809dd01fc34e6f6bf40716c164
++P 6c84ae4ba83713c751fddff8be41686bbcb525ac8135e1520436c62d0bc23d2c a2b6883ac2ef878f525ee847b170beb9f9ab9d1fa67557101be2cdae1e7f7a57
++R 637b81d4c3e7af813b40107004ae4ce7
 +U larrybr
- Z 312bc23355f0a0c3b5b7524b3c0fa709
++Z 5b90ebbf3db611ecc14a602bf84a6a56
  # Remove this line to create a well-formed Fossil manifest.
diff --cc manifest.uuid
index 11257f9dabf5aaeeeea5e7b34cf4a5f04a6e02bd,bea5150fe53f538cd4418f880be42713d76c66d5..30ba16d8f090ac9ba01182ea92c4f84581205aa0
@@@ -1,1 -1,1 +1,1 @@@
- 6c84ae4ba83713c751fddff8be41686bbcb525ac8135e1520436c62d0bc23d2c
 -a2b6883ac2ef878f525ee847b170beb9f9ab9d1fa67557101be2cdae1e7f7a57
++17b823500a2ed135c1f40aa7f4d87ba5b2eab35c0abd9e0856041cf0f510cbee
diff --cc src/shell.c.in
index 44bf97c7045c163ff1b1494392fe59155bb8ab5a,e1f0ecfcd41ae332d6daf11622bc44f4418ca708..4409f4240d0679bebd20ac6352497ea0e19a7216
@@@ -1066,13 -1066,12 +1066,15 @@@ INCLUDE ../ext/expert/sqlite3expert.
  #define SQLITE_SHELL_HAVE_RECOVER 0
  #endif
  #if SQLITE_SHELL_HAVE_RECOVER
- INCLUDE ../ext/recover/dbdata.c
  INCLUDE ../ext/recover/sqlite3recover.h
+ # ifndef SQLITE_HAVE_SQLITE3R
+ INCLUDE ../ext/recover/dbdata.c
  INCLUDE ../ext/recover/sqlite3recover.c
+ # endif
  #endif
 +#ifdef SQLITE_SHELL_EXTSRC
 +# include SHELL_STRINGIFY(SQLITE_SHELL_EXTSRC)
 +#endif
  
  #if defined(SQLITE_ENABLE_SESSION)
  /*