Class: TLV::TLV

Inherits:
Object
  • Object
show all
Defined in:
lib/tlv/b.rb,
lib/tlv/raw.rb,
lib/tlv/tag.rb,
lib/tlv/tlv.rb,
lib/tlv/field.rb,
lib/tlv/parse.rb,
lib/tlv/constructed.rb

Defined Under Namespace

Classes: B, Field, Raw

Constant Summary collapse

CLASS_MASK =
0xC0
UNIVERSAL_CLASS =
0x00
APPLICATION =
0x40
CTX_SPECIFIC =
0x80
PRIVATE =
0xC0
BYTE6 =
0x20
DEBUG =

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(bytes = nil) ⇒ TLV

Returns a new instance of TLV.



46
47
48
# File 'lib/tlv/parse.rb', line 46

def initialize bytes=nil
  parse(bytes) if bytes
end

Class Attribute Details

.accessor_nameObject

If this TLV is placed into another as a subfield, this will be the name of the accessor, default is the rubyfied display_name



61
62
63
# File 'lib/tlv/tlv.rb', line 61

def accessor_name
  @accessor_name
end

.display_nameObject

Returns the value of attribute display_name.



58
59
60
# File 'lib/tlv/tlv.rb', line 58

def display_name
  @display_name
end

.tagObject

Returns the value of attribute tag.



57
58
59
# File 'lib/tlv/tlv.rb', line 57

def tag
  @tag
end

Class Method Details

.&(flag) ⇒ Object



69
70
71
# File 'lib/tlv/tlv.rb', line 69

def @tag.& flag
  self[0] & flag
end

._parse(bytes) ⇒ Object



9
10
11
12
13
14
15
16
# File 'lib/tlv/parse.rb', line 9

def self._parse bytes
  return nil unless bytes && bytes.length>0
  tag, _ = self.get_tag bytes
  impl = lookup(tag)
  tlv = impl.new 
  rest = tlv.parse(bytes)
  [tlv, rest]
end

.application?Boolean

Returns:

  • (Boolean)


16
17
18
# File 'lib/tlv/tag.rb', line 16

def application?
  ((@tag & CLASS_MASK) == APPLICATION) if @tag
end

.b(len, desc, name = nil) ⇒ Object



84
85
86
87
# File 'lib/tlv/tlv.rb', line 84

def b len, desc, name=nil
  raise "invalid len #{len}" unless (len%8 == 0)
  fields << B.new(self, desc, name, len)
end

.b2s(bytes) ⇒ Object



21
22
23
# File 'lib/tlv/tlv.rb', line 21

def self.b2s bytes
	::TLV.b2s bytes
end

.check_tagObject

check the tag is approriate length



36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/tlv/tag.rb', line 36

def check_tag
  if (tag & 0x1f) == 0x1f # last 5 bits set, 2 byte tag
    raise "Tag too short: #{b2s(tag)} should be 2 bytes" unless tag.length > 1
    if (tag[1]&0x80) == 0x80
      raise "Tag length incorrect: #{b2s(tag)} should be 3 bytes" unless tag.length == 3
    else
      raise "Tag too long: #{b2s(tag)} should be 2 bytes" if tag.length > 2
    end
  else
    raise "Tag too long: #{b2s(tag)} should be 1 bytes" if tag.length > 1
  end
end

.constructed?Boolean

Returns:

  • (Boolean)


32
33
34
# File 'lib/tlv/tag.rb', line 32

def constructed?
  ((@tag & BYTE6) == BYTE6) if @tag
end

.context_specific?Boolean

Returns:

  • (Boolean)


19
20
21
# File 'lib/tlv/tag.rb', line 19

def context_specific?
  ((@tag & CLASS_MASK) == CTX_SPECIFIC) if @tag
end

.define_accessor(tlv_class) ⇒ Object



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
# File 'lib/tlv/constructed.rb', line 80

def define_accessor tlv_class
  s = tlv_class
  # check we are actually creating an accessor for an TLV
  while s = s.superclass
    break if s == TLV
  end      
  raise "not a TLV class!" unless s

  # determine the accessor name
  # currently the call graph of this method ensures the class 
  # will have an accessor name.
  name = tlv_class.accessor_name
  
  define_method("#{name}="){ |val|
    # must either be an instance of tlv_val
    # or a raw value.
    if val.is_a? TLV
      self.instance_variable_set("@#{name}", val)
    else
      v = tlv_class.new
      # _should_ be a String, but we'll bang anything 
      # into value for now...
      v.value = val.to_s
      self.instance_variable_set("@#{name}", v)
    end 
  }

  define_method("#{name}") {
    self.instance_variable_get("@#{name}") 
  }

end

.fieldsObject



80
81
82
# File 'lib/tlv/tlv.rb', line 80

