packetgen 2.1.2 → 2.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/README.md +10 -3
- data/bin/pgconsole +25 -28
- data/lib/packetgen/config.rb +47 -31
- data/lib/packetgen/header/arp.rb +1 -2
- data/lib/packetgen/header/asn1_base.rb +4 -0
- data/lib/packetgen/header/dns/question.rb +16 -30
- data/lib/packetgen/header/dot11.rb +73 -8
- data/lib/packetgen/header/dot11/control.rb +11 -1
- data/lib/packetgen/header/dot11/data.rb +14 -0
- data/lib/packetgen/header/dot11/element.rb +2 -0
- data/lib/packetgen/header/dot11/management.rb +43 -0
- data/lib/packetgen/header/dot11/sub_mngt.rb +84 -1
- data/lib/packetgen/header/dot1x.rb +8 -27
- data/lib/packetgen/header/ike.rb +10 -25
- data/lib/packetgen/header/ike/cert.rb +15 -31
- data/lib/packetgen/header/ike/notify.rb +35 -78
- data/lib/packetgen/header/ike/sa.rb +13 -25
- data/lib/packetgen/header/ike/ts.rb +4 -3
- data/lib/packetgen/inspect.rb +6 -4
- data/lib/packetgen/packet.rb +1 -1
- data/lib/packetgen/pcapng/epb.rb +1 -1
- data/lib/packetgen/pcapng/idb.rb +2 -2
- data/lib/packetgen/pcapng/shb.rb +1 -1
- data/lib/packetgen/pcapng/spb.rb +1 -1
- data/lib/packetgen/pcapng/unknown_block.rb +2 -2
- data/lib/packetgen/proto.rb +1 -1
- data/lib/packetgen/types.rb +1 -0
- data/lib/packetgen/types/enum.rb +165 -0
- data/lib/packetgen/types/fields.rb +29 -9
- data/lib/packetgen/types/int.rb +4 -0
- data/lib/packetgen/utils.rb +101 -0
- data/lib/packetgen/utils/arp_spoofer.rb +191 -0
- data/lib/packetgen/version.rb +1 -1
- data/packetgen.gemspec +1 -1
- metadata +7 -4
@@ -40,70 +40,44 @@ module PacketGen
|
|
40
40
|
# pkt = PacketGen.gen('IP').add('UDP').add('IKE').add('IKE::Notify', protocol: 'ESP', spi_size: 4, type: 'INVALID_SYNTAX')
|
41
41
|
# pkt.ike_notify.spi.read PacketGen::Types::Int32.new(0x12345678).to_s
|
42
42
|
# pkt.calc_length
|
43
|
-
#
|
43
|
+
# @author Sylvain Daubert
|
44
44
|
class Notify < Payload
|
45
45
|
|
46
46
|
# Payload type number
|
47
47
|
PAYLOAD_TYPE = 41
|
48
48
|
|
49
|
-
#
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
# Child SA not found
|
82
|
-
TYPE_CHILD_SA_NOT_FOUND = 44
|
83
|
-
# Initial contact
|
84
|
-
TYPE_INITIAL_CONTACT = 16384
|
85
|
-
# Set window size
|
86
|
-
TYPE_SET_WINDOW_SIZE = 16385
|
87
|
-
# Additional traffic selector possible
|
88
|
-
TYPE_ADDITIONAL_TS_POSSIBLE = 16386
|
89
|
-
# IPcomp supported
|
90
|
-
TYPE_IPCOMP_SUPPORTED = 16387
|
91
|
-
# NAT detection source IP
|
92
|
-
TYPE_NAT_DETECTION_SOURCE_IP = 16388
|
93
|
-
# NAT detection destination IP
|
94
|
-
TYPE_NAT_DETECTION_DESTINATION_IP = 16389
|
95
|
-
# Cookie
|
96
|
-
TYPE_COOKIE = 16390
|
97
|
-
# Use transport mode (tunnel mode is default)
|
98
|
-
TYPE_USE_TRANSPORT_MODE = 16391
|
99
|
-
# HTTP certificate look up supported
|
100
|
-
TYPE_HTTP_CERT_LOOKUP_SUPPORTED = 16392
|
101
|
-
# Rekey SA
|
102
|
-
TYPE_REKEY_SA = 16393
|
103
|
-
# ESP TFC paddin not supported
|
104
|
-
TYPE_ESP_TFC_PADDING_NOT_SUPPORTED = 16394
|
105
|
-
# Non-first fragment also
|
106
|
-
TYPE_NON_FIRST_FRAGMENTS_ALSO = 16395
|
49
|
+
# Message types
|
50
|
+
TYPES = {
|
51
|
+
'UNSUPPORTED_CRITICAL_PAYLOAD' => 1,
|
52
|
+
'INVALID_IKE_SPI' => 4,
|
53
|
+
'INVALID_MAJOR_VERSION' => 5,
|
54
|
+
'INVALID_SYNTAX' => 7,
|
55
|
+
'INVALID_MESSAGE_ID' => 9,
|
56
|
+
'INVALID_SPI' => 11,
|
57
|
+
'NO_PROPOSAL_CHOSEN' => 14,
|
58
|
+
'INVALID_KE_PAYLOAD' => 17,
|
59
|
+
'AUTHENTICATION_FAILED' => 24,
|
60
|
+
'SINGLE_PAIR_REQUIRED' => 34,
|
61
|
+
'NO_ADDITIONAL_SAS' => 35,
|
62
|
+
'INTERNAL_ADDRESS_FAILURE' => 36,
|
63
|
+
'FAILED_CP_REQUIRED' => 37,
|
64
|
+
'TS_UNACCEPTABLE' => 38,
|
65
|
+
'INVALID_SELECTORS' => 39,
|
66
|
+
'TEMPORARY_FAILURE' => 43,
|
67
|
+
'CHILD_SA_NOT_FOUND' => 44,
|
68
|
+
'INITIAL_CONTACT' => 16384,
|
69
|
+
'SET_WINDOW_SIZE' => 16385,
|
70
|
+
'ADDITIONAL_TS_POSSIBLE' => 16386,
|
71
|
+
'IPCOMP_SUPPORTED' => 16387,
|
72
|
+
'NAT_DETECTION_SOURCE_IP' => 16388,
|
73
|
+
'NAT_DETECTION_DESTINATION_IP' => 16389,
|
74
|
+
'COOKIE' => 16390,
|
75
|
+
'USE_TRANSPORT_MODE' => 16391,
|
76
|
+
'HTTP_CERT_LOOKUP_SUPPORTED' => 16392,
|
77
|
+
'REKEY_SA' => 16393,
|
78
|
+
'ESP_TFC_PADDING_NOT_SUPPORTED' => 16394,
|
79
|
+
'NON_FIRST_FRAGMENTS_ALSO' => 16395,
|
80
|
+
}.freeze
|
107
81
|
|
108
82
|
# @!attribute [r] protocol
|
109
83
|
# 8-bit protocol ID. If this notification concerns an existing
|
@@ -127,7 +101,7 @@ module PacketGen
|
|
127
101
|
# @!attribute message_type
|
128
102
|
# 16-bit notify message type. Specifies the type of notification message.
|
129
103
|
# @return [Integer]
|
130
|
-
define_field_before :content, :message_type, Types::
|
104
|
+
define_field_before :content, :message_type, Types::Int16Enum, enum: TYPES, default: 0
|
131
105
|
# @!attribute spi
|
132
106
|
# the sending entity's SPI. When the {#spi_size} field is zero,
|
133
107
|
# this field is not present in the proposal.
|
@@ -162,20 +136,6 @@ module PacketGen
|
|
162
136
|
self[:protocol].value = proto
|
163
137
|
end
|
164
138
|
|
165
|
-
# Set message type
|
166
|
-
# @param [Integer,String] value
|
167
|
-
# @return [Integer]
|
168
|
-
def message_type=(value)
|
169
|
-
type = case value
|
170
|
-
when Integer
|
171
|
-
value
|
172
|
-
else
|
173
|
-
c = self.class.constants.grep(/TYPE_#{value}/).first
|
174
|
-
c ? self.class.const_get(c) : nil
|
175
|
-
end
|
176
|
-
raise ArgumentError, "unknown message type #{value.inspect}" unless type
|
177
|
-
self[:message_type].value = type
|
178
|
-
end
|
179
139
|
alias type= message_type=
|
180
140
|
|
181
141
|
# Get protocol name
|
@@ -190,10 +150,7 @@ module PacketGen
|
|
190
150
|
# Get message type name
|
191
151
|
# @return [String]
|
192
152
|
def human_message_type
|
193
|
-
|
194
|
-
select { |c| self.class.const_get(c) == type }.
|
195
|
-
first || "type #{type}"
|
196
|
-
name.to_s.sub(/TYPE_/, '')
|
153
|
+
self[:message_type].to_human
|
197
154
|
end
|
198
155
|
alias human_type human_message_type
|
199
156
|
|
@@ -122,11 +122,13 @@ module PacketGen
|
|
122
122
|
# @author Sylvain Daubert
|
123
123
|
class Transform < Types::Fields
|
124
124
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
125
|
+
TYPES = {
|
126
|
+
'ENCR' => 1,
|
127
|
+
'PRF' => 2,
|
128
|
+
'INTG' => 3,
|
129
|
+
'DH' => 4,
|
130
|
+
'ESN' => 5
|
131
|
+
}.freeze
|
130
132
|
|
131
133
|
ENCR_DES_IV64 = 1
|
132
134
|
ENCR_DES = 2
|
@@ -215,7 +217,7 @@ module PacketGen
|
|
215
217
|
# 8-bit transform type. The Transform Type is the cryptographic
|
216
218
|
# algorithm type (i.e. encryption, PRF, integrity, etc.)
|
217
219
|
# @return [Integer]
|
218
|
-
define_field :type, Types::
|
220
|
+
define_field :type, Types::Int8Enum, enum: TYPES
|
219
221
|
# @!attribute rsv2
|
220
222
|
# 8-bit reserved field
|
221
223
|
# @return [Integer]
|
@@ -237,21 +239,6 @@ module PacketGen
|
|
237
239
|
self.id = options[:id] if options[:id]
|
238
240
|
end
|
239
241
|
|
240
|
-
# Set transform type
|
241
|
-
# @param [Integer,String] value
|
242
|
-
# @return [Integer]
|
243
|
-
def type=(value)
|
244
|
-
type = case value
|
245
|
-
when Integer
|
246
|
-
value
|
247
|
-
else
|
248
|
-
c = self.class.constants.grep(/TYPE_#{value}/).first
|
249
|
-
c ? self.class.const_get(c) : nil
|
250
|
-
end
|
251
|
-
raise ArgumentError, "unknown type #{value.inspect}" unless type
|
252
|
-
self[:type].value = type
|
253
|
-
end
|
254
|
-
|
255
242
|
# Set transform ID
|
256
243
|
# @param [Integer,String] value
|
257
244
|
# @return [Integer]
|
@@ -295,10 +282,11 @@ module PacketGen
|
|
295
282
|
# Get human-readable type
|
296
283
|
# @return [String]
|
297
284
|
def human_type
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
285
|
+
if self[:type].enum.has_value? self.type
|
286
|
+
self[:type].to_human
|
287
|
+
else
|
288
|
+
"type[#{self.type}]"
|
289
|
+
end
|
302
290
|
end
|
303
291
|
|
304
292
|
# Get human-readable ID
|
@@ -203,7 +203,8 @@ module PacketGen
|
|
203
203
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
204
204
|
# These specific fields are:
|
205
205
|
# * {#num_ts},
|
206
|
-
# * {#
|
206
|
+
# * {#rsv1},
|
207
|
+
# * {#rsv2},
|
207
208
|
# * and {#traffic_selectors}.
|
208
209
|
#
|
209
210
|
# == Create a TSi payload
|
@@ -228,7 +229,7 @@ module PacketGen
|
|
228
229
|
# First 8-bit RESERVED field
|
229
230
|
# @return [Integer]
|
230
231
|
define_field_before :body, :rsv1, Types::Int8
|
231
|
-
# @!attribute
|
232
|
+
# @!attribute rsv2
|
232
233
|
# Last 16-bit RESERVED field
|
233
234
|
# @return [Integer]
|
234
235
|
define_field_before :body, :rsv2, Types::Int16
|
@@ -276,7 +277,7 @@ module PacketGen
|
|
276
277
|
end
|
277
278
|
str
|
278
279
|
end
|
279
|
-
|
280
|
+
end
|
280
281
|
|
281
282
|
class TSr < TSi
|
282
283
|
# Payload type number
|
data/lib/packetgen/inspect.rb
CHANGED
@@ -60,10 +60,12 @@ module PacketGen
|
|
60
60
|
end
|
61
61
|
|
62
62
|
# Format a ASN.1 attribute for +#inspect+.
|
63
|
-
#
|
64
|
-
# * attribute value is a
|
63
|
+
# 4 cases are handled:
|
64
|
+
# * attribute value is a =RANS1::Types::Enumerated+: show named value and
|
65
|
+
# its integer value as hexdecimal format,
|
66
|
+
# * attribute value is a +RASN1::Types::Integer+: show value as integer and in
|
65
67
|
# hexdecimal format,
|
66
|
-
# * attribute value
|
68
|
+
# * attribute value is a +RASN1::Model+: only show its root type,
|
67
69
|
# * else, +#to_s+ is used to format attribute value.
|
68
70
|
# @param [Symbol] name attribute name
|
69
71
|
# @param [RASN1::Types::Base,RASN1::Model] attr attribute
|
@@ -98,7 +100,7 @@ module PacketGen
|
|
98
100
|
o_str = octets.map { |v| " %02x" % v}.join
|
99
101
|
str << o_str
|
100
102
|
str << ' ' * (3*16 - o_str.size) unless o_str.size >= 3*16
|
101
|
-
str << ' ' << octets.map { |v| v < 128 && v >
|
103
|
+
str << ' ' << octets.map { |v| v < 128 && v > 31 ? v.chr : '.' }.join
|
102
104
|
str << "\n"
|
103
105
|
end
|
104
106
|
end
|
data/lib/packetgen/packet.rb
CHANGED
@@ -99,7 +99,7 @@ module PacketGen
|
|
99
99
|
|
100
100
|
# Read packets from +filename+. Mays read Pcap and Pcap-NG formats.
|
101
101
|
#
|
102
|
-
# For more control, see {PcapNG::File} or
|
102
|
+
# For more control, see {PcapNG::File} or +PCAPRUB::Pcap+.
|
103
103
|
# @param [String] filename PcapNG or Pcap file.
|
104
104
|
# @return [Array<Packet>]
|
105
105
|
# @author Sylvain Daubert
|
data/lib/packetgen/pcapng/epb.rb
CHANGED
data/lib/packetgen/pcapng/idb.rb
CHANGED
@@ -89,7 +89,7 @@ module PacketGen
|
|
89
89
|
check_len_coherency
|
90
90
|
self
|
91
91
|
end
|
92
|
-
|
92
|
+
|
93
93
|
# Add a xPB to this section
|
94
94
|
# @param [EPB,SPB] xpb
|
95
95
|
# @return [self]
|
@@ -137,7 +137,7 @@ module PacketGen
|
|
137
137
|
def to_s
|
138
138
|
pad_field :options
|
139
139
|
recalc_block_len
|
140
|
-
|
140
|
+
super << @packets.map(&:to_s).join
|
141
141
|
end
|
142
142
|
|
143
143
|
end
|
data/lib/packetgen/pcapng/shb.rb
CHANGED
data/lib/packetgen/pcapng/spb.rb
CHANGED
@@ -54,7 +54,7 @@ module PacketGen
|
|
54
54
|
self[:block_len].read io.read(4)
|
55
55
|
self[:body].read io.read(self[:block_len].to_i - MIN_SIZE)
|
56
56
|
self[:block_len2].read io.read(4)
|
57
|
-
|
57
|
+
|
58
58
|
unless self[:block_len].to_i == self[:block_len2].to_i
|
59
59
|
raise InvalidFileError, 'Incoherency in Header Block'
|
60
60
|
end
|
@@ -67,7 +67,7 @@ module PacketGen
|
|
67
67
|
def to_s
|
68
68
|
pad_field :body
|
69
69
|
recalc_block_len
|
70
|
-
|
70
|
+
super
|
71
71
|
end
|
72
72
|
|
73
73
|
end
|
data/lib/packetgen/proto.rb
CHANGED
data/lib/packetgen/types.rb
CHANGED
@@ -0,0 +1,165 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# This file is part of PacketGen
|
3
|
+
# See https://github.com/sdaubert/packetgen for more informations
|
4
|
+
# Copyright (C) 2032 Sylvain Daubert <sylvain.daubert@laposte.net>
|
5
|
+
# This program is published under MIT license.
|
6
|
+
|
7
|
+
module PacketGen
|
8
|
+
module Types
|
9
|
+
|
10
|
+
# @abstract Base enum class to handle binary integers with limited
|
11
|
+
# authorized values
|
12
|
+
# An {Enum} type is used to handle an {Int} field with limited
|
13
|
+
# and named values.
|
14
|
+
#
|
15
|
+
# == Simple example
|
16
|
+
# enum = Int8Enum.new(0, 'low' => 0, 'medium' => 1, 'high' => 2})
|
17
|
+
# In this example, +enum+ is a 8-bit field which may take one
|
18
|
+
# among three values: +low+, +medium+ or +high+:
|
19
|
+
# enum.value = 'high'
|
20
|
+
# enum.value # => 2
|
21
|
+
# enum.value = 1
|
22
|
+
# enum.value # => 1
|
23
|
+
# enum.to_human # => "medium"
|
24
|
+
# Setting an unknown value will raise an exception:
|
25
|
+
# enum.value = 4 # => raise!
|
26
|
+
# enum.value = 'unknown' # => raise!
|
27
|
+
# But {#read} will not raise when reading an outbound value. This
|
28
|
+
# to enable decoding (or forging) of bad packets.
|
29
|
+
# @since 2.1.3
|
30
|
+
# @author Sylvain Daubert
|
31
|
+
class Enum < Int
|
32
|
+
|
33
|
+
# @return [Hash]
|
34
|
+
attr_reader :enum
|
35
|
+
|
36
|
+
# @param [Hash] enum enumerated values. Default value is taken from
|
37
|
+
# first element unless given.
|
38
|
+
# @param [:little,:big,nil] endian
|
39
|
+
# @param [Integer,nil] width
|
40
|
+
# @param [Integer,nil] default default value
|
41
|
+
def initialize(enum, endian=nil, width=nil, default=nil)
|
42
|
+
default ||= enum[enum.keys.first]
|
43
|
+
super(nil, endian, width, default)
|
44
|
+
@enum = enum
|
45
|
+
end
|
46
|
+
|
47
|
+
# Setter for value attribute
|
48
|
+
# @param [Integer, String,nil] v value as an Integer or as a String
|
49
|
+
# from enumration
|
50
|
+
# @return [Integer]
|
51
|
+
def value=(v)
|
52
|
+
ival = case v
|
53
|
+
when NilClass
|
54
|
+
nil
|
55
|
+
when ::String
|
56
|
+
raise ArgumentError, "#{v.inspect} not in enumeration" unless @enum.has_key? v
|
57
|
+
@enum[v]
|
58
|
+
else
|
59
|
+
raise ArgumentError, "#{v.inspect} not in enumeration" unless @enum.has_value? v
|
60
|
+
v
|
61
|
+
end
|
62
|
+
@value = ival
|
63
|
+
end
|
64
|
+
|
65
|
+
# To handle human API: set value from a String
|
66
|
+
alias :from_human :value=
|
67
|
+
|
68
|
+
# Get human readable value (enum name)
|
69
|
+
# @return [String]
|
70
|
+
def to_human
|
71
|
+
@enum.key(to_i) || "<unknown:#{@value}>"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Enumeration on one byte. See {Enum}.
|
76
|
+
# @author Sylvain Daubert
|
77
|
+
# @since 2.1.3
|
78
|
+
class Int8Enum < Enum
|
79
|
+
# @param [Integer] default
|
80
|
+
# @param [Hash] enum
|
81
|
+
def initialize(enum, default=nil)
|
82
|
+
super(enum, nil, 1, default)
|
83
|
+
@packstr = { nil => 'C' }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Enumeration on 2-byte integer. See {Enum}.
|
88
|
+
# @author Sylvain Daubert
|
89
|
+
# @since 2.1.3
|
90
|
+
class Int16Enum < Enum
|
91
|
+
# @param [Hash] enum
|
92
|
+
# @param [:big, :little] endian
|
93
|
+
# @param [Integer,nil] default default value
|
94
|
+
def initialize(enum, endian=:big, default=nil)
|
95
|
+
super(enum, endian, 2, default)
|
96
|
+
@packstr = { big: 'n', little: 'v' }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Enumeration on big endian 2-byte integer. See {Enum}.
|
101
|
+
# @author Sylvain Daubert
|
102
|
+
# @since 2.1.3
|
103
|
+
class Int16beEnum < Int16Enum
|
104
|
+
undef endian=
|
105
|
+
|
106
|
+
# @param [Hash] enum
|
107
|
+
# @param [Integer,nil] default default value
|
108
|
+
def initialize(enum, default=nil)
|
109
|
+
super(enum, :big, default)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Enumeration on big endian 2-byte integer. See {Enum}.
|
114
|
+
# @author Sylvain Daubert
|
115
|
+
# @since 2.1.3
|
116
|
+
class Int16leEnum < Int16Enum
|
117
|
+
undef endian=
|
118
|
+
|
119
|
+
# @param [Hash] enum
|
120
|
+
# @param [Integer,nil] default default value
|
121
|
+
def initialize(enum, default=nil)
|
122
|
+
super(enum, :little, default)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Enumeration on 4-byte integer. See {Enum}.
|
127
|
+
# @author Sylvain Daubert
|
128
|
+
# @since 2.1.3
|
129
|
+
class Int32Enum < Enum
|
130
|
+
# @param [Hash] enum
|
131
|
+
# @param [:big, :little] endian
|
132
|
+
# @param [Integer,nil] default default value
|
133
|
+
def initialize(enum, endian=:big, default=nil)
|
134
|
+
super(enum, endian, 4, default)
|
135
|
+
@packstr = { big: 'N', little: 'V' }
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Enumeration on big endian 4-byte integer. See {Enum}.
|
140
|
+
# @author Sylvain Daubert
|
141
|
+
# @since 2.1.3
|
142
|
+
class Int32beEnum < Int32Enum
|
143
|
+
undef endian=
|
144
|
+
|
145
|
+
# @param [Hash] enum
|
146
|
+
# @param [Integer,nil] default default value
|
147
|
+
def initialize(enum, default=nil)
|
148
|
+
super(enum, :big, default)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Enumeration on big endian 4-byte integer. See {Enum}.
|
153
|
+
# @author Sylvain Daubert
|
154
|
+
# @since 2.1.3
|
155
|
+
class Int32leEnum < Int32Enum
|
156
|
+
undef endian=
|
157
|
+
|
158
|
+
# @param [Hash] enum
|
159
|
+
# @param [Integer,nil] default default value
|
160
|
+
def initialize(enum, default=nil)
|
161
|
+
super(enum, :little, default)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|