packetgen 3.1.1 → 3.1.6

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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/bin/pgconsole +1 -0
  3. data/lib/packetgen.rb +33 -4
  4. data/lib/packetgen/capture.rb +51 -28
  5. data/lib/packetgen/config.rb +17 -11
  6. data/lib/packetgen/deprecation.rb +34 -8
  7. data/lib/packetgen/header.rb +2 -9
  8. data/lib/packetgen/header/arp.rb +2 -2
  9. data/lib/packetgen/header/asn1_base.rb +2 -2
  10. data/lib/packetgen/header/base.rb +70 -74
  11. data/lib/packetgen/header/bootp.rb +3 -3
  12. data/lib/packetgen/header/dhcp.rb +2 -2
  13. data/lib/packetgen/header/dhcp/option.rb +2 -2
  14. data/lib/packetgen/header/dhcp/options.rb +2 -2
  15. data/lib/packetgen/header/dhcpv6.rb +12 -12
  16. data/lib/packetgen/header/dhcpv6/duid.rb +11 -5
  17. data/lib/packetgen/header/dhcpv6/option.rb +8 -16
  18. data/lib/packetgen/header/dhcpv6/options.rb +2 -2
  19. data/lib/packetgen/header/dhcpv6/relay.rb +2 -2
  20. data/lib/packetgen/header/dns.rb +9 -9
  21. data/lib/packetgen/header/dns/name.rb +20 -9
  22. data/lib/packetgen/header/dns/opt.rb +2 -2
  23. data/lib/packetgen/header/dns/option.rb +2 -2
  24. data/lib/packetgen/header/dns/qdsection.rb +3 -3
  25. data/lib/packetgen/header/dns/question.rb +37 -35
  26. data/lib/packetgen/header/dns/rr.rb +3 -3
  27. data/lib/packetgen/header/dns/rrsection.rb +2 -2
  28. data/lib/packetgen/header/dot11.rb +30 -51
  29. data/lib/packetgen/header/dot11/control.rb +5 -5
  30. data/lib/packetgen/header/dot11/data.rb +11 -7
  31. data/lib/packetgen/header/dot11/element.rb +16 -16
  32. data/lib/packetgen/header/dot11/management.rb +2 -2
  33. data/lib/packetgen/header/dot11/sub_mngt.rb +2 -12
  34. data/lib/packetgen/header/dot1q.rb +2 -2
  35. data/lib/packetgen/header/dot1x.rb +7 -20
  36. data/lib/packetgen/header/eap.rb +30 -33
  37. data/lib/packetgen/header/eap/fast.rb +2 -2
  38. data/lib/packetgen/header/eap/md5.rb +2 -2
  39. data/lib/packetgen/header/eap/tls.rb +2 -2
  40. data/lib/packetgen/header/eap/ttls.rb +2 -2
  41. data/lib/packetgen/header/eth.rb +13 -11
  42. data/lib/packetgen/header/gre.rb +2 -2
  43. data/lib/packetgen/header/http.rb +2 -0
  44. data/lib/packetgen/header/http/headers.rb +6 -4
  45. data/lib/packetgen/header/http/request.rb +36 -21
  46. data/lib/packetgen/header/http/response.rb +7 -7
  47. data/lib/packetgen/header/http/verbs.rb +3 -3
  48. data/lib/packetgen/header/icmp.rb +2 -2
  49. data/lib/packetgen/header/icmpv6.rb +2 -2
  50. data/lib/packetgen/header/igmp.rb +4 -4
  51. data/lib/packetgen/header/igmpv3.rb +3 -3
  52. data/lib/packetgen/header/igmpv3/group_record.rb +8 -6
  53. data/lib/packetgen/header/igmpv3/mq.rb +2 -2
  54. data/lib/packetgen/header/igmpv3/mr.rb +2 -2
  55. data/lib/packetgen/header/ip.rb +30 -31
  56. data/lib/packetgen/header/ip/addr.rb +10 -3
  57. data/lib/packetgen/header/ip/option.rb +8 -10
  58. data/lib/packetgen/header/ip/options.rb +3 -5
  59. data/lib/packetgen/header/ipv6.rb +2 -2
  60. data/lib/packetgen/header/ipv6/addr.rb +9 -2
  61. data/lib/packetgen/header/ipv6/extension.rb +2 -2
  62. data/lib/packetgen/header/ipv6/hop_by_hop.rb +3 -3
  63. data/lib/packetgen/header/llc.rb +2 -2
  64. data/lib/packetgen/header/mdns.rb +2 -2
  65. data/lib/packetgen/header/mld.rb +2 -2
  66. data/lib/packetgen/header/mldv2.rb +2 -2
  67. data/lib/packetgen/header/mldv2/mcast_address_record.rb +4 -2
  68. data/lib/packetgen/header/mldv2/mlq.rb +2 -2
  69. data/lib/packetgen/header/mldv2/mlr.rb +2 -2
  70. data/lib/packetgen/header/ospfv2.rb +9 -9
  71. data/lib/packetgen/header/ospfv2/db_description.rb +2 -2
  72. data/lib/packetgen/header/ospfv2/hello.rb +2 -2
  73. data/lib/packetgen/header/ospfv2/ls_ack.rb +2 -2
  74. data/lib/packetgen/header/ospfv2/ls_request.rb +4 -2
  75. data/lib/packetgen/header/ospfv2/ls_update.rb +2 -2
  76. data/lib/packetgen/header/ospfv2/lsa.rb +9 -5
  77. data/lib/packetgen/header/ospfv2/lsa_header.rb +8 -7
  78. data/lib/packetgen/header/ospfv3.rb +2 -2
  79. data/lib/packetgen/header/ospfv3/db_description.rb +2 -2
  80. data/lib/packetgen/header/ospfv3/hello.rb +2 -2
  81. data/lib/packetgen/header/ospfv3/ipv6_prefix.rb +4 -2
  82. data/lib/packetgen/header/ospfv3/ls_ack.rb +2 -2
  83. data/lib/packetgen/header/ospfv3/ls_request.rb +4 -2
  84. data/lib/packetgen/header/ospfv3/ls_update.rb +2 -2
  85. data/lib/packetgen/header/ospfv3/lsa.rb +5 -5
  86. data/lib/packetgen/header/ospfv3/lsa_header.rb +9 -8
  87. data/lib/packetgen/header/snmp.rb +33 -29
  88. data/lib/packetgen/header/tcp.rb +4 -23
  89. data/lib/packetgen/header/tcp/option.rb +11 -11
  90. data/lib/packetgen/header/tcp/options.rb +2 -2
  91. data/lib/packetgen/header/tftp.rb +6 -6
  92. data/lib/packetgen/header/udp.rb +3 -3
  93. data/lib/packetgen/headerable.rb +5 -4
  94. data/lib/packetgen/inject.rb +23 -0
  95. data/lib/packetgen/inspect.rb +23 -20
  96. data/lib/packetgen/packet.rb +96 -53
  97. data/lib/packetgen/pcap.rb +29 -0
  98. data/lib/packetgen/pcapng.rb +13 -13
  99. data/lib/packetgen/pcapng/block.rb +26 -13
  100. data/lib/packetgen/pcapng/epb.rb +25 -22
  101. data/lib/packetgen/pcapng/file.rb +260 -138
  102. data/lib/packetgen/pcapng/idb.rb +36 -38
  103. data/lib/packetgen/pcapng/shb.rb +51 -53
  104. data/lib/packetgen/pcapng/spb.rb +19 -19
  105. data/lib/packetgen/pcapng/unknown_block.rb +5 -13
  106. data/lib/packetgen/pcaprub_wrapper.rb +81 -0
  107. data/lib/packetgen/proto.rb +2 -2
  108. data/lib/packetgen/types.rb +3 -0
  109. data/lib/packetgen/types/abstract_tlv.rb +28 -8
  110. data/lib/packetgen/types/array.rb +22 -15
  111. data/lib/packetgen/types/cstring.rb +40 -16
  112. data/lib/packetgen/types/enum.rb +8 -3
  113. data/lib/packetgen/types/fieldable.rb +65 -0
  114. data/lib/packetgen/types/fields.rb +182 -117
  115. data/lib/packetgen/types/int.rb +18 -6
  116. data/lib/packetgen/types/int_string.rb +10 -2
  117. data/lib/packetgen/types/length_from.rb +20 -12
  118. data/lib/packetgen/types/oui.rb +4 -2
  119. data/lib/packetgen/types/string.rb +46 -8
  120. data/lib/packetgen/types/tlv.rb +4 -2
  121. data/lib/packetgen/utils.rb +4 -4
  122. data/lib/packetgen/utils/arp_spoofer.rb +2 -2
  123. data/lib/packetgen/version.rb +3 -3
  124. metadata +35 -50
  125. data/.gitignore +0 -13
  126. data/.rubocop.yml +0 -28
  127. data/.travis.yml +0 -17
  128. data/Gemfile +0 -4
  129. data/Rakefile +0 -21
  130. data/packetgen.gemspec +0 -36
