Class: BinStruct::AbstractTLV Abstract

Inherits:
Struct
  • Object
show all
Includes:
Structable
Defined in:
lib/bin_struct/abstract_tlv.rb

Overview

This class is abstract.

Base class to define type-length-value data.

You have to define a concrete class from AbstractTLV

MyTLV = BinStruct::AbstractTLV.create
MyTLV.define_type_enum 'one' => 1, 'two' => 2

This will define a new MyTLV class, subclass of AbstractTLV. This class will define 3 attributes:

  • #type, as a Int8Enum by default,

  • #length, as a Int8 by default,

  • and #value, as a String by default.

.define_type_enum is, here, necessary to define enum hash to be used for #type accessor, as this one is defined as an Enum.

Examples:

Basic usage

MyTLV = BinStruct::AbstractTLV.create
MyTLV.define_type_enum 'one' => 1, 'two' => 2

tlv = MyTLV.new(type: 1, value: 'abcd')  # automagically set #length from value
tlv.type        #=> 1
tlv.human_type  #=> 'one'
tlv.length      #=> 4
tlv.value       #=> "abcd"

Change attribute types

# Change type for each attribute
# Type and length are 16-bit big endian integers
# Value is a OUI
MyTLV = BinStruct::AbstractTLV.create(type_class: BinStruct::Int16,
                                      length_class: BinStruct::Int16,
                                      value_class: BinStruct::OUI)
tlv = MyTLV.new(type: 1, value: '01:02:03')
tlv.type        #=> 1
tlv.length      #=> 3
tlv.value       #=> '01:02:03'
tlv.to_s        #=> "\x00\x01\x00\x03\x01\x02\x03"

Using aliases

# Type and length are 16-bit big endian integers
# Value is a string
# code is an alias for type
MyTLV = BinStruct::AbstractTLV.create(type_class: BinStruct::Int16,
                                      length_class: BinStruct::Int16,
                                      aliases: { code: :type })
tlv = MyTLV.new(code: 1, value: 'abcd')
tlv.code        #=> 1
tlv.type        #=> 1
tlv.length      #=> 4
tlv.value       #=> 'abcd'

Author:

  • Sylvain Daubert (2016-2024)

  • LemonTree55

Constant Summary collapse

ATTR_TYPES =
{ 'T' => :type, 'L' => :length, 'V' => :value }.freeze

Constants inherited from Struct

Struct::FMT_ATTR

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Structable

#format_inspect, #sz, #to_s, #type_name

Methods inherited from Struct

#[], #[]=, #attribute?, #attributes, attributes, #bits_on, define_attr, define_attr_after, define_attr_before, define_bit_attr, define_bit_attr_after, define_bit_attr_before, #inspect, #offset_of, #optional?, #optional_attributes, #present?, remove_attr, #sz, #to_h, #to_s, update_attr

Constructor Details

#initialize(options = {}) ⇒ AbstractTLV

This method is abstract.

Should only be called on real TLV classes, created by create.

Return a new instance of a real TLV class.

Parameters:

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :type (Integer)
  • :length (Integer)
  • :value (Object)


250
251
252
253
254
255
256
257
258
259
260
# File 'lib/bin_struct/abstract_tlv.rb', line 250

def initialize(options = {})
  @attr_in_length = self.class.attr_in_length
  self.class.aliases.each do |al, orig|
    options[orig] = options[al] if options.key?(al)
  end

  super
  # used #value= defined below, which set length if needed
  self.value = options[:value] if options[:value]
  calc_length unless options.key?(:length)
end

Class Attribute Details

.aliasesHash

Aliases defined in create

Returns:

  • (Hash)


70
71
72
# File 'lib/bin_struct/abstract_tlv.rb', line 70

def aliases
  @aliases
end

.attr_in_lengthObject



72
73
74
# File 'lib/bin_struct/abstract_tlv.rb', line 72

def attr_in_length
  @attr_in_length
end

.lengthInteger

This method is abstract.

Length attribute for real TLV class

Returns:

  • (Integer)


# File 'lib/bin_struct/abstract_tlv.rb', line 155

.typeInteger

This method is abstract.

Type attribute for real TLV class

Returns:

  • (Integer)


# File 'lib/bin_struct/abstract_tlv.rb', line 155

.valueObject

This method is abstract.

Value attribute for real TLV class

Returns:

  • (Object)


# File 'lib/bin_struct/abstract_tlv.rb', line 155

Instance Attribute Details

#lengthInteger

This method is abstract.

Length attribute

Returns:

  • (Integer)


# File 'lib/bin_struct/abstract_tlv.rb', line 231

#typeInteger

This method is abstract.

Type attribute

Returns:

  • (Integer)


# File 'lib/bin_struct/abstract_tlv.rb', line 231

#valueObject

This method is abstract.

Value attribute

Returns:

  • (Object)

    enum



# File 'lib/bin_struct/abstract_tlv.rb', line 231

Class Method Details

.create(type_class: Int8Enum, length_class: Int8, value_class: String, aliases: {}, attr_order: 'TLV', attr_in_length: 'V') ⇒ Class

Generate a TLV class

Parameters:

  • type_class (Class) (defaults to: Int8Enum)

    Class to use for type

  • length_class (Class) (defaults to: Int8)

    Class to use for length

  • value_class (Class) (defaults to: String)

    Class to use for value

  • attr_order (::String) (defaults to: 'TLV')

    gives attribute order. Each character in [T,L,V] MUST be present once, in the desired order.

  • attr_in_length (::String) (defaults to: 'V')

    give attributes to compute length on.

