/*
  A simple ASN.1 parser, similiar to 'dumpasn1' or 'openssl asn1parse', though
  without some of the bells and whistles of those. Primarily used for testing
  the BER decoder. The output format is modeled loosely on 'asn1parse -i'

  The output is actually less precise than the other decoders named, because
  the underlying BER_Decoder hides quite a bit from userspace, such as the use
  of indefinite length encodings (and the EOC markers). At some point it will
  also hide the constructed string types from the user, but right now you'll
  seem them as-is.

  Written by Jack Lloyd, November 9-10, 2003
    - Nov 22: Updated to new BER_Object format (tag -> class_tag/type_tag)
    - Nov 25: Much improved BIT STRING output
              Can deal with non-constructed taggings
              Can produce UTF-8 output

  This file is in the public domain.
*/

/*******************************************************************/

// Set this if your terminal understands UTF-8; otherwise output is in Latin-1
#define UTF8_TERMINAL 1

/*
   What level the outermost layer of stuff is at. Probably 0 or 1; asn1parse
   uses 0 as the outermost, while 1 makes more sense to me. 2+ doesn't make
   much sense at all.
*/
#define INITIAL_LEVEL 0

/*******************************************************************/

#include <botan/ber_dec.h>
#include <botan/asn1_obj.h>
#include <botan/pem.h>
#include <botan/oids.h>
#include <botan/pipe.h>
#include <botan/hex.h>
#include <botan/pem.h>
using namespace Botan;

#include <stdio.h>
#include <ctype.h>

void decode(BER_Decoder&, u32bit);
void emit(const std::string&, u32bit, u32bit, const std::string& = "");
std::string type_name(ASN1_Tag);

int main(int argc, char* argv[])
   {
   if(argc != 2)
      {
      printf("Usage: %s <file>\n", argv[0]);
      return 1;
      }

   try {
      LibraryInitializer init;

      DataSource_Stream in(argv[1]);

      if(!PEM_Code::matches(in))
         {
         BER_Decoder decoder(in);
         decode(decoder, INITIAL_LEVEL);
         }
      else
         {
         std::string label; // ignored
         BER_Decoder decoder(PEM_Code::decode(in, label));
         decode(decoder, INITIAL_LEVEL);
         }

   }
   catch(std::exception& e)
      {
      printf("%s\n", e.what());
      return 1;
      }
   return 0;
   }

