aboutsummaryrefslogtreecommitdiffstats
path: root/lib/asn1kit/types/bit_string.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/asn1kit/types/bit_string.rb')
-rw-r--r--lib/asn1kit/types/bit_string.rb112
1 files changed, 112 insertions, 0 deletions
diff --git a/lib/asn1kit/types/bit_string.rb b/lib/asn1kit/types/bit_string.rb
new file mode 100644
index 0000000..53426dd
--- /dev/null
+++ b/lib/asn1kit/types/bit_string.rb
@@ -0,0 +1,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