<?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 Object Id implementation.
 *
 * @package asn1
 */
class ASN1ObjectId extends ASN1Object {

    protected static $ALLOWED_CHARACTERS = array(
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.'
    );

    protected $value;

    /**
     * Constructs a new ASN.1 Object Id.
     *
     * @param  string $value value as string
     * @return void
     */
    public function __construct($value = null) {

        if (!is_null($value)) {

            if (strlen($value) == 0) {
                throw new GTException("Invalid OID: 0 length");
            }

            foreach (GTUtil::toArray($value) as $char) {

                if (!in_array($char, self::$ALLOWED_CHARACTERS, true)) {
                    throw new GTException("Invalid OID: character {$char} not allowed");
                }

            }

            $tokens = explode('.', $value);

            if (count($tokens) < 2) {
                throw new GTException("Invaid OID: must contain at least 2 '.' separated tokens");
            }

            $this->value = $value;
        }

    }

    /**
     * Gets the value of this object id.
     *
     * @return string value
     */
    public function getValue() {
        return $this->value;
    }

    /**
     * Encodes the contents of this ASN1 Object Id as DER.
     *
     * @return array DER encoding of this object Id
     */
    public function encodeDER() {

        $bytes = array();

        $tokens = explode('.', $this->value);

        $b1 = (int) array_shift($tokens);
        $b2 = (int) array_shift($tokens);

        $this->append($bytes, ($b1 * 40 + $b2));

        $zero = new GTBigInteger(0);

        $mask1 = new GTBigInteger(0x80);
        $mask2 = new GTBigInteger(0x7F);

        foreach ($tokens as $token) {

            $integer = new GTBigInteger($token);

            $size = 1;
            $buff = $integer;

            while (true) {

                $buff = $buff->shiftRight(7);

                if ($buff->comp($zero) == 0) {
                    break;
                }

                $size++;
            }

            for ($i = ($size - 1) * 7; $i >= 0; $i -= 7) {

                if ($i == 0) {
                    $this->append($bytes, (int) $integer->bitAnd($mask2)->getValue());

                } else {
                    $this->append($bytes, (int) $integer->shiftRight($i)->bitAnd($mask2)->bitOr($mask1)->getValue());
                }

            }
        }

        $this->prepend($bytes, ASN1DER::encodeLength(count($bytes)));
        $this->prepend($bytes, ASN1DER::encodeType(ASN1_TAG_OBJECT_ID));

        return $bytes;

    }

    /**
     * Decodes an ASN1ObjectId from the given byte stream.
     *
     * @param  $bytes V bytes from the encoding of ASN1ObjectId TLV.
     * @return void
     */
    public function decodeDER($bytes) {

        $this->value = '';

        $current = null;

        for ($i = 0; $i < count($bytes); $i++) {

            if ($current == null) {
                $current = new GTBigInteger(0);
            }

            $byte = $bytes[$i];

            $current = $current->shiftLeft(7);
            $current = $current->bitOr(new GTBigInteger($byte & 0x7F));

            if (($byte & 0x80) != 0) {
                continue;
            }

            if ($i == 0) {

                // int is always big enough to hold the first chunk

                $current = (int) $current->getValue();

                if ($current < 40) {
                    $byte1 = 0;

                } else if ($current < 80) {
                    $byte1 = 1;

                } else {
                    $byte1 = 2;
                }

                $byte2 = $current - ($byte1 * 40);

                $this->value .= $byte1;
                $this->value .= '.';
                $this->value .= $byte2;
                $this->value .= '.';

                $current = null;

            } else {

                $this->value .= $current->getValue();

                if ($i != count($bytes) - 1) {
                    $this->value .= '.';
                }

                $current = null;

            }

        }

    }
}

?>
