<?php
/*
 * Copyright 2008-2010 GuardTime AS
 *
 * This file is part of the GuardTime PHP SDK.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @package asn1
 */

/**
 * ASN.1 Tag implementation.
 *
 * @throws GTException
 * @package asn1
 */
class ASN1Tag extends ASN1Object {

    private $bytes;
    private $object;

    private $explicit = true;

    /**
     * Decodes an ASN1Tag from the given byte stream.
     *
     * @param  $bytes V bytes from the encoding of an ASN1Tag TLV
     * @return void
     */
    public function decodeDER($bytes) {
        $this->bytes = $bytes;
    }

    /**
     * Encodes the contents of this ASN1Tag.
     *
     * @throws GTException
     * @return array array of bytes containing the encoding of this ASN1Tag
     * @see setExplicit
     * @see setObject
     */
    public function encodeDER() {

        $bytes = array();

        if ($this->object != null) {

            // this is for objects we have actually decoded
            $this->append($bytes, $this->object->encodeDER());

        } else if ($this->bytes != null) {

            // for passthrough objects that we don't decode (like x509 certs)
            $this->append($bytes, $this->bytes);

        } else {

            throw new GTException("Nothing to encode inside ASN1Tag");

        }

        if ($this->explicit) {

            $this->prepend($bytes, ASN1DER::encodeLength(count($bytes)));
            $this->prepend($bytes, array(0)); // placeholder

        }

        $tag = $this->tagValue;

        if ($this->tagType == ASN1_TAG_CONSTRUCTED) {
            $tag = $tag | 0x20;
        }

        switch ($this->tagClass) {

            case ASN1_TAG_APPLICATION:
                $tag = $tag | 0x40;
                break;

            case ASN1_TAG_PRIVATE:
                $tag = $tag | 0xC0;
                break;

            case ASN1_TAG_CONTEXT:
                $tag = $tag | 0x80;
                break;

        }

        $bytes[0] = $tag;

        return $bytes;

    }

    /**
     * Return the object embedded inside this ASN.1 Tag.
     *
     * This method is for explicit tags only.
     *
     * @throws GTException
     * @return ASN1Object object embedded inside this tag
     */
    public function getObject() {
        $object = ASN1DER::decodeType($this->bytes);
        $length = ASN1DER::decodeLength($this->bytes);

        $object->decodeDER($this->readBytes($this->bytes, $length));

        if (count($this->bytes) > 0) {
            throw new GTException("Invalid explicit tag encoding with trailing bytes");
        }

        return $object;
    }

    /**
     * Returns the object embedded inside this ASN.1 Tag.
     *
     * This method is for implicit tags only.
     *
     * @param  $tag the type of asn1 object to decode this tag as
     * @return ASN1Object object embedded inside this tag
     */
    public function getObjectAs($tag) {

        $tag = array($tag);

        $object = ASN1DER::decodeType($tag);
        $object->decodeDER($this->bytes);

        return $object;
    }

    /**
     * Sets the object embedded inside this ASN.1 Tag.
     *
     * @param  ASN1Object $object ASN1Object to embed inside this tag
     * @return void
     */
    public function setObject($object) {
        $this->object = $object;
    }


    /**
     * Marks this tag as explicit.
     *
     * @param  $explicit boolean indicating if this tag is explicit
     * @return void
     */
    public function setExplicit($explicit) {
        $this->explicit = $explicit;
    }

    /**
     * Checks if this tag is marked as explicit.
     *
     * @return bool true when this tag is explicit
     */
    public function isExplicit() {
        return $this->explicit;
    }

}

?>