@@ -1,11 +1,11 @@
1
1
  # coding: utf-8
2
+ # frozen_string_literal: true
3
+
2
4
  # This file is part of PacketGen
3
5
  # See https://github.com/sdaubert/packetgen for more informations
4
6
  # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
5
7
  # This program is published under MIT license.
6
8
 
7
- # frozen_string_literal: true
8
-
9
9
  require 'socket'
10
10
 
11
11
  module PacketGen
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of PacketGen
2
4
  # See https://github.com/sdaubert/packetgen for more informations
3
5
  # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
@@ -10,6 +12,7 @@ module PacketGen
10
12
  end
11
13
 
12
14
  require_relative 'types/length_from'
15
+ require_relative 'types/fieldable'
13
16
  require_relative 'types/int'
14
17
  require_relative 'types/enum'
15
18
  require_relative 'types/string'
@@ -1,16 +1,16 @@
1
1
  # coding: utf-8
2
+ # frozen_string_literal: true
3
+
2
4
  # This file is part of PacketGen
3
5
  # See https://github.com/sdaubert/packetgen for more informations
4
6
  # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
5
7
  # This program is published under MIT license.
6
8
 
7
- # frozen_string_literal: true
8
-
9
9
  module PacketGen
10
10
  module Types
11
11
  # This class is an abstract class to define type-length-value data.
