# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# This file implements very minimal ASN.1, DER serialization.


def ToDER(obj):
  '''ToDER converts the given object into DER encoding'''
  if obj is None:
    # None turns into NULL
    return TagAndLength(5, 0)
  if isinstance(obj, (str, bytes)):
    # There are many ASN.1 string types, so rather than pick one implicitly,
    # require the caller explicitly specify the encoding with asn1.UTF8String,
    # etc., below.
    raise TypeError("String types must be specified explicitly")
  if isinstance(obj, bool):
    val = b"\x00"
    if obj:
      val = b"\xff"
    return TagAndData(1, val)
  if isinstance(obj, int):
    big_endian = bytearray()
    val = obj
    while val != 0:
      big_endian.append(val & 0xff)
      val >>= 8

    if len(big_endian) == 0 or big_endian[-1] >= 128:
      big_endian.append(0)

    big_endian.reverse()
    return TagAndData(2, bytes(big_endian))

  return obj.ToDER()


def TagAndLength(tag, length):
  der = bytearray([tag])
  if length < 128:
    der.append(length)
  elif length < 256:
    der.append(0x81)
    der.append(length)
  elif length < 65535:
    der.append(0x82)
    der.append(length >> 8)
    der.append(length & 0xff)
  else:
    assert False

  return bytes(der)


def TagAndData(tag, data):
  return TagAndLength(tag, len(data)) + data


class Raw(object):
  '''Raw contains raw DER encoded bytes that are used verbatim'''

  def __init__(self, der):
    self.der = der

  def ToDER(self):
    return self.der


class Explicit(object):
  '''Explicit prepends an explicit tag'''

  def __init__(self, tag, child):
    self.tag = tag
    self.child = child

  def ToDER(self):
    der = ToDER(self.child)
    tag = self.tag
    tag |= 0x80  # content specific
    tag |= 0x20  # complex
    return TagAndData(tag, der)


class ENUMERATED(object):
  def __init__(self, value):
    self.value = value

  def ToDER(self):
    return TagAndData(10, bytes([self.value]))


class SEQUENCE(object):
  def __init__(self, children):
    self.children = children

  def ToDER(self):
    der = b''.join([ToDER(x) for x in self.children])
    return TagAndData(0x30, der)


class SET(object):
  def __init__(self, children):
    self.children = children

  def ToDER(self):
    der = b''.join([ToDER(x) for x in self.children])
    return TagAndData(0x31, der)


class OCTETSTRING(object):
  def __init__(self, val):
    self.val = val

  def ToDER(self):
    return TagAndData(4, self.val)


class PrintableString(object):
  def __init__(self, val):
    self.val = val

  def ToDER(self):
    return TagAndData(19, self.val)


class UTF8String(object):
  def __init__(self, val):
    self.val = val

  def ToDER(self):
    return TagAndData(12, self.val)


class OID(object):
  def __init__(self, parts):
    self.parts = parts

  def ToDER(self):
    if len(self.parts) < 2 or self.parts[0] > 6 or self.parts[1] >= 40:
      assert False

    der = bytearray([self.parts[0] * 40 + self.parts[1]])
    for x in self.parts[2:]:
      if x == 0:
        der.append(0)
      else:
        octets = bytearray()
        while x != 0:
          v = x & 0x7f
          if len(octets) > 0:
            v |= 0x80
          octets.append(v)
          x >>= 7
        octets.reverse()
        der = der + octets

    return TagAndData(6, bytes(der))


class UTCTime(object):
  def __init__(self, time_str):
    self.time_str = time_str

  def ToDER(self):
    return TagAndData(23, self.time_str.encode('ascii'))


class GeneralizedTime(object):
  def __init__(self, time_str):
    self.time_str = time_str

  def ToDER(self):
    return TagAndData(24, self.time_str.encode('ascii'))


class BitString(object):
  def __init__(self, bits):
    self.bits = bits

  def ToDER(self):
    return TagAndData(3, b"\x00" + self.bits)