void decode(BER_Decoder& decoder, u32bit level)
   {
   BER_Object obj = decoder.get_next_object();

   while(obj.type_tag != NO_OBJECT)
      {
      const ASN1_Tag type_tag = obj.type_tag;
      const ASN1_Tag class_tag = obj.class_tag;
      const u32bit length = obj.value.size();

      /* hack to insert the tag+length back in front of the stuff now
         that we've gotten the type info */
      DER_Encoder encoder;
      encoder.add_object(type_tag, class_tag, obj.value, obj.value.size());
      SecureVector<byte> bits = encoder.get_contents();

      BER_Decoder data(bits);

      if(class_tag & CONSTRUCTED)
         {
         BER_Decoder cons_info(obj.value);
         if(type_tag == SEQUENCE)
            {
            emit("SEQUENCE", level, length);
            decode(cons_info, level+1);
            }
         else if(type_tag == SET)
            {
            emit("SET", level, length);
            decode(cons_info, level+1);
            }
         else
            {
            std::string name;

            if((class_tag & APPLICATION) || (class_tag & CONTEXT_SPECIFIC) ||
               (class_tag & PRIVATE))
               name = "cons [" + to_string(type_tag) + "]";
            else
               name = type_name(type_tag) + " (cons)";

            emit(name, level, length);
            decode(cons_info, level+1);
            }
         }
      else if(class_tag == APPLICATION || class_tag == CONTEXT_SPECIFIC ||
              class_tag == PRIVATE)
         {
         bool not_text = false;

         for(u32bit j = 0; j != bits.size(); j++)
            if(!isgraph(bits[j]) && !isspace(bits[j]))
               not_text = true;

         Pipe pipe(((not_text) ? new Hex_Encoder : 0));
         pipe.process_msg(bits);
         emit("[" + to_string(type_tag) + "]", level, length,
              pipe.read_all_as_string());
         }
      else if(type_tag == OBJECT_ID)
         {
         OID oid;
         BER::decode(data, oid);
         emit(type_name(type_tag), level, length, OIDS::lookup(oid));
         }
      else if(type_tag == INTEGER)
         {
         BigInt number;
         BER::decode(data, number);

         SecureVector<byte> rep;

         /* If it's small, it's probably a number, not a hash */
         if(number.bits() <= 16)
            rep = BigInt::encode(number, BigInt::Decimal);
         else
            rep = BigInt::encode(number, BigInt::Hexadecimal);

         std::string str;
         for(u32bit j = 0; j != rep.size(); j++)
            str += (char)rep[j];

         emit(type_name(type_tag), level, length, str);
         }
      else if(type_tag == BOOLEAN)
         {
         bool boolean;
         BER::decode(data, boolean);
         emit(type_name(type_tag),
              level, length, (boolean ? "true" : "false"));
         }
      else if(type_tag == NULL_TAG)
         {
         emit(type_name(type_tag), level, length);
         }
      else if(type_tag == OCTET_STRING)
         {
         SecureVector<byte> bits;
         BER::decode(data, bits, type_tag);
         bool not_text = false;

         for(u32bit j = 0; j != bits.size(); j++)
            if(!isgraph(bits[j]) && !isspace(bits[j]))
               not_text = true;

         Pipe pipe(((not_text) ? new Hex_Encoder : 0));
         pipe.process_msg(bits);
         emit(type_name(type_tag), level, length, pipe.read_all_as_string());
         }
      else if(type_tag == BIT_STRING)
         {
         SecureVector<byte> bits;
         BER::decode(data, bits, type_tag);

         std::vector<bool> bit_set;

         for(u32bit j = 0; j != bits.size(); j++)
            for(u32bit k = 0; k != 8; k++)
               bit_set.push_back((bool)((bits[bits.size()-j-1] >> (7-k)) & 1));

         std::string bit_str;
         for(u32bit j = 0; j != bit_set.size(); j++)
            {
            bool the_bit = bit_set[bit_set.size()-j-1];

            if(!the_bit && bit_str.size() == 0)
               continue;
            bit_str += (the_bit ? "1" : "0");
            }

         emit(type_name(type_tag), level, length, bit_str);
         }
      else if(type_tag == PRINTABLE_STRING ||
              type_tag == NUMERIC_STRING ||
              type_tag == IA5_STRING ||
              type_tag == T61_STRING ||
              type_tag == VISIBLE_STRING ||
              type_tag == UTF8_STRING ||
              type_tag == BMP_STRING)
         {
         ASN1_String str;
         BER::decode(data, str);
         if(UTF8_TERMINAL)
            emit(type_name(type_tag), level, length, iso2utf(str.iso_8859()));
         else
            emit(type_name(type_tag), level, length, str.iso_8859());
         }
      else if(type_tag == UTC_TIME || type_tag == GENERALIZED_TIME)
         {
         X509_Time time;
         BER::decode(data, time);
         emit(type_name(type_tag), level, length, time.readable_string());
         }
      else
         fprintf(stderr, "Unknown tag: class=%02X, type=%02X\n",
                 class_tag, type_tag);

      obj = decoder.get_next_object();
      }
   }

void emit(const std::string& type, u32bit level, u32bit length,
          const std::string& value)
   {
   const u32bit LIMIT = 128;
   const u32bit BIN_LIMIT = 64;

   int written = 0;
   written += printf("  d=%2d, l=%4d: ", level, length);
   for(u32bit j = INITIAL_LEVEL; j != level; j++)
      written += printf(" ");
   written += printf("%s   ", type.c_str());

   bool should_skip = false;
   if(value.length() > LIMIT) should_skip = true;
   if((type == "OCTET STRING" || type == "BIT STRING") &&
      value.length() > BIN_LIMIT)
      should_skip = true;

   if(value != "" && !should_skip)
      {
      if(written % 2 == 0) printf(" ");
      while(written < 50) written += printf("  ");
      printf(":%s\n", value.c_str());
      }
   else
      printf("\n");
   }

std::string type_name(ASN1_Tag type)
   {
   if(type == PRINTABLE_STRING) return "PRINTABLE STRING";
   if(type == NUMERIC_STRING)   return "NUMERIC STRING";
   if(type == IA5_STRING)       return "IA5 STRING";
   if(type == T61_STRING)       return "T61 STRING";
   if(type == UTF8_STRING)      return "UTF8 STRING";
   if(type == VISIBLE_STRING)   return "VISIBLE STRING";
   if(type == BMP_STRING)       return "BMP STRING";

   if(type == UTC_TIME)         return "UTC TIME";
   if(type == GENERALIZED_TIME) return "GENERALIZED TIME";

   if(type == OCTET_STRING)     return "OCTET STRING";
   if(type == BIT_STRING)       return "BIT STRING";

   if(type == INTEGER)          return "INTEGER";
   if(type == NULL_TAG)         return "NULL";
   if(type == OBJECT_ID)        return "OBJECT";
   if(type == BOOLEAN)          return "BOOLEAN";
   return "(UNKNOWN)";
   }
