aboutsummaryrefslogtreecommitdiffstats
path: root/lib/asn1kit/types/bit_string.rb
blob: 53426dd682ee3f21062fd6a1922e6db21c4a8533 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# coding: ASCII-8BIT

class ASN1Kit::BitString < ASN1Kit::Type
  asn1_tag :IMPLICIT, :UNIVERSAL, 3
  asn1_alias "BIT STRING"

  NAMED_BITS = {}
  NAMED_BITS_INVERTED = {}

  class << self
    def [](*named_bits)
      return self if named_bits.empty?
      ret = Class.new(self)
      hash = named_bits.to_h
      hash_inverted = hash.invert
      raise "duplicate name(s) in named bit list" if named_bits.size != hash.size
      raise "duplicate value(s) in named bit list" if hash.size != hash_inverted.size
      ret.const_set(:NAMED_BITS, hash)
      ret.const_set(:NAMED_BITS_INVERTED, hash.invert)
      ret
    end

    def named_bit(name)
      self::NAMED_BITS[name]
    end

    def named_bits
      self::NAMED_BITS.keys
    end

    private def inspect_inner
      return nil if self::NAMED_BITS.empty?
      nns = self::NAMED_BITS.map { |name, value|
        "#{name}(#{value})"
      }.join(", ")
      "{ #{nns} }"
    end

    def new_with_names(*list)
      if self::NAMED_BITS.empty?
        raise TypeError, "BIT STRING without NamedBits defined cannot be " \
          "instantiated by #from_names"
      end
      return new(String.new, 0) if list.empty?

      bits = list.map { |name|
        named_bit(name) or
          raise ArgumentError, "invalid name: %p" % name
      }
      bit_length = bits.max + 1
      str = "0".b * bit_length
      bits.each { |pos| str[pos] = "1" }
      new([str].pack("B*"), bit_length)
    end
  end

  attr_reader :string, :bit_length

  def initialize(str, bit_length)
    if bit_length <= (str.bytesize - 1) * 8 || bit_length > str.bytesize * 8
      raise ArgumentError, "invalid bit_length"
    end
    @string = str
    @bit_length = bit_length
  end

  def set?(name_or_pos)
    if name_or_pos.is_a?(String)
      name_or_pos = self.class.named_bit(name_or_pos)
    end
    unless name_or_pos.is_a?(::Integer)
      raise TypeError, "invalid argument type: %p" % name_or_pos
    end
    byte = @string.getbyte(name_or_pos / 8)
    byte[7 - name_or_pos % 8] == 1
  end

  def to_der
    byte_length = (bit_length / 8r).ceil
    unused_bits = byte_length * 8 - bit_length
    content = [unused_bits].pack("C") << @string
    der_header(byte_length + 1) << content
  end

  def ==(other)
    return false unless other.is_a?(ASN1Kit::BitString)
    return false unless self.class::NAMED_BITS.equal?(other.class::NAMED_BITS)
    value == other.value && bit_length == other.bit_length
  end
end

module ASN1Kit::Internal::CompileBitString
  refine ASN1Kit::BitString.singleton_class do
    def _compile_fixup(state)
      self::NAMED_BITS.transform_values! do |value|
        if value.is_a?(ASN1Kit::Internal::UnresolvedValue)
          vname = value.value.name
          value = state.value_symbols[vname] ||
            raise("unresolved value: %s" % vname)
          if !value.is_a?(ASN1Kit::Integer)
            raise ASN1Kit::ParseError, "DefinedValue in NamedBit resolved to non-integer: %s" % vname
          end
          if value.value < 0
            raise ASN1Kit::ParseError, "DefinedValue in NamedBit resolved to negative integer: %s" % vname
          end
          value = value.value
        end
        value
      end
    end
  end
end