def fields
  @fields ||= (self == TLV ? [] : superclass.fields.dup) 
end

.get_length(bytes) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/tlv/parse.rb', line 29

def self.get_length bytes
  len = bytes[0,1][0]
  num_bytes=0

  if (len & 0x80) == 0x80     # if MSB set 
    num_bytes = len & 0x0F    # 4 LSB are num bytes total
    raise "Don't be silly: #{b2s(bytes)}" if num_bytes > 4
    len = bytes[1,num_bytes]
    len = ("#{"\x00"*(4-num_bytes)}%s" % len).unpack("N")[0]
  end
  # this will return ALL the rest, not just `len` bytes of the rest. Does this make sense?
  rest = bytes[1+num_bytes, bytes.length]
  # TODO handle errors...
  # warn if rest.length > length || rest.length < length ?
  [len, rest]
end

.get_tag(bytes) ⇒ Object



18
19
20
21
22
23
24
25
26
27
# File 'lib/tlv/parse.rb', line 18

def self.get_tag bytes
  tag = (bytes[0,1])
  if (tag[0] & 0x1f) == 0x1f # last 5 bits set, 2 byte tag
    tag = bytes[0,2]
    if (tag[1] & 0x80) == 0x80 # bit 8 set -> 3 byte tag
      tag = bytes[0,3]
    end
  end
  [tag, bytes[tag.length, bytes.length]]
end

.handle_options(options) ⇒ Object



61
62
63
64
65
66
67
68
69
# File 'lib/tlv/constructed.rb', line 61

def handle_options options
  tag = options[:tag]
  display_name = options[:display_name]
  classname = options[:classname] || rubify_c(display_name)
  new_class = self.const_set(classname, Class.new(TLV))
  new_class.tlv(tag, display_name, options[:accessor_name])
  new_class.raw
  new_class
end

.handle_subtag(arr, tlv, accessor_name) ⇒ Object

internal, common functionality for mndatory and optional



48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/tlv/constructed.rb', line 48

def handle_subtag arr, tlv, accessor_name
  tlv = handle_options(tlv) if tlv.is_a? Hash
  raise "#{tlv} must be a subclass of TLV!" unless tlv.ancestors.include? TLV
  if accessor_name
    tlv= tlv.dup
    tlv.accessor_name= accessor_name
  end
  define_accessor(tlv)
  
  register(tlv)
  arr << tlv
end

.is_raw?Boolean

Returns:

  • (Boolean)


94
95
96
# File 'lib/tlv/tlv.rb', line 94

def is_raw?
  @is_raw == true
end

.lookup(tag) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
# File 'lib/tlv/constructed.rb', line 35

def lookup tag
  return self if tag == self.tag
  @tlv_classes ||= {}
  tlv = @tlv_classes[tag]
  if !tlv && ! (self == TLV)
    warn "looking up tag #{TLV.b2s(tag)} in super!"
    raise "bla"
    tlv ||= super
  end
  tlv
end

.mand_tagsObject



5
6
7
# File 'lib/tlv/constructed.rb', line 5

def mand_tags
  @mand_tags ||= (self == TLV ? [] : superclass.mand_tags.dup)
end

.mandatory(tlv, accessor_name = nil) ⇒ Object

Takes either a subclass of TLV or the following options, which are used to

create an subclass.
Options:
  :tag
  :display_name

  :classname (will be derived from display_name if not given)
  :accessor_name (will be derived from display_name if not given)


20
21
22
# File 'lib/tlv/constructed.rb', line 20

def mandatory tlv, accessor_name=nil
  handle_subtag mand_tags, tlv, accessor_name
end

.opt_tagsObject



8
9
10
# File 'lib/tlv/constructed.rb', line 8

def opt_tags
  @opt_tags ||= (self == TLV ? [] : superclass.opt_tags.dup)
end

.optional(tlv, accessor_name = nil) ⇒ Object

for constructed tlv’s, add subtags that may be present



25
26
27
# File 'lib/tlv/constructed.rb', line 25

def optional tlv, accessor_name=nil
  handle_subtag opt_tags, tlv, accessor_name
end

.parse(bytes) ⇒ Object

return [tlv, rest], the parsed TLV and any leftover bytes.



4
5
6
7
# File 'lib/tlv/parse.rb', line 4

def self.parse bytes
  tlv, _ = self._parse bytes
  return tlv
end

.primitive?Boolean

Returns:

  • (Boolean)


25
26
27
28
29
30
31
# File 'lib/tlv/tag.rb', line 25

def primitive?
  if @tag
    ((@tag & BYTE6) == 0x00) 
  else
    true # `tagless` datastructs are by default primitive
  end
end

.private?Boolean

Returns:

  • (Boolean)