12
12
  #
13
- # This class supersede {TLV} class, which is not well defined on some corner
13
+ # This class supersedes {TLV} class, which is not well defined on some corner
14
14
  # cases.
15
15
  #
16
16
  # ===Usage
@@ -33,7 +33,7 @@ module PacketGen
33
33
  # tlv.value #=> "abcd"
34
34
  #
35
35
  # ===Advanced usage
36
- # Each field's type may be change at generating TLV class:
36
+ # Each field's type may be changed at generating TLV class:
37
37
  # MyTLV = PacketGen::Types::AbstractTLV.create(type_class: PacketGen::Types::Int16,
38
38
  # length_class: PacketGen::Types::Int16,
39
39
  # value_class: PacketGen::Header::IP::Addr)
@@ -58,9 +58,12 @@ module PacketGen
58
58
  # @since 3.1.0
59
59
  # @since 3.1.1 add +:aliases+ keyword to {#initialize}
60
60
  class AbstractTLV < Types::Fields
61
+ include Fieldable
62
+
61
63
  class <<self
62
64
  # @return [Hash]
63
65
  attr_accessor :aliases
66
+ attr_accessor :header_in_length
64
67
  end
65
68
  self.aliases = {}
66
69
 
@@ -68,12 +71,17 @@ module PacketGen
68
71
  # @param [Class] type_class Class to use for +type+
69
72
  # @param [Class] length_class Class to use for +length+
70
73
  # @param [Class] value_class Class to use for +value+
74
+ # @param [Boolean] header_in_length if +true +, +type+ and +length+ fields are
75
+ # included in length
71
76
  # @return [Class]
72
- def self.create(type_class: Int8Enum, length_class: Int8, value_class: String, aliases: {})
77
+ # @since 3.1.4 Add +header_in_length+ parameter
78
+ def self.create(type_class: Int8Enum, length_class: Int8, value_class: String,
79
+ aliases: {}, header_in_length: false)
73
80
  raise Error, '.create cannot be called on a subclass of PacketGen::Types::AbstractTLV' unless self.equal? AbstractTLV
74
81
 
75
82
  klass = Class.new(self)
76
83
  klass.aliases = aliases
84
+ klass.header_in_length = header_in_length
77
85
 
78
86
  if type_class < Enum
79
87
  klass.define_field :type, type_class, enum: {}
@@ -117,6 +125,7 @@ module PacketGen
117
125
  # @option options [Integer] :length
118
126
  # @option options [Object] :value
119
127
  def initialize(options={})
128
+ @header_in_length = self.class.header_in_length
120
129
  self.class.aliases.each do |al, orig|
121
130
  options[orig] = options[al] if options.key?(al)
122
131
  end
@@ -136,7 +145,7 @@ module PacketGen
136
145
  idx += self[:type].sz
137
146
  self[:length].read str[idx, self[:length].sz]
138
147
  idx += self[:length].sz
139
- self[:value].read str[idx, self.length]
148
+ self[:value].read str[idx, real_length]
140
149
  self
141
150
  end
142
151
 
@@ -147,6 +156,7 @@ module PacketGen
147
156
  def value=(val)
148
157
  self[:value].from_human val
149
158
  self.length = self[:value].sz
159
+ self.length += self[:type].sz + self[:length].sz if @header_in_length
150
160
  val
151
161
  end
152
162
 
@@ -160,8 +170,18 @@ module PacketGen
160
170
  # @abstract Should only be called on real TLV class instances.
161
171
  # @return [String]
162
172
  def to_human
163
- my_value = self[:value].is_a?(String) ? value.inspect : value.to_human
164
- "type:%s,length:%u,value:#{my_value}" % [human_type, length]
173
+ my_value = self[:value].is_a?(String) ? self[:value].inspect : self[:value].to_human
174
+ 'type:%s,length:%u,value:%s' % [human_type, length, my_value]
175
+ end
176
+
177
+ private
178
+
179
+ def real_length
180
+ if @header_in_length
181
+ self.length - self[:type].sz - self[:length].sz
182
+ else
183
+ self.length
184
+ end
165
185
  end
166
186
  end
167
187
  end
@@ -1,10 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of PacketGen
2
4
  # See https://github.com/sdaubert/packetgen for more informations
3
5
  # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
4
6
  # This program is published under MIT license.
5
7
 
6
- # frozen_string_literal: true
7
-
8
8
  require 'forwardable'
9
9
 
10
10
  module PacketGen
@@ -27,6 +27,9 @@ module PacketGen
27
27
  # @author Sylvain Daubert
28
28
  class Array
29
29
  extend Forwardable
30
+ include Enumerable
31
+ include Fieldable
32
+ include LengthFrom
30
33
 
31
34
  # @!method [](index)
32
35
  # Return the element at +index+.
@@ -54,9 +57,6 @@ module PacketGen
54
57
  def_delegators :@array, :[], :clear, :each, :empty?, :first, :last, :size
55
58
  alias length size
56
59
 
57
- include Enumerable
58
- include LengthFrom
59
-
60
60
  # Separator used in {#to_human}.
61
61
  # May be ovverriden by subclasses
62
62
  HUMAN_SEPARATOR = ','
@@ -106,7 +106,7 @@ module PacketGen
106
106
  # @return [void]
