8cccac | 2014-08-28 | Per Hedbor | | #include "global.h"
#include "object.h"
#include "interpret.h"
#include "operators.h"
#include "bignum.h"
#include "sscanf.h"
#include "builtin_functions.h"
#include "port.h"
#include "whitespace.h"
#include "pike_types.h"
#include "iobuffer.h"
#include <arpa/inet.h>
#define DEFAULT_CMOD_STORAGE static
DECLARATIONS
/*! @class IOBuffer
*!
*! A buffer to use as input or buffering when doing I/O. It is
*! similar to @[String.Buffer], but can only contain 8bit data and is
*! designed for protocol parsing. It is optimized for reading from
*! the beginning and adding to the end, and will try to minimize the
*! amount of data copying that is done.
*!
*! It can also directly read from and write to filedescriptors if so
*! desired. This eliminates at least one memory copy.
*/
PIKECLASS IOBuffer
{
CVAR IOBuffer b;
static void io_set_error_mode( IOBuffer *io, int m )
{
io->error_mode = m;
}
static size_t io_len( IOBuffer *io )
{
return io->len-io->offset;
}
static void io_unlock( IOBuffer *io )
{
io->locked--;
}
static void io_lock( IOBuffer *io )
{
io->locked++;
}
static void io_ensure_unlocked(IOBuffer *io)
{
if( io->locked )
Pike_error("Can not modify the buffer right now, "
" there are active subbuffers.\n");
}
static INT_TYPE io_consume( IOBuffer *io, int num )
{
io->offset += num;
return io_len(io);
}
static unsigned char *io_read_pointer(IOBuffer *io)
{
return io->buffer + io->offset;
}
static unsigned char *io_add_space( IOBuffer *io, int bytes, int force )
{
io_ensure_unlocked(io);
if( !io->malloced )
{
/* convert to malloced buffer from a shared one. */
unsigned char *old = io->buffer;
io->buffer = xalloc( io->len + bytes + 100 );
io->allocated = io->len + bytes + 100;
memcpy( io->buffer, old, io->len );
if( io->sub ) {
io_unlock( get_storage(io->sub,IOBuffer_program ) );
free_object( io->sub );
}
if( io->str ) free_string( io->str );
io->sub = 0;
io->str = 0;
io->malloced = 1;
}
if( force || (io->offset > io->len>>1) ) /* more than half used up. */
{
memmove( io->buffer, io->buffer+io->offset, io->len-io->offset );
io->len -= io->offset;
io->offset = 0;
}
if( io->len + bytes > io->allocated )
{
size_t new_len = io->allocated;
do
new_len = ((new_len+32)*2)-32;
while( new_len < io->len + bytes );
io->buffer = xrealloc( io->buffer, new_len );
io->allocated = new_len;
}
return io->buffer+io->len;
}
static void io_range_error( IOBuffer *io, int howmuch )
{
/* throw error if so desired. */
if( io->error_mode )
Pike_error("Trying to read %d outside allowed range\n", howmuch);
}
static int io_avail( IOBuffer *io, int len )
{
if( len < 0 || len + io->offset > io->len )
{
io_range_error( io, len );
return 0;
}
return 1;
}
static size_t io_append( IOBuffer *io, void *p, int bytes )
{
memcpy( io_add_space( io, bytes, 0 ), p, bytes );
io->len += bytes;
return io_len(io);
}
static size_t io_read( IOBuffer *io, void *to, size_t len )
{
if( !io_avail(io,len))
return 0;
memcpy( to, io_read_pointer(io), len );
io_consume( io, len );
return len;
}
static struct pike_string *io_read_string( IOBuffer *io, size_t len )
{
struct pike_string *s;
if( !io_avail(io,len))
return NULL;
if( len > 0x7fffffff )
Pike_error("This region is too large to convert to a string.\n");
s = begin_shared_string( len );
io_read( io, s->str, s->len );
return end_shared_string(s);
}
static struct object *io_read_buffer( IOBuffer *io, size_t len, int do_copy )
{
struct object *b;
IOBuffer *to;
if( !io_avail(io,len))
return NULL;
b = low_clone( IOBuffer_program );
to = get_storage(b,IOBuffer_program);
io_lock( io );
to->buffer = io_read_pointer(io);
to->len = len;
to->sub = Pike_fp->current_object;
to->sub->refs++;
io_consume( io, len );
if( do_copy )
io_add_space( to, 0, 0 );/* copies data. */
return b;
}
static int io_read_byte_uc( IOBuffer *io )
{
return io->buffer[io->offset++];
}
#if 0
static int io_read_byte( IOBuffer *io )
{
if( !io_avail(io,1))
return -1;
return io_read_byte_uc( io );
}
#endif
static LONGEST io_read_number( IOBuffer *io, size_t len )
{
size_t i;
LONGEST res;
if( !io_avail(io, len) )
return -1;
if( len > SIZEOF_INT_TYPE )
{
unsigned int extra = len-SIZEOF_INT_TYPE;
/* ensure only 0:s */
for( i=0; i<extra; i++ )
{
if( io_read_byte_uc(io) )
Pike_error("Integer (%dbit) overflow.\n", SIZEOF_INT_TYPE*8);
}
len=SIZEOF_INT_TYPE;
}
if( len == SIZEOF_INT_TYPE )
{
res = io_read_byte_uc(io);
if( res > 127 )
Pike_error("Signed (%dbit) overflow.\n", SIZEOF_INT_TYPE*8);
len--;
}
else
res = 0;
for( i=0; i<len; i++ )
{
res <<= 8;
res |= io_read_byte_uc(io);
}
return res;
}
static struct object *io_read_bignum( IOBuffer *io, size_t len )
{
int i;
struct pike_string *num;
LONGEST res;
num = io_read_string( io, len );
if( !num ) return NULL;
push_string( num );
push_int( 256 );
return clone_object( get_auto_bignum_program(), 2 );
}
static size_t io_add_int( IOBuffer *io, INT_TYPE i, size_t bytes )
{
unsigned char *x = io_add_space(io, bytes, 0);
io->len += bytes;
while(bytes--)
{
x[bytes] = i&255;
i>>=8;
}
return io_len( io );
}
static size_t io_rewind( IOBuffer *io, INT_TYPE n )
{
if( n < 0 || (io->offset < (unsigned)n) )
{
io_range_error(io,-n);
return -1;
}
io->offset -= n;
return io->offset;
}
/* pike functions */
#undef THIS
#define THIS (&(((struct IOBuffer_struct *)Pike_fp->current_storage)->b))
PIKEFUN int _size_object( )
{
RETURN THIS->malloced ? THIS->allocated : 0;
}
/* PIKEFUN void add( object(String.IOBuffer) x[, int len[,int start]] )*/
/* PIKEFUN void add( object(String.Buffer) x[, int len[,int start]] ) */
/* PIKEFUN void add( System.Memory x[, int len[,int start]] ) */
/*! @decl void add( string(8bit)|int(8bit) ... data )
*!
*! Add the items in data one at a time.
*! Integers are assumed to be 8bit numbers (characters)
*/
PIKEFUN int add( string|int ... argp)
{
int i;
IOBuffer *io = THIS;
for(i=0; i<args; i++ )
{
if( TYPEOF(argp[i]) == PIKE_T_STRING )
{
struct pike_string *s = argp[i].u.string;
if( s->size_shift ) Pike_error("IOBuffer only handles 8bit data\n");
io_append( io, s->str, s->len );
}
else
{
unsigned char a = argp[i].u.integer & 255;
io_append( io, &a, 1 );
}
}
RETURN io_len(io);
}
/*! @decl int add_byte( int(0..255) )
*! @decl int add_int8( int(0..255) )
*! Adds a single byte to the buffer.
*/
PIKEFUN int add_byte( int(0..255) i )
{
unsigned char a = i&255;
RETURN io_append( THIS, &a, 1 );
}
PIKEFUN int add_int8( int(0..255) i )
{
unsigned char a = i&255;
RETURN io_append( THIS, &a, 1 );
}
/*! @decl int add_int16( int(0..65535) )
*! @decl int add_short( int(0..65535) )
*!
*! Add a 16-bit network byte order value to the buffer
*/
PIKEFUN int add_int16( int(0..65535) i )
{
unsigned short a = htons((i&65535));
push_int(io_append( THIS, &a, 2 ));
}
PIKEFUN int add_short( int(0..65535) i )
{
unsigned short a = htons((i&65535));
RETURN io_append( THIS, &a, 2 );
}
/*! @decl int add_int32( int i )
*! Adds a 32 bit network byte order value to the buffer
*/
PIKEFUN int add_int32( int i )
{
INT32 a = htonl(i);
push_int(io_append( THIS, &a, 4 ));
}
/*! @decl void add_hstring( string(0..255) data, int size_size )
*!
*! Adds length/data for @[data] to the buffer.
*!
*! This is identical to @[sprintf("%"+size_size+"H",data)] but
*! significantly faster.
*!
*! @[size_size] must be less than Int.NATIVE_MAX.
*/
PIKEFUN int add_hstring( string str, int size_size )
{
if( str->size_shift )
Pike_error("Only 8bit is supported\n");
/* We know this is safe for len>=4, since pike strings are at
* most 0x7ffffff bytes long.
*/
if( size_size < 4 &&
str->len > (1<<(8*(size_size<0?-size_size:size_size)))-1 )
Pike_error("Too long string\n");
io_add_int( THIS, str->len, size_size );
push_int(io_append( THIS, str->str, str->len ));
}
/*! @decl int add_int( int i, int(0..) width )
*!
*! Adds a generic integer to the buffer as an (width*8)bit
*! network byteorder number.
*!
*! @[width] must be less than Int.NATIVE_MAX.
*!
*/
PIKEFUN int add_int( int i, int width )
{
RETURN io_add_int( THIS, i, width );
}
PIKEFUN int add_int( object o, int width )
{
char *p;
int extra, len;
pop_stack();
convert_stack_top_to_bignum();
/* we now know for sure it's a bignum. */
push_int(256);
apply( Pike_sp[-2].u.object, "digits", 1 );
p = Pike_sp[-1].u.string->str;
len = Pike_sp[-1].u.string->len;
if( len > width )
Pike_error("Number too large to store in %d bits\n", width*8);
/* p += len-width;*/
if( len < width )
{
INT_TYPE null = 0;
while( len < width )
{
int a = MIN(width-len,sizeof(INT_TYPE));
io_append( THIS, &null, a );
width-=a;
}
}
RETURN io_append( THIS, p, width );
}
/*! @decl protected int `[](int off)
*!
*! Return the character at the specified offset.
*/
PIKEFUN int `[]( int off )
flags ID_PROTECTED;
{
IOBuffer *io = THIS;
pop_stack();
if( off < 0 )
off = io_len(io)-off;
if( io_avail( io, off ) )
push_int( io_read_pointer(io)[off] );
else
push_int(-1);
}
/*! @decl protected int `[]=(int off, int char)
*!
*! Set the character at the specified offset to @[char].
*/
PIKEFUN int `[]=( int off, int val )
flags ID_PROTECTED;
{
IOBuffer *io = THIS;
io_add_space( io, 0, 0 );
if( off < 0 ) off = io_len(io)-off;
if( io_avail( io, off ) )
{
io_read_pointer(io)[off]=(val&0xff);
}
else
{
/* hm, well. We could extend the buffer? */
push_int(-1);
}
}
/*! @decl int _sizeof()
*!
*! Returns the buffer size, in bytes.
*! This is how much you can read from the buffer until it runs out of data.
*/
PIKEFUN int _sizeof()
flags ID_PROTECTED;
{
push_int64(io_len(THIS));
}
/*! @decl string cast(string type)
*!
*! Convert the buffer to a string.
*!
*!@note
*! This only works for buffers whose length is less than 0x7fffffff.
*/
PIKEFUN string cast(string to)
{
if( to != literal_string_string )
{
push_undefined();
return;
}
if( io_len(THIS) > 0x7fffffff )
Pike_error("This buffer is too large to convert to a string.\n");
push_string(make_shared_binary_string((void*)io_read_pointer(THIS),
(INT32)io_len(THIS)));
}
/*! @decl void set_error_mode(int m)
*!
*! Set the error mode of this buffer to @[m].
*!
*! If true operations that would normally return 0 (like trying to read too much)
*! will instead thrown an error.
*!
*! This is useful when parsing received data, you do not have to
*! verify that each and every read operation suceeds.
*!
*! However, the non-error mode is more useful when checking to see
*! if a packet/segment/whatever has arrived.
*!
*! The thrown error object will have the constant buffer_error set
*! to a non-false value.
*!
*! @example
*! @code
*! void read_callback(int i, string new_data)
*! {
*! inbuffer->add( new_data );
*!
*! while( IOBuffer packet = inbuffer->read_hbuffer(2) )
*! {
*! packet->set_error_mode(Buffer.THROW_ERROR);
*! if( mixed e = catch( handle_packet( packet ) ) )
*! if( e->buffer_error )
*! protocol_error(); // illegal data in packet
*! else
*! throw(e); // the other code did something bad
*! }
*! }
*!
*!
*! void handle_packet( IOBuffer pack )
*! {
*! switch( pack->read_int8() )
*! {
*! ...
*! case HEADER_FRAME:
*! int num_headers = pack->read_int32();
*! for( int i = 0; i<num_headers; i++ )
*! headers[pack->read_hstring(2)] = pack->read_hstring(2);
*! ...
*! }
*! }
*! @endcode
*/
PIKEFUN void set_error_mode( int m )
{
io_set_error_mode( THIS, m );
pop_stack();
}
/*! @decl object lock()
*!
*! Makes this buffer read only until the returned object is released.
*!
*! @note
*! This currently simply returns a 0-length subbuffer.
*/
PIKEFUN IOBuffer lock()
{
push_object( io_read_buffer( THIS, 0, 0 ) );
}
PIKEFUN string _sprintf(int o, mapping ignore)
flags ID_PROTECTED;
{
pop_n_elems(args);
if( o == 'O' )
{
push_text("IOBuffer(%d bytes, read=[..%d] data=[%d..%d] free=[%d..%d] %s%s)");
/* io_len [..offset] [offset..len] [..allocated] */
push_int(io_len(THIS));
push_int(THIS->offset-1);
push_int(THIS->offset);
push_int(THIS->len-1);
push_int(THIS->len);
push_int(THIS->allocated);
push_text( (THIS->str ? "string" : THIS->sub ? "subbuffer" : "allocated" ) );
if( THIS->locked )
push_text(" (read only)");
else
push_text("");
f_sprintf(9);
}
else
push_undefined();
}
/*! @decl string(8bit) read_hstring( int(0..) n )
*!
*! Identical in functionality to @[read](@[read_number](@[n])) but
*! faster.
*!
*! Read a network byte order number of size n*8 bits, then return the
*! indicated number of bytes as a string.
*!
*! If there is not enough data available return 0.
*!
*! Note that pike string can not be longer than 0x7fffffff bytes (~2Gb).
*/
PIKEFUN string(0..255) read_hstring( int(0..) bytes )
{
LONGEST len;
struct pike_string *s;
len = io_read_number( THIS, bytes );
s = io_read_string( THIS, len );
if( s )
push_string(s);
else
push_int(0);
}
/*! @decl IOBuffer read_hbuffer( int n )
*! @decl IOBuffer read_hbuffer( int n, bool copy )
*!
*! Same as @[read_hstring], but returns the result as an IOBuffer.
*!
*! No data is copied unless @[copy] is specified and true, the new
*! buffer points into the old one.
*!
*! @note
*! As long as the subbuffer exists no data can be added to the
*! main buffer.
*!
*! Usually this is OK, since it often represents something that
*! should be parsed before the next whatever is extracted from
*! the buffer, but do take care.
*!
*! If you need to unlink the new buffer after it has been
*! created, call @[trim] in it.
*/
PIKEFUN IOBuffer read_hbuffer( int(0..) bytes, int|void copy )
{
LONGEST len = io_read_number( THIS, bytes );
int do_copy = 0;
struct object *o;
if( copy ) do_copy = copy->u.integer;
pop_n_elems(args);
if( (o = io_read_buffer( THIS, len, do_copy )) )
push_object(o);
else
push_int(0);
}
/*! @decl IOBuffer read_buffer( int n )
*! @decl IOBuffer read_buffer( int n, bool copy )
*!
*! Same as @[read], but returns the result as an IOBuffer.
*!
*! No data is copied unless @[copy] is specified and true, the new buffer
*! points into the old one.
*!
*! @note
*! As long as the subbuffer exists no data can be added to the main buffer.
*!
*! Usually this is OK, since it often represents something that
*! should be parsed before the next whatever is extracted from
*! the buffer, but do take care.
*/
PIKEFUN IOBuffer read_buffer( int bytes, int|void copy )
{
int do_copy = 0;
struct object *o;
if( copy )
do_copy = copy->u.integer;
pop_n_elems(args);
if( (o = io_read_buffer( THIS, bytes, do_copy )) )
push_object(o);
else
push_int(0);
}
/*! @decl int sprintf(strict_sprintf_format format, sprintf_args ... args)
*!
*! Appends the output from @[sprintf] at the end of the buffer.
*!
*! This is somewhat faster than add(sprintf(...)) since no
*! intermediate string is created.
*/
PIKEFUN int sprintf(mixed ... ignored)
rawtype tFuncV(tAttr("strict_sprintf_format", tOr(tStr, tObj)),
tAttr("sprintf_args", tMix), tStr);
{
ONERROR _e;
struct string_builder tmp;
INT_TYPE sz;
init_string_builder(&tmp,0);
SET_ONERROR(_e, free_string_builder, &tmp);
low_f_sprintf(args, 0, &tmp );
if( tmp.s->size_shift )
Pike_error("IOBuffer only handles 8bit data\n");
sz = io_append( THIS, tmp.s->str, tmp.s->len );
pop_n_elems(args);
CALL_AND_UNSET_ONERROR(_e);
push_int(sz);
}
/*! @decl array sscanf(string(8bit) format)
*!
*! Reads data from the beginning of the buffer to match the
*! specifed format, then return an array with the matches.
*!
*! The non-matching data will be left in the buffer.
*!
*! See @[array_sscanf] for more information.
*/
PIKEFUN array sscanf( string format )
{
INT32 i;
ptrdiff_t num_used;
struct svalue *start = Pike_sp;
i = low_sscanf_pcharp(
MKPCHARP(io_read_pointer(THIS), 0), io_len(THIS),
MKPCHARP(format->str,format->size_shift), format->len,
&num_used, 0);
if( !num_used )
{
io_range_error(THIS,-1);
pop_n_elems(Pike_sp-start);
push_int(0);
}
else
{
io_consume( THIS, num_used );
f_aggregate(Pike_sp-start);
}
}
/*! @decl mixed match(string(8bit) format)
*!
*! Reads data from the beginning of the buffer to match the
*! specifed format, then return the match.
*!
*! The non-matching data will be left in the buffer.
*!
*! This function is very similar to @[sscanf], but the
*! result is the sum of the matches. Most useful to match
*! a single value.
*!
*! @example
*! @code
*! // get the next whitespace separated word from the buffer.
*! buffer->match("%*[ \t\r\n]%*[^ \t\r\n]");
*! @endcode
*/
PIKEFUN string|int|float|array match( string format )
{
INT32 i;
ptrdiff_t num_used;
struct svalue *start = Pike_sp;
i = low_sscanf_pcharp(
MKPCHARP(io_read_pointer(THIS), 0), io_len(THIS),
MKPCHARP(format->str,format->size_shift), format->len,
&num_used,
0);
if( !num_used )
{
io_range_error(THIS,-1);
pop_n_elems(Pike_sp-start);
push_int(0);
}
else
{
io_consume( THIS, num_used );
if( Pike_sp-start > 1 )
f_add(Pike_sp-start);
}
}
/*! @decl void clear()
*!
*! Clear the buffer.
*/
PIKEFUN void clear( )
{
IOBuffer *io = THIS;
io->offset = io->len = 0;
}
/*! @decl void trim()
*!
*! Frees unused memory.
*!
*! Note that calling this function excessively will slow things
*! down, since the data often has to be copied.
*!
*! @note
*! This function could possibly throw an out-of-memory error
*! if the realloc fails to find a new (smaller) memory area.
*/
PIKEFUN void trim( )
{
IOBuffer *io = THIS;
io_add_space( io, 0, 1 );
if( io->allocated > io->len+32 )
{
io->buffer = xrealloc( io->buffer, io->len );
io->allocated = io->len;
}
}
/*! @decl int(0..)|int(-1..-1) consume( int(0..) n )
*!
*! Discard the first @[n] bytes from the buffer
*/
PIKEFUN int(-1..) consume( int n )
{
if( !io_avail( THIS, n ) )
{
io_range_error( THIS, n );
push_int(-1);
}
else
push_int64( io_consume( THIS, n ) );
}
/*! @decl int(0..)|int(-1..-1) unread( int(0..) n )
*!
*! Rewind the buffer @[n] bytes.
*!
*! @returns
*!
*! This function returns how many more bytes of buffer is
*! available to rewind, or -1 on error.
*!
*! @note
*!
*! Unless you add new data to the buffer using any of the add
*! functions you can always rewind.
*!
*! You can call @[undread(0)] to see how much.
*/
PIKEFUN int(-1..) unread( int(0..) bytes )
{
push_int64( io_rewind( THIS, bytes ) );
}
/*! @decl string(8bit) read( int n )
*!
*! Read @[bytes] bytes of data from the buffer.
*!
*! If there is not enough data available this returns 0.
*/
PIKEFUN string(0..255) read( int(0..) bytes )
{
struct pike_string * s = io_read_string(THIS, bytes );
pop_stack();
if( s )
push_string(s);
else
push_int(0);
}
/*! @decl string(8bit) read( )
*!
*! Read all data from the buffer.
*!
*! If there is not enough data available this returns 0.
*!
*! This is basically equivalent to (string)buffer, but it also
*! removes the data from the buffer.
*/
PIKEFUN string(0..255) read()
{
struct pike_string * s = io_read_string(THIS, io_len(THIS));
if( !s )
push_int(0);
else
push_string(s);
}
|