packetfu 1.1.2 → 1.1.3
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.
- data/.gitignore +3 -0
- data/INSTALL.rdoc +40 -0
- data/LICENSE.txt +25 -0
- data/examples/100kpackets.rb +41 -0
- data/examples/ackscan.rb +38 -0
- data/examples/arp.rb +60 -0
- data/examples/arphood.rb +59 -0
- data/examples/dissect_thinger.rb +22 -0
- data/examples/ethernet.rb +10 -0
- data/examples/examples.rb +3 -0
- data/examples/ids.rb +4 -0
- data/examples/idsv2.rb +6 -0
- data/examples/new-simple-stats.rb +52 -0
- data/examples/oui.txt +84177 -0
- data/examples/packetfu-shell.rb +113 -0
- data/examples/simple-sniffer.rb +40 -0
- data/examples/simple-stats.rb +50 -0
- data/examples/slammer.rb +33 -0
- data/examples/uniqpcap.rb +15 -0
- data/lib/packetfu.rb +147 -0
- data/lib/packetfu/capture.rb +169 -0
- data/lib/packetfu/config.rb +58 -0
- data/lib/packetfu/inject.rb +65 -0
- data/lib/packetfu/packet.rb +533 -0
- data/lib/packetfu/pcap.rb +594 -0
- data/lib/packetfu/protos/arp.rb +268 -0
- data/lib/packetfu/protos/eth.rb +296 -0
- data/lib/packetfu/protos/hsrp.rb +206 -0
- data/lib/packetfu/protos/icmp.rb +179 -0
- data/lib/packetfu/protos/invalid.rb +55 -0
- data/lib/packetfu/protos/ip.rb +378 -0
- data/lib/packetfu/protos/ipv6.rb +250 -0
- data/lib/packetfu/protos/tcp.rb +1127 -0
- data/lib/packetfu/protos/udp.rb +240 -0
- data/lib/packetfu/structfu.rb +294 -0
- data/lib/packetfu/utils.rb +194 -0
- data/lib/packetfu/version.rb +50 -0
- data/packetfu.gemspec +21 -0
- data/setup.rb +1586 -0
- data/test/all_tests.rb +41 -0
- data/test/ethpacket_spec.rb +74 -0
- data/test/packet_spec.rb +73 -0
- data/test/packet_subclasses_spec.rb +13 -0
- data/test/packetfu_spec.rb +90 -0
- data/test/ptest.rb +16 -0
- data/test/sample-ipv6.pcap +0 -0
- data/test/sample.pcap +0 -0
- data/test/sample2.pcap +0 -0
- data/test/sample_hsrp_pcapr.cap +0 -0
- data/test/structfu_spec.rb +335 -0
- data/test/tcp_spec.rb +101 -0
- data/test/test_arp.rb +135 -0
- data/test/test_eth.rb +91 -0
- data/test/test_hsrp.rb +20 -0
- data/test/test_icmp.rb +54 -0
- data/test/test_inject.rb +31 -0
- data/test/test_invalid.rb +28 -0
- data/test/test_ip.rb +69 -0
- data/test/test_ip6.rb +68 -0
- data/test/test_octets.rb +37 -0
- data/test/test_packet.rb +174 -0
- data/test/test_pcap.rb +209 -0
- data/test/test_structfu.rb +112 -0
- data/test/test_tcp.rb +327 -0
- data/test/test_udp.rb +73 -0
- data/test/vlan-pcapr.cap +0 -0
- metadata +85 -6
@@ -0,0 +1,240 @@
|
|
1
|
+
module PacketFu
|
2
|
+
|
3
|
+
# UDPHeader is a complete UDP struct, used in UDPPacket. Many Internet-critical protocols
|
4
|
+
# rely on UDP, such as DNS and World of Warcraft.
|
5
|
+
#
|
6
|
+
# For more on UDP packets, see http://www.networksorcery.com/enp/protocol/udp.htm
|
7
|
+
#
|
8
|
+
# ==== Header Definition
|
9
|
+
# Int16 :udp_src
|
10
|
+
# Int16 :udp_dst
|
11
|
+
# Int16 :udp_len Default: calculated
|
12
|
+
# Int16 :udp_sum Default: 0. Often calculated.
|
13
|
+
# String :body
|
14
|
+
class UDPHeader < Struct.new(:udp_src, :udp_dst, :udp_len, :udp_sum, :body)
|
15
|
+
|
16
|
+
include StructFu
|
17
|
+
|
18
|
+
def initialize(args={})
|
19
|
+
super(
|
20
|
+
Int16.new(args[:udp_src]),
|
21
|
+
Int16.new(args[:udp_dst]),
|
22
|
+
Int16.new(args[:udp_len] || udp_calc_len),
|
23
|
+
Int16.new(args[:udp_sum]),
|
24
|
+
StructFu::String.new.read(args[:body])
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the object in string form.
|
29
|
+
def to_s
|
30
|
+
self.to_a.map {|x| x.to_s}.join
|
31
|
+
end
|
32
|
+
|
33
|
+
# Reads a string to populate the object.
|
34
|
+
def read(str)
|
35
|
+
force_binary(str)
|
36
|
+
return self if str.nil?
|
37
|
+
self[:udp_src].read(str[0,2])
|
38
|
+
self[:udp_dst].read(str[2,2])
|
39
|
+
self[:udp_len].read(str[4,2])
|
40
|
+
self[:udp_sum].read(str[6,2])
|
41
|
+
self[:body].read(str[8,str.size])
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
# Setter for the UDP source port.
|
46
|
+
def udp_src=(i); typecast i; end
|
47
|
+
# Getter for the UDP source port.
|
48
|
+
def udp_src; self[:udp_src].to_i; end
|
49
|
+
# Setter for the UDP destination port.
|
50
|
+
def udp_dst=(i); typecast i; end
|
51
|
+
# Getter for the UDP destination port.
|
52
|
+
def udp_dst; self[:udp_dst].to_i; end
|
53
|
+
# Setter for the length field. Usually should be recalc()'ed instead.
|
54
|
+
def udp_len=(i); typecast i; end
|
55
|
+
# Getter for the length field.
|
56
|
+
def udp_len; self[:udp_len].to_i; end
|
57
|
+
# Setter for the checksum. Usually should be recalc()'ed instad.
|
58
|
+
def udp_sum=(i); typecast i; end
|
59
|
+
# Getter for the checksum.
|
60
|
+
def udp_sum; self[:udp_sum].to_i; end
|
61
|
+
|
62
|
+
# Returns the true length of the UDP packet.
|
63
|
+
def udp_calc_len
|
64
|
+
body.to_s.size + 8
|
65
|
+
end
|
66
|
+
|
67
|
+
# Recalculates calculated fields for UDP.
|
68
|
+
def udp_recalc(args=:all)
|
69
|
+
arg = arg.intern if arg.respond_to? :intern
|
70
|
+
case args
|
71
|
+
when :udp_len
|
72
|
+
self.udp_len = udp_calc_len
|
73
|
+
when :all
|
74
|
+
self.udp_recalc(:udp_len)
|
75
|
+
else
|
76
|
+
raise ArgumentError, "No such field `#{arg}'"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Equivalent to udp_src.to_i
|
81
|
+
def udp_sport
|
82
|
+
self.udp_src
|
83
|
+
end
|
84
|
+
|
85
|
+
# Equivalent to udp_src=
|
86
|
+
def udp_sport=(arg)
|
87
|
+
self.udp_src=(arg)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Equivalent to udp_dst
|
91
|
+
def udp_dport
|
92
|
+
self.udp_dst
|
93
|
+
end
|
94
|
+
|
95
|
+
# Equivalent to udp_dst=
|
96
|
+
def udp_dport=(arg)
|
97
|
+
self.udp_dst=(arg)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Readability aliases
|
101
|
+
|
102
|
+
def udp_sum_readable
|
103
|
+
"0x%04x" % udp_sum
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
# UDPPacket is used to construct UDP Packets. They contain an EthHeader, an IPHeader, and a UDPHeader.
|
109
|
+
#
|
110
|
+
# == Example
|
111
|
+
#
|
112
|
+
# udp_pkt = PacketFu::UDPPacket.new
|
113
|
+
# udp_pkt.udp_src=rand(0xffff-1024) + 1024
|
114
|
+
# udp_pkt.udp_dst=53
|
115
|
+
#
|
116
|
+
# udp_pkt.ip_saddr="1.2.3.4"
|
117
|
+
# udp_pkt.ip_daddr="10.20.30.40"
|
118
|
+
#
|
119
|
+
# udp_pkt.recalc
|
120
|
+
# udp_pkt.to_f('/tmp/udp.pcap')
|
121
|
+
#
|
122
|
+
# == Parameters
|
123
|
+
#
|
124
|
+
# :eth
|
125
|
+
# A pre-generated EthHeader object.
|
126
|
+
# :ip
|
127
|
+
# A pre-generated IPHeader object.
|
128
|
+
# :flavor
|
129
|
+
# TODO: Sets the "flavor" of the UDP packet. UDP packets don't tend have a lot of
|
130
|
+
# flavor, but their underlying ip headers do.
|
131
|
+
# :config
|
132
|
+
# A hash of return address details, often the output of Utils.whoami?
|
133
|
+
class UDPPacket < Packet
|
134
|
+
|
135
|
+
attr_accessor :eth_header, :ip_header, :udp_header
|
136
|
+
|
137
|
+
def self.can_parse?(str)
|
138
|
+
return false unless str.size >= 28
|
139
|
+
return false unless EthPacket.can_parse? str
|
140
|
+
return false unless IPPacket.can_parse? str
|
141
|
+
return false unless str[23,1] == "\x11"
|
142
|
+
return true
|
143
|
+
end
|
144
|
+
|
145
|
+
def read(str=nil, args={})
|
146
|
+
raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
|
147
|
+
@eth_header.read(str)
|
148
|
+
@ip_header.read(str[14,str.size])
|
149
|
+
@eth_header.body = @ip_header
|
150
|
+
if args[:strip]
|
151
|
+
udp_len = str[16,2].unpack("n")[0] - 20
|
152
|
+
@udp_header.read(str[14+(@ip_header.ip_hlen),udp_len])
|
153
|
+
else
|
154
|
+
@udp_header.read(str[14+(@ip_header.ip_hlen),str.size])
|
155
|
+
end
|
156
|
+
@ip_header.body = @udp_header
|
157
|
+
super(args)
|
158
|
+
self
|
159
|
+
end
|
160
|
+
|
161
|
+
def initialize(args={})
|
162
|
+
@eth_header = EthHeader.new(args).read(args[:eth])
|
163
|
+
@ip_header = IPHeader.new(args).read(args[:ip])
|
164
|
+
@ip_header.ip_proto=0x11
|
165
|
+
@udp_header = UDPHeader.new(args).read(args[:icmp])
|
166
|
+
@ip_header.body = @udp_header
|
167
|
+
@eth_header.body = @ip_header
|
168
|
+
@headers = [@eth_header, @ip_header, @udp_header]
|
169
|
+
super
|
170
|
+
udp_calc_sum
|
171
|
+
end
|
172
|
+
|
173
|
+
# udp_calc_sum() computes the UDP checksum, and is called upon intialization.
|
174
|
+
# It usually should be called just prior to dropping packets to a file or on the wire.
|
175
|
+
def udp_calc_sum
|
176
|
+
# This is /not/ delegated down to @udp_header since we need info
|
177
|
+
# from the IP header, too.
|
178
|
+
checksum = (ip_src.to_i >> 16)
|
179
|
+
checksum += (ip_src.to_i & 0xffff)
|
180
|
+
checksum += (ip_dst.to_i >> 16)
|
181
|
+
checksum += (ip_dst.to_i & 0xffff)
|
182
|
+
checksum += 0x11
|
183
|
+
checksum += udp_len.to_i
|
184
|
+
checksum += udp_src.to_i
|
185
|
+
checksum += udp_dst.to_i
|
186
|
+
checksum += udp_len.to_i
|
187
|
+
if udp_len.to_i >= 8
|
188
|
+
# For IP trailers. This isn't very reliable. :/
|
189
|
+
real_udp_payload = payload.to_s[0,(udp_len.to_i-8)]
|
190
|
+
else
|
191
|
+
# I'm not going to mess with this right now.
|
192
|
+
real_udp_payload = payload
|
193
|
+
end
|
194
|
+
chk_payload = (real_udp_payload.size % 2 == 0 ? real_udp_payload : real_udp_payload + "\x00")
|
195
|
+
chk_payload.unpack("n*").each {|x| checksum = checksum+x}
|
196
|
+
checksum = checksum % 0xffff
|
197
|
+
checksum = 0xffff - checksum
|
198
|
+
checksum == 0 ? 0xffff : checksum
|
199
|
+
@udp_header.udp_sum = checksum
|
200
|
+
end
|
201
|
+
|
202
|
+
# udp_recalc() recalculates various fields of the UDP packet. Valid arguments are:
|
203
|
+
#
|
204
|
+
# :all
|
205
|
+
# Recomputes all calculated fields.
|
206
|
+
# :udp_sum
|
207
|
+
# Recomputes the UDP checksum.
|
208
|
+
# :udp_len
|
209
|
+
# Recomputes the UDP length.
|
210
|
+
def udp_recalc(args=:all)
|
211
|
+
case args
|
212
|
+
when :udp_len
|
213
|
+
@udp_header.udp_recalc
|
214
|
+
when :udp_sum
|
215
|
+
udp_calc_sum
|
216
|
+
when :all
|
217
|
+
@udp_header.udp_recalc
|
218
|
+
udp_calc_sum
|
219
|
+
else
|
220
|
+
raise ArgumentError, "No such field `#{arg}'"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Peek provides summary data on packet contents.
|
225
|
+
def peek_format
|
226
|
+
peek_data = ["U "]
|
227
|
+
peek_data << "%-5d" % self.to_s.size
|
228
|
+
peek_data << "%-21s" % "#{self.ip_saddr}:#{self.udp_sport}"
|
229
|
+
peek_data << "->"
|
230
|
+
peek_data << "%21s" % "#{self.ip_daddr}:#{self.udp_dport}"
|
231
|
+
peek_data << "%23s" % "I:"
|
232
|
+
peek_data << "%04x" % self.ip_id
|
233
|
+
peek_data.join
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
239
|
+
|
240
|
+
# vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby
|
@@ -0,0 +1,294 @@
|
|
1
|
+
# StructFu, a nifty way to leverage Ruby's built in Struct class
|
2
|
+
# to create meaningful binary data.
|
3
|
+
|
4
|
+
module StructFu
|
5
|
+
|
6
|
+
# Normally, self.size and self.length will refer to the Struct
|
7
|
+
# size as an array. It's a hassle to redefine, so this introduces some
|
8
|
+
# shorthand to get at the size of the resultant string.
|
9
|
+
def sz
|
10
|
+
self.to_s.size
|
11
|
+
end
|
12
|
+
|
13
|
+
alias len sz
|
14
|
+
|
15
|
+
# Typecast is used mostly by packet header classes, such as IPHeader,
|
16
|
+
# TCPHeader, and the like. It takes an argument, and casts it to the
|
17
|
+
# expected type for that element.
|
18
|
+
def typecast(i)
|
19
|
+
c = caller[0].match(/.*`([^']+)='/)[1]
|
20
|
+
self[c.intern].read i
|
21
|
+
end
|
22
|
+
|
23
|
+
# Used like typecast(), but specifically for casting Strings to StructFu::Strings.
|
24
|
+
def body=(i)
|
25
|
+
if i.kind_of? ::String
|
26
|
+
typecast(i)
|
27
|
+
elsif i.kind_of? StructFu
|
28
|
+
self[:body] = i
|
29
|
+
elsif i.nil?
|
30
|
+
self[:body] = StructFu::String.new.read("")
|
31
|
+
else
|
32
|
+
raise ArgumentError, "Can't cram a #{i.class} into a StructFu :body"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Handle deep copies correctly. Marshal in 1.9, re-read myself on 1.8
|
37
|
+
def clone
|
38
|
+
begin
|
39
|
+
Marshal.load(Marshal.dump(self))
|
40
|
+
rescue
|
41
|
+
self.class.new.read(self.to_s)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Ints all have a value, an endianness, and a default value.
|
46
|
+
# Note that the signedness of Int values are implicit as
|
47
|
+
# far as the subclasses are concerned; to_i and to_f will
|
48
|
+
# return Integer/Float versions of the input value, instead
|
49
|
+
# of attempting to unpack the pack value. (This can be a useful
|
50
|
+
# hint to other functions).
|
51
|
+
#
|
52
|
+
# ==== Header Definition
|
53
|
+
#
|
54
|
+
# Fixnum :value
|
55
|
+
# Symbol :endian
|
56
|
+
# Fixnum :width
|
57
|
+
# Fixnum :default
|
58
|
+
class Int < Struct.new(:value, :endian, :width, :default)
|
59
|
+
alias :v= :value=
|
60
|
+
alias :v :value
|
61
|
+
alias :e= :endian=
|
62
|
+
alias :e :endian
|
63
|
+
alias :w= :width=
|
64
|
+
alias :w :width
|
65
|
+
alias :d= :default=
|
66
|
+
alias :d :default
|
67
|
+
|
68
|
+
# This is a parent class definition and should not be used directly.
|
69
|
+
def to_s
|
70
|
+
raise StandardError, "StructFu::Int#to_s accessed, must be redefined."
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns the Int as an Integer.
|
74
|
+
def to_i
|
75
|
+
(self.v || self.d).to_i
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns the Int as a Float.
|
79
|
+
def to_f
|
80
|
+
(self.v || self.d).to_f
|
81
|
+
end
|
82
|
+
|
83
|
+
def initialize(value=nil, endian=nil, width=nil, default=nil)
|
84
|
+
super(value,endian,width,default=0)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Reads either an Integer or a packed string, and populates the value accordingly.
|
88
|
+
def read(i)
|
89
|
+
self.v = i.kind_of?(Integer) ? i.to_i : i.to_s.unpack(@packstr).first
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
# Int8 is a one byte value.
|
96
|
+
class Int8 < Int
|
97
|
+
|
98
|
+
def initialize(v=nil)
|
99
|
+
super(v,nil,w=1)
|
100
|
+
@packstr = "C"
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns a one byte value as a packed string.
|
104
|
+
def to_s
|
105
|
+
[(self.v || self.d)].pack("C")
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
# Int16 is a two byte value.
|
111
|
+
class Int16 < Int
|
112
|
+
def initialize(v=nil, e=:big)
|
113
|
+
super(v,e,w=2)
|
114
|
+
@packstr = (self.e == :big) ? "n" : "v"
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns a two byte value as a packed string.
|
118
|
+
def to_s
|
119
|
+
@packstr = (self.e == :big) ? "n" : "v"
|
120
|
+
[(self.v || self.d)].pack(@packstr)
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
# Int16be is a two byte value in big-endian format. The endianness cannot be altered.
|
126
|
+
class Int16be < Int16
|
127
|
+
undef :endian=
|
128
|
+
end
|
129
|
+
|
130
|
+
# Int16le is a two byte value in little-endian format. The endianness cannot be altered.
|
131
|
+
class Int16le < Int16
|
132
|
+
undef :endian=
|
133
|
+
def initialize(v=nil, e=:little)
|
134
|
+
super(v,e)
|
135
|
+
@packstr = (self.e == :big) ? "n" : "v"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Int32 is a four byte value.
|
140
|
+
class Int32 < Int
|
141
|
+
def initialize(v=nil, e=:big)
|
142
|
+
super(v,e,w=4)
|
143
|
+
@packstr = (self.e == :big) ? "N" : "V"
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns a four byte value as a packed string.
|
147
|
+
def to_s
|
148
|
+
@packstr = (self.e == :big) ? "N" : "V"
|
149
|
+
[(self.v || self.d)].pack(@packstr)
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
# Int32be is a four byte value in big-endian format. The endianness cannot be altered.
|
155
|
+
class Int32be < Int32
|
156
|
+
undef :endian=
|
157
|
+
end
|
158
|
+
|
159
|
+
# Int32le is a four byte value in little-endian format. The endianness cannot be altered.
|
160
|
+
class Int32le < Int32
|
161
|
+
undef :endian=
|
162
|
+
def initialize(v=nil, e=:little)
|
163
|
+
super(v,e)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Strings are just like regular strings, except it comes with a read() function
|
168
|
+
# so that it behaves like other StructFu elements.
|
169
|
+
class String < ::String
|
170
|
+
def read(str)
|
171
|
+
str = str.to_s
|
172
|
+
self.replace str
|
173
|
+
self
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Provides a primitive for creating strings, preceeded by
|
178
|
+
# an Int type of length. By default, a string of length zero with
|
179
|
+
# a one-byte length is presumed.
|
180
|
+
#
|
181
|
+
# Note that IntStrings aren't used for much, but it seemed like a good idea at the time.
|
182
|
+
class IntString < Struct.new(:int, :string, :mode)
|
183
|
+
|
184
|
+
def initialize(string='',int=Int8,mode=nil)
|
185
|
+
if int < Int
|
186
|
+
super(int.new,string,mode)
|
187
|
+
calc
|
188
|
+
else
|
189
|
+
raise "IntStrings need a StructFu::Int for a length."
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Calculates the size of a string, and sets it as the value.
|
194
|
+
def calc
|
195
|
+
int.v = string.to_s.size
|
196
|
+
self.to_s
|
197
|
+
end
|
198
|
+
|
199
|
+
# Returns the object as a string, depending on the mode set upon object creation.
|
200
|
+
def to_s
|
201
|
+
if mode == :parse
|
202
|
+
"#{int}" + [string].pack("a#{len}")
|
203
|
+
elsif mode == :fix
|
204
|
+
self.int.v = string.size
|
205
|
+
"#{int}#{string}"
|
206
|
+
else
|
207
|
+
"#{int}#{string}"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# By redefining #string=, we can ensure the correct value
|
212
|
+
# is calculated upon assignment. If you'd prefer to have
|
213
|
+
# an incorrect value, use the syntax, obj[:string]="value"
|
214
|
+
# instead. Note, by using the alternate form, you must
|
215
|
+
# #calc before you can trust the int's value. Think of the =
|
216
|
+
# assignment as "set to equal," while the []= assignment
|
217
|
+
# as "boxing in" the value. Maybe.
|
218
|
+
def string=(s)
|
219
|
+
self[:string] = s
|
220
|
+
calc
|
221
|
+
end
|
222
|
+
|
223
|
+
# Shorthand for querying a length. Note that the usual "length"
|
224
|
+
# and "size" refer to the number of elements of this struct.
|
225
|
+
def len
|
226
|
+
self[:int].value
|
227
|
+
end
|
228
|
+
|
229
|
+
# Override the size, if you must.
|
230
|
+
def len=(i)
|
231
|
+
self[:int].value=i
|
232
|
+
end
|
233
|
+
|
234
|
+
# Read takes a string, assumes an int width as previously
|
235
|
+
# defined upon initialization, but makes no guarantees
|
236
|
+
# the int value isn't lying. You're on your own to test
|
237
|
+
# for that (or use parse() with a :mode set).
|
238
|
+
def read(s)
|
239
|
+
unless s[0,int.width].size == int.width
|
240
|
+
raise StandardError, "String is too short for type #{int.class}"
|
241
|
+
else
|
242
|
+
int.read(s[0,int.width])
|
243
|
+
self[:string] = s[int.width,s.size]
|
244
|
+
end
|
245
|
+
self.to_s
|
246
|
+
end
|
247
|
+
|
248
|
+
# parse() is like read(), except that it interprets the string, either
|
249
|
+
# based on the declared length, or the actual length. Which strategy
|
250
|
+
# is used is dependant on which :mode is set (with self.mode).
|
251
|
+
#
|
252
|
+
# :parse : Read the length, and then read in that many bytes of the string.
|
253
|
+
# The string may be truncated or padded out with nulls, as dictated by the value.
|
254
|
+
#
|
255
|
+
# :fix : Skip the length, read the rest of the string, then set the length
|
256
|
+
# to what it ought to be.
|
257
|
+
#
|
258
|
+
# else : If neither of these modes are set, just perfom a normal read().
|
259
|
+
# This is the default.
|
260
|
+
def parse(s)
|
261
|
+
unless s[0,int.width].size == int.width
|
262
|
+
raise StandardError, "String is too short for type #{int.class}"
|
263
|
+
else
|
264
|
+
case mode
|
265
|
+
when :parse
|
266
|
+
int.read(s[0,int.width])
|
267
|
+
self[:string] = s[int.width,int.value]
|
268
|
+
if string.size < int.value
|
269
|
+
self[:string] += ("\x00" * (int.value - self[:string].size))
|
270
|
+
end
|
271
|
+
when :fix
|
272
|
+
self.string = s[int.width,s.size]
|
273
|
+
else
|
274
|
+
return read(s)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
self.to_s
|
278
|
+
end
|
279
|
+
|
280
|
+
end
|
281
|
+
|
282
|
+
end
|
283
|
+
|
284
|
+
class Struct
|
285
|
+
|
286
|
+
# Monkeypatch for Struct to include some string safety -- anything that uses
|
287
|
+
# Struct is going to presume binary strings anyway.
|
288
|
+
def force_binary(str)
|
289
|
+
PacketFu.force_binary(str)
|
290
|
+
end
|
291
|
+
|
292
|
+
end
|
293
|
+
|
294
|
+
# vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby
|