107
107
  def clear!
108
108
  @array.clear
109
- @counter.read(0) if @counter
109
+ @counter&.read(0)
110
110
  end
111
111
 
112
112
  # Delete an object from this array. Update associated counter if any
@@ -150,7 +150,7 @@ module PacketGen
150
150
  # @return [Array] self
151
151
  def <<(obj)
152
152
  push obj
153
- @counter.read(@counter.to_i + 1) if @counter
153
+ @counter&.read(@counter.to_i + 1)
154
154
  self
155
155
  end
156
156
 
@@ -160,14 +160,11 @@ module PacketGen
160
160
  def read(str)
161
161
  clear
162
162
  return self if str.nil?
163
- return self if @counter && @counter.to_i.zero?
163
+ return self if @counter&.to_i&.zero?
164
164
 
165
165
  str = read_with_length_from(str)
166
- klass = self.class.set_of_klass
167
166
  until str.empty?
168
- obj = klass.new.read(str)
169
- real_klass = real_type(obj)
170
- obj = real_klass.new.read(str) unless real_klass == klass
167
+ obj = create_object_from_str(str)
171
168
  @array << obj
172
169
  str.slice!(0, obj.sz)
173
170
  break if @counter && self.size == @counter.to_i
@@ -203,9 +200,7 @@ module PacketGen
203
200
 
204
201
  def record_from_hash(hsh)
205
202
  obj_klass = self.class.set_of_klass
206
- unless obj_klass
207
- raise NotImplementedError, 'class should define #record_from_hash or declare type of elements in set with .set_of'
208
- end
203
+ raise NotImplementedError, 'class should define #record_from_hash or declare type of elements in set with .set_of' unless obj_klass
209
204
 
210
205
  obj = obj_klass.new(hsh) if obj_klass
211
206
  klass = real_type(obj)
@@ -215,6 +210,18 @@ module PacketGen
215
210
  def real_type(obj)
216
211
  obj.class
217
212
  end
213
+
214
+ def create_object_from_str(str)
215
+ klass = self.class.set_of_klass
216
+ obj = klass.new.read(str)
217
+ real_klass = real_type(obj)
218
+
219
+ if real_klass == klass
220
+ obj
221
+ else
222
+ real_klass.new.read(str)
223
+ end
224
+ end
218
225
  end
219
226
 
220
227
  # Specialized array to handle serie of {Int8}.
@@ -1,20 +1,34 @@
1
1
  # coding: utf-8
2
+ # frozen_string_literal: true
3
+
2
4
  # This file is part of PacketGen
3
5
  # See https://github.com/sdaubert/packetgen for more informations
4
6
  # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
5
7
  # This program is published under MIT license.
6
8
 
7
- # frozen_string_literal: true
9
+ require 'forwardable'
8
10
 
9
11
  module PacketGen
10
12
  module Types
11
13
  # This class handles null-terminated strings (aka C strings).
12
14
  # @author Sylvain Daubert
13
- class CString < ::String
15
+ # @since 3.1.6 no more a subclass or regular String
16
+ class CString
17
+ extend Forwardable
18
+ include Fieldable
19
+
20
+ def_delegators :@string, :[], :length, :size, :inspect, :==, :<<,
21
+ :unpack, :force_encoding, :encoding, :index, :empty?
22
+
23
+ # @return [::String]
24
+ attr_reader :string
25
+ # @return [Integer]
26
+ attr_reader :static_length
27
+
14
28
  # @param [Hash] options
15
29
  # @option options [Integer] :static_length set a static length for this string
16
30
  def initialize(options={})
17
- super()
31
+ register_internal_string ''
18
32
  @static_length = options[:static_length]
19
33
  end
20
34
 
@@ -22,38 +36,41 @@ module PacketGen
22
36
  # @return [String] self
23
37
  def read(str)
24
38
  s = str.to_s
25
- s = s[0, @static_length] if @static_length.is_a? Integer
39
+ s = s[0, static_length] if static_length?
26
40
  idx = s.index(0.chr)
27
41
  s = s[0, idx] unless idx.nil?
28
- self.replace s
42
+ register_internal_string s
29
43
  self
30
44
  end
31
45
 
32
46
  # get null-terminated string
33
47
  # @return [String]
34
48
  def to_s
35
- if defined?(@static_length) && @static_length.is_a?(Integer)
36
- if self.size >= @static_length
37
- s = self[0, @static_length]
38
- s[-1] = "\x00".encode(s.encoding)
39
- PacketGen.force_binary s
40
- else
41
- PacketGen.force_binary(self + "\0" * (@static_length - self.length))
42
- end
49
+ if static_length?
50
+ s = string[0, static_length - 1]
51
+ s << "\x00" * (static_length - s.length)
43
52
  else
44
- PacketGen.force_binary(self + +"\x00".encode(self.encoding))
53
+ s = "#{string}\x00"
45
54
  end
55
+ PacketGen.force_binary(s)
46
56
  end
47
57
 
48
58
  # @return [Integer]
49
59
  def sz
50
- if @static_length.is_a? Integer
51
- @static_length
60
+ if static_length?
61
+ static_length
52
62
  else
53
63
  to_s.size
54
64
  end
