javaclass 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. data/Rakefile +51 -0
  2. data/Readme.txt +47 -0
  3. data/history.txt +29 -0
  4. data/lib/javaclass/access_flags.rb +44 -0
  5. data/lib/javaclass/class_magic.rb +27 -0
  6. data/lib/javaclass/class_version.rb +65 -0
  7. data/lib/javaclass/constant_pool.rb +85 -0
  8. data/lib/javaclass/constants/base.rb +31 -0
  9. data/lib/javaclass/constants/double_reference.rb +81 -0
  10. data/lib/javaclass/constants/single_reference.rb +58 -0
  11. data/lib/javaclass/constants/value.rb +114 -0
  12. data/lib/javaclass/java_class_header.rb +88 -0
  13. data/lib/javaclass/references.rb +31 -0
  14. data/lib/javaclass/string_ux.rb +39 -0
  15. data/lib/javaclass.rb +14 -0
  16. data/test/data/AccessFlagsTestAbstract.class +0 -0
  17. data/test/data/AccessFlagsTestAbstract.java +3 -0
  18. data/test/data/AccessFlagsTestFinal.class +0 -0
  19. data/test/data/AccessFlagsTestFinal.java +3 -0
  20. data/test/data/AccessFlagsTestInterface.class +0 -0
  21. data/test/data/AccessFlagsTestInterface.java +3 -0
  22. data/test/data/AccessFlagsTestPackage.class +0 -0
  23. data/test/data/AccessFlagsTestPackage.java +3 -0
  24. data/test/data/AccessFlagsTestPublic$InnerClass.class +0 -0
  25. data/test/data/AccessFlagsTestPublic.class +0 -0
  26. data/test/data/AccessFlagsTestPublic.java +7 -0
  27. data/test/data/ClassVersionTest.java +1 -0
  28. data/test/data/ClassVersionTest10.class +0 -0
  29. data/test/data/ClassVersionTest11.class +0 -0
  30. data/test/data/ClassVersionTest12.class +0 -0
  31. data/test/data/ClassVersionTest13.class +0 -0
  32. data/test/data/ClassVersionTest14.class +0 -0
  33. data/test/data/ClassVersionTest15.class +0 -0
  34. data/test/data/ClassVersionTest16.class +0 -0
  35. data/test/data/ConstantPoolTest.class +0 -0
  36. data/test/data/ConstantPoolTest.java +20 -0
  37. data/test/data/makeAccessFlagsTest.bat +9 -0
  38. data/test/data/makeClassVersionTest.bat +35 -0
  39. data/test/data/makeConstantPoolTest.bat +6 -0
  40. data/test/setup.rb +14 -0
  41. data/test/test_access_flags.rb +53 -0
  42. data/test/test_base.rb +22 -0
  43. data/test/test_class_version.rb +59 -0
  44. data/test/test_constant_pool.rb +90 -0
  45. data/test/test_java_class_header.rb +30 -0
  46. data/test/test_references.rb +29 -0
  47. data/test/test_string_ux.rb +30 -0
  48. data/test/ts_all_tests.rb +36 -0
  49. metadata +106 -0
