rubyntlm 0.5.3 → 0.6.0
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.
- checksums.yaml +4 -4
- data/.gitignore +3 -3
- data/.rspec +2 -2
- data/.travis.yml +10 -11
- data/CHANGELOG.md +5 -5
- data/Gemfile +3 -3
- data/LICENSE +19 -19
- data/Rakefile +22 -22
- data/lib/net/ntlm.rb +266 -263
- data/lib/net/ntlm/blob.rb +28 -28
- data/lib/net/ntlm/channel_binding.rb +65 -0
- data/lib/net/ntlm/client.rb +65 -65
- data/lib/net/ntlm/client/session.rb +237 -223
- data/lib/net/ntlm/encode_util.rb +49 -49
- data/lib/net/ntlm/exceptions.rb +14 -0
- data/lib/net/ntlm/field.rb +34 -34
- data/lib/net/ntlm/field_set.rb +129 -129
- data/lib/net/ntlm/int16_le.rb +25 -25
- data/lib/net/ntlm/int32_le.rb +24 -24
- data/lib/net/ntlm/int64_le.rb +25 -25
- data/lib/net/ntlm/message.rb +129 -129
- data/lib/net/ntlm/message/type0.rb +16 -16
- data/lib/net/ntlm/message/type1.rb +18 -18
- data/lib/net/ntlm/message/type2.rb +102 -102
- data/lib/net/ntlm/message/type3.rb +131 -131
- data/lib/net/ntlm/security_buffer.rb +47 -47
- data/lib/net/ntlm/string.rb +34 -34
- data/lib/net/ntlm/target_info.rb +89 -0
- data/lib/net/ntlm/version.rb +11 -11
- data/rubyntlm.gemspec +28 -28
- data/spec/lib/net/ntlm/blob_spec.rb +16 -16
- data/spec/lib/net/ntlm/channel_binding_spec.rb +17 -0
- data/spec/lib/net/ntlm/client/session_spec.rb +68 -68
- data/spec/lib/net/ntlm/client_spec.rb +64 -64
- data/spec/lib/net/ntlm/encode_util_spec.rb +16 -16
- data/spec/lib/net/ntlm/field_set_spec.rb +33 -33
- data/spec/lib/net/ntlm/field_spec.rb +34 -34
- data/spec/lib/net/ntlm/int16_le_spec.rb +17 -17
- data/spec/lib/net/ntlm/int32_le_spec.rb +18 -18
- data/spec/lib/net/ntlm/int64_le_spec.rb +18 -18
- data/spec/lib/net/ntlm/message/type0_spec.rb +20 -20
- data/spec/lib/net/ntlm/message/type1_spec.rb +131 -131
- data/spec/lib/net/ntlm/message/type2_spec.rb +132 -132
- data/spec/lib/net/ntlm/message/type3_spec.rb +225 -225
- data/spec/lib/net/ntlm/message_spec.rb +16 -16
- data/spec/lib/net/ntlm/security_buffer_spec.rb +64 -64
- data/spec/lib/net/ntlm/string_spec.rb +72 -72
- data/spec/lib/net/ntlm/target_info_spec.rb +76 -0
- data/spec/lib/net/ntlm/version_spec.rb +27 -27
- data/spec/lib/net/ntlm_spec.rb +127 -127
- data/spec/spec_helper.rb +22 -22
- data/spec/support/certificates/sha_256_hash.pem +19 -0
- data/spec/support/shared/examples/net/ntlm/field_shared.rb +25 -25
- data/spec/support/shared/examples/net/ntlm/fieldset_shared.rb +239 -239
- data/spec/support/shared/examples/net/ntlm/int_shared.rb +43 -43
- data/spec/support/shared/examples/net/ntlm/message_shared.rb +35 -35
- metadata +12 -3
@@ -1,131 +1,131 @@
|
|
1
|
-
module Net
|
2
|
-
module NTLM
|
3
|
-
class Message
|
4
|
-
|
5
|
-
# @private false
|
6
|
-
class Type3 < Message
|
7
|
-
|
8
|
-
string :sign, {:size => 8, :value => SSP_SIGN}
|
9
|
-
int32LE :type, {:value => 3}
|
10
|
-
security_buffer :lm_response, {:value => ""}
|
11
|
-
security_buffer :ntlm_response, {:value => ""}
|
12
|
-
security_buffer :domain, {:value => ""}
|
13
|
-
security_buffer :user, {:value => ""}
|
14
|
-
security_buffer :workstation, {:value => ""}
|
15
|
-
security_buffer :session_key, {:value => "", :active => false }
|
16
|
-
int32LE :flag, {:value => 0, :active => false }
|
17
|
-
string :os_version, {:size => 8, :active => false }
|
18
|
-
|
19
|
-
class << Type3
|
20
|
-
# Builds a Type 3 packet
|
21
|
-
# @note All options must be properly encoded with either unicode or oem encoding
|
22
|
-
# @return [Type3]
|
23
|
-
# @option arg [String] :lm_response The LM hash
|
24
|
-
# @option arg [String] :ntlm_response The NTLM hash
|
25
|
-
# @option arg [String] :domain The domain to authenticate to
|
26
|
-
# @option arg [String] :workstation The name of the calling workstation
|
27
|
-
# @option arg [String] :session_key The session key
|
28
|
-
# @option arg [Integer] :flag Flags for the packet
|
29
|
-
def create(arg, opt ={})
|
30
|
-
t = new
|
31
|
-
t.lm_response = arg[:lm_response]
|
32
|
-
t.ntlm_response = arg[:ntlm_response]
|
33
|
-
t.domain = arg[:domain]
|
34
|
-
t.user = arg[:user]
|
35
|
-
|
36
|
-
if arg[:workstation]
|
37
|
-
t.workstation = arg[:workstation]
|
38
|
-
end
|
39
|
-
|
40
|
-
if arg[:session_key]
|
41
|
-
t.enable(:session_key)
|
42
|
-
t.session_key = arg[:session_key]
|
43
|
-
end
|
44
|
-
|
45
|
-
if arg[:flag]
|
46
|
-
t.enable(:session_key)
|
47
|
-
t.enable(:flag)
|
48
|
-
t.flag = arg[:flag]
|
49
|
-
end
|
50
|
-
t
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
# @param server_challenge (see #password?)
|
55
|
-
def blank_password?(server_challenge)
|
56
|
-
password?('', server_challenge)
|
57
|
-
end
|
58
|
-
|
59
|
-
# @param password [String]
|
60
|
-
# @param server_challenge [String] The server's {Type2#challenge challenge} from the
|
61
|
-
# {Type2} message for which this object is a response.
|
62
|
-
# @return [true] if +password+ was the password used to generate this
|
63
|
-
# {Type3} message
|
64
|
-
# @return [false] otherwise
|
65
|
-
def password?(password, server_challenge)
|
66
|
-
case ntlm_version
|
67
|
-
when :ntlm2_session
|
68
|
-
ntlm2_session_password?(password, server_challenge)
|
69
|
-
when :ntlmv2
|
70
|
-
ntlmv2_password?(password, server_challenge)
|
71
|
-
else
|
72
|
-
raise
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
# @return [Symbol]
|
77
|
-
def ntlm_version
|
78
|
-
if ntlm_response.size == 24 && lm_response[0,8] != "\x00"*8 && lm_response[8,16] == "\x00"*16
|
79
|
-
:ntlm2_session
|
80
|
-
elsif ntlm_response.size == 24
|
81
|
-
:ntlmv1
|
82
|
-
elsif ntlm_response.size > 24
|
83
|
-
:ntlmv2
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
private
|
88
|
-
|
89
|
-
def ntlm2_session_password?(password, server_challenge)
|
90
|
-
hash = ntlm_response
|
91
|
-
_lm, empty_hash = NTLM.ntlm2_session(
|
92
|
-
{
|
93
|
-
:ntlm_hash => NTLM.ntlm_hash(password),
|
94
|
-
:challenge => server_challenge,
|
95
|
-
},
|
96
|
-
{
|
97
|
-
:client_challenge => lm_response[0,8]
|
98
|
-
}
|
99
|
-
)
|
100
|
-
hash == empty_hash
|
101
|
-
end
|
102
|
-
|
103
|
-
def ntlmv2_password?(password, server_challenge)
|
104
|
-
|
105
|
-
# The first 16 bytes of the ntlm_response are the HMAC of the blob
|
106
|
-
# that follows it.
|
107
|
-
blob = Blob.new
|
108
|
-
blob.parse(ntlm_response[16..-1])
|
109
|
-
|
110
|
-
empty_hash = NTLM.ntlmv2_response(
|
111
|
-
{
|
112
|
-
# user and domain came from the serialized data here, so
|
113
|
-
# they're already unicode
|
114
|
-
:ntlmv2_hash => NTLM.ntlmv2_hash(user, '', domain, :unicode => true),
|
115
|
-
:challenge => server_challenge,
|
116
|
-
:target_info => blob.target_info
|
117
|
-
},
|
118
|
-
{
|
119
|
-
:client_challenge => blob.challenge,
|
120
|
-
# The blob's timestamp is already in milliseconds since 1601,
|
121
|
-
# so convert it back to epoch time first
|
122
|
-
:timestamp => (blob.timestamp / 10_000_000) - NTLM::TIME_OFFSET,
|
123
|
-
}
|
124
|
-
)
|
125
|
-
|
126
|
-
empty_hash == ntlm_response
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
1
|
+
module Net
|
2
|
+
module NTLM
|
3
|
+
class Message
|
4
|
+
|
5
|
+
# @private false
|
6
|
+
class Type3 < Message
|
7
|
+
|
8
|
+
string :sign, {:size => 8, :value => SSP_SIGN}
|
9
|
+
int32LE :type, {:value => 3}
|
10
|
+
security_buffer :lm_response, {:value => ""}
|
11
|
+
security_buffer :ntlm_response, {:value => ""}
|
12
|
+
security_buffer :domain, {:value => ""}
|
13
|
+
security_buffer :user, {:value => ""}
|
14
|
+
security_buffer :workstation, {:value => ""}
|
15
|
+
security_buffer :session_key, {:value => "", :active => false }
|
16
|
+
int32LE :flag, {:value => 0, :active => false }
|
17
|
+
string :os_version, {:size => 8, :active => false }
|
18
|
+
|
19
|
+
class << Type3
|
20
|
+
# Builds a Type 3 packet
|
21
|
+
# @note All options must be properly encoded with either unicode or oem encoding
|
22
|
+
# @return [Type3]
|
23
|
+
# @option arg [String] :lm_response The LM hash
|
24
|
+
# @option arg [String] :ntlm_response The NTLM hash
|
25
|
+
# @option arg [String] :domain The domain to authenticate to
|
26
|
+
# @option arg [String] :workstation The name of the calling workstation
|
27
|
+
# @option arg [String] :session_key The session key
|
28
|
+
# @option arg [Integer] :flag Flags for the packet
|
29
|
+
def create(arg, opt ={})
|
30
|
+
t = new
|
31
|
+
t.lm_response = arg[:lm_response]
|
32
|
+
t.ntlm_response = arg[:ntlm_response]
|
33
|
+
t.domain = arg[:domain]
|
34
|
+
t.user = arg[:user]
|
35
|
+
|
36
|
+
if arg[:workstation]
|
37
|
+
t.workstation = arg[:workstation]
|
38
|
+
end
|
39
|
+
|
40
|
+
if arg[:session_key]
|
41
|
+
t.enable(:session_key)
|
42
|
+
t.session_key = arg[:session_key]
|
43
|
+
end
|
44
|
+
|
45
|
+
if arg[:flag]
|
46
|
+
t.enable(:session_key)
|
47
|
+
t.enable(:flag)
|
48
|
+
t.flag = arg[:flag]
|
49
|
+
end
|
50
|
+
t
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# @param server_challenge (see #password?)
|
55
|
+
def blank_password?(server_challenge)
|
56
|
+
password?('', server_challenge)
|
57
|
+
end
|
58
|
+
|
59
|
+
# @param password [String]
|
60
|
+
# @param server_challenge [String] The server's {Type2#challenge challenge} from the
|
61
|
+
# {Type2} message for which this object is a response.
|
62
|
+
# @return [true] if +password+ was the password used to generate this
|
63
|
+
# {Type3} message
|
64
|
+
# @return [false] otherwise
|
65
|
+
def password?(password, server_challenge)
|
66
|
+
case ntlm_version
|
67
|
+
when :ntlm2_session
|
68
|
+
ntlm2_session_password?(password, server_challenge)
|
69
|
+
when :ntlmv2
|
70
|
+
ntlmv2_password?(password, server_challenge)
|
71
|
+
else
|
72
|
+
raise
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# @return [Symbol]
|
77
|
+
def ntlm_version
|
78
|
+
if ntlm_response.size == 24 && lm_response[0,8] != "\x00"*8 && lm_response[8,16] == "\x00"*16
|
79
|
+
:ntlm2_session
|
80
|
+
elsif ntlm_response.size == 24
|
81
|
+
:ntlmv1
|
82
|
+
elsif ntlm_response.size > 24
|
83
|
+
:ntlmv2
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def ntlm2_session_password?(password, server_challenge)
|
90
|
+
hash = ntlm_response
|
91
|
+
_lm, empty_hash = NTLM.ntlm2_session(
|
92
|
+
{
|
93
|
+
:ntlm_hash => NTLM.ntlm_hash(password),
|
94
|
+
:challenge => server_challenge,
|
95
|
+
},
|
96
|
+
{
|
97
|
+
:client_challenge => lm_response[0,8]
|
98
|
+
}
|
99
|
+
)
|
100
|
+
hash == empty_hash
|
101
|
+
end
|
102
|
+
|
103
|
+
def ntlmv2_password?(password, server_challenge)
|
104
|
+
|
105
|
+
# The first 16 bytes of the ntlm_response are the HMAC of the blob
|
106
|
+
# that follows it.
|
107
|
+
blob = Blob.new
|
108
|
+
blob.parse(ntlm_response[16..-1])
|
109
|
+
|
110
|
+
empty_hash = NTLM.ntlmv2_response(
|
111
|
+
{
|
112
|
+
# user and domain came from the serialized data here, so
|
113
|
+
# they're already unicode
|
114
|
+
:ntlmv2_hash => NTLM.ntlmv2_hash(user, '', domain, :unicode => true),
|
115
|
+
:challenge => server_challenge,
|
116
|
+
:target_info => blob.target_info
|
117
|
+
},
|
118
|
+
{
|
119
|
+
:client_challenge => blob.challenge,
|
120
|
+
# The blob's timestamp is already in milliseconds since 1601,
|
121
|
+
# so convert it back to epoch time first
|
122
|
+
:timestamp => (blob.timestamp / 10_000_000) - NTLM::TIME_OFFSET,
|
123
|
+
}
|
124
|
+
)
|
125
|
+
|
126
|
+
empty_hash == ntlm_response
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -1,48 +1,48 @@
|
|
1
|
-
|
2
|
-
module Net
|
3
|
-
module NTLM
|
4
|
-
|
5
|
-
class SecurityBuffer < FieldSet
|
6
|
-
|
7
|
-
int16LE :length, {:value => 0}
|
8
|
-
int16LE :allocated, {:value => 0}
|
9
|
-
int32LE :offset, {:value => 0}
|
10
|
-
|
11
|
-
attr_accessor :active
|
12
|
-
def initialize(opts={})
|
13
|
-
super()
|
14
|
-
@value = opts[:value]
|
15
|
-
@active = opts[:active].nil? ? true : opts[:active]
|
16
|
-
@size = 8
|
17
|
-
end
|
18
|
-
|
19
|
-
def parse(str, offset=0)
|
20
|
-
if @active and str.size >= offset + @size
|
21
|
-
super(str, offset)
|
22
|
-
@value = str[self.offset, self.length]
|
23
|
-
@size
|
24
|
-
else
|
25
|
-
0
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def serialize
|
30
|
-
super if @active
|
31
|
-
end
|
32
|
-
|
33
|
-
def value
|
34
|
-
@value
|
35
|
-
end
|
36
|
-
|
37
|
-
def value=(val)
|
38
|
-
@value = val
|
39
|
-
self.length = self.allocated = val.size
|
40
|
-
end
|
41
|
-
|
42
|
-
def data_size
|
43
|
-
@active ? @value.size : 0
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
end
|
1
|
+
|
2
|
+
module Net
|
3
|
+
module NTLM
|
4
|
+
|
5
|
+
class SecurityBuffer < FieldSet
|
6
|
+
|
7
|
+
int16LE :length, {:value => 0}
|
8
|
+
int16LE :allocated, {:value => 0}
|
9
|
+
int32LE :offset, {:value => 0}
|
10
|
+
|
11
|
+
attr_accessor :active
|
12
|
+
def initialize(opts={})
|
13
|
+
super()
|
14
|
+
@value = opts[:value]
|
15
|
+
@active = opts[:active].nil? ? true : opts[:active]
|
16
|
+
@size = 8
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse(str, offset=0)
|
20
|
+
if @active and str.size >= offset + @size
|
21
|
+
super(str, offset)
|
22
|
+
@value = str[self.offset, self.length]
|
23
|
+
@size
|
24
|
+
else
|
25
|
+
0
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def serialize
|
30
|
+
super if @active
|
31
|
+
end
|
32
|
+
|
33
|
+
def value
|
34
|
+
@value
|
35
|
+
end
|
36
|
+
|
37
|
+
def value=(val)
|
38
|
+
@value = val
|
39
|
+
self.length = self.allocated = val.size
|
40
|
+
end
|
41
|
+
|
42
|
+
def data_size
|
43
|
+
@active ? @value.size : 0
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
48
|
end
|
data/lib/net/ntlm/string.rb
CHANGED
@@ -1,35 +1,35 @@
|
|
1
|
-
module Net
|
2
|
-
module NTLM
|
3
|
-
|
4
|
-
class String < Field
|
5
|
-
def initialize(opts)
|
6
|
-
super(opts)
|
7
|
-
@size = opts[:size]
|
8
|
-
end
|
9
|
-
|
10
|
-
def parse(str, offset=0)
|
11
|
-
if @active and str.size >= offset + @size
|
12
|
-
@value = str[offset, @size]
|
13
|
-
@size
|
14
|
-
else
|
15
|
-
0
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def serialize
|
20
|
-
if @active
|
21
|
-
@value.to_s
|
22
|
-
else
|
23
|
-
""
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def value=(val)
|
28
|
-
@value = val
|
29
|
-
@size = @value.nil? ? 0 : @value.size
|
30
|
-
@active = (@size > 0)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
end
|
1
|
+
module Net
|
2
|
+
module NTLM
|
3
|
+
|
4
|
+
class String < Field
|
5
|
+
def initialize(opts)
|
6
|
+
super(opts)
|
7
|
+
@size = opts[:size]
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse(str, offset=0)
|
11
|
+
if @active and str.size >= offset + @size
|
12
|
+
@value = str[offset, @size]
|
13
|
+
@size
|
14
|
+
else
|
15
|
+
0
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def serialize
|
20
|
+
if @active
|
21
|
+
@value.to_s
|
22
|
+
else
|
23
|
+
""
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def value=(val)
|
28
|
+
@value = val
|
29
|
+
@size = @value.nil? ? 0 : @value.size
|
30
|
+
@active = (@size > 0)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
35
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Net
|
2
|
+
module NTLM
|
3
|
+
|
4
|
+
# Represents a list of AV_PAIR structures
|
5
|
+
# @see https://msdn.microsoft.com/en-us/library/cc236646.aspx
|
6
|
+
class TargetInfo
|
7
|
+
|
8
|
+
# Allowed AvId values for an AV_PAIR
|
9
|
+
MSV_AV_EOL = "\x00\x00".freeze
|
10
|
+
MSV_AV_NB_COMPUTER_NAME = "\x01\x00".freeze
|
11
|
+
MSV_AV_NB_DOMAIN_NAME = "\x02\x00".freeze
|
12
|
+
MSV_AV_DNS_COMPUTER_NAME = "\x03\x00".freeze
|
13
|
+
MSV_AV_DNS_DOMAIN_NAME = "\x04\x00".freeze
|
14
|
+
MSV_AV_DNS_TREE_NAME = "\x05\x00".freeze
|
15
|
+
MSV_AV_FLAGS = "\x06\x00".freeze
|
16
|
+
MSV_AV_TIMESTAMP = "\x07\x00".freeze
|
17
|
+
MSV_AV_SINGLE_HOST = "\x08\x00".freeze
|
18
|
+
MSV_AV_TARGET_NAME = "\x09\x00".freeze
|
19
|
+
MSV_AV_CHANNEL_BINDINGS = "\x0A\x00".freeze
|
20
|
+
|
21
|
+
# @param av_pair_sequence [String] AV_PAIR list from challenge message
|
22
|
+
def initialize(av_pair_sequence)
|
23
|
+
@av_pairs = read_pairs(av_pair_sequence)
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :av_pairs
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
result = ''
|
30
|
+
av_pairs.each do |k,v|
|
31
|
+
result << k
|
32
|
+
result << [v.length].pack('S')
|
33
|
+
result << v
|
34
|
+
end
|
35
|
+
result << Net::NTLM::TargetInfo::MSV_AV_EOL
|
36
|
+
result << [0].pack('S')
|
37
|
+
result.force_encoding(Encoding::ASCII_8BIT)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
VALID_PAIR_ID = [
|
43
|
+
MSV_AV_EOL,
|
44
|
+
MSV_AV_NB_COMPUTER_NAME,
|
45
|
+
MSV_AV_NB_DOMAIN_NAME,
|
46
|
+
MSV_AV_DNS_COMPUTER_NAME,
|
47
|
+
MSV_AV_DNS_DOMAIN_NAME,
|
48
|
+
MSV_AV_DNS_TREE_NAME,
|
49
|
+
MSV_AV_FLAGS,
|
50
|
+
MSV_AV_TIMESTAMP,
|
51
|
+
MSV_AV_SINGLE_HOST,
|
52
|
+
MSV_AV_TARGET_NAME,
|
53
|
+
MSV_AV_CHANNEL_BINDINGS
|
54
|
+
].freeze
|
55
|
+
|
56
|
+
def read_pairs(av_pair_sequence)
|
57
|
+
offset = 0
|
58
|
+
result = {}
|
59
|
+
return result if av_pair_sequence.nil?
|
60
|
+
|
61
|
+
until offset >= av_pair_sequence.length
|
62
|
+
id = av_pair_sequence[offset..offset+1]
|
63
|
+
|
64
|
+
unless VALID_PAIR_ID.include?(id)
|
65
|
+
raise Net::NTLM::InvalidTargetDataError.new(
|
66
|
+
"Invalid AvId #{to_hex(id)} in AV_PAIR structure",
|
67
|
+
av_pair_sequence
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
length = av_pair_sequence[offset+2..offset+3].unpack('S')[0].to_i
|
72
|
+
if length > 0
|
73
|
+
value = av_pair_sequence[offset+4..offset+4+length-1]
|
74
|
+
result[id] = value
|
75
|
+
end
|
76
|
+
|
77
|
+
offset += 4 + length
|
78
|
+
end
|
79
|
+
|
80
|
+
result
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_hex(str)
|
84
|
+
return nil if str.nil?
|
85
|
+
str.bytes.map {|b| '0x' + b.to_s(16).rjust(2,'0').upcase}.join('-')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|