packetgen 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.travis.yml +14 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +116 -0
- data/Rakefile +18 -0
- data/lib/packetgen.rb +83 -0
- data/lib/packetgen/capture.rb +105 -0
- data/lib/packetgen/header.rb +21 -0
- data/lib/packetgen/header/arp.rb +148 -0
- data/lib/packetgen/header/eth.rb +155 -0
- data/lib/packetgen/header/header_class_methods.rb +28 -0
- data/lib/packetgen/header/header_methods.rb +51 -0
- data/lib/packetgen/header/ip.rb +283 -0
- data/lib/packetgen/header/ipv6.rb +215 -0
- data/lib/packetgen/header/udp.rb +133 -0
- data/lib/packetgen/packet.rb +357 -0
- data/lib/packetgen/pcapng.rb +39 -0
- data/lib/packetgen/pcapng/block.rb +32 -0
- data/lib/packetgen/pcapng/epb.rb +131 -0
- data/lib/packetgen/pcapng/file.rb +345 -0
- data/lib/packetgen/pcapng/idb.rb +145 -0
- data/lib/packetgen/pcapng/shb.rb +173 -0
- data/lib/packetgen/pcapng/spb.rb +103 -0
- data/lib/packetgen/pcapng/unknown_block.rb +80 -0
- data/lib/packetgen/structfu.rb +357 -0
- data/lib/packetgen/version.rb +7 -0
- data/packetgen.gemspec +30 -0
- metadata +155 -0
@@ -0,0 +1,145 @@
|
|
1
|
+
module PacketGen
|
2
|
+
module PcapNG
|
3
|
+
|
4
|
+
# {IDB} represents a Interface Description Block (IDB) of a pcapng file.
|
5
|
+
#
|
6
|
+
# == IDB Definition
|
7
|
+
# Int32 :type Default: 0x00000001
|
8
|
+
# Int32 :block_len
|
9
|
+
# Int16 :link_type Default: 1
|
10
|
+
# Int16 :reserved Default: 0
|
11
|
+
# Int64 :snaplen Default: 0 (no limit)
|
12
|
+
# String :options
|
13
|
+
# Int32 :block_len2
|
14
|
+
class IDB < Struct.new(:type, :block_len, :link_type, :reserved,
|
15
|
+
:snaplen, :options, :block_len2)
|
16
|
+
include StructFu
|
17
|
+
include Block
|
18
|
+
|
19
|
+
# @return [:little, :big]
|
20
|
+
attr_accessor :endian
|
21
|
+
# @return [SHB]
|
22
|
+
attr_accessor :section
|
23
|
+
# @return [Array<EPB,SPB>]
|
24
|
+
attr_accessor :packets
|
25
|
+
|
26
|
+
# Minimum IDB size
|
27
|
+
MIN_SIZE = 5*4
|
28
|
+
|
29
|
+
# Option code for if_tsresol option
|
30
|
+
OPTION_IF_TSRESOL = 9
|
31
|
+
|
32
|
+
# @param [Hash] options
|
33
|
+
# @option options [:little, :big] :endian set block endianness
|
34
|
+
# @option options [Integer] :type
|
35
|
+
# @option options [Integer] :block_len block total length
|
36
|
+
# @option options [Integer] :link_type
|
37
|
+
# @option options [Integer] :reserved
|
38
|
+
# @option options [Integer] :snaplen maximum number of octets captured from
|
39
|
+
# each packet
|
40
|
+
# @option options [::String] :options
|
41
|
+
# @option options [Integer] :block_len2 block total length
|
42
|
+
def initialize(options={})
|
43
|
+
@endian = set_endianness(options[:endian] || :little)
|
44
|
+
@packets = []
|
45
|
+
@options_decoded = false
|
46
|
+
init_fields(options)
|
47
|
+
super(options[:type], options[:block_len], options[:link_type], options[:reserved],
|
48
|
+
options[:snaplen], options[:options], options[:block_len2])
|
49
|
+
end
|
50
|
+
|
51
|
+
# Used by {#initialize} to set the initial fields
|
52
|
+
# @see #initialize possible options
|
53
|
+
# @param [Hash] options
|
54
|
+
# @return [Hash] return +options+
|
55
|
+
def init_fields(options={})
|
56
|
+
options[:type] = @int32.new(options[:type] || PcapNG::IDB_TYPE.to_i)
|
57
|
+
options[:block_len] = @int32.new(options[:block_len] || MIN_SIZE)
|
58
|
+
options[:link_type] = @int16.new(options[:link_type] || 1)
|
59
|
+
options[:reserved] = @int16.new(options[:reserved] || 0)
|
60
|
+
options[:snaplen] = @int32.new(options[:snaplen] || 0)
|
61
|
+
options[:options] = StructFu::String.new(options[:options] || '')
|
62
|
+
options[:block_len2] = @int32.new(options[:block_len2] || MIN_SIZE)
|
63
|
+
options
|
64
|
+
end
|
65
|
+
|
66
|
+
# Reads a String or a IO to populate the object
|
67
|
+
# @param [::String,IO] str_or_io
|
68
|
+
# @return [self]
|
69
|
+
def read(str_or_io)
|
70
|
+
if str_or_io.respond_to? :read
|
71
|
+
io = str_or_io
|
72
|
+
else
|
73
|
+
io = StringIO.new(force_binary(str_or_io.to_s))
|
74
|
+
end
|
75
|
+
return self if io.eof?
|
76
|
+
|
77
|
+
self[:type].read io.read(4)
|
78
|
+
self[:block_len].read io.read(4)
|
79
|
+
self[:link_type].read io.read(2)
|
80
|
+
self[:reserved].read io.read(2)
|
81
|
+
self[:snaplen].read io.read(4)
|
82
|
+
self[:options].read io.read(self[:block_len].to_i - MIN_SIZE)
|
83
|
+
self[:block_len2].read io.read(4)
|
84
|
+
|
85
|
+
unless self[:block_len].to_i == self[:block_len2].to_i
|
86
|
+
raise InvalidFileError, 'Incoherency in Interface Description Block'
|
87
|
+
end
|
88
|
+
|
89
|
+
self
|
90
|
+
end
|
91
|
+
|
92
|
+
# Add a xPB to this section
|
93
|
+
# @param [EPB,SPB] xpb
|
94
|
+
# @return [self]
|
95
|
+
def <<(xpb)
|
96
|
+
@packets << xpb
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
# Give timestamp resolution for this interface
|
101
|
+
# @param [Boolean] force if +true+, force decoding even if already done
|
102
|
+
# @return [Float]
|
103
|
+
def ts_resol(force: false)
|
104
|
+
if @options_decoded and not force
|
105
|
+
@ts_resol
|
106
|
+
else
|
107
|
+
packstr = (@endian == :little) ? 'v' : 'n'
|
108
|
+
idx = 0
|
109
|
+
options = self[:options]
|
110
|
+
opt_code = opt_len = 0
|
111
|
+
|
112
|
+
while idx < options.length do
|
113
|
+
opt_code, opt_len = options[idx, 4].unpack("#{packstr}2")
|
114
|
+
if opt_code == OPTION_IF_TSRESOL and opt_len == 1
|
115
|
+
tsresol = options[idx+4, 1].unpack('C').first
|
116
|
+
if tsresol & 0x80 == 0
|
117
|
+
@ts_resol = 10 ** -tsresol
|
118
|
+
else
|
119
|
+
@ts_resol = 2 ** -(tsresol & 0x7f)
|
120
|
+
end
|
121
|
+
|
122
|
+
@options_decoded = true
|
123
|
+
return @ts_resol
|
124
|
+
else
|
125
|
+
idx += 4 + opt_len
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
@options_decoded = true
|
130
|
+
@ts_resol = 1E-6 # default value
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Return the object as a String
|
135
|
+
# @return [String]
|
136
|
+
def to_s
|
137
|
+
pad_field :options
|
138
|
+
recalc_block_len
|
139
|
+
to_a.map(&:to_s).join + @packets.map(&:to_s).join
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
module PacketGen
|
2
|
+
module PcapNG
|
3
|
+
|
4
|
+
# {SHB} represents a Section Header Block (SHB) of a pcapng file.
|
5
|
+
#
|
6
|
+
# == SHB Definition
|
7
|
+
# Int32 :type Default: 0x0A0D0D0A
|
8
|
+
# Int32 :block_len
|
9
|
+
# Int32 :magic Default: 0x1A2B3C4D # :big is 0x4D3C2C1A
|
10
|
+
# Int16 :ver_major Default: 1
|
11
|
+
# Int16 :ver_minor Default: 0
|
12
|
+
# Int64 :section_len
|
13
|
+
# String :options Default: ''
|
14
|
+
# Int32 :block_len2
|
15
|
+
class SHB < Struct.new(:type, :block_len, :magic, :ver_major, :ver_minor,
|
16
|
+
:section_len, :options, :block_len2)
|
17
|
+
include StructFu
|
18
|
+
include Block
|
19
|
+
|
20
|
+
# @return [:little, :big]
|
21
|
+
attr_accessor :endian
|
22
|
+
# Get interfaces for this section
|
23
|
+
# @return [Array<IDB>]
|
24
|
+
attr_reader :interfaces
|
25
|
+
# Get unsupported blocks given in pcapng file as raw data
|
26
|
+
# @return [Array<UnknownBlock>]
|
27
|
+
attr_reader :unknown_blocks
|
28
|
+
|
29
|
+
# Magic value to retrieve SHB
|
30
|
+
MAGIC_INT32 = 0x1A2B3C4D
|
31
|
+
# Magic value (little endian version)
|
32
|
+
MAGIC_LITTLE = [MAGIC_INT32].pack('V')
|
33
|
+
# Magic value (big endian version)
|
34
|
+
MAGIC_BIG = [MAGIC_INT32].pack('N')
|
35
|
+
|
36
|
+
# Minimum SHB size
|
37
|
+
MIN_SIZE = 7*4
|
38
|
+
# +section_len+ value for undefined length
|
39
|
+
SECTION_LEN_UNDEFINED = 0xffffffff_ffffffff
|
40
|
+
|
41
|
+
# @param [Hash] options
|
42
|
+
# @option options [:little, :big] :endian set block endianness
|
43
|
+
# @option options [Integer] :type
|
44
|
+
# @option options [Integer] :block_len block total length
|
45
|
+
# @option options [Integer] :magic magic number to distinguish little endian
|
46
|
+
# sessions and big endian ones
|
47
|
+
# @option options [Integer] :ver_major number of the current major version of
|
48
|
+
# the format
|
49
|
+
# @option options [Integer] :ver_minor number of the current minor version of
|
50
|
+
# the format
|
51
|
+
# @option options [Integer] :section_len length of following section, excluding
|
52
|
+
# he SHB itself
|
53
|
+
# @option options [::String] :options
|
54
|
+
# @option options [Integer] :block_len2 block total length
|
55
|
+
def initialize(options={})
|
56
|
+
@endian = set_endianness(options[:endian] || :little)
|
57
|
+
@interfaces = []
|
58
|
+
@unknown_blocks = []
|
59
|
+
init_fields(options)
|
60
|
+
super(options[:type], options[:block_len], options[:magic], options[:ver_major],
|
61
|
+
options[:ver_minor], options[:section_len], options[:options], options[:block_len2])
|
62
|
+
end
|
63
|
+
|
64
|
+
# Used by {#initialize} to set the initial fields
|
65
|
+
# @see #initialize possible options
|
66
|
+
# @param [Hash] options
|
67
|
+
# @return [Hash] return +options+
|
68
|
+
def init_fields(options={})
|
69
|
+
options[:type] = @int32.new(options[:type] || PcapNG::SHB_TYPE.to_i)
|
70
|
+
options[:block_len] = @int32.new(options[:block_len] || MIN_SIZE)
|
71
|
+
options[:magic] = @int32.new(options[:magic] || MAGIC_INT32)
|
72
|
+
options[:ver_major] = @int16.new(options[:ver_major] || 1)
|
73
|
+
options[:ver_minor] = @int16.new(options[:ver_minor] || 0)
|
74
|
+
options[:section_len] = @int64.new(options[:section_len] || SECTION_LEN_UNDEFINED)
|
75
|
+
options[:options] = StructFu::String.new(options[:options] || '')
|
76
|
+
options[:block_len2] = @int32.new(options[:block_len2] || MIN_SIZE)
|
77
|
+
options
|
78
|
+
end
|
79
|
+
|
80
|
+
# Reads a String or a IO to populate the object
|
81
|
+
# @param [::String,IO] str_or_io
|
82
|
+
# @return [self]
|
83
|
+
def read(str_or_io)
|
84
|
+
if str_or_io.respond_to? :read
|
85
|
+
io = str_or_io
|
86
|
+
else
|
87
|
+
io = StringIO.new(force_binary(str_or_io.to_s))
|
88
|
+
end
|
89
|
+
return self if io.eof?
|
90
|
+
|
91
|
+
type_str = io.read(4)
|
92
|
+
unless type_str == PcapNG::SHB_TYPE.to_s
|
93
|
+
type = type_str.unpack('H*').join
|
94
|
+
raise InvalidFileError, "Incorrect type (#{type})for Section Header Block"
|
95
|
+
end
|
96
|
+
|
97
|
+
block_len_str = io.read(4)
|
98
|
+
|
99
|
+
magic_str = io.read(4)
|
100
|
+
case @endian
|
101
|
+
when :little
|
102
|
+
case magic_str
|
103
|
+
when MAGIC_LITTLE
|
104
|
+
when MAGIC_BIG
|
105
|
+
force_endianness :big
|
106
|
+
else
|
107
|
+
raise InvalidFileError, 'Incorrect magic for Section Header Block'
|
108
|
+
end
|
109
|
+
when :big
|
110
|
+
case magic_str
|
111
|
+
when MAGIC_BIG
|
112
|
+
when MAGIC_LITTLE
|
113
|
+
force_endianness :little
|
114
|
+
else
|
115
|
+
raise InvalidFileError, 'Incorrect magic for Section Header Block'
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
self[:type].read type_str
|
120
|
+
self[:block_len].read block_len_str
|
121
|
+
self[:magic].read magic_str
|
122
|
+
self[:ver_major].read io.read(2)
|
123
|
+
self[:ver_minor].read io.read(2)
|
124
|
+
self[:section_len].read io.read(8)
|
125
|
+
self[:options].read io.read(self[:block_len].to_i - MIN_SIZE)
|
126
|
+
self[:block_len2].read io.read(4)
|
127
|
+
|
128
|
+
unless self[:block_len].to_i == self[:block_len2].to_i
|
129
|
+
raise InvalidFileError, 'Incoherency in Section Header Block'
|
130
|
+
end
|
131
|
+
|
132
|
+
self
|
133
|
+
end
|
134
|
+
|
135
|
+
# Add a IDB to this section
|
136
|
+
# @param [IDB] idb
|
137
|
+
# @return [self]
|
138
|
+
def <<(idb)
|
139
|
+
@interfaces << idb
|
140
|
+
self
|
141
|
+
end
|
142
|
+
|
143
|
+
# Return the object as a String
|
144
|
+
# @return [String]
|
145
|
+
def to_s
|
146
|
+
body = @interfaces.map(&:to_s).join
|
147
|
+
unless self[:section_len].to_i == SECTION_LEN_UNDEFINED
|
148
|
+
self.section_len.value = body.size
|
149
|
+
end
|
150
|
+
pad_field :options
|
151
|
+
recalc_block_len
|
152
|
+
to_a.map(&:to_s).join + body
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def force_endianness(endian)
|
159
|
+
set_endianness endian
|
160
|
+
@endian = endian
|
161
|
+
self[:type] = @int32.new(self[:type].to_i)
|
162
|
+
self[:block_len] = @int32.new(self[:block_len].to_i)
|
163
|
+
self[:magic] = @int32.new(self[:magic].to_i)
|
164
|
+
self[:ver_major] = @int16.new(self[:ver_major].to_i)
|
165
|
+
self[:ver_minor] = @int16.new(self[:ver_minor].to_i)
|
166
|
+
self[:section_len] = @int64.new(self[:section_len].to_i)
|
167
|
+
self[:block_len2] = @int32.new(self[:block_len2].to_i)
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module PacketGen
|
2
|
+
module PcapNG
|
3
|
+
|
4
|
+
# {SPB} represents a Section Simple Packet Block (SPB) of a pcapng file.
|
5
|
+
#
|
6
|
+
# == Pcapng::SPB Definition
|
7
|
+
# Int32 :type Default: 0x00000003
|
8
|
+
# Int32 :block_len
|
9
|
+
# Int32 :orig_len
|
10
|
+
# String :data
|
11
|
+
# Int32 :block_len2
|
12
|
+
class SPB < Struct.new(:type, :block_len, :orig_len, :data, :block_len2)
|
13
|
+
include StructFu
|
14
|
+
include Block
|
15
|
+
|
16
|
+
# @return [:little, :big]
|
17
|
+
attr_accessor :endian
|
18
|
+
# @return [IPB]
|
19
|
+
attr_accessor :interface
|
20
|
+
|
21
|
+
# Minimum SPB size
|
22
|
+
MIN_SIZE = 4*4
|
23
|
+
|
24
|
+
# @param [Hash] options
|
25
|
+
# @option options [:little, :big] :endian set block endianness
|
26
|
+
# @option options [Integer] :type
|
27
|
+
# @option options [Integer] :block_len block total length
|
28
|
+
# @option options [Integer] :orig_len actual length of the packet when it was
|
29
|
+
# transmitted on the network
|
30
|
+
# @option options [::String] :data
|
31
|
+
# @option options [::String] :options
|
32
|
+
# @option options [Integer] :block_len2 block total length
|
33
|
+
def initialize(options={})
|
34
|
+
@endian = set_endianness(options[:endian] || :little)
|
35
|
+
init_fields(options)
|
36
|
+
super(options[:type], options[:block_len], options[:orig_len], options[:data],
|
37
|
+
options[:block_len2])
|
38
|
+
end
|
39
|
+
|
40
|
+
# Used by {#initialize} to set the initial fields
|
41
|
+
# @param [Hash] options
|
42
|
+
# @see #initialize possible options
|
43
|
+
# @return [Hash] return +options+
|
44
|
+
def init_fields(options={})
|
45
|
+
options[:type] = @int32.new(options[:type] || PcapNG::SPB_TYPE.to_i)
|
46
|
+
options[:block_len] = @int32.new(options[:block_len] || MIN_SIZE)
|
47
|
+
options[:orig_len] = @int32.new(options[:orig_len] || 0)
|
48
|
+
options[:data] = StructFu::String.new(options[:data] || '')
|
49
|
+
options[:block_len2] = @int32.new(options[:block_len2] || MIN_SIZE)
|
50
|
+
options
|
51
|
+
end
|
52
|
+
|
53
|
+
# Has this block option?
|
54
|
+
# @return [false]
|
55
|
+
def has_options?
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
59
|
+
# Reads a String or a IO to populate the object
|
60
|
+
# @param [::String,IO] str_or_io
|
61
|
+
# @return [self]
|
62
|
+
def read(str_or_io)
|
63
|
+
if str_or_io.respond_to? :read
|
64
|
+
io = str_or_io
|
65
|
+
else
|
66
|
+
io = StringIO.new(force_binary(str_or_io.to_s))
|
67
|
+
end
|
68
|
+
return self if io.eof?
|
69
|
+
|
70
|
+
self[:type].read io.read(4)
|
71
|
+
self[:block_len].read io.read(4)
|
72
|
+
self[:orig_len].read io.read(4)
|
73
|
+
# Take care of IDB snaplen
|
74
|
+
# CAUTION: snaplen == 0 -> no capture limit
|
75
|
+
if interface and interface.snaplen.to_i > 0
|
76
|
+
data_len = [self[:orig_len].to_i, interface.snaplen.to_i].min
|
77
|
+
else
|
78
|
+
data_len = self[:orig_len].to_i
|
79
|
+
end
|
80
|
+
data_pad_len = (4 - (data_len % 4)) % 4
|
81
|
+
self[:data].read io.read(data_len)
|
82
|
+
io.read data_pad_len
|
83
|
+
self[:block_len2].read io.read(4)
|
84
|
+
|
85
|
+
unless self[:block_len].to_i == self[:block_len2].to_i
|
86
|
+
raise InvalidFileError, 'Incoherency in Simple Packet Block'
|
87
|
+
end
|
88
|
+
|
89
|
+
self
|
90
|
+
end
|
91
|
+
|
92
|
+
# Return the object as a String
|
93
|
+
# @return [String]
|
94
|
+
def to_s
|
95
|
+
pad_field :data
|
96
|
+
recalc_block_len
|
97
|
+
to_a.map(&:to_s).join
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module PacketGen
|
2
|
+
module PcapNG
|
3
|
+
|
4
|
+
# {UnknownBlock} is used to handle unsupported blocks of a pcapng file.
|
5
|
+
class UnknownBlock < Struct.new(:type, :block_len, :body, :block_len2)
|
6
|
+
include StructFu
|
7
|
+
include Block
|
8
|
+
|
9
|
+
# @return [:little, :big]
|
10
|
+
attr_accessor :endian
|
11
|
+
# @return [SHB]
|
12
|
+
attr_accessor :section
|
13
|
+
|
14
|
+
# Minimum Iblock size
|
15
|
+
MIN_SIZE = 12
|
16
|
+
|
17
|
+
# @option options [:little, :big] :endian set block endianness
|
18
|
+
# @option options [Integer] :type
|
19
|
+
# @option options [Integer] :block_len block total length
|
20
|
+
# @option options [::String] :body
|
21
|
+
# @option options [Integer] :block_len2 block total length
|
22
|
+
def initialize(options={})
|
23
|
+
@endian = set_endianness(options[:endian] || :little)
|
24
|
+
init_fields(options)
|
25
|
+
super(options[:type], options[:block_len], options[:body], options[:block_len2])
|
26
|
+
end
|
27
|
+
|
28
|
+
# Used by {#initialize} to set the initial fields
|
29
|
+
# @see #initialize possible options
|
30
|
+
# @param [Hash] options
|
31
|
+
# @return [Hash] return +options+
|
32
|
+
def init_fields(options={})
|
33
|
+
options[:type] = @int32.new(options[:type] || 0)
|
34
|
+
options[:block_len] = @int32.new(options[:block_len] || MIN_SIZE)
|
35
|
+
options[:body] = StructFu::String.new(options[:body] || '')
|
36
|
+
options[:block_len2] = @int32.new(options[:block_len2] || MIN_SIZE)
|
37
|
+
options
|
38
|
+
end
|
39
|
+
|
40
|
+
# Has this block option?
|
41
|
+
# @return [false]
|
42
|
+
def has_options?
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
# Reads a String or a IO to populate the object
|
47
|
+
# @param [::String,IO] str_or_io
|
48
|
+
# @return [self]
|
49
|
+
def read(str_or_io)
|
50
|
+
if str_or_io.respond_to? :read
|
51
|
+
io = str_or_io
|
52
|
+
else
|
53
|
+
io = StringIO.new(force_binary(str_or_io.to_s))
|
54
|
+
end
|
55
|
+
return self if io.eof?
|
56
|
+
|
57
|
+
self[:type].read io.read(4)
|
58
|
+
self[:block_len].read io.read(4)
|
59
|
+
self[:body].read io.read(self[:block_len].to_i - MIN_SIZE)
|
60
|
+
self[:block_len2].read io.read(4)
|
61
|
+
|
62
|
+
unless self[:block_len].to_i == self[:block_len2].to_i
|
63
|
+
raise InvalidFileError, 'Incoherency in Header Block'
|
64
|
+
end
|
65
|
+
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
# Return the object as a String
|
70
|
+
# @return [String]
|
71
|
+
def to_s
|
72
|
+
pad_field :body
|
73
|
+
recalc_block_len
|
74
|
+
to_a.map(&:to_s).join
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|