8cccac2014-08-28Per 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); }
74e52c2014-08-28Per Hedbor  /*! @decl int read_int( int n )
8cccac2014-08-28Per Hedbor  *! *! Read a network (if n is positive) or little endian (if n is *! negative) byte order unsigned number of size n*8 bits, then *! return it. *! *! Will return -1 if there is not enough buffer space available *! unless error mode is set to throw errors. */ PIKEFUN int read_int( int len ) { IOBuffer *io = THIS; struct object *o; pop_stack(); if( len < SIZEOF_INT_TYPE-1 ) /* will for sure fit. */ { push_int( io_read_number( io, len ) ); return; } if( (o = io_read_bignum(io, len )) ) { push_object(o); reduce_stack_top_bignum(); return; } push_int(-1); } /*! @decl void create( string(8bit) x ) *! *! Create a new buffer with the contents of the given *! string. *! *! @note *! This will not copy the string data, instead data will *! be read from the string until it needs to be modified, so the *! buffer creation is fast regardless of the length of the *! string. */ PIKEFUN void create( string x ) flags ID_PROTECTED; { IOBuffer *this = THIS; if( this->buffer ) Pike_error("Can not initialize twice.\n"); if( x->size_shift ) Pike_error("Only string(0..255) supported.\n"); this->buffer = (unsigned char*)x->str; this->len = x->len; this->malloced = 0; this->str = x; x->refs++; } /*! @decl void create( int|void len ) *! *! Create a new buffer of the given size. *! The buffer will grow if needed, so the length is only the *! initial size. *! *! The default initial size is 4K */ PIKEFUN void create( int len ) flags ID_PROTECTED; { IOBuffer *this = THIS; if( this->buffer ) Pike_error("Can not initialize twice.\n"); this->buffer = xalloc(len); this->allocated = len; this->malloced = 1; } PIKEFUN void create( ) flags ID_PROTECTED; { IOBuffer *this = THIS; if( this->buffer ) Pike_error("Can not initialize twice.\n"); this->buffer = xalloc(4096-32); this->allocated = 4096-32; this->malloced = 1; }
74e52c2014-08-28Per Hedbor /*! @endclass */
8cccac2014-08-28Per Hedbor  INIT { IOBuffer *this = THIS; memset( this, 0, sizeof(IOBuffer)); } EXIT { IOBuffer *this = THIS; if( this->sub ) { free_object( this->sub ); io_unlock( get_storage(this->sub,IOBuffer_program ) ); } if( this->str ) free_string( this->str ); if( this->malloced ) free( this->buffer ); } } void init_stdio_buffer(void) { INIT } void exit_stdio_buffer(void) { EXIT }