aboutsummaryrefslogtreecommitdiffstats
path: root/lib/asn1kit/types/enumerated.rb
blob: 79a26c7856997bdd6ade99a86540777cb6744866 (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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# coding: ASCII-8BIT

class ASN1Kit::Enumerated < ASN1Kit::Type
  asn1_tag :IMPLICIT, :UNIVERSAL, 10
  asn1_alias "ENUMERATED"

  class << self
    def [](hash, extensible: false)
      ret = Class.new(self)
      ret.const_set(:EXTENSIBILITY, extensible)
      ret.const_set(:ENUMERATIONS, hash)
      ret
    end

    def enumerations
      self::ENUMERATIONS.keys
    end

    def name?(name)
      self::ENUMERATIONS.key?(name)
    end

    def value_for(name)
      self::ENUMERATIONS[name]
    end

    def extensible?
      self::EXTENSIBILITY
    end

    # Will be used during the parsing as well as from #[]
    private def set_enumerations(root, additional)
      last = -1
      root.map! do |name, value|
        if value
          raise TypeError, "value must be an Integer" if !value.is_a?(::Integer)
          [name, last = value]
        else
          [name, last += 1]
        end
      end
      additional.map! do |name, value|
        if value
          raise TypeError, "value must be an Integer" if !value.is_a?(::Integer)
          if last >= value
            raise ArgumentError, "AdditionalEnumeration must be ordered"
          end
          [name, last = value]
        else
          [name, last += 1]
        end
      end

      hash = (root + additional).to_h
      if hash.size != root.size + additional.size
        raise ArgumentError, "duplicate identifier in enumerations"
      end
      if hash.values.uniq.size != hash.size
        raise ArgumentError, "duplicate value in enumerations"
      end
      const_set(:ENUMERATIONS, hash)
    end
  end

  attr_reader :name

  def initialize(name)
    unless defined?(self.class::ENUMERATIONS)
      raise TypeError, "uninitialized ENUMERATED"
    end
    self.class::ENUMERATIONS[name]
    @name = name
  end

  def value
    self.class::ENUMERATIONS[@name]
  end

  def to_der
    dig = value.digits(256)
    dig << 0x00 if dig[-1] >= 0x80
    s = dig.pack("C*").reverse
    der_header(s.bytesize) << s
  end

  def ==(other)
    return false unless other.is_a?(ASN1Kit::Enumerated)
    return false unless other.class::ENUMERATIONS.equal?(self.class::ENUMERATIONS)
    @name == other.name
  end
end

module ASN1Kit::Internal::CompileEnumerated
  refine ASN1Kit::Enumerated.singleton_class do
    # Basically same as ASN1Kit::Enumerated::[], except this won't
    # do validation of the enumerations.
    def _new_internal(root, additional, extensible:)
      ret = Class.new(self)
      ret.const_set(:EXTENSIBILITY, extensible)

      # Let #enumerations work
      hash = (root + (additional ? additional : [])).to_h
      if hash.size != root.size + (additional ? additional.size : 0)
        raise ASN1Kit::ParseError, "duplicate identifier in Enumerations"
      end
      ret.const_set(:ENUMERATIONS, hash)

      # For #_compile_fixup
      ret.instance_variable_set(:@_compile_root, root)
      ret.instance_variable_set(:@_compile_additional, additional)
      ret
    end

    def _compile_fixup(state)
      root = @_compile_root
      remove_instance_variable(:@_compile_root)
      additional = @_compile_additional
      remove_instance_variable(:@_compile_additional)

      hash = {}
      values_seen = {}
      unspecified = []
      root.each do |name, value|
        if value
          v = value.unwrap_as_number(state)
          if values_seen[v]
            raise ASN1Kit::ParseError, "duplicate value in RootEnumeration: %p" % v
          end
          values_seen[v] = true
        else
          unspecified << name
        end
        hash[name] = v
      end
      next_value = 0
      unspecified.each do |name|
        while values_seen[next_value] do next_value += 1 end
        hash[name] = next_value
        next_value += 1
      end

      additional.each do |name, value|
        if value
          v = value.unwrap_as_number(state)
          if values_seen[v]
            raise ASN1Kit::ParseError, "duplicate value in AdditionalEnumeration: %p" % v
          end
          if next_value > v
            raise ASN1Kit::ParseError, "AdditionalEnumeration must be ordered"
          end
        else
          v = next_value + 1
        end
        next_value = hash[name] = v
        values_seen[v] = true
      end
      self::ENUMERATIONS.replace(hash)
    end
  end
end