text: /*    Copyright 2012 10gen Inc.
   *
   *    This program is free software: you can redistribute it and/or  modify
   *    it under the terms of the GNU Affero General Public License, version 3,
   *    as published by the Free Software Foundation.
   *
   *    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.  See the
   *    GNU Affero General Public License for more details.
   *
   *    You should have received a copy of the GNU Affero General Public License
   *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
   *
   *    As a special exception, the copyright holders give permission to link the
   *    code of portions of this program with the OpenSSL library under certain
   *    conditions as described in each individual source file and distribute
   *    linked combinations including the program with the OpenSSL library. You
   *    must comply with the GNU Affero General Public License in all respects
   *    for all of the code used other than as permitted herein. If you modify
   *    file(s) with this exception, you may extend this exception to your
   *    version of the file(s), but you are not obligated to do so. If you do not
   *    wish to do so, delete this exception statement from your version. If you
   *    delete this exception statement from all source files in the program,
   *    then also delete it in the license file.
   */
  
  #include "mongo/base/data_view.h"
  #include "mongo/db/jsobj.h"
  #include "mongo/unittest/unittest.h"
  #include "mongo/platform/random.h"
  #include "mongo/bson/bson_validate.h"
  #include "mongo/util/log.h"
  
  namespace {
  
      using namespace mongo;
  
      void appendInvalidStringElement(const char* fieldName, BufBuilder* bb) {
          // like a BSONObj string, but without a NUL terminator.
          bb->appendChar(String);
          bb->appendStr(fieldName, /*withNUL*/true);
          bb->appendNum(4);
          bb->appendStr("asdf", /*withNUL*/false);
      }
  
      TEST(BSONValidate, Basic) {
          BSONObj x;
          ASSERT_TRUE( x.valid() );
  
          x = BSON( "x" << 1 );
          ASSERT_TRUE( x.valid() );
      }
  
      TEST(BSONValidate, RandomData) {
          PseudoRandom r(17);
  
          int numValid = 0;
          int numToRun = 1000;
          long long jsonSize = 0;
  
          for ( int i=0; i<numToRun; i++ ) {
              int size = 1234;
  
              char* x = new char[size];
              DataView(x).writeLE(size);
  
              for ( int i=4; i<size; i++ ) {
                  x[i] = r.nextInt32( 255 );
              }
  
              x[size-1] = 0;
  
              BSONObj o( x );
  
              ASSERT_EQUALS( size, o.objsize() );
  
              if ( o.valid() ) {
                  numValid++;
                  jsonSize += o.jsonString().size();
                  ASSERT_OK( validateBSON( o.objdata(), o.objsize() ) );
              }
              else {
                  ASSERT_NOT_OK( validateBSON( o.objdata(), o.objsize() ) );
              }
  
              delete[] x;
          }
  
          log() << "RandomData: didn't crash valid/total: " << numValid << "/" << numToRun
                << " (want few valid ones)"
                << " jsonSize: " << jsonSize << endl;
      }
  
      TEST(BSONValidate, MuckingData1) {
  
          BSONObj theObject;
  
          {
              BSONObjBuilder b;
              b.append( "name" , "eliot was here" );
              b.append( "yippee" , "asd" );
              BSONArrayBuilder a( b.subarrayStart( "arr" ) );
              for ( int i=0; i<100; i++ ) {
                  a.append( BSON( "x" << i << "who" << "me" << "asd" << "asd" ) );
              }
              a.done();
              b.done();
  
              theObject = b.obj();
          }
  
          int numValid = 0;
          int numToRun = 1000;
          long long jsonSize = 0;
  
          for ( int i=4; i<theObject.objsize()-1; i++ ) {
              BSONObj mine = theObject.copy();
  
              char* data = const_cast<char*>(mine.objdata());
  
              data[ i ] = 200;
  
              numToRun++;
              if ( mine.valid() ) {
                  numValid++;
                  jsonSize += mine.jsonString().size();
                  ASSERT_OK( validateBSON( mine.objdata(), mine.objsize() ) );
              }
              else {
                  ASSERT_NOT_OK( validateBSON( mine.objdata(), mine.objsize() ) );
              }
  
          }
  
          log() << "MuckingData1: didn't crash valid/total: " << numValid << "/" << numToRun
                << " (want few valid ones) "
                << " jsonSize: " << jsonSize << endl;
      }
  
      TEST( BSONValidate, Fuzz ) {
          int64_t seed = time( 0 );
          log() << "BSONValidate Fuzz random seed: " << seed << endl;
          PseudoRandom randomSource( seed );
  
          BSONObj original = BSON( "one" << 3 <<
                                   "two" << 5 <<
                                   "three" << BSONObj() <<
                                   "four" << BSON( "five" << BSON( "six" << 11 ) ) <<
                                   "seven" << BSON_ARRAY( "a" << "bb" << "ccc" << 5 ) <<
                                   "eight" << BSONDBRef( "rrr", OID( "01234567890123456789aaaa" ) ) <<
                                   "_id" << OID( "deadbeefdeadbeefdeadbeef" ) <<
                                   "nine" << BSONBinData( "\x69\xb7", 2, BinDataGeneral ) <<
                                   "ten" << Date_t( 44 ) <<
                                   "eleven" << BSONRegEx( "foooooo", "i" ) );
          
          int32_t fuzzFrequencies[] = { 2, 10, 20, 100, 1000 };
          for( size_t i = 0; i < sizeof( fuzzFrequencies ) / sizeof( int32_t ); ++i ) {
              int32_t fuzzFrequency = fuzzFrequencies[ i ];
  
              // Copy the 'original' BSONObj to 'buffer'.
              scoped_array<char> buffer( new char[ original.objsize() ] );
              memcpy( buffer.get(), original.objdata(), original.objsize() );
  
              // Randomly flip bits in 'buffer', with probability determined by 'fuzzFrequency'. The
              // first four bytes, representing the size of the object, are excluded from bit
              // flipping.
              for( int32_t byteIdx = 4; byteIdx < original.objsize(); ++byteIdx ) {
                  for( int32_t bitIdx = 0; bitIdx < 8; ++bitIdx ) {
                      if ( randomSource.nextInt32( fuzzFrequency ) == 0 ) {
                          reinterpret_cast<unsigned char&>( buffer[ byteIdx ] ) ^= ( 1U << bitIdx );
                      }
                  }
              }
              BSONObj fuzzed( buffer.get() );
  
              // Check that the two validation implementations agree (and neither crashes).
              ASSERT_EQUALS( fuzzed.valid(),
                             validateBSON( fuzzed.objdata(), fuzzed.objsize() ).isOK() );
          }
      }
  
      TEST( BSONValidateFast, Empty ) {
          BSONObj x;
          ASSERT_OK( validateBSON( x.objdata(), x.objsize() ) );
      }
  
      TEST( BSONValidateFast, RegEx ) {
          BSONObjBuilder b;
          b.appendRegex( "foo", "i" );
          BSONObj x = b.obj();
          ASSERT_OK( validateBSON( x.objdata(), x.objsize() ) );
      }
  
      TEST(BSONValidateFast, Simple0 ) {
          BSONObj x;
          ASSERT_OK( validateBSON( x.objdata(), x.objsize() ) );
  
          x = BSON( "foo" << 17 << "bar" << "eliot" );
          ASSERT_OK( validateBSON( x.objdata(), x.objsize() ) );
  
      }
  
      TEST(BSONValidateFast, Simple2 ) {
          char buf[64];
          for ( int i=1; i<=JSTypeMax; i++ ) {
              BSONObjBuilder b;
              sprintf( buf, "foo%d", i );
              b.appendMinForType( buf, i );
              sprintf( buf, "bar%d", i );
              b.appendMaxForType( buf, i );
              BSONObj x = b.obj();
              ASSERT_OK( validateBSON( x.objdata(), x.objsize() ) );
          }
      }
  
  
      TEST(BSONValidateFast, Simple3 ) {
          BSONObjBuilder b;
          char buf[64];
          for ( int i=1; i<=JSTypeMax; i++ ) {
              sprintf( buf, "foo%d", i );
              b.appendMinForType( buf, i );
              sprintf( buf, "bar%d", i );
              b.appendMaxForType( buf, i );
          }
          BSONObj x = b.obj();
          ASSERT_OK( validateBSON( x.objdata(), x.objsize() ) );
      }
  
      TEST(BSONValidateFast, NestedObject) {
          BSONObj x = BSON( "a" << 1 << "b" << BSON("c" << 2 << "d" << BSONArrayBuilder().obj() << "e" << BSON_ARRAY("1" << 2 << 3)));
          ASSERT_OK(validateBSON(x.objdata(), x.objsize()));
          ASSERT_NOT_OK(validateBSON(x.objdata(), x.objsize() / 2));
      }
  
      TEST(BSONValidateFast, ErrorWithId) {
          BufBuilder bb;
          BSONObjBuilder ob(bb);
          ob.append("_id", 1);
          appendInvalidStringElement("not_id", &bb);
          const BSONObj x = ob.done();
          const Status status = validateBSON(x.objdata(), x.objsize());
          ASSERT_NOT_OK(status);
          ASSERT_EQUALS(status.reason(), "not null terminated string in object with _id: 1");
      }
  
      TEST(BSONValidateFast, ErrorBeforeId) {
          BufBuilder bb;
          BSONObjBuilder ob(bb);
          appendInvalidStringElement("not_id", &bb);
          ob.append("_id", 1);
          const BSONObj x = ob.done();
          const Status status = validateBSON(x.objdata(), x.objsize());
          ASSERT_NOT_OK(status);
          ASSERT_EQUALS(status.reason(), "not null terminated string in object with unknown _id");
      }
  
      TEST(BSONValidateFast, ErrorNoId) {
          BufBuilder bb;
          BSONObjBuilder ob(bb);
          appendInvalidStringElement("not_id", &bb);
          const BSONObj x = ob.done();
          const Status status = validateBSON(x.objdata(), x.objsize());
          ASSERT_NOT_OK(status);
          ASSERT_EQUALS(status.reason(), "not null terminated string in object with unknown _id");
      }
  
      TEST(BSONValidateFast, ErrorIsInId) {
          BufBuilder bb;
          BSONObjBuilder ob(bb);
          appendInvalidStringElement("_id", &bb);
          const BSONObj x = ob.done();
          const Status status = validateBSON(x.objdata(), x.objsize());
          ASSERT_NOT_OK(status);
          ASSERT_EQUALS(status.reason(), "not null terminated string in object with unknown _id");
      }
  
      TEST(BSONValidateFast, NonTopLevelId) {
          BufBuilder bb;
          BSONObjBuilder ob(bb);
          ob.append("not_id1", BSON("_id" << "not the real _id"));
          appendInvalidStringElement("not_id2", &bb);
          const BSONObj x = ob.done();
          const Status status = validateBSON(x.objdata(), x.objsize());
          ASSERT_NOT_OK(status);
          ASSERT_EQUALS(status.reason(), "not null terminated string in object with unknown _id");
      }
  }
  