22
23
24
# File 'lib/tlv/tag.rb', line 22

def private?
  ((@tag & CLASS_MASK) == PRIVATE) if @tag
end

.raw(desc = nil, name = nil) ⇒ Object



89
90
91
92
# File 'lib/tlv/tlv.rb', line 89

def raw desc=nil, name=nil
  @is_raw = true
  fields << Raw.new(self, desc, name)
end

.register(tlv) ⇒ Object



29
30
31
32
33
# File 'lib/tlv/constructed.rb', line 29

def register tlv
  @tlv_classes ||= {}
  warn "tag #{TLV.b2s(tlv.tag)} already defined!" if @tlv_classes[tlv.tag]
  @tlv_classes[tlv.tag] = tlv
end

.rubify_a(display) ⇒ Object



71
72
73
74
# File 'lib/tlv/constructed.rb', line 71

def rubify_a display
  name = display.strip.gsub(/\s+/, "_")
  name = name.downcase.to_sym
end

.rubify_c(display) ⇒ Object



76
77
78
# File 'lib/tlv/constructed.rb', line 76

def rubify_c display
  name = display.gsub(/\s+/, "")
end

.s2b(str) ⇒ Object



18
19
20
# File 'lib/tlv/tlv.rb', line 18

def self.s2b str
	::TLV.s2b str
end

.tlv(tag, display_name, accessor_name = nil) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/tlv/tlv.rb', line 62

def tlv tag, display_name, accessor_name=nil
  @tag = case tag
         when String
           TLV.s2b(tag)
         when Fixnum
           TLV::s2b("%x" % tag)
         end
  def @tag.& flag
    self[0] & flag
  end
  check_tag
  @display_name = display_name
  @accessor_name = accessor_name || rubify_a(display_name)
  TLV.register self
end

.universal?Boolean

Returns:

  • (Boolean)


13
14
15
# File 'lib/tlv/tag.rb', line 13

def universal?
  ((@tag & CLASS_MASK) == UNIVERSAL_CLASS) if @tag
end

.warn(mes) ⇒ Object

Outputs a warning in case the enironment variable ‘DEBUG` is set.



34
35
36
# File 'lib/tlv/tlv.rb', line 34

def self.warn mes
  STDERR.puts "[warn] #{mes}" if ENV["DEBUG"]
end

Instance Method Details

#display_nameObject

meta class thingie



101
102
103
# File 'lib/tlv/tlv.rb', line 101

def display_name
   self.class.display_name
end

#fieldsObject



128
129
130
# File 'lib/tlv/tlv.rb', line 128

def fields
  self.class.fields
end

#mandatoryObject



132
133
134
# File 'lib/tlv/tlv.rb', line 132

def mandatory
  self.class.mand_tags
end

#optionalObject



135
136
137
# File 'lib/tlv/tlv.rb', line 135

def optional
  self.class.opt_tags
end

#parse(bytes) ⇒ Object

returns the leftover bytes



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/tlv/parse.rb', line 51

def parse bytes
  if tag
    tag, rest = TLV.get_tag(bytes)
    length, bytes = self.class.get_length(rest)
  end
  
  if self.class.primitive?
    bytes = parse_fields bytes, length
  else
    bytes = parse_tlv bytes, length
  end

  bytes

end

#parse_fields(bytes, length) ⇒ Object



78
79
80
81
82
83
# File 'lib/tlv/parse.rb', line 78

def parse_fields bytes, length
  fields.each { |field|
    bytes = field.parse(self, bytes, length)    
  } if fields
  bytes
end

#parse_tlv(bytes, length) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
# File 'lib/tlv/parse.rb', line 67

def parse_tlv bytes, length
  b = bytes[0,length]
  rest = bytes[length, bytes.length]

  while b && b.length != 0
    tlv, b = self.class._parse(b)
    self.send("#{tlv.class.accessor_name}=", tlv)
  end

  rest
end

#tagObject



139
140
141
# File 'lib/tlv/tlv.rb', line 139

def tag
  self.class.tag
end

#to_sObject



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/tlv/tlv.rb', line 104

def to_s
  longest = 0
  fields.each { |field|
    longest = field.display_name.length if field.display_name.length > longest
  }
  fmt = "%#{longest}s : %s\n"
  str = "#{display_name}"
  str << " (0x#{TLV.b2s(tag)})" if tag
  str << "\n"

  str << "-" * (str.length-1) << "\n"
  fields.each { |field|
    str << (fmt % [field.display_name, TLV.b2s(self.send(field.name))])
  }
  (mandatory+optional).each { |tlv_class|
    temp_tlv = self.send(tlv_class.accessor_name)
    temp = temp_tlv.to_s
    temp.gsub!(/^/, "  ")
    str << temp
  }
  str
end