data/Rakefile ADDED
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+
9
+ jcversion = '0.0.2'
10
+
11
+ gemspec = Gem::Specification.new do |s|
12
+ s.name = 'javaclass'
13
+ s.version = jcversion
14
+ s.summary ='A parser and disassembler for Java class files'
15
+ s.files = FileList['Readme.txt', '{lib,test}/**/*.*', 'history.txt', 'Rakefile']
16
+ s.test_files = FileList["{test}/**/test_*.rb"]
17
+ s.require_path = 'lib'
18
+ s.has_rdoc = true
19
+ s.rubyforge_project = 'javaclass'
20
+ s.homepage = 'http://javaclass.rubyforge.org/'
21
+ s.author = 'Peter Kofler'
22
+ s.email = 'bruno41 at rubyforge dot org'
23
+ # TODO use new cc email
24
+ s.platform = Gem::Platform::RUBY
25
+ end
26
+
27
+ Rake::GemPackageTask.new(gemspec) do |pkg|
28
+ pkg.need_zip = true
29
+ end
30
+
31
+ Rake::PackageTask.new(gemspec.name, gemspec.version) do |pkg|
32
+ #pkg.need_tar = true - no compress in tar on unxutils in Windows
33
+ #pkg.need_tar_gz = true - no compress in tar on unxutils in Windows
34
+ pkg.need_zip = true
35
+ pkg.package_files.include gemspec.files
36
+ end
37
+
38
+ Rake::TestTask.new do |t|
39
+ t.pattern = 'test/**/test_*.rb'
40
+ t.warning = true
41
+ t.verbose = false
42
+ end
43
+
44
+ Rake::RDocTask.new do |rdoc|
45
+ # rdoc.rdoc_dir = 'html'
46
+ rdoc.title = "JavaClass javaclass-#{jcversion} Documentation"
47
+ rdoc.main = 'Readme.txt'
48
+ rdoc.rdoc_files.include 'Readme.txt', 'lib/**/*.rb', 'history.txt'
49
+ end
50
+
51
+ task :default => :test
data/Readme.txt ADDED
@@ -0,0 +1,47 @@
1
+ JavaClass
2
+ by {Peter Kofler}[http://kofler.dot.at/work/]
3
+
4
+ * {Homepage}[http://javaclass.rubyforge.org/]
5
+ * {Rubyforge Project}[http://rubyforge.org/projects/javaclass]
6
+ * email bruno41 at rubyforge dot org
7
+
8
+ == Description
9
+
10
+ JavaClass (Java Class File Parser) is a
11
+ parser and disassembler for Java class files, similar to the javap command.
12
+ It provides access to the package, protected, and public fields and methods
13
+ of the classes passed to it together with a list of all outgoing references.
14
+
15
+ == Motivation
16
+
17
+ I am still doing Java most of the time. I used to be quite enthusiatic about
18
+ it, but after 9 years I can see the advantages of being a polyglot. So I use
19
+ Ruby for all kind of stuff, just for fun. Recently I needed some Java class
20
+ analysis and happened to write it with Ruby. As I am a puritan, I did not
21
+ want to call javap from my script, so I started disassembling the class files,
22
+ which might be the the base for some serious static code analysis tools. (I
23
+ started adding methods to that end...)
24
+
25
+ == Install
26
+
27
+ sudo gem install javaclass
28
+
29
+ == Usage
30
+
31
+ require 'javaclass'
32
+
33
+ clazz = JavaClass.parse('packagename/Public.class')
34
+ clazz.version # => 50.0
35
+ clazz.constant_pool.items[1] # => packagename/Public
36
+ clazz.access_flags.public? # => true
37
+ clazz.this_class # => packagename/Public
38
+ clazz.super_class # => java/lang/Object
39
+ clazz.references.referenced_methods[0] # => java/lang/Object.<init>:()V
40
+
41
+ == Requirements
42
+
43
+ JavaClass does not depend on any other installed libraries or gems.
44
+
45
+ == License
46
+
47
+ Same as Ruby.
data/history.txt ADDED
@@ -0,0 +1,29 @@
1
+ === 0.0.2 08/04/2009
2
+
3
+ * refactored code to smaller objects for version and constant pool
4
+ * fixed float and double constant pool items
5
+ * added tests
6
+ * continued implementation with class names and references
7
+
8
+ === 0.0.1 01/03/2009
9
+
10
+ * initial version extracted from ClassList project
11
+ * reads the class version and package/public flag of a class
12
+ * understands the constant pool but does not use it
13
+
14
+ === Planned
15
+
16
+ * continue function for fields and methods
17
+ * implement
18
+ * dump to return the class same as javap
19
+ * isAnnotation (checks version>=5 & superclass)
20
+ * are Annotation extendable, if so how do they look? (javap)
21
+ * isEnum (checks version>=5 & superclass)
22
+ * are Enums extendable (yes), if so how do they look? (javap)
23
+ * test
24
+ * test with all classes of all JDKs against javap from this version
25
+ * Java 1.0 - private protected fields.
26
+ * Java 1.0 - http://www.javaworld.com/javaworld/javaqa/1999-06/03-synchronized.html
27
+ * think about
28
+ * "metric" if all referencing classes of a class is another package, propose moving it there.
29
+ * add zip gem to load classes directly from the classpath (i.e. the jars), add gem dependency
@@ -0,0 +1,44 @@
1
+ require 'javaclass/string_ux'
2
+
3
+ module JavaClass
4
+
5
+ # The access flags of a class or interface.
6
+ # Author:: Peter Kofler
7
+ class AccessFlags
8
+
9
+ # Access flags as defined by JVM spec.
10
+ ACC_PUBLIC = 0x0001
11
+ ACC_FINAL = 0x0010
12
+ ACC_SUPER = 0x0020 # old invokespecial instruction semantics
13
+ ACC_INTERFACE = 0x0200
14
+ ACC_ABSTRACT = 0x0400
15
+ ACC_OTHER = 0xffff ^ ACC_PUBLIC ^ ACC_FINAL ^ ACC_SUPER ^ ACC_INTERFACE ^ ACC_ABSTRACT
16
+
17
+ def initialize(data, pos)
18
+ @flags = data.u2(pos)
19
+ raise "inconsistent flags #{flags}" if abstract? && final?
20
+ raise "inconsistent flags #{flags}" if interface? && (!abstract? || final?)
21
+ raise "inconsistent flags #{flags}" if (@flags & ACC_OTHER) != 0
22
+ end
23
+
24
+ # Return +true+ if the class is public.
25
+ def public?
26
+ (@flags & ACC_PUBLIC) != 0
27
+ end
28
+ alias accessible? public?
29
+
30
+ def final?
31
+ (@flags & ACC_FINAL) != 0
32
+ end
33
+
34
+ def abstract?
35
+ (@flags & ACC_ABSTRACT) != 0
36
+ end
37
+
38
+ def interface?
39
+ (@flags & ACC_INTERFACE) != 0
40
+ end
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,27 @@
1
+ module JavaClass
2
+
3
+ # The +CAFEBABE+ magic of a class file.
4
+ # Author:: Peter Kofler
5
+ class ClassMagic
6
+
7
+ CAFE_BABE = "\xCA\xFE\xBA\xBE"
8
+
9
+ # Check the class magic in the _data_ beginning at position _start_ (which is usually 0).
10
+ def initialize(data, start=0)
11
+ # "parsing"
12
+ @bytes = data[start..start+3]
13
+ end
14
+
15
+ # Return +true+ if the data was valid, i.e. if the class started with +CAFEBABE+.
16
+ def valid?
17
+ @bytes == CAFE_BABE
18
+ end
19
+
20
+ # Return the value of the magic in this class.
21
+ def bytes
22
+ @bytes.dup
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,65 @@
1
+ require 'javaclass/string_ux'
2
+
3
+ module JavaClass
4
+
5
+ # Version of a class file.
6
+ # Author:: Peter Kofler
7
+ # See:: http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#75883
8
+ class ClassVersion
9
+
10
+ attr_reader :minor
11
+ attr_reader :major
12
+
13
+ # Extract the class version from the bytes _data_ starting at position _start_ (which is usually 4).
14
+ def initialize(data, start=4)
15
+ # parsing
16
+ @minor = data.u2(start)
17
+ @major = data.u2(start+2)
18
+ end
19
+
20
+ # Return the class file version as +major+.+minor+ string like 48.0 (Java 1.4) or 50.0 (Java 6).
21
+ def to_s
22
+ "#{@major}.#{@minor}"
23
+ end
24
+
25
+ # Return the version as +major+.+minor+ float.
26
+ def to_f
27
+ if @minor <= 0
28
+ denom = 1.0
29
+ else
30
+ denom = 1.0 * 10**(Math.log10(@minor).floor + 1)
31
+ end
32
+
33
+ @major + @minor/denom
34
+ end
35
+
36
+ # Return a debug output of this version.
37
+ def dump
38
+ [" minor version: #{@minor}", " major version: #{@major}"]
39
+ end
40
+
41
+ # Return the JDK version corresponding to this version like "1.6" or "unknown" if none matched.
42
+ def jdk_version
43
+ v = to_f
44
+ if v >= 45.0 && v <= 45.3 # 1.0.2 supports class file format versions 45.0 through 45.3 inclusive.
45
+ '1.0'
46
+ elsif v > 45.3 && v <= 45.65535 # 1.1.X can support class file formats of versions in the range 45.0 through 45.65535 inclusive
47
+ '1.1'
48
+ elsif v == 46.0 # JDK 1.2=46
49
+ '1.2'
50
+ elsif v == 47.0 # JDK 1.3=47
51
+ '1.3'
52
+ elsif v == 48.0 # JDK 1.4=48
53
+ '1.4'
54
+ elsif v == 49.0 # J2SE 5.0=49
55
+ '1.5'
56
+ elsif v == 50.0 # J2SE 6.0=50
57
+ '1.6'
58
+ else
59
+ 'unknown'
60
+ end
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,85 @@
1
+ require 'javaclass/string_ux'
2
+ require 'javaclass/constants/value'
3
+ require 'javaclass/constants/single_reference'
4
+ require 'javaclass/constants/double_reference'
5
+
6
+ module JavaClass
7
+
8
+ # Container of the constant pool's constants.
9
+ # Author:: Peter Kofler
10
+ class ConstantPool
11
+
12
+ # Types of constants by their +tag+.
13
+ CONSTANT_TYPE_TAGS = {
14
+ CLASS_TAG = 7 => Constants::ConstantClass,
15
+ FIELD_TAG = 9 => Constants::ConstantField,
16
+ METHOD_TAG = 10 => Constants::ConstantMethod,
17
+ INTERFACE_METHOD_TAG = 11 => Constants::ConstantInterfaceMethod,
18
+ STRING_TAG = 8 => Constants::ConstantString,
19
+ INT_TAG = 3 => Constants::ConstantInt,
20
+ FLOAT_TAG = 4 => Constants::ConstantFloat,
21
+ LONG_TAG = 5 => Constants::ConstantLong,
22
+ DOUBLE_TAG = 6 => Constants::ConstantDouble,
23
+ NAME_AND_TYPE_TAG = 12 => Constants::ConstantNameAndType,
24
+ ASCIZ_TAG = 1 => Constants::ConstantAsciz,
25
+ }
26
+
27
+ # Size of the whole constant pool in bytes.
28
+ attr_reader :size
29
+
30
+ # Parse the constant pool from the bytes _data_ beginning at position _start_ (which is usually 8).
31
+ def initialize(data, start=8)
32
+ @pool = {} # cnt (fixnum) => constant
33
+
34
+ # parsing
35
+ @item_count = data.u2(start)
36
+ pos = start + 2
37
+ cnt = 1
38
+ while cnt <= @item_count-1
39
+
40
+ type = CONSTANT_TYPE_TAGS[data.u1(pos)]
41
+ unless type
42
+ #puts dump.join("\n")
43
+ raise "const ##{cnt} = unknown constant pool tag #{data[pos]} at pos #{pos} in class"
44
+ end
45
+
46
+ constant = type.new(@pool, data, pos)
47
+ @pool[cnt] = constant
48
+ pos += constant.size
49
+ cnt += constant.slots
50
+
51
+ end
52
+
53
+ @size = pos - start
54
+ end
55
+
56
+ # Return the number of pool items. This number might be larger than +items+ available,
57
+ # because +long+ and +double+ constants take two slots.
58
+ def item_count
59
+ @item_count-1
60
+ end
61
+
62
+ # Return the _index_'th pool item. _index_ is the real index in the pool which may skip numbers.
63
+ def[](index)
64
+ @pool[index]
65
+ end
66
+
67
+ # Return an array of the ordered list of constants.
68
+ def items
69
+ @pool.keys.sort.collect { |k| self[k] }
70
+ end
71
+
72
+ # Return an array of all constants of the given _tags_ types.
73
+ def find(*tags)
74
+ items.find_all { |item| tags.include? item.tag }
75
+ end
76
+
77
+ # Return a debug output of the whole pool.
78
+ def dump
79
+ [" Constant pool:"] + @pool.keys.sort.collect { |k| "const ##{k} = #{self[k].dump}"}
80
+ end
81
+
82
+ end
83
+
84
+ end
85
+
@@ -0,0 +1,31 @@
1
+ require 'javaclass/string_ux'
2
+
3
+ module JavaClass
4
+ module Constants # :nodoc:
5
+
6
+ # Superclass of constant values in the constant pool. Every constant has a +name+, a +tag+ and a +size+ in bytes.
7
+ # Author:: Peter Kofler
8
+ class Base
9
+
10
+ attr_reader :name
11
+ attr_reader :tag
12
+ attr_reader :size
13
+ attr_reader :slots
14
+
15
+ # Set default constants.
16
+ def initialize(name=nil)
17
+ @name = self.class.to_s[/::[^:]+$/][10..-1] # skip modules (::) and "Constant"
18
+ @name = name if name
19
+ @size = 3
20
+ @slots = 1
21
+ end
22
+
23
+ # Return part of debug output.
24
+ def dump
25
+ "#{@name}\t" # #{@tag}
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,81 @@
1
+ require 'javaclass/constants/single_reference'
2
+
3
+ module JavaClass
4
+ module Constants # :nodoc:
5
+
6
+ # Superclass of double reference constants like +ConstantField+ (+FieldRef+) in the constant pool.
7
+ # Author:: Peter Kofler
8
+ class DoubleReference < SingleReference
9
+
10
+ attr_reader :second_index
11
+
12
+ # Define a double reference into _pool_ from _data_ beginning at _start_
13
+ def initialize(pool, data, start, name=nil)
14
+ super(pool, data, start, name)
15
+ @size = 5
16
+
17
+ @second_index = data.u2(start+3)
18
+ end
19
+
20
+ # Return the second value, which is the referenced value from the pool.
21
+ def second_value
22
+ get(@second_index)
23
+ end
24
+
25
+ # Return the value, which are both referenced values from the pool.
26
+ def to_s
27
+ "#{super}.#{second_value}"
28
+ end
29
+
30
+ # Return part of debug output.
31
+ def dump
32
+ "#{@name}\t##{@first_index}.##{@second_index};\t// #{to_s}"
33
+ end
34
+
35
+ end
36
+
37
+ class ConstantField < DoubleReference
38
+ alias class_index first_index
39
+ alias name_and_type_index second_index
40
+ def initialize(pool, data, start)
41
+ super(pool, data, start)
42
+ end
43
+ alias class_name first_value
44
+ end
45
+
46
+ class ConstantMethod < DoubleReference
47
+ alias class_index first_index
48
+ alias name_and_type_index second_index
49
+ def initialize(pool, data, start)
50
+ super(pool, data, start)
51
+ end
52
+ alias class_name first_value
53
+ end
54
+
55
+ class ConstantInterfaceMethod < DoubleReference
56
+ alias class_index first_index
57
+ alias name_and_type_index second_index
58
+ def initialize(pool, data, start)
59
+ super(pool, data, start)
60
+ end
61
+ alias class_name first_value
62
+ end
63
+
64
+ class ConstantNameAndType < DoubleReference
65
+ alias name_index first_index
66
+ alias descriptor_index second_index
67
+ def initialize(pool, data, start)
68
+ super(pool, data, start)
69
+ end
70
+
71
+ def to_s
72
+ "#{get(name_index)}:#{get(descriptor_index)}"
73
+ end
74
+
75
+ def dump
76
+ "#{@name}\t##{name_index}:##{descriptor_index};// #{to_s}"
77
+ end
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,58 @@
1
+ require 'javaclass/constants/base'
2
+
3
+ module JavaClass
4
+ module Constants # :nodoc:
5
+
6
+ # Superclass of single reference constants like +ConstantClass+ (+Class+) in the constant pool.
7
+ # Author:: Peter Kofler
8
+ class SingleReference < Base
9
+
10
+ attr_reader :first_index
11
+
12
+ # Define a single reference into _pool_ from _data_ beginning at _start_
13
+ def initialize(pool, data, start, name=nil)
14
+ super(name)
15
+ @tag = data.u1(start)
16
+
17
+ @enclosing_pool = pool
18
+ @first_index = data.u2(start+1)
19
+ end
20
+
21
+ # Return the value, which is the referenced value from the pool.
22
+ def first_value
23
+ get(@first_index)
24
+ end
25
+ alias to_s first_value
26
+
27
+ # Return part of debug output.
28
+ def dump
29
+ super + "##{@first_index};\t// #{to_s}"
30
+ end
31
+
32
+ protected
33
+
34
+ # Get a reference _ref_ from the +enclosing_pool+
35
+ def get(ref)
36
+ @enclosing_pool[ref].to_s
37
+ end
38
+
39
+ end
40
+
41
+ class ConstantClass < SingleReference
42
+ alias name_index first_index
43
+ def initialize(pool, data, start)
44
+ super(pool, data, start, "class")
45
+ end
46
+ alias class_name first_value
47
+ end
48
+
49
+ class ConstantString < SingleReference
50
+ alias string_index first_index
51
+ def initialize(pool, data, start)
52
+ super(pool, data, start)
53
+ end
54
+ alias string_value first_value
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,114 @@
1
+ require 'javaclass/constants/base'
2
+
3
+ module JavaClass
4
+ module Constants # :nodoc:
5
+
6
+ # Superclass of value constants like +ConstantInt+ (+Integer+) in the constant pool.
7
+ # Author:: Peter Kofler
8
+ class Value < Base
9
+
10
+ attr_reader :value
11
+
12
+ # Create a constant value with an optional downcase _name_
13
+ def initialize(name=self.class.to_s[/::[^:]+$/][10..-1].downcase)
14
+ super(name)
15
+ end
16
+
17
+ # Return the value as string.
18
+ def to_s
19
+ @value.to_s
20
+ end
21
+
22
+ # Return part of debug output.
23
+ def dump
24
+ super + "#{@value}"
25
+ end
26
+
27
+ protected
28
+
29
+ # Define a +value+ from _data_ beginning at position _start_ with the _size_ in bytes and _slots_ (1 or 2).
30
+ def get_value(data, start, size, slots=1)
31
+ @tag = data.u1(start)
32
+ @size = size
33
+ @slots = slots
34
+
35
+ data[start+1..start+size-1]
36
+ end
37
+
38
+ # Dummy method to "fix" unused warning of param _pool_ in Eclipse.
39
+ def silence_unused_warning(pool)
40
+ raise "pool is nil" unless pool
41
+ end
42
+
43
+ end
44
+
45
+ class ConstantInt < Value
46
+ def initialize(pool, data, start)
47
+ super()
48
+ silence_unused_warning(pool)
49
+ @value = get_value(data, start, 5).u4
50
+ end
51
+
52
+ def dump
53
+ super + ';'
54
+ end
55
+ end
56
+
57
+ class ConstantFloat < Value
58
+ def initialize(pool, data, start)
59
+ super()
60
+ silence_unused_warning(pool)
61
+ @value = get_value(data, start, 5).single
62
+ end
63
+ def to_s
64
+ super.upcase # sprintf('%E',@value)
65
+ end
66
+ def dump
67
+ super + 'f;'
68
+ end
69
+ end
70
+
71
+ class ConstantLong < Value
72
+ def initialize(pool, data, start)
73
+ super()
74
+ silence_unused_warning(pool)
75
+ @value = get_value(data, start, 9, 2).u8
76
+ end
77
+ def dump
78
+ super + 'l;'
79
+ end
80
+ end
81
+
82
+ class ConstantDouble < Value
83
+ def initialize(pool, data, start)
84
+ super()
85
+ silence_unused_warning(pool)
86
+ @value = get_value(data, start, 9, 2).double
87
+ end
88
+ def to_s
89
+ @value.to_s.upcase # sprintf('%E',@value)
90
+ end
91
+ def dump
92
+ super + 'd;'
93
+ end
94
+ end
95
+
96
+ class ConstantAsciz < Value
97
+ alias string value
98
+ def initialize(pool, data, start)
99
+ super('Asciz')
100
+ silence_unused_warning(pool)
101
+ @tag = data.u1(start)
102
+
103
+ @length = data.u2(start+1)
104
+ @size = 3 + @length
105
+ @value = data[start+3..start+3+@length-1]
106
+ end
107
+
108
+ def dump
109
+ super + ';'
110
+ end
111
+ end
112
+
113
+ end
114
+ end