55
65
  end
56
66
 
67
+ # Say if a static length is defined
68
+ # @return [Boolean]
69
+ # @since 3.1.6
70
+ def static_length?
71
+ !static_length.nil?
72
+ end
73
+
57
74
  # Populate CString from a human readable string
58
75
  # @param [String] str
59
76
  # @return [self]
@@ -66,6 +83,13 @@ module PacketGen
66
83
  idx = self.index(+"\x00".encode(self.encoding)) || self.sz
67
84
  self[0, idx]
68
85
  end
86
+
87
+ private
88
+
89
+ def register_internal_string(str)
90
+ @string = str
91
+ PacketGen.force_binary(@string)
92
+ end
69
93
  end
70
94
  end
71
95
  end
@@ -1,11 +1,11 @@
1
1
  # coding: utf-8
2
+ # frozen_string_literal: true
3
+
2
4
  # This file is part of PacketGen
3
5
  # See https://github.com/sdaubert/packetgen for more informations
4
- # Copyright (C) 2032 Sylvain Daubert <sylvain.daubert@laposte.net>
6
+ # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
5
7
  # This program is published under MIT license.
6
8
 
7
- # frozen_string_literal: true
8
-
9
9
  module PacketGen
10
10
  module Types
11
11
  # @abstract Base enum class to handle binary integers with limited
@@ -55,6 +55,7 @@ module PacketGen
55
55
  nil
56
56
  when ::String
57
57
  raise ArgumentError, "#{value.inspect} not in enumeration" unless @enum.key? value
58
+
58
59
  @enum[value]
59
60
  else
60
61
  value.to_i
@@ -70,6 +71,10 @@ module PacketGen
70
71
  def to_human
71
72
  @enum.key(to_i) || "<unknown:#{@value}>"
72
73
  end
74
+
75
+ def format_inspect
76
+ format_str % [to_human, to_i]
77
+ end
73
78
  end
74
79
 
75
80
  # Enumeration on one byte. See {Enum}.
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of PacketGen
4
+ # See https://github.com/sdaubert/packetgen for more informations
5
+ # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
6
+ # This program is published under MIT license.
7
+
8
+ module PacketGen
9
+ module Types
10
+ # Mixin to define minimal API for a class to be embbeded as a field in
11
+ # {Fields} type.
12
+ #
13
+ # == Optional methods
14
+ # These methods may, optionally, be defined by fieldable types:
15
+ # * +from_human+ to load data from a human-readable string.
16
+ # @author Sylvain Daubert
17
+ # @since 3.1.6
18
+ module Fieldable
19
+ # Get type name
20
+ # @return [String]
21
+ def type_name
22
+ self.class.to_s.split('::').last
23
+ end
24
+
25
+ # rubocop:disable Lint/UselessMethodDefinition
26
+ # These methods are defined for documentation.
27
+
28
+ # Populate object from a binary string
29
+ # @param [String] str
30
+ # @return [Fields] self
31
+ # @abstract subclass should overload it.
32
+ def read(str)
33
+ super
34
+ end
35
+
36
+ # Return object as a binary string
37
+ # @return [String]
38
+ # @abstract subclass should overload it.
39
+ def to_s
40
+ super
41
+ end
42
+
43
+ # Size of object as binary string
44
+ # @return [Integer]
45
+ def sz
46
+ to_s.size
47
+ end
48
+
49
+ # Return a human-readbale string
50
+ # @return [String]
51
+ # @abstract subclass should overload it.
52
+ def to_human
53
+ super
54
+ end
55
+
56
+ # rubocop:enable Lint/UselessMethodDefinition
57
+
58
+ # Format object when inspecting a {Field} object
59
+ # @return [String]
60
+ def format_inspect
61
+ to_human
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of PacketGen
2
4
  # See https://github.com/sdaubert/packetgen for more informations
3
5
  # Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
4
6
  # This program is published under MIT license.
5
7
 
6
- # frozen_string_literal: true
8
+ # rubocop:disable Metrics/ClassLength
7
9
 
8
10
  module PacketGen
9
11
  module Types
@@ -13,8 +15,7 @@ module PacketGen
13
15
  #
14
16
  # == Basics
15
17
  # A {Fields} subclass is generaly composed of multiple binary fields. These fields
16
- # have each a given type. All types from {Types} module are supported, and all
17
- # {Fields} subclasses may also be used as field type.
18
+ # have each a given type. All {Fieldable} types are supported.
18
19
  #
19
20
  # To define a new subclass, it has to inherit from {Fields}. And some class
20
21
  # methods have to be used to declare attributes/fields:
@@ -119,17 +120,23 @@ module PacketGen
119
120
  # @return [Hash]
120
121
  # @since 3.1.0
121
122
  attr_reader :field_defs
123
+ # Get bit fields defintions for this class
124
+ # @return [Hash]
125
+ # @since 3.1.5
126
+ attr_reader :bit_fields
122
127
 
123
128
  # On inheritage, create +@field_defs+ class variable
124
129
  # @param [Class] klass
125
130
  # @return [void]
126
131
  def inherited(klass)
132
+ super
133
+
127
134
  field_defs = {}
128
135
  @field_defs.each do |k, v|
129
136
  field_defs[k] = v.clone