Returns:

  • (Class)

Raises:



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/bin_struct/abstract_tlv.rb', line 85

def create(type_class: Int8Enum, length_class: Int8, value_class: String,
           aliases: {}, attr_order: 'TLV', attr_in_length: 'V')
  unless equal?(AbstractTLV)
    raise Error,
          '.create cannot be called on a subclass of BinStruct::AbstractTLV'
  end

  klass = Class.new(self)
  klass.aliases = aliases
  klass.attr_in_length = attr_in_length

  check_attr_in_length(attr_in_length)
  check_attr_order(attr_order)
  generate_attributes(klass, attr_order, type_class, length_class, value_class)
  generate_aliases_for(klass, aliases)
  aliases.each do |al, orig|
    klass.instance_eval do
      alias_method al, orig if klass.method_defined?(orig)
      alias_method :"#{al}=", :"#{orig}=" if klass.method_defined?(:"#{orig}=")
    end
  end

  klass
end

.define_type_default(default) ⇒ void

This method is abstract.

Should only be called on real TLV classes, created by create.

This method returns an undefined value.

Set default value for #type attribute.

Parameters:

  • default (Integer, ::String, Symbol, nil)

    default value from hsh for type



181
182
183
# File 'lib/bin_struct/abstract_tlv.rb', line 181

def define_type_default(default)
  attr_defs[:type][:default] = default
end

.define_type_enum(hsh) ⇒ void

This method is abstract.

Should only be called on real TLV classes, created by create.

This method returns an undefined value.

Set enum hash for #type attribute.

Parameters:

  • hsh (Hash{::String, Symbol => Integer})

    enum hash



172
173
174
175
# File 'lib/bin_struct/abstract_tlv.rb', line 172

def define_type_enum(hsh)
  attr_defs[:type][:options][:enum].clear
  attr_defs[:type][:options][:enum].merge!(hsh)
end

.derive(type_class: nil, length_class: nil, value_class: nil, aliases: {}) ⇒ Class

Derive a new TLV class from an existing one

Examples:

# TLV with type and length on 16 bits, value is a BinStruct::String
FirstTLV = BinStruct::AbstractTLV.create(type_class: BinStruct::Int16, length_class: BinStruct::Int16)
# TLV with same type and length classes than FirstTLV, but value is an array of Int8
SecondTLV = FirstTLV.derive(value_class: BinStruct::ArrayOfInt8)

Parameters:

  • type_class (Class, nil) (defaults to: nil)

    New class to use for type. Unchanged if nil.

  • length_class (Class, nil) (defaults to: nil)

    New class to use for length. Unchanged if nil.

  • value_class (Class, nil) (defaults to: nil)

    New class to use for value. Unchanged if nil.

Returns:

  • (Class)

Raises:

Author:

  • LemonTree55

Since:

  • 0.4.0



141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/bin_struct/abstract_tlv.rb', line 141

def derive(type_class: nil, length_class: nil, value_class: nil, aliases: {})
  raise Error, ".derive cannot be called on #{name}" if equal?(AbstractTLV)

  klass = Class.new(self)
  klass.aliases.merge!(aliases)
  generate_aliases_for(klass, aliases)

  klass.attr_defs[:type].type = type_class unless type_class.nil?
  klass.attr_defs[:length].type = length_class unless length_class.nil?
  klass.attr_defs[:value].type = value_class unless value_class.nil?

  klass
end

.inherited(klass) ⇒ void

This method returns an undefined value.

On inheritage, copy aliases and attr_in_length

Parameters:

  • klass (Class)

    inheriting class

Author:

  • LemonTree55

Since:

  • 0.4.0



116
117
118
119
120
121
122
123
124
125
126
# File 'lib/bin_struct/abstract_tlv.rb', line 116

def inherited(klass)
  super

  aliases = @aliases.clone
  attr_in_length = @attr_in_length.clone

  klass.class_eval do
    @aliases = aliases
    @attr_in_length = attr_in_length
  end
end

Instance Method Details

#calc_lengthInteger

Calculate length

Returns:

  • (Integer)


311
312
313
314
315
316
317
318
319
# File 'lib/bin_struct/abstract_tlv.rb', line 311

def calc_length
  ail = @attr_in_length

  length = 0
  ail.each_char do |attr_type|
    length += self[ATTR_TYPES[attr_type]].sz
  end
  self.length = length
end

#human_type::String

This method is abstract.

Should only be called on real TLV class instances.

Get human-readable type

Returns:

  • (::String)


298
299
300
# File 'lib/bin_struct/abstract_tlv.rb', line 298

def human_type
  self[:type].to_human.to_s
end

#read(str) ⇒ AbstractTLV

This method is abstract.

Should only be called on real TLV class instances.

Populate object from a binary string

Parameters:

  • str (::String, nil)

Returns:



266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/bin_struct/abstract_tlv.rb', line 266

def read(str)
  return self if str.nil?

  idx = 0
  attributes.each do |attr_name|
    attr = self[attr_name]
    length = attr_name == :value ? real_length : attr.sz
    attr.read(str[idx, length])
    idx += attr.sz
  end

  self
end

#to_human::String

This method is abstract.

Should only be called on real TLV class instances.

Returns:

  • (::String)


304
305
306
307
# File 'lib/bin_struct/abstract_tlv.rb', line 304

def to_human
  my_value = self[:value].is_a?(String) ? self[:value].inspect : self[:value].to_human
  'type:%s,length:%u,value:%s' % [human_type, length, my_value]
end