codtls 0.0.1.alpha
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 +7 -0
- data/.rubocop.yml +12 -0
- data/.yardopts +4 -0
- data/Gemfile +12 -0
- data/LICENSE +21 -0
- data/README.md +78 -0
- data/Rakefile +29 -0
- data/lib/codtls.rb +186 -0
- data/lib/codtls/abstract_session.rb +179 -0
- data/lib/codtls/alert.rb +64 -0
- data/lib/codtls/decrypt.rb +72 -0
- data/lib/codtls/ecc.rb +26 -0
- data/lib/codtls/encrypt.rb +29 -0
- data/lib/codtls/h_changecipherspec.rb +25 -0
- data/lib/codtls/h_chello.rb +79 -0
- data/lib/codtls/h_content.rb +57 -0
- data/lib/codtls/h_finished.rb +30 -0
- data/lib/codtls/h_keyexchange.rb +131 -0
- data/lib/codtls/h_shello.rb +51 -0
- data/lib/codtls/h_shellodone.rb +22 -0
- data/lib/codtls/h_type.rb +22 -0
- data/lib/codtls/h_verify.rb +30 -0
- data/lib/codtls/handshake.rb +173 -0
- data/lib/codtls/models/codtls_connection.rb +3 -0
- data/lib/codtls/models/codtls_device.rb +3 -0
- data/lib/codtls/prf.rb +40 -0
- data/lib/codtls/pskdb.rb +104 -0
- data/lib/codtls/ram_session.rb +214 -0
- data/lib/codtls/rampskdb.rb +87 -0
- data/lib/codtls/record.rb +202 -0
- data/lib/codtls/session.rb +284 -0
- data/lib/codtls/version.rb +3 -0
- data/lib/generators/codtls/codtls_generator.rb +56 -0
- data/lib/generators/codtls/templates/create_codtls_connections.rb +15 -0
- data/lib/generators/codtls/templates/create_codtls_devices.rb +11 -0
- data/test/test_codtls.rb +75 -0
- data/test/test_ecc.rb +44 -0
- data/test/test_h_chello.rb +40 -0
- data/test/test_h_content.rb +59 -0
- data/test/test_h_keyexchange.rb +36 -0
- data/test/test_helper.rb +3 -0
- data/test/test_pskdb.rb +37 -0
- data/test/test_ram_session.rb +131 -0
- data/test/test_rampskdb.rb +26 -0
- data/test/test_record.rb +128 -0
- data/test/test_send_recv.rb +178 -0
- data/test/test_session.rb +164 -0
- metadata +303 -0
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'codtls/abstract_session'
|
2
|
+
|
3
|
+
module CoDTLS
|
4
|
+
# Error class for wrong data inputs (for exampe keyblock)
|
5
|
+
class SessionError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
# Storage class for a CoDTLS-Session with the following Fields:
|
9
|
+
# Type | Name | Standard-Value
|
10
|
+
# -------------------------------------------------------
|
11
|
+
# uint8_t | ip[16]; | Given IP
|
12
|
+
# uint8_t | id[8]; | empty
|
13
|
+
# uint16_t | epoch; | 0
|
14
|
+
# uint48_t | seq_num_r; | depends on implementation (-1, 0 or 1)
|
15
|
+
# uint48_t | seq_num_w; | depends on implementation (-1, 0 or 1)
|
16
|
+
# uint8_t | key_block[40]; | empty
|
17
|
+
# uint8_t | key_block_new[40]; | empty
|
18
|
+
# uint8_t | handshake; | 0
|
19
|
+
class RAMSession < CoDTLS::AbstractSession
|
20
|
+
@connections = []
|
21
|
+
# Constructor to create a new session for the given ip. When only ip is
|
22
|
+
# given, its the value for searching in database. When its not found, a
|
23
|
+
# new database entry with standard values will be created. When id is
|
24
|
+
# also given, id is the first value for searching in database. When its
|
25
|
+
# found, ip will be updated and other values are unchanged. In the other
|
26
|
+
# case, when id isnt found, its the same behavior as without id but with
|
27
|
+
# additional storing of the id. Throws excpetion if ip == nil.
|
28
|
+
#
|
29
|
+
# @param ip [IP] IP for this Session
|
30
|
+
# @param id [String] Session-Id for this Session
|
31
|
+
def initialize(ip, id = nil)
|
32
|
+
fail SessionError 'IP is nil, not a valid value.' if ip.nil? ||
|
33
|
+
ip.class != String
|
34
|
+
ip = IPAddr.new(ip)
|
35
|
+
fail SessionError 'IP is not in a valid format' if ip.nil?
|
36
|
+
ip = ip.to_s
|
37
|
+
@ip = ip
|
38
|
+
if id.nil?
|
39
|
+
entry = CoDTLS::RAMSession.connections.select { |c| c.ip == @ip }
|
40
|
+
if entry.empty?
|
41
|
+
create_standard_ip_object
|
42
|
+
else
|
43
|
+
@database_object = entry[0]
|
44
|
+
end
|
45
|
+
else
|
46
|
+
entry = CoDTLS::RAMSession.connections.select { |c| c.id == id }
|
47
|
+
create_standard_ip_object if entry.empty?
|
48
|
+
@database_object.id = id
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Sets the ID of the current session.
|
53
|
+
#
|
54
|
+
# @param id [String] the Session-Id
|
55
|
+
def id=(id)
|
56
|
+
create_standard_ip_object if @database_object.nil?
|
57
|
+
@database_object.session_id = id
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns the ID of the current session.
|
61
|
+
#
|
62
|
+
# @return [String] the Session-ID. nil if Session-ID is unknown
|
63
|
+
def id
|
64
|
+
create_standard_ip_object if @database_object.nil?
|
65
|
+
@database_object.session_id
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the Epoch of the session for the specified IP.
|
69
|
+
#
|
70
|
+
# @return [Number] the Epoch
|
71
|
+
def epoch
|
72
|
+
create_standard_ip_object if @database_object.nil?
|
73
|
+
@database_object.epoch
|
74
|
+
end
|
75
|
+
|
76
|
+
# Increases the Epoch of the session by 1.
|
77
|
+
# Also copy key_block_new to key_block and sets key_block_new to empty.
|
78
|
+
# seq_num_r and seq_num_w are set back to standard value.
|
79
|
+
# Throws excpetion if keyblock is not 40 bytes long.
|
80
|
+
def increase_epoch
|
81
|
+
create_standard_ip_object if @database_object.nil?
|
82
|
+
if @database_object.key_block_new.nil?
|
83
|
+
fail CoDTLS::SessionError, 'no new keyblock to set for this epoch.'
|
84
|
+
end
|
85
|
+
@database_object.epoch = @database_object.epoch + 1
|
86
|
+
@database_object.seq_num_r = 0
|
87
|
+
@database_object.seq_num_w = 0
|
88
|
+
@database_object.key_block = @database_object.key_block_new
|
89
|
+
@database_object.key_block_new = nil
|
90
|
+
end
|
91
|
+
|
92
|
+
# Checks the sequenze number of an incoming paket. Valid number is
|
93
|
+
# -10 ... + 100 of the expected number.
|
94
|
+
# The inital LAST number is 0, so the next expected is 1.
|
95
|
+
#
|
96
|
+
# @param num [Number] the recieved sequence number
|
97
|
+
# @return [Bool] true if the number is valid. false if invalid
|
98
|
+
def check_seq(num)
|
99
|
+
create_standard_ip_entry if @database_object.nil?
|
100
|
+
return false if num < @database_object.seq_num_r - 9 ||
|
101
|
+
num > @database_object.seq_num_r + 101
|
102
|
+
true
|
103
|
+
end
|
104
|
+
|
105
|
+
# Sets the sequence number of the last received paket.
|
106
|
+
#
|
107
|
+
# @param num [Number] the new sequence number
|
108
|
+
def seq=(num)
|
109
|
+
create_standard_ip_object if @database_object.nil?
|
110
|
+
@database_object.seq_num_r = num
|
111
|
+
true
|
112
|
+
end
|
113
|
+
|
114
|
+
# Returns the sequence number for the next outgoing paket.
|
115
|
+
# The inital sequence number is 1. Every return value needs
|
116
|
+
# to be last value + 1.
|
117
|
+
#
|
118
|
+
# @return [Number] the sequence number for the next outgoing paket
|
119
|
+
def seq
|
120
|
+
create_standard_ip_object if @database_object.nil?
|
121
|
+
temp = @database_object.seq_num_w + 1
|
122
|
+
@database_object.seq_num_w = temp
|
123
|
+
end
|
124
|
+
|
125
|
+
# Inserts a new keyblock to key_block_new. Throws excpetion if
|
126
|
+
# keyblock is not 40 bytes long.
|
127
|
+
#
|
128
|
+
# @param keyBlock [String] the 40 byte long new keyblock to be inserted
|
129
|
+
def key_block=(keyBlock)
|
130
|
+
if keyBlock.b.length != 40
|
131
|
+
fail CoDTLS::SessionError, 'key blocks have to be 40 byte long'
|
132
|
+
end
|
133
|
+
create_standard_ip_object if @database_object.nil?
|
134
|
+
@database_object.key_block_new = keyBlock
|
135
|
+
end
|
136
|
+
|
137
|
+
# Returns the active keyblock (key_block) for the specified IP.
|
138
|
+
# If keyblock is empty, nil will be returned.
|
139
|
+
#
|
140
|
+
# @return [String] the 40 byte long keyblock or nil if empty
|
141
|
+
def key_block
|
142
|
+
create_standard_ip_object if @database_object.nil?
|
143
|
+
@database_object.key_block
|
144
|
+
end
|
145
|
+
|
146
|
+
# Causes the next messages to be send as handshake messages.
|
147
|
+
def enable_handshake
|
148
|
+
create_standard_ip_object if @database_object.nil?
|
149
|
+
@database_object.handshake = true
|
150
|
+
end
|
151
|
+
|
152
|
+
# Causes the next messages to not be send as handshake messages.
|
153
|
+
def disable_handshake
|
154
|
+
create_standard_ip_object if @database_object.nil?
|
155
|
+
@database_object.handshake = false
|
156
|
+
end
|
157
|
+
|
158
|
+
# Checks if the next message should be send as a handshake message.
|
159
|
+
#
|
160
|
+
# @return [Bool] true if the next messages are handshake messages
|
161
|
+
def handshake?
|
162
|
+
create_standard_ip_object if @database_object.nil?
|
163
|
+
@database_object.handshake
|
164
|
+
end
|
165
|
+
|
166
|
+
# Removes the whole entry from database.
|
167
|
+
def clear
|
168
|
+
fail CoDTLS::SessionError 'IP of a current session has'\
|
169
|
+
'been deleted by a '\
|
170
|
+
'third party.' if @database_object.nil?
|
171
|
+
CoDTLS::RAMSession.connections.delete(@database_object)
|
172
|
+
@database_object = nil
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.clear_all
|
176
|
+
@connections = []
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.connections
|
180
|
+
@connections unless @connections.nil?
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
def create_standard_ip_object
|
186
|
+
@database_object = TempDTLSConnection.new(@ip)
|
187
|
+
CoDTLS::RAMSession.connections.push(@database_object)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# class for representing a single connection. The IP address is unique.
|
192
|
+
class TempDTLSConnection
|
193
|
+
attr_accessor :ip,
|
194
|
+
:epoch,
|
195
|
+
:handshake,
|
196
|
+
:seq_num_w,
|
197
|
+
:seq_num_r,
|
198
|
+
:key_block,
|
199
|
+
:key_block_new,
|
200
|
+
:session_id
|
201
|
+
|
202
|
+
def initialize(ip)
|
203
|
+
@ip = ip
|
204
|
+
@epoch = 0
|
205
|
+
@handshake = false
|
206
|
+
@seq_num_w = 0
|
207
|
+
@seq_num_r = 0
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def create_sqlite_db(dbname)
|
213
|
+
SQLite3::Database.new(dbname)
|
214
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'sqlite3'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module CoDTLS
|
6
|
+
# A Class for managing PSKs per IP. The class has no initializer, because
|
7
|
+
# every method is static. All data are not saved, so they are lost at a
|
8
|
+
# restart.
|
9
|
+
class RAMPSKDB
|
10
|
+
@psks = []
|
11
|
+
# Sets PSK for the specified UUID. If PSK for UUID is already set,
|
12
|
+
# PSK ist saved into PSK_new. So PSK is set one time, while PSK_new
|
13
|
+
# maybe gets overwritten more times. Other values are set to standard.
|
14
|
+
#
|
15
|
+
# @param uuid [Binary] the UUID of the device in 16 byte binary form
|
16
|
+
# @param psk [String] the 16 byte long pre-shared key of the device
|
17
|
+
def self.set_psk(uuid, psk, desc = '')
|
18
|
+
entry = @psks.select { |p| p.uuid = uuid }
|
19
|
+
if entry.empty?
|
20
|
+
@psks.push(PSK.new(uuid, psk, desc))
|
21
|
+
else
|
22
|
+
entry = entry[0]
|
23
|
+
if entry.psk.nil?
|
24
|
+
entry.psk = psk
|
25
|
+
else
|
26
|
+
entry.psk_new = psk
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Gets PSK for the specified UUID. If PSK_new is set, the return value
|
32
|
+
# is PSK_new, else PSK is return value. If UUID is not existing
|
33
|
+
# nil will be returned.
|
34
|
+
#
|
35
|
+
# @param uuid [Binary] the UUID of the device in 16 byte binary form
|
36
|
+
# @return [String] the 16 byte long pre-shared key for the UUID
|
37
|
+
def self.get_psk(uuid)
|
38
|
+
entry = @psks.select { |p| p.uuid = uuid }
|
39
|
+
return nil if entry.empty?
|
40
|
+
entry = entry[0]
|
41
|
+
if entry.psk_new.nil?
|
42
|
+
return entry.psk
|
43
|
+
else
|
44
|
+
return entry.psk_new
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Deletes the PSK for the provided UUID. Handle with care, PSK_new and PSK
|
49
|
+
# are lost after this.
|
50
|
+
#
|
51
|
+
# @param uuid [Binary] the UUID of the device in 16 byte binary form
|
52
|
+
# @return [NilCLass] nil if uuid couldn't be found
|
53
|
+
def self.del_psk!(uuid)
|
54
|
+
entry = @psks.select { |p| p.uuid = uuid }
|
55
|
+
return nil if entry.empty?
|
56
|
+
@psks.delete(entry)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns an array of all registered UUIDs.
|
60
|
+
#
|
61
|
+
# @return [Array] of {uuid: A, psk: B, desc: C}
|
62
|
+
def self.all_registered
|
63
|
+
entries = @psks
|
64
|
+
entries.map { |i| [i.uuid, i.psk_new.nil? ? i.psk : i.psk_new, i.desc] }
|
65
|
+
end
|
66
|
+
|
67
|
+
# Removes the all entrys from database in TABLE 2.
|
68
|
+
def self.clear_all
|
69
|
+
@psks = []
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Class representn the dtls_devices table in the database
|
74
|
+
class PSK
|
75
|
+
attr_accessor :uuid, :psk, :psk_new, :desc
|
76
|
+
|
77
|
+
def initialize(uuid, psk, desc)
|
78
|
+
@uuid = uuid
|
79
|
+
@psk = psk
|
80
|
+
@desc = desc
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def create_sqlite_db(dbname)
|
86
|
+
SQLite3::Database.new(dbname)
|
87
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
module CoDTLS
|
2
|
+
# TODO
|
3
|
+
class RecordError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
# Tolle Klasse
|
7
|
+
class Record
|
8
|
+
TYPE = { bit8: 0, alert: 1, handshake: 2, appdata: 3 }
|
9
|
+
VERSION = { v10: 0, bit16: 1, v12: 2, reserved: 3 }
|
10
|
+
EPOCH = { e0: 0, e1: 1, e2: 2, e3: 3, e4: 4,
|
11
|
+
bit8: 5, bit16: 6, implizit: 7 }
|
12
|
+
SEQ_NUM = { none: 0, bit8: 1, bit16: 2, bit24: 3,
|
13
|
+
bit32: 4, bit40: 5, bit48: 6, implizit: 7 }
|
14
|
+
LENGTH = { l0: 0, bit8: 1, bit16: 2, implizit: 3 }
|
15
|
+
|
16
|
+
attr_reader :type, :version, :epoch, :seq_num, :length
|
17
|
+
|
18
|
+
def self.parse(data)
|
19
|
+
data.force_encoding('ASCII-8BIT')
|
20
|
+
fail RecordError, 'GR1' if data.length < 2
|
21
|
+
header = data.slice!(0...2).unpack('n')[0]
|
22
|
+
|
23
|
+
type = TYPE.keys[header >> 13]
|
24
|
+
if type == :bit8
|
25
|
+
fail RecordError, 'GR1' if data.length < 1
|
26
|
+
type = data.slice!(0...1).unpack('C')[0]
|
27
|
+
end
|
28
|
+
|
29
|
+
version = VERSION.keys[(header >> 11) & 0x03]
|
30
|
+
if version == :bit16
|
31
|
+
fail RecordError, 'GR1' if data.length < 2
|
32
|
+
version = data.slice!(0...2).unpack('n')[0]
|
33
|
+
end
|
34
|
+
|
35
|
+
epoch = EPOCH.keys[(header >> 8) & 0x07]
|
36
|
+
epoch = case epoch
|
37
|
+
when :implizit then :implizit
|
38
|
+
when :bit16
|
39
|
+
fail RecordError, 'GR1' if data.length < 2
|
40
|
+
data.slice!(0...2).unpack('n')[0]
|
41
|
+
when :bit8
|
42
|
+
fail RecordError, 'GR1' if data.length < 1
|
43
|
+
data.slice!(0...1).unpack('C')[0]
|
44
|
+
else EPOCH[epoch]
|
45
|
+
end
|
46
|
+
|
47
|
+
sequence = SEQ_NUM.keys[(header >> 2) & 0x07]
|
48
|
+
sequence = case sequence
|
49
|
+
when :none then :none
|
50
|
+
when :implizit then :implizit
|
51
|
+
else
|
52
|
+
bytes = SEQ_NUM[sequence]
|
53
|
+
fail RecordError, 'GR1' if data.length < bytes
|
54
|
+
num = data.slice!(0...bytes).reverse
|
55
|
+
num += "\x00" while num.length < 8
|
56
|
+
num.unpack('Q')[0]
|
57
|
+
end
|
58
|
+
|
59
|
+
length = LENGTH.keys[header & 0x03]
|
60
|
+
length = case length
|
61
|
+
when :implizit then :implizit
|
62
|
+
when :bit16
|
63
|
+
fail RecordError, 'GR1' if data.length < 2
|
64
|
+
data.slice!(0...2).unpack('n')[0]
|
65
|
+
when :bit8
|
66
|
+
fail RecordError, 'GR1' if data.length < 1
|
67
|
+
data.slice!(0...1).unpack('C')[0]
|
68
|
+
else LENGTH[length]
|
69
|
+
end
|
70
|
+
|
71
|
+
r = Record.new(type, epoch, sequence)
|
72
|
+
r.version = version
|
73
|
+
r.length = length
|
74
|
+
[r, data.slice!(length == :implizit ? 0..-1 : 0...length)]
|
75
|
+
end
|
76
|
+
|
77
|
+
public
|
78
|
+
|
79
|
+
def initialize(type, epoch, seq_num)
|
80
|
+
self.type = type
|
81
|
+
self.version = :v12
|
82
|
+
self.epoch = epoch
|
83
|
+
self.seq_num = seq_num
|
84
|
+
self.length = :implizit
|
85
|
+
|
86
|
+
@header_add = ''.force_encoding('ASCII-8BIT')
|
87
|
+
end
|
88
|
+
|
89
|
+
def type=(type)
|
90
|
+
check_param(type, TYPE, [1, 2, 3], 2**8 - 1)
|
91
|
+
@type = type
|
92
|
+
end
|
93
|
+
|
94
|
+
def version=(version)
|
95
|
+
check_param(version, VERSION, [0, 2], 2**16 - 1)
|
96
|
+
@version = version
|
97
|
+
end
|
98
|
+
|
99
|
+
def epoch=(epoch)
|
100
|
+
check_param(epoch, EPOCH, [7], 2**16 - 1)
|
101
|
+
@epoch = epoch
|
102
|
+
end
|
103
|
+
|
104
|
+
def seq_num=(seq_num)
|
105
|
+
check_param(seq_num, SEQ_NUM, [0, 7], 2**48 - 1)
|
106
|
+
@seq_num = seq_num
|
107
|
+
end
|
108
|
+
|
109
|
+
def length=(length)
|
110
|
+
check_param(length, LENGTH, [3], 2**16 - 1)
|
111
|
+
@length = length
|
112
|
+
end
|
113
|
+
|
114
|
+
def nonce(iv)
|
115
|
+
nonce = iv + [@seq_num.class == Symbol ? 0 : @seq_num].pack('Q').reverse
|
116
|
+
nonce[4..5] = [@epoch.class == Symbol ? 0 : @epoch].pack('n')
|
117
|
+
nonce
|
118
|
+
end
|
119
|
+
|
120
|
+
def to_wire
|
121
|
+
@header = 0x00C0
|
122
|
+
@header_add.clear
|
123
|
+
|
124
|
+
append_type
|
125
|
+
append_version
|
126
|
+
append_epoch
|
127
|
+
append_seq_num
|
128
|
+
append_length
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def check_param(value, hash, valid, max)
|
134
|
+
case value
|
135
|
+
when Symbol
|
136
|
+
fail RecordError, 'GR1' unless valid.include?(hash[value])
|
137
|
+
when Integer
|
138
|
+
fail RecordError, 'GR2' unless value >= 0 && value <= max
|
139
|
+
else fail RecordError, "GRU #{hash}, Input: #{value.class}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def append_type
|
144
|
+
case @type
|
145
|
+
when Symbol then @header |= TYPE[@type] << 13
|
146
|
+
when Integer
|
147
|
+
@header |= TYPE[:bit8] << 13
|
148
|
+
@header_add += [type].pack('C')
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def append_version
|
153
|
+
case @version
|
154
|
+
when Symbol then @header |= VERSION[@version] << 11
|
155
|
+
when Integer
|
156
|
+
@header |= VERSION[:bit16] << 11
|
157
|
+
@header_add += [version].pack('n')
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def append_epoch
|
162
|
+
case @epoch
|
163
|
+
when Symbol then @header |= EPOCH[@epoch] << 8
|
164
|
+
when Integer
|
165
|
+
case
|
166
|
+
when @epoch < 5 then @header |= @epoch << 8
|
167
|
+
else
|
168
|
+
num = [@epoch].pack('n').bytes.drop_while { |i| i == 0 }
|
169
|
+
@header_add += num.pack('C*')
|
170
|
+
@header |= (4 + num.length) << 8
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def append_seq_num
|
176
|
+
case @seq_num
|
177
|
+
when Symbol then @header |= SEQ_NUM[@seq_num] << 2
|
178
|
+
when Integer
|
179
|
+
if @seq_num == 0
|
180
|
+
@header_add += "\x00"
|
181
|
+
@header |= 1 << 2
|
182
|
+
else
|
183
|
+
num = [@seq_num].pack('Q').reverse.bytes.drop_while { |i| i == 0 }
|
184
|
+
@header_add += num.pack('C*')
|
185
|
+
@header |= num.length << 2
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def append_length
|
191
|
+
case @length
|
192
|
+
when Symbol then @header |= LENGTH[@length]
|
193
|
+
when Integer
|
194
|
+
num = [@length].pack('n').bytes.drop_while { |i| i == 0 }
|
195
|
+
@header_add += num.pack('C*')
|
196
|
+
@header |= num.length
|
197
|
+
end
|
198
|
+
|
199
|
+
[@header].pack('n') + @header_add
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|