/// <summary> /// Decode a Qap1-encoded Sexp /// </summary> /// <param name="data">The byte stream in which the Sexp is encoded</param> /// <param name="start">At which index of data does the Sexp begin?</param> /// <returns>The decoded Sexp.</returns> private static Sexp DecodeSexp(byte[] data, ref long start) { // pull sexp type byte xt = data[start]; // calculate length of payload var lengthBuf = new byte[8]; Array.Copy(data, start + 1, lengthBuf, 0, 3); start += 4; if ((xt & XtLarge) == XtLarge) { Array.Copy(data, start, lengthBuf, 3, 4); start += 4; xt -= XtLarge; } var length = ( long )BitConverter.ToUInt64(lengthBuf, 0); // has attributes? process first SexpTaggedList attrs = null; if ((xt & XtHasAttr) == XtHasAttr) { xt -= XtHasAttr; long oldstart = start; attrs = ( SexpTaggedList )DecodeSexp(data, ref start); length -= start - oldstart; } long end = start + length; Sexp result; switch (xt) { case XtNull: { if (length != 0) { throw new RserveException("Attempting to decode an SexpNull, but it is followed by data when it shouldn't be."); } result = new SexpNull(); } break; case XtSymName: { // keep all characters up to the first null var symnNamBuf = new byte[length]; Array.Copy(data, start, symnNamBuf, 0, length); string res = Encoding.UTF8.GetString(symnNamBuf); result = new SexpSymname(res.Split('\x00')[0]); } break; case XtArrayInt: { var res = new int[length / 4]; var intBuf = new byte[4]; for (long i = 0; i < length; i += 4) { Array.Copy(data, start + i, intBuf, 0, 4); res[i / 4] = BitConverter.ToInt32(intBuf, 0); } // is date or just an integer? if ((attrs != null) && (attrs.ContainsKey("class") && attrs["class"].AsStrings.Contains("Date"))) { result = new SexpArrayDate(res); } else { result = new SexpArrayInt(res); } } break; case XtArrayBool: { if (length < 4) { throw new RserveException("Decoding an SexpArrayBool where data doesn't seem to contain a data length field."); } var boolLengthBuf = new byte[4]; Array.Copy(data, start, boolLengthBuf, 0, 4); var datalength = BitConverter.ToInt32(boolLengthBuf, 0); if (datalength > length - 4) { throw new RserveException("Decoding an SexpArrayBool where transmitted data field too short for number of entries."); } var res = new bool?[datalength]; for (int i = 0; i < datalength; i++) { // R logical is false if 0, true if 1, and NA if 2 switch (data[start + i + 4]) { case 0: res[i] = false; break; case 1: res[i] = true; break; case 2: res[i] = null; break; default: throw new RserveException("Decoding an SexpArrayBool and found an element in the array that is not an R bool: " + data[start + i + 4]); } } result = new SexpArrayBool(res); } break; case XtArrayDouble: { var res = new double[length / 8]; var doubleBuf = new byte[8]; for (long i = 0; i < length; i += 8) { Array.Copy(data, start + i, doubleBuf, 0, 8); res[i / 8] = BitConverter.ToDouble(doubleBuf, 0); } // is date or just a double? if ((attrs != null) && (attrs.ContainsKey("class") && attrs["class"].AsStrings.Contains("Date"))) { result = new SexpArrayDate(res.Select(Convert.ToInt32)); } else { result = new SexpArrayDouble(res); } } break; case XtArrayString: { var res = new List <string>(); long i = 0; for (long j = 0; j < length; j++) { if (data[start + j] != 0) { continue; } if ((j == i + 1) && (data[start + i] == 255)) { res.Add(null); } else { if (data[start + i] == 255) { i++; } var stringBuf = new byte[j - i]; Array.Copy(data, start + i, stringBuf, 0, j - i); res.Add(Encoding.UTF8.GetString(stringBuf)); } i = j + 1; } result = new SexpArrayString(res); } break; case XtListNoTag: case XtLangNoTag: case XtVector: result = new SexpList(); while (start < end) { result.Add(DecodeSexp(data, ref start)); } break; case XtLangTag: case XtListTag: result = new SexpTaggedList(); while (start < end) { Sexp val = DecodeSexp(data, ref start); Sexp key = DecodeSexp(data, ref start); result.Add(key.IsNull ? String.Empty : key.AsString, val); } break; case XtRaw: { var d = new byte[length]; Array.Copy(data, start, d, 0, length); result = new SexpQap1Raw(xt, d); } break; default: throw new RserveException("Cannot decode an Sexp because the type is not recognized: " + xt); } if (start > end) { throw new RserveException("When decoding an Sexp, more data consumed than provided."); } start = end; if (attrs != null) { foreach (var a in attrs.AsSexpDictionary) { result.Attributes.Add(a.Key, a.Value); } } return(result); }
/// <summary> /// Decode a Qap1-encoded Sexp /// </summary> /// <param name="data">The byte stream in which the Sexp is encoded</param> /// <param name="start">At which index of data does the Sexp begin?</param> /// <returns>The decoded Sexp.</returns> private static Sexp DecodeSexp( byte[] data , ref long start ) { // pull sexp type byte xt = data[ start ]; // calculate length of payload var lengthBuf = new byte[ 8 ]; Array.Copy( data , start + 1 , lengthBuf , 0 , 3 ); start += 4; if ( ( xt & XtLarge ) == XtLarge ) { Array.Copy( data , start , lengthBuf , 3 , 4 ); start += 4; xt -= XtLarge; } var length = ( long )BitConverter.ToUInt64( lengthBuf , 0 ); // has attributes? process first SexpTaggedList attrs = null; if ( ( xt & XtHasAttr ) == XtHasAttr ) { xt -= XtHasAttr; long oldstart = start; attrs = ( SexpTaggedList )DecodeSexp( data , ref start ); length -= start - oldstart; } long end = start + length; Sexp result; switch ( xt ) { case XtNull: { if ( length != 0 ) { throw new RserveException( "Attempting to decode an SexpNull, but it is followed by data when it shouldn't be." ); } result = new SexpNull(); } break; case XtSymName: { // keep all characters up to the first null var symnNamBuf = new byte[ length ]; Array.Copy( data , start , symnNamBuf , 0 , length ); string res = Encoding.UTF8.GetString( symnNamBuf ); result = new SexpSymname( res.Split( '\x00' )[ 0 ] ); } break; case XtArrayInt: { var res = new int[ length / 4 ]; var intBuf = new byte[ 4 ]; for ( long i = 0 ; i < length ; i += 4 ) { Array.Copy( data , start + i , intBuf , 0 , 4 ); res[ i / 4 ] = BitConverter.ToInt32( intBuf , 0 ); } // is date or just an integer? if ( ( attrs != null ) && ( attrs.ContainsKey( "class" ) && attrs[ "class" ].AsStrings.Contains( "Date" ) ) ) { result = new SexpArrayDate( res ); } else { result = new SexpArrayInt( res ); } } break; case XtArrayBool: { if ( length < 4 ) { throw new RserveException( "Decoding an SexpArrayBool where data doesn't seem to contain a data length field." ); } var boolLengthBuf = new byte[ 4 ]; Array.Copy( data , start , boolLengthBuf , 0 , 4 ); var datalength = BitConverter.ToInt32( boolLengthBuf , 0 ); if ( datalength > length - 4 ) { throw new RserveException( "Decoding an SexpArrayBool where transmitted data field too short for number of entries." ); } var res = new bool?[ datalength ]; for ( int i = 0 ; i < datalength ; i++ ) { // R logical is false if 0, true if 1, and NA if 2 switch ( data[ start + i + 4 ] ) { case 0: res[ i ] = false; break; case 1: res[ i ] = true; break; case 2: res[ i ] = null; break; default: throw new RserveException( "Decoding an SexpArrayBool and found an element in the array that is not an R bool: " + data[ start + i + 4 ] ); } } result = new SexpArrayBool( res ); } break; case XtArrayDouble: { var res = new double[ length / 8 ]; var doubleBuf = new byte[ 8 ]; for ( long i = 0 ; i < length ; i += 8 ) { Array.Copy( data , start + i , doubleBuf , 0 , 8 ); res[ i / 8 ] = BitConverter.ToDouble( doubleBuf , 0 ); } // is date or just a double? if ( ( attrs != null ) && ( attrs.ContainsKey( "class" ) && attrs[ "class" ].AsStrings.Contains( "Date" ) ) ) { result = new SexpArrayDate( res.Select( Convert.ToInt32 ) ); } else { result = new SexpArrayDouble( res ); } } break; case XtArrayString: { var res = new List<string>(); long i = 0; for ( long j = 0 ; j < length ; j++ ) { if ( data[ start + j ] != 0 ) { continue; } if ( ( j == i + 1 ) && ( data[ start + i ] == 255 ) ) { res.Add( null ); } else { if ( data[ start + i ] == 255 ) { i++; } var stringBuf = new byte[ j - i ]; Array.Copy( data , start + i , stringBuf , 0 , j - i ); res.Add( Encoding.UTF8.GetString( stringBuf ) ); } i = j + 1; } result = new SexpArrayString( res ); } break; case XtListNoTag: case XtLangNoTag: case XtVector: result = new SexpList(); while ( start < end ) { result.Add( DecodeSexp( data , ref start ) ); } break; case XtLangTag: case XtListTag: result = new SexpTaggedList(); while ( start < end ) { Sexp val = DecodeSexp( data , ref start ); Sexp key = DecodeSexp( data , ref start ); result.Add( key.IsNull ? String.Empty : key.AsString , val ); } break; case XtRaw: { var d = new byte[ length ]; Array.Copy( data , start , d , 0 , length ); result = new SexpQap1Raw( xt , d ); } break; default: throw new RserveException( "Cannot decode an Sexp because the type is not recognized: " + xt ); } if ( start > end ) { throw new RserveException( "When decoding an Sexp, more data consumed than provided." ); } start = end; if ( attrs != null ) { foreach ( var a in attrs.AsSexpDictionary ) { result.Attributes.Add( a.Key , a.Value ); } } return result; }