/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/*
** Copyright (c) 2013 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
*/
#include "fossil-scm/fossil.h"
#include <sqlite3.h>
#include <assert.h>
#include <string.h> /* strlen() */
#include <stddef.h> /* NULL on linux */
#include <errno.h>
#include <zlib.h>
#define MARKER(pfexp) \
do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \
printf pfexp; \
} while(0)
int fsl_buffer_reset( fsl_buffer * b ){
if(!b) return FSL_RC_MISUSE;
else{
if(b->capacity){
assert(b->mem);
b->mem[0] = 0;
}
b->used = 0;
return 0;
}
}
void fsl_buffer_clear( fsl_buffer * buf ){
if(buf){
if(buf->mem) fsl_free(buf->mem);
*buf = fsl_buffer_empty;
}
}
int fsl_buffer_reserve( fsl_buffer * buf, fsl_size_t n ){
if( ! buf ) return FSL_RC_MISUSE;
else if( 0 == n ){
fsl_free(buf->mem);
*buf = fsl_buffer_empty;
return 0;
}
else if( buf->capacity >= n ){
return 0;
}
else{
unsigned char * x = (unsigned char *)fsl_realloc( buf->mem, n );
if( ! x ) return FSL_RC_OOM;
memset( x + buf->used, 0, n - buf->used );
buf->mem = x;
buf->capacity = n;
return 0;
}
}
int fsl_buffer_resize( fsl_buffer * buf, fsl_size_t n ){
if( !buf ) return FSL_RC_MISUSE;
else if(n && (buf->capacity == n+1)){
buf->used = n;
buf->mem[n] = 0;
return 0;
}
else {
unsigned char * x = (unsigned char *)fsl_realloc( buf->mem,
n+1/*NUL*/ );
if( ! x ) return FSL_RC_OOM;
if(n > buf->capacity){
/* zero-fill new parts */
memset( x + buf->capacity, 0, n - buf->capacity +1/*NUL*/ );
}
buf->capacity = n + 1 /*NUL*/;
buf->used = n;
buf->mem = x;
buf->mem[buf->used] = 0;
return 0;
}
}
int fsl_buffer_compare(fsl_buffer const * lhs, fsl_buffer const * rhs){
fsl_size_t const szL = lhs->used;
fsl_size_t const szR = rhs->used;
fsl_size_t const sz = (szL<szR) ? szL : szR;
int rc = memcmp(lhs->mem, rhs->mem, sz);
if(0 == rc){
rc = (szL==szR)
? 0
: ((szL<szR) ? -1 : 1);
}
return rc;
}
/*
** Compare two blobs in constant time and return zero if they are equal.
** Constant time comparison only applies for blobs of the same length.
** If lengths are different, immediately returns 1.
*/
int fsl_buffer_compare_O1(fsl_buffer const * lhs, fsl_buffer const * rhs){
fsl_size_t const szL = lhs->used;
fsl_size_t const szR = rhs->used;
fsl_size_t i;
unsigned char const *buf1;
unsigned char const *buf2;
unsigned char rc = 0;
if( szL!=szR || szL==0 ) return 1;
buf1 = lhs->mem;
buf2 = rhs->mem;
for( i=0; i<szL; i++ ){
rc = rc | (buf1[i] ^ buf2[i]);
}
return rc;
}
int fsl_buffer_append( fsl_buffer * b,
void const * data,
fsl_int_t len ){
if(!b || !data) return FSL_RC_MISUSE;
else{
fsl_size_t sz = b->used;
int rc = 0;
assert(b->capacity ? !!b->mem : !b->mem);
assert(b->used <= b->capacity);
if(len<0){
len = (fsl_int_t)fsl_strlen((char const *)data);
}
sz += len + 1/*NUL*/;
rc = fsl_buffer_reserve( b, sz );
if(!rc){
assert(b->capacity >= sz);
if(len>0) memcpy(b->mem + b->used, data, (size_t)len);
b->used += len;
b->mem[b->used] = 0;
}
return rc;
}
}
/*
** Internal helper for implementing fsl_buffer_appendf()
*/
typedef struct BufferAppender {
fsl_buffer * b;
/*
** Result code of the appending process.
*/
int rc;
} BufferAppender;
/*
** fsl_appendf_f() impl which requires arg to be a (fsl_buffer*).
** It appends the data to arg.
*/
static fsl_int_t fsl_appendf_f_buffer( void * arg,
char const * data, fsl_int_t n ){
BufferAppender * ba = (BufferAppender*)arg;
fsl_buffer * sb = ba->b;
if( !sb || (n<0) ) return -1;
else if( ! n ) return 0;
else{
fsl_int_t rc;
fsl_size_t npos = sb->used + n;
if( npos >= sb->capacity ){
const size_t asz = npos ? ((4 * npos / 3) + 1) : 16;
if( asz < npos ) {
ba->rc = FSL_RC_RANGE;
return -1; /* overflow */
}
else{
rc = fsl_buffer_reserve( sb, asz );
if(rc) {
ba->rc = FSL_RC_OOM;
return -1;
}
}
}
rc = 0;
for( ; rc < n; ++rc, ++sb->used ){
sb->mem[sb->used] = data[rc];
}
sb->mem[sb->used] = 0;
return rc;
}
}
int fsl_buffer_appendfv( fsl_buffer * b,
char const * fmt, va_list args){
if(!b || !fmt) return FSL_RC_MISUSE;
else{
BufferAppender ba;
ba.b = b;
ba.rc = 0;
fsl_appendfv( fsl_appendf_f_buffer, &ba, fmt, args );
return ba.rc;
}
}
char const * fsl_buffer_cstr(fsl_buffer const *b){
return b ? (char const *)b->mem : NULL;
}
char const * fsl_buffer_cstr2(fsl_buffer const *b, fsl_size_t * len){
char const * rc = NULL;
if(b){
rc = (char const *)b->mem;
if(len) *len = b->used;
}
return rc;
}
char * fsl_buffer_str(fsl_buffer const *b){
return b ? (char *)b->mem : NULL;
}
fsl_size_t fsl_buffer_size(fsl_buffer const * b){
return b ? b->used : 0U;
}
fsl_size_t fsl_buffer_capacity(fsl_buffer const * b){
return b ? b->capacity : 0;
}
int fsl_buffer_appendf( fsl_buffer * b,
char const * fmt, ... ){
if(!b || !fmt) return FSL_RC_MISUSE;
else{
int rc;
va_list args;
va_start(args,fmt);
rc = fsl_buffer_appendfv( b, fmt, args );
va_end(args);
return rc;
}
}
char fsl_data_is_compressed(unsigned char const * mem, fsl_size_t len){
if(!mem || (len<6)) return 0;
#if 1
else return ('x'==mem[4])
&& (0234==mem[5]);
#else
else{
/**
Adapted from:
http://blog.2of1.org/2011/03/03/decompressing-zlib-images/
Remember that fossil-compressed data has a 4-byte big-endian
header holding the uncompressed size of the data, so we skip
those first 4 bytes.
See also:
http://tools.ietf.org/html/rfc6713
search for "magic number".
*/
fsl_int16_t const head = (((fsl_int16_t)mem[4]) << 8) | mem[5];
/* MARKER(("isCompressed header=%04x\n", head)); */
switch(head){
case 0x083c: case 0x087a: case 0x08b8: case 0x08f6:
case 0x1838: case 0x1876: case 0x18b4: case 0x1872:
case 0x2834: case 0x2872: case 0x28b0: case 0x28ee:
case 0x3830: case 0x386e: case 0x38ac: case 0x38ea:
case 0x482c: case 0x486a: case 0x48a8: case 0x48e6:
case 0x5828: case 0x5866: case 0x58a4: case 0x58e2:
case 0x6824: case 0x6862: case 0x68bf: case 0x68fd:
case 0x7801: case 0x785e: case 0x789c: case 0x78da:
return 1;
default:
return 0;
}
}
#endif
}
char fsl_buffer_is_compressed(fsl_buffer const *buf){
return buf
? fsl_data_is_compressed( buf->mem, buf->used )
: 0;
}
fsl_int_t fsl_data_uncompressed_size(unsigned char const *mem,
fsl_size_t len){
return fsl_data_is_compressed(mem,len)
? ((mem[0]<<24) + (mem[1]<<16) + (mem[2]<<8) + mem[3])
: -1;
}
fsl_int_t fsl_buffer_uncompressed_size(fsl_buffer const * b){
return b
? fsl_data_uncompressed_size(b->mem, b->used)
: -1;
}
int fsl_buffer_compress(fsl_buffer const *pIn, fsl_buffer *pOut){
unsigned int nIn = pIn->used;
unsigned int nOut = 13 + nIn + (nIn+999)/1000;
fsl_buffer temp = fsl_buffer_empty;
int rc = fsl_buffer_resize(&temp, nOut+4);
if(rc) return rc;
else{
unsigned long int nOut2;
unsigned char *outBuf;
outBuf = temp.mem;
outBuf[0] = nIn>>24 & 0xff;
outBuf[1] = nIn>>16 & 0xff;
outBuf[2] = nIn>>8 & 0xff;
outBuf[3] = nIn & 0xff;
nOut2 = (long int)nOut;
rc = compress(&outBuf[4], &nOut2,
pIn->mem, pIn->used);
if(rc){
fsl_buffer_reserve(&temp, 0);
return FSL_RC_ERROR;
}
fsl_buffer_reserve(pOut, 0);
*pOut = temp;
if(!rc){
rc = fsl_buffer_resize(pOut, nOut2+4);
if(!rc){
pOut->used = nOut2+4;
}
}
return rc;
}
}
int fsl_buffer_compress2(fsl_buffer const *pIn1,
fsl_buffer const *pIn2, fsl_buffer *pOut){
unsigned int nIn = pIn1->used + pIn2->used;
unsigned int nOut = 13 + nIn + (nIn+999)/1000;
fsl_buffer temp = fsl_buffer_empty;
int rc;
rc = fsl_buffer_resize(&temp, nOut+4);
if(rc) return rc;
else{
unsigned char *outBuf;
z_stream stream;
outBuf = temp.mem;
outBuf[0] = nIn>>24 & 0xff;
outBuf[1] = nIn>>16 & 0xff;
outBuf[2] = nIn>>8 & 0xff;
outBuf[3] = nIn & 0xff;
stream.zalloc = (alloc_func)0;
stream.zfree = (free_func)0;
stream.opaque = 0;
stream.avail_out = nOut;
stream.next_out = &outBuf[4];
deflateInit(&stream, 9);
stream.avail_in = pIn1->used;
stream.next_in = pIn1->mem;
deflate(&stream, 0);
stream.avail_in = pIn2->used;
stream.next_in = pIn2->mem;
deflate(&stream, 0);
deflate(&stream, Z_FINISH);
rc = fsl_buffer_resize(&temp, stream.total_out + 4);
deflateEnd(&stream);
if(!rc){
temp.used = stream.total_out + 4;
if( pOut==pIn1 ) fsl_buffer_reserve(pOut, 0);
else if( pOut==pIn2 ) fsl_buffer_reserve(pOut, 0);
assert(!pOut->mem);
*pOut = temp;
}else{
fsl_buffer_reserve(&temp, 0);
}
return rc;
}
}
int fsl_buffer_uncompress(fsl_buffer const *pIn, fsl_buffer *pOut){
unsigned int nOut;
unsigned char *inBuf;
unsigned int nIn = pIn->used;
fsl_buffer temp = fsl_buffer_empty;
int rc;
unsigned long int nOut2;
if( nIn<=4 ){
return FSL_RC_RANGE;
}
inBuf = pIn->mem;
nOut = (inBuf[0]<<24) + (inBuf[1]<<16) + (inBuf[2]<<8) + inBuf[3];
rc = fsl_buffer_reserve(&temp, nOut+1);
if(rc) return rc;
nOut2 = (long int)nOut;
rc = uncompress(temp.mem, &nOut2,
&inBuf[4], nIn - 4)
/* valgrind says there's an uninitialized memory access
somewhere under uncompress(), _presumably_ for one of
these arguments, but i can't find it. fsl_buffer_reserve()
always memsets() new bytes to 0.
Turns out it's a known problem:
http://www.zlib.net/zlib_faq.html#faq36
*/;
if( rc!=Z_OK ){
fsl_buffer_reserve(&temp, 0);
return FSL_RC_ERROR;
}
rc = fsl_buffer_resize(&temp, nOut2);
if(!rc){
temp.used = (fsl_size_t)nOut2;
if( pOut==pIn ){
fsl_buffer_reserve(pOut, 0);
}
assert(!pOut->mem);
*pOut = temp;
}else{
fsl_buffer_reserve(&temp, 0);
}
return rc;
}
int fsl_buffer_fill_from( fsl_buffer * dest, fsl_input_f src, void * state )
{
int rc;
enum { BufSize = 512 * 4 };
char rbuf[BufSize];
fsl_size_t total = 0;
fsl_size_t rlen = 0;
if( !dest || ! src ) return FSL_RC_MISUSE;
dest->used = 0;
while(1){
rlen = BufSize;
rc = src( state, rbuf, &rlen );
if( rc ) break;
total += rlen;
if(total<rlen){
/* Overflow! */
rc = FSL_RC_RANGE;
break;
}
if( dest->capacity < (total+1) ){
rc = fsl_buffer_reserve( dest,
total + ((rlen<BufSize) ? 1 : BufSize)
);
if( 0 != rc ) break;
}
memcpy( dest->mem + dest->used, rbuf, rlen );
dest->used += rlen;
if( rlen < BufSize ) break;
}
if( !rc && dest->used ){
assert( dest->used < dest->capacity );
dest->mem[dest->used] = 0;
}
return rc;
}
int fsl_buffer_fill_from_FILE( fsl_buffer * dest, FILE * src ){
return (!dest || !src)
? FSL_RC_MISUSE
: fsl_buffer_fill_from( dest, fsl_input_f_FILE, src );
}
int fsl_buffer_fill_from_filename( fsl_buffer * dest, char const * filename ){
if(!dest || !filename || !*filename) return FSL_RC_MISUSE;
else{
int rc;
FILE * src;
fsl_fstat st = fsl_fstat_empty;
rc = fsl_stat( filename, &st, 1 );
if(rc && st.size>0){ /* Might not be a real file, e.g. "-" */
rc = fsl_buffer_reserve(dest, st.size+1);
if(rc) return rc;
}
src = fsl_fopen(filename,"rb");
if(!src) rc = fsl_errno_to_rc(errno, FSL_RC_IO);
else {
rc = fsl_buffer_fill_from( dest, fsl_input_f_FILE, src );
fsl_fclose(src);
}
return rc;
}
}
void fsl_buffer_swap( fsl_buffer * left, fsl_buffer * right ){
fsl_buffer const tmp = *left;
*left = *right;
*right = tmp;
}
void fsl_buffer_swap_free( fsl_buffer * left, fsl_buffer * right, char clearWhich ){
fsl_buffer_swap(left, right);
if(0 != clearWhich) fsl_buffer_reserve((clearWhich<0) ? left : right, 0);
}
int fsl_buffer_copy( fsl_buffer const * src, fsl_buffer * dest ){
dest->used = 0;
return src->used
? fsl_buffer_append( dest, src->mem, src->used )
: 0;
}
int fsl_buffer_delta_apply2( fsl_buffer const * orig,
fsl_buffer const * pDelta,
fsl_buffer * pTarget,
fsl_error * pErr){
int rc;
fsl_size_t n = 0;
fsl_buffer out = fsl_buffer_empty;
rc = fsl_delta_applied_size( pDelta->mem, pDelta->used, &n);
if(rc){
if(pErr){
fsl_error_set(pErr, rc, "fsl_delta_applied_size() failed.");
}
return rc;
}
rc = fsl_buffer_resize( &out, n );
if(rc) return rc;
rc = fsl_delta_apply2( orig->mem, orig->used,
pDelta->mem, pDelta->used,
out.mem, pErr);
if(rc){
fsl_buffer_clear(&out);
}else{
fsl_buffer_clear(pTarget);
*pTarget = out;
}
return rc;
}
int fsl_buffer_delta_apply( fsl_buffer const * orig,
fsl_buffer const * pDelta,
fsl_buffer * pTarget){
return fsl_buffer_delta_apply2(orig, pDelta, pTarget, NULL);
}
void fsl_buffer_defossilize( fsl_buffer * b ){
if(b){
fsl_bytes_defossilize( b->mem, &b->used );
}
}
int fsl_buffer_to_filename( fsl_buffer * b, char const * fname ){
FILE * f;
int rc = 0;
if(!b || !fname) return FSL_RC_MISUSE;
f = fsl_fopen(fname, "wb");
if(!f) rc = fsl_errno_to_rc(errno, FSL_RC_IO);
else if(b->used) {
size_t const frc = fwrite(b->mem, b->used, 1, f);
rc = (1==frc) ? 0 : FSL_RC_IO;
}
fsl_fclose(f);
return rc;
}
int fsl_buffer_delta_create( fsl_buffer const * src,
fsl_buffer const * newVers,
fsl_buffer * delta){
if(!src || !newVers || !delta) return FSL_RC_MISUSE;
else if((src == newVers)
|| (src==delta)
|| (newVers==delta)) return FSL_RC_MISUSE;
else{
int rc = fsl_buffer_reserve( delta, newVers->used + 60 );
if(!rc){
delta->used = 0;
rc = fsl_delta_create( src->mem, src->used,
newVers->mem, newVers->used,
delta->mem, &delta->used );
if(!rc){
rc = fsl_buffer_resize( delta, delta->used );
}
}
return rc;
}
}
int fsl_output_f_buffer( void * state,
void const * src, fsl_size_t n ){
return !state
? FSL_RC_MISUSE
: fsl_buffer_append((fsl_buffer*)state, src, n);
}
int fsl_finalizer_f_buffer( void * state, void * mem ){
fsl_buffer * b = (fsl_buffer*)mem;
fsl_buffer_reserve(b, 0);
*b = fsl_buffer_empty;
return 0;
}
#undef MARKER