130
137
  end
131
138
  ordered = @ordered_fields.clone
132
- bf = @bit_fields.clone
139
+ bf = bit_fields.clone
133
140
 
134
141
  klass.class_eval do
135
142
  @ordered_fields = ordered
@@ -158,7 +165,7 @@ module PacketGen
158
165
  # bs[value1] # => Types::Int8
159
166
  # bs.value1 # => Integer
160
167
  # @param [Symbol] name field name
161
- # @param [Object] type class or instance
168
+ # @param [Fieldable] type class or instance
162
169
  # @param [Hash] options Unrecognized options are passed to object builder if
163
170
  # +:builder+ option is not set.
164
171
  # @option options [Object] :default default value. May be a proc. This lambda
@@ -172,44 +179,22 @@ module PacketGen
172
179
  # Define enumeration: hash's keys are +String+, and values are +Integer+.
173
180
  # @return [void]
174
181
  def define_field(name, type, options={})
175
- define = []
176
- if type < Types::Enum
177
- define << "def #{name}; self[:#{name}].to_i; end"
178
- define << "def #{name}=(val) self[:#{name}].value = val; end"
179
- else
180
- define << "def #{name}\n" \
181
- " if self[:#{name}].respond_to?(:to_human) && self[:#{name}].respond_to?(:from_human)\n" \
182
- " self[:#{name}].to_human\n" \
183
- " else\n" \
184
- " self[:#{name}]\n" \
185
- " end\n" \
186
- "end"
187
- define << "def #{name}=(val)\n" \
188
- " if self[:#{name}].respond_to?(:to_human) && self[:#{name}].respond_to?(:from_human)\n" \
189
- " self[:#{name}].from_human val\n" \
190
- " else\n" \
191
- " self[:#{name}].read val\n" \
192
- " end\n" \
193
- "end"
194
- end
195
-
196
- define.delete_at(1) if instance_methods.include? "#{name}=".to_sym
197
- define.delete_at(0) if instance_methods.include? name
198
- class_eval define.join("\n")
182
+ fields << name
199
183
  field_defs[name] = FieldDef.new(type,
200
184
  options.delete(:default),
201
185
  options.delete(:builder),
202
186
  options.delete(:optional),
203
187
  options.delete(:enum),
204
188
  options)
205
- fields << name
189
+
190
+ add_methods(name, type)
206
191
  end
207
192
 
208
193
  # Define a field, before another one
209
194
  # @param [Symbol,nil] other field name to create a new one before. If +nil+,
210
195
  # new field is appended.
211
196
  # @param [Symbol] name field name to create
212
- # @param [Object] type class or instance
197
+ # @param [Fieldable] type class or instance
213
198
  # @param [Hash] options See {.define_field}.
214
199
  # @return [void]
215
200
  # @see .define_field
@@ -228,7 +213,7 @@ module PacketGen
228
213
  # @param [Symbol,nil] other field name to create a new one after. If +nil+,
229
214
  # new field is appended.
230
215
  # @param [Symbol] name field name to create
231
- # @param [Object] type class or instance
216
+ # @param [Fieldable] type class or instance
232
217
  # @param [Hash] options See {.define_field}.
233
218
  # @return [void]
234
219
  # @see .define_field
@@ -262,12 +247,12 @@ module PacketGen
262
247
  # @raise [ArgumentError] unknown +field+
263
248
  # @since 2.8.4
264
249
  def update_field(field, options)
265
- raise ArgumentError, "unkown #{field} field for #{self}" unless field_defs.key?(field)
250
+ check_existence_of field
251
+
252
+ %i[default builder optional enum].each do |property|
253
+ field_defs_property_from(field, property, options)
254
+ end
266
255
 
267
- field_defs[field].default = options.delete(:default) if options.key?(:default)
268
- field_defs[field].builder = options.delete(:builder) if options.key?(:builder)
269
- field_defs[field].optional = options.delete(:optional) if options.key?(:optional)
270
- field_defs[field].enum = options.delete(:enum) if options.key?(:enum)
271
256
  field_defs[field].options.merge!(options)
272
257
  end
273
258
 
@@ -289,63 +274,29 @@ module PacketGen
289
274
  # subclass)
290
275
  # @param [Array] args list of bitfield names. Name may be followed
291
276
  # by bitfield size. If no size is given, 1 bit is assumed.
277
+ # @raise [ArgumentError] unknown +attr+
292
278
  # @return [void]
293
279
  def define_bit_fields_on(attr, *args)
294
- attr_def = field_defs[attr]
295
- raise ArgumentError, "unknown #{attr} field" if attr_def.nil?
280
+ check_existence_of attr
296
281
 
297
- type = attr_def.type
298
- unless type < Types::Int
299
- raise TypeError, "#{attr} is not a PacketGen::Types::Int"
300
- end
282
+ type = field_defs[attr].type
283
+ raise TypeError, "#{attr} is not a PacketGen::Types::Int" unless type < Types::Int
301
284
 
302
285
  total_size = type.new.width * 8
303
286
  idx = total_size - 1
304
287
 
305
- field = args.shift
306
- while field
288
+ until args.empty?
289
+ field = args.shift
307
290
  next unless field.is_a? Symbol
308
291
 
