codtls 0.0.1.alpha
Sign up to get free protection for your applications and to get access to all the features.
- 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
|