309
- size = if args.first.is_a? Integer
310
- args.shift
311
- else
312
- 1
313
- end
292
+ size = size_from(args)
293
+
314
294
  unless field == :_
315
- shift = idx - (size - 1)
316
- field_mask = (2**size - 1) << shift
317
- clear_mask = (2**total_size - 1) & (~field_mask & (2**total_size - 1))
318
-
319
- if size == 1
320
- class_eval <<-METHODS
321
- def #{field}?
322
- val = (self[:#{attr}].to_i & #{field_mask}) >> #{shift}
323
- val != 0
324
- end
325
- def #{field}=(v)
326
- val = v ? 1 : 0
327
- self[:#{attr}].value = self[:#{attr}].to_i & #{clear_mask}
328
- self[:#{attr}].value |= val << #{shift}
329
- end
330
- METHODS
331
- else
332
- class_eval <<-METHODS
333
- def #{field}
334
- (self[:#{attr}].to_i & #{field_mask}) >> #{shift}
335
- end
336
- def #{field}=(v)
337
- self[:#{attr}].value = self[:#{attr}].to_i & #{clear_mask}
338
- self[:#{attr}].value |= (v & #{2**size - 1}) << #{shift}
339
- end
340
- METHODS
341
- end
342
-
343
- @bit_fields[attr] = {} if @bit_fields[attr].nil?
344
- @bit_fields[attr][field] = size
295
+ add_bit_methods(attr, field, size, total_size, idx)
296
+ register_bit_field_size(attr, field, size)
345
297
  end
346
298
 
347
299
  idx -= size
348
- field = args.shift
349
300
  end
350
301
  end
351
302
 
@@ -354,7 +305,7 @@ module PacketGen
354
305
  # @return [void]
355
306
  # @since 2.8.4
356
307
  def remove_bit_fields_on(attr)
357
- fields = @bit_fields.delete(attr)
308
+ fields = bit_fields.delete(attr)
358
309
  return if fields.nil?
359
310
 
360
311
  fields.each do |field, size|
@@ -362,6 +313,98 @@ module PacketGen
362
313
  undef_method(size == 1 ? "#{field}?" : field)
363
314
  end
364
315
  end
316
+
317
+ private
318
+
319
+ def add_methods(name, type)
320
+ define = []
321
+ if type < Types::Enum
322
+ define << "def #{name}; self[:#{name}].to_i; end"
323
+ define << "def #{name}=(val) self[:#{name}].value = val; end"
324
+ else
325
+ define << "def #{name}\n" \
326
+ " to_and_from_human?(:#{name}) ? self[:#{name}].to_human : self[:#{name}]\n" \
327
+ 'end'
328
+ define << "def #{name}=(val)\n" \
329
+ " to_and_from_human?(:#{name}) ? self[:#{name}].from_human(val) : self[:#{name}].read(val)\n" \
330
+ 'end'
331
+ end
332
+
333
+ define.delete_at(1) if instance_methods.include? "#{name}=".to_sym
334
+ define.delete_at(0) if instance_methods.include? name
335
+ class_eval define.join("\n")
336
+ end
337
+
338
+ def add_bit_methods(attr, name, size, total_size, idx)
339
+ shift = idx - (size - 1)
340
+
341
+ if size == 1
342
+ add_single_bit_methods(attr, name, size, total_size, shift)
343
+ else
344
+ add_multibit_methods(attr, name, size, total_size, shift)
345
+ end
346
+ end
347
+
348
+ def compute_field_mask(size, shift)
349
+ (2**size - 1) << shift
350
+ end
351
+
352
+ def compute_clear_mask(total_size, field_mask)
353
+ (2**total_size - 1) & (~field_mask & (2**total_size - 1))
354
+ end
355
+
356
+ def add_single_bit_methods(attr, name, size, total_size, shift)
357
+ field_mask = compute_field_mask(size, shift)
358
+ clear_mask = compute_clear_mask(total_size, field_mask)
359
+
360
+ class_eval <<-METHODS
361
+ def #{name}?
362
+ val = (self[:#{attr}].to_i & #{field_mask}) >> #{shift}
363
+ val != 0
364
+ end
365
+ def #{name}=(v)
366
+ val = v ? 1 : 0
367
+ self[:#{attr}].value = self[:#{attr}].to_i & #{clear_mask}
368
+ self[:#{attr}].value |= val << #{shift}
369
+ end
370
+ METHODS
371
+ end
372
+
373
+ def add_multibit_methods(attr, name, size, total_size, shift)
374
+ field_mask = compute_field_mask(size, shift)
375
+ clear_mask = compute_clear_mask(total_size, field_mask)
376
+
377
+ class_eval <<-METHODS
378
+ def #{name}
379
+ (self[:#{attr}].to_i & #{field_mask}) >> #{shift}
380
+ end
381
+ def #{name}=(v)
382
+ self[:#{attr}].value = self[:#{attr}].to_i & #{clear_mask}
383
+ self[:#{attr}].value |= (v & #{2**size - 1}) << #{shift}
384
+ end
385
+ METHODS
386
+ end
387
+
388
+ def register_bit_field_size(attr, field, size)
389
+ bit_fields[attr] = {} if bit_fields[attr].nil?
390
+ bit_fields[attr][field] = size
391
+ end
392
+
393
+ def field_defs_property_from(field, property, options)
394
+ field_defs[field].send("#{property}=", options.delete(property)) if options.key?(property)
395
+ end
396
+
397
+ def size_from(args)
398
+ if args.first.is_a? Integer
399
+ args.shift
400
+ else
401
+ 1
402
+ end
403
+ end
404
+
405
+ def check_existence_of(field)
406
+ raise ArgumentError, "unknown #{field} field for #{self}" unless field_defs.key?(field)
407
+ end
365
408
  end
366
409
 
367
410
  # Create a new fields object
@@ -371,36 +414,13 @@ module PacketGen
371
414
  @fields = {}
372
415
  @optional_fields = {}
373
416
 
374
- field_defs = self.class.field_defs
375
417
  self.class.fields.each do |field|
376
- type = field_defs[field].type
377
- default = field_defs[field].default
378
- default = default.to_proc.call(self) if default.is_a?(Proc)
379
- builder = field_defs[field].builder
380
- optional = field_defs[field].optional
381
- enum = field_defs[field].enum
382
- field_options = field_defs[field].options
383
-
384
- @fields[field] = if builder
385
- builder.call(self, type)
386
- elsif enum
387
- type.new(enum)
388
- elsif !field_options.empty?
389
- type.new(field_options)
390
- else
391
- type.new
392
- end
393
-
394
- value = options[field] || default
395
- if value.class <= type
396
- @fields[field] = value
397
- elsif @fields[field].respond_to? :from_human
398
- @fields[field].from_human(value)
399
- end
400
-
401
- @optional_fields[field] = optional if optional
418
+ build_field field
419
+ initialize_value field, options[field]
420
+ initialize_optional field
402
421
  end
403
- self.class.class_eval { @bit_fields }.each do |_, hsh|
422
+
423
+ self.class.bit_fields.each do |_, hsh|
404
424
  hsh.each_key do |bit_field|
405
425
  self.send "#{bit_field}=", options[bit_field] if options[bit_field]
406
426
  end
@@ -409,7 +429,7 @@ module PacketGen
409
429
 
410
430
  # Get field object
411
431
  # @param [Symbol] field
412
- # @return [Object]
432
+ # @return [Fieldable]
413
433
  def [](field)
414
434
  @fields[field]
415
435
  end
@@ -429,6 +449,7 @@ module PacketGen
429
449
  end
430
450
 
431
451
  # Get all optional field name
452
+ # @return[Array<Symbol>,nil]
432
453
  def optional_fields
433
454
  @optional_fields.keys
434
455
  end
@@ -459,11 +480,7 @@ module PacketGen
459
480
  next unless present?(field)
460
481
 
461
482
  obj = self[field].read str[start..-1]
462
- if self[field].respond_to? :sz
463
- start += self[field].sz
464
- else
465
- start = str.size
466
- end
483
+ start += self[field].sz
467
484
  self[field] = obj unless obj == self[field]
468
485
  end
469
486
 
@@ -530,7 +547,7 @@ module PacketGen
530
547
  # @return [Hash,nil] keys: bit fields, values: their size in bits
531
548
  # @since 2.8.3
532
549
  def bits_on(field)
533
- self.class.class_eval { @bit_fields }[field]
550
+ self.class.bit_fields[field]
534
551
  end
535
552
 
536
553
  private
@@ -549,6 +566,54 @@ module PacketGen
549
566
  def force_binary(str)
550
567
  PacketGen.force_binary(str)
551
568
  end
569
+
570
+ # @param [Symbol] attr attribute
571
+ # @return [Boolean] +tru+e if #from_human and #to_human are both defined for given attribute
572
+ def to_and_from_human?(attr)
573
+ self[attr].respond_to?(:to_human) && self[attr].respond_to?(:from_human)
574
+ end
575
+
576
+ def field_defs
577
+ self.class.field_defs
578
+ end
579
+
580
+ # rubocop:disable Metrics/AbcSize
581
+ def build_field(field)
582
+ type = field_defs[field].type
583
+
584
+ @fields[field] = if field_defs[field].builder
585
+ field_defs[field].builder.call(self, type)
586
+ elsif field_defs[field].enum
587
+ type.new(field_defs[field].enum)
588
+ elsif !field_defs[field].options.empty?
589
+ type.new(field_defs[field].options)
590
+ else
591
+ type.new
592
+ end
593
+ end
594
+ # rubocop:enable Metrics/AbcSize
595
+
596
+ def initialize_value(field, val)
597
+ type = field_defs[field].type
598
+ default = field_defs[field].default
599
+ default = default.to_proc.call(self) if default.is_a?(Proc)
600
+
601
+ value = val || default
602
+ if value.class <= type
603
+ @fields[field] = value
604
+ elsif @fields[field].respond_to? :from_human
605
+ @fields[field].from_human(value)
606
+ else
607
+ @fields[field].read(value)
608
+ end
609
+ end
610
+
611
+ def initialize_optional(field)
612
+ optional = field_defs[field].optional
613
+ @optional_fields[field] = optional if optional
614
+ end
552
615
  end
553
616
  end
554
617
  end
618
+
619
+ # rubocop:enable Metrics/ClassLength