ruby_smb 0.0.20 → 0.0.21
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
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +2 -3
- data/examples/pipes.rb +45 -0
- data/lib/ruby_smb/client.rb +25 -3
- data/lib/ruby_smb/client/negotiation.rb +10 -3
- data/lib/ruby_smb/nbss/session_header.rb +3 -3
- data/lib/ruby_smb/smb1.rb +1 -0
- data/lib/ruby_smb/smb1/bit_field.rb +1 -0
- data/lib/ruby_smb/smb1/bit_field/trans_flags.rb +15 -0
- data/lib/ruby_smb/smb1/commands.rb +1 -0
- data/lib/ruby_smb/smb1/packet.rb +1 -0
- data/lib/ruby_smb/smb1/packet/trans.rb +16 -0
- data/lib/ruby_smb/smb1/packet/trans/data_block.rb +49 -0
- data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_request.rb +24 -0
- data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_response.rb +59 -0
- data/lib/ruby_smb/smb1/packet/trans/request.rb +50 -0
- data/lib/ruby_smb/smb1/packet/trans/response.rb +46 -0
- data/lib/ruby_smb/smb1/packet/trans/subcommands.rb +11 -0
- data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +1 -1
- data/lib/ruby_smb/smb1/pipe.rb +65 -0
- data/lib/ruby_smb/smb1/tree.rb +8 -1
- data/lib/ruby_smb/smb2.rb +1 -0
- data/lib/ruby_smb/smb2/file.rb +6 -6
- data/lib/ruby_smb/smb2/packet/tree_disconnect_request.rb +1 -0
- data/lib/ruby_smb/smb2/pipe.rb +69 -0
- data/lib/ruby_smb/smb2/tree.rb +11 -1
- data/lib/ruby_smb/version.rb +1 -1
- data/spec/lib/ruby_smb/nbss/session_header_spec.rb +4 -4
- data/spec/lib/ruby_smb/smb1/bit_field/trans_flags_spec.rb +26 -0
- data/spec/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_request_spec.rb +47 -0
- data/spec/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_response_spec.rb +31 -0
- data/spec/lib/ruby_smb/smb1/packet/trans/request_spec.rb +94 -0
- data/spec/lib/ruby_smb/smb1/packet/trans/response_spec.rb +85 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/open2_response_spec.rb +1 -1
- data/spec/lib/ruby_smb/smb1/pipe_spec.rb +65 -0
- data/spec/lib/ruby_smb/smb2/pipe_spec.rb +64 -0
- metadata +27 -2
- metadata.gz.sig +0 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
module RubySMB
|
2
|
+
module SMB1
|
3
|
+
# Represents a pipe on the Remote server that we can perform
|
4
|
+
# various I/O operations on.
|
5
|
+
class Pipe < File
|
6
|
+
|
7
|
+
# Reference: https://msdn.microsoft.com/en-us/library/ee441883.aspx
|
8
|
+
STATUS_DISCONNECTED = 0x0001
|
9
|
+
STATUS_LISTENING = 0x0002
|
10
|
+
STATUS_OK = 0x0003
|
11
|
+
STATUS_CLOSED = 0x0004
|
12
|
+
|
13
|
+
# Performs a peek operation on the named pipe
|
14
|
+
#
|
15
|
+
# @param peek_size [Integer] Amount of data to peek
|
16
|
+
# @return [RubySMB::SMB1::Packet::Trans::PeekNmpipeResponse]
|
17
|
+
# @raise [RubySMB::Error::InvalidPacket] If not a valid PeekNmpipeResponse
|
18
|
+
# @raise [RubySMB::Error::UnexpectedStatusCode] If status is not STATUS_BUFFER_OVERFLOW or STATUS_SUCCESS
|
19
|
+
def peek(peek_size: 0)
|
20
|
+
packet = RubySMB::SMB1::Packet::Trans::PeekNmpipeRequest.new
|
21
|
+
packet.fid = @fid
|
22
|
+
packet.parameter_block.max_data_count = peek_size
|
23
|
+
packet = @tree.set_header_fields(packet)
|
24
|
+
raw_response = @tree.client.send_recv(packet)
|
25
|
+
response = RubySMB::SMB1::Packet::Trans::PeekNmpipeResponse.read(raw_response)
|
26
|
+
|
27
|
+
unless response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW or response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
28
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
|
29
|
+
end
|
30
|
+
|
31
|
+
unless response.smb_header.command == RubySMB::SMB1::Commands::SMB_COM_TRANSACTION
|
32
|
+
raise RubySMB::Error::InvalidPacket, 'Not a TransResponse packet'
|
33
|
+
end
|
34
|
+
response
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Integer] The number of bytes available to be read from the pipe
|
38
|
+
def peek_available
|
39
|
+
packet = peek
|
40
|
+
# Only 1 of these should be non-zero
|
41
|
+
packet.data_block.trans_parameters.read_data_available or packet.data_block.trans_parameters.message_bytes_length
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Integer] Pipe status
|
45
|
+
def peek_state
|
46
|
+
packet = peek
|
47
|
+
packet.data_block.trans_parameters.pipe_state
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [Boolean] True if pipe is connected, false otherwise
|
51
|
+
def is_connected?
|
52
|
+
begin
|
53
|
+
state = peek_state
|
54
|
+
rescue RubySMB::Error::UnexpectedStatusCode => e
|
55
|
+
if e.message == 'STATUS_INVALID_HANDLE'
|
56
|
+
return false
|
57
|
+
end
|
58
|
+
raise e
|
59
|
+
end
|
60
|
+
state == STATUS_OK
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/ruby_smb/smb1/tree.rb
CHANGED
@@ -120,7 +120,14 @@ module RubySMB
|
|
120
120
|
raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
|
121
121
|
end
|
122
122
|
|
123
|
-
|
123
|
+
case response.parameter_block.resource_type
|
124
|
+
when RubySMB::SMB1::ResourceType::BYTE_MODE_PIPE, RubySMB::SMB1::ResourceType::MESSAGE_MODE_PIPE
|
125
|
+
RubySMB::SMB1::Pipe.new(name: filename, tree: self, response: response)
|
126
|
+
when RubySMB::SMB1::ResourceType::DISK
|
127
|
+
RubySMB::SMB1::File.new(name: filename, tree: self, response: response)
|
128
|
+
else
|
129
|
+
raise RubySMB::Error::RubySMBError
|
130
|
+
end
|
124
131
|
end
|
125
132
|
|
126
133
|
# List `directory` on the remote share.
|
data/lib/ruby_smb/smb2.rb
CHANGED
data/lib/ruby_smb/smb2/file.rb
CHANGED
@@ -94,8 +94,8 @@ module RubySMB
|
|
94
94
|
# @param offset [Integer] the byte offset in the file to start reading from
|
95
95
|
# @return [String] the data read from the file
|
96
96
|
def read(bytes: size, offset: 0)
|
97
|
-
atomic_read_size = if bytes >
|
98
|
-
|
97
|
+
atomic_read_size = if bytes > tree.client.server_max_read_size
|
98
|
+
tree.client.server_max_read_size
|
99
99
|
else
|
100
100
|
bytes
|
101
101
|
end
|
@@ -110,7 +110,7 @@ module RubySMB
|
|
110
110
|
|
111
111
|
while remaining_bytes > 0
|
112
112
|
offset += atomic_read_size
|
113
|
-
atomic_read_size = remaining_bytes if remaining_bytes <
|
113
|
+
atomic_read_size = remaining_bytes if remaining_bytes < tree.client.server_max_read_size
|
114
114
|
|
115
115
|
read_request = read_packet(read_length: atomic_read_size, offset: offset)
|
116
116
|
raw_response = tree.client.send_recv(read_request)
|
@@ -184,9 +184,9 @@ module RubySMB
|
|
184
184
|
def write(data:'', offset: 0)
|
185
185
|
buffer = data.dup
|
186
186
|
bytes = data.length
|
187
|
-
atomic_write_size = if bytes >
|
188
|
-
|
189
|
-
|
187
|
+
atomic_write_size = if bytes > tree.client.server_max_write_size
|
188
|
+
tree.client.server_max_write_size
|
189
|
+
else
|
190
190
|
bytes
|
191
191
|
end
|
192
192
|
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module RubySMB
|
2
|
+
module SMB2
|
3
|
+
# Represents a pipe on the Remote server that we can perform
|
4
|
+
# various I/O operations on.
|
5
|
+
class Pipe < File
|
6
|
+
|
7
|
+
STATUS_CONNECTED = 0x00000003
|
8
|
+
STATUS_CLOSING = 0x00000004
|
9
|
+
|
10
|
+
# Performs a peek operation on the named pipe
|
11
|
+
#
|
12
|
+
# @param peek_size [Integer] Amount of data to peek
|
13
|
+
# @return [RubySMB::SMB2::Packet::IoctlResponse]
|
14
|
+
# @raise [RubySMB::Error::InvalidPacket] if not a valid FSCTL_PIPE_PEEK response
|
15
|
+
# @raise [RubySMB::Error::UnexpectedStatusCode] If status is not STATUS_BUFFER_OVERFLOW or STATUS_SUCCESS
|
16
|
+
def peek(peek_size: 0)
|
17
|
+
packet = RubySMB::SMB2::Packet::IoctlRequest.new
|
18
|
+
packet.ctl_code = RubySMB::Fscc::ControlCodes::FSCTL_PIPE_PEEK
|
19
|
+
packet.flags.is_fsctl = true
|
20
|
+
# read at least 16 bytes for state, avail, msg_count, first_msg_len
|
21
|
+
packet.max_output_response = 16 + peek_size
|
22
|
+
packet = set_header_fields(packet)
|
23
|
+
raw_response = @tree.client.send_recv(packet)
|
24
|
+
begin
|
25
|
+
response = RubySMB::SMB2::Packet::IoctlResponse.read(raw_response)
|
26
|
+
rescue IOError
|
27
|
+
response = RubySMB::SMB2::Packet::ErrorPacket.read(raw_response)
|
28
|
+
end
|
29
|
+
|
30
|
+
unless response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW or response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
31
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
|
32
|
+
end
|
33
|
+
|
34
|
+
unless response.smb2_header.command == RubySMB::SMB2::Commands::IOCTL
|
35
|
+
raise RubySMB::Error::InvalidPacket, 'Not an IoctlResponse packet'
|
36
|
+
end
|
37
|
+
response
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Integer] The number of bytes available to be read from the pipe
|
41
|
+
def peek_available
|
42
|
+
packet = peek
|
43
|
+
state, avail, msg_count, first_msg_len = packet.buffer.unpack('VVVV')
|
44
|
+
# Only 1 of these should be non-zero
|
45
|
+
avail or first_msg_len
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [Integer] Pipe status
|
49
|
+
def peek_state
|
50
|
+
packet = peek
|
51
|
+
packet.buffer.unpack('V')[0]
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Boolean] True if pipe is connected, false otherwise
|
55
|
+
def is_connected?
|
56
|
+
begin
|
57
|
+
state = peek_state
|
58
|
+
rescue RubySMB::Error::UnexpectedStatusCode => e
|
59
|
+
if e.message == 'STATUS_FILE_CLOSED'
|
60
|
+
return false
|
61
|
+
end
|
62
|
+
raise e
|
63
|
+
end
|
64
|
+
state == STATUS_CONNECTED
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/ruby_smb/smb2/tree.rb
CHANGED
@@ -28,6 +28,7 @@ module RubySMB
|
|
28
28
|
@share = share
|
29
29
|
@id = response.smb2_header.tree_id
|
30
30
|
@permissions = response.maximal_access
|
31
|
+
@share_type = response.share_type
|
31
32
|
end
|
32
33
|
|
33
34
|
# Disconnects this Tree from the current session
|
@@ -89,7 +90,16 @@ module RubySMB
|
|
89
90
|
raw_response = client.send_recv(create_request)
|
90
91
|
response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
|
91
92
|
|
92
|
-
|
93
|
+
case @share_type
|
94
|
+
when 0x01
|
95
|
+
RubySMB::SMB2::File.new(name: filename, tree: self, response: response)
|
96
|
+
when 0x02
|
97
|
+
RubySMB::SMB2::Pipe.new(name: filename, tree: self, response: response)
|
98
|
+
# when 0x03
|
99
|
+
# it's a printer!
|
100
|
+
else
|
101
|
+
raise RubySMB::Error::RubySMBError
|
102
|
+
end
|
93
103
|
end
|
94
104
|
|
95
105
|
# List `directory` on the remote share.
|
data/lib/ruby_smb/version.rb
CHANGED
@@ -16,14 +16,14 @@ RSpec.describe RubySMB::Nbss::SessionHeader do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
describe '#flags' do
|
19
|
-
it 'is a
|
20
|
-
expect(session_header.flags).to be_a BinData::
|
19
|
+
it 'is a 7-bit Unsigned Integer' do
|
20
|
+
expect(session_header.flags).to be_a BinData::Bit7
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
24
|
describe '#packet_length' do
|
25
|
-
it 'is a
|
26
|
-
expect(session_header.packet_length).to be_a BinData::
|
25
|
+
it 'is a 17-bit Unsigned Integer' do
|
26
|
+
expect(session_header.packet_length).to be_a BinData::Bit17
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
RSpec.describe RubySMB::SMB1::BitField::TransFlags do
|
2
|
+
subject(:flags) { described_class.new }
|
3
|
+
|
4
|
+
it { is_expected.to respond_to :no_response }
|
5
|
+
it { is_expected.to respond_to :disconnect }
|
6
|
+
|
7
|
+
it 'is little endian' do
|
8
|
+
expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#no_response' do
|
12
|
+
it 'should be a 1-bit field per the SMB spec' do
|
13
|
+
expect(flags.no_response).to be_a BinData::Bit1
|
14
|
+
end
|
15
|
+
|
16
|
+
it_behaves_like 'bit field with one flag set', :no_response, 'C', 0x02
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#disconnect' do
|
20
|
+
it 'should be a 1-bit field per the SMB spec' do
|
21
|
+
expect(flags.disconnect).to be_a BinData::Bit1
|
22
|
+
end
|
23
|
+
|
24
|
+
it_behaves_like 'bit field with one flag set', :disconnect, 'C', 0x01
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RubySMB::SMB1::Packet::Trans::PeekNmpipeRequest do
|
4
|
+
|
5
|
+
subject(:packet) { described_class.new }
|
6
|
+
|
7
|
+
describe '#smb_header' do
|
8
|
+
subject(:header) { packet.smb_header }
|
9
|
+
|
10
|
+
it 'is a standard SMB Header' do
|
11
|
+
expect(header).to be_a RubySMB::SMB1::SMBHeader
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should have the command set to SMB_COM_TRANSACTION' do
|
15
|
+
expect(header.command).to eq RubySMB::SMB1::Commands::SMB_COM_TRANSACTION
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should not have the response flag set' do
|
19
|
+
expect(header.flags.reply).to eq 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#parameter_block' do
|
24
|
+
subject(:parameter_block) { packet.parameter_block }
|
25
|
+
|
26
|
+
it 'is a standard Trans ParameterBlock' do
|
27
|
+
expect(parameter_block).to be_a RubySMB::SMB1::Packet::Trans::Request::ParameterBlock
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should have a setup_count of 2' do
|
31
|
+
expect(parameter_block.setup_count).to eq 2
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should have subcommand PEEK_NMPIPE' do
|
35
|
+
expect(parameter_block.setup[0]).to eq RubySMB::SMB1::Packet::Trans::Subcommands::PEEK_NMPIPE
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#data_block' do
|
40
|
+
subject(:data_block) { packet.data_block }
|
41
|
+
|
42
|
+
it 'should have a name of \\PIPE\\' do
|
43
|
+
expect(data_block.name).to eq "\\PIPE\\"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RubySMB::SMB1::Packet::Trans::PeekNmpipeResponse do
|
4
|
+
|
5
|
+
subject(:packet) { described_class.new }
|
6
|
+
|
7
|
+
describe '#smb_header' do
|
8
|
+
subject(:header) { packet.smb_header }
|
9
|
+
|
10
|
+
it 'is a standard SMB Header' do
|
11
|
+
expect(header).to be_a RubySMB::SMB1::SMBHeader
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should have the command set to SMB_COM_TRANSACTION' do
|
15
|
+
expect(header.command).to eq RubySMB::SMB1::Commands::SMB_COM_TRANSACTION
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should have the response flag set' do
|
19
|
+
expect(header.flags.reply).to eq 1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#parameter_block' do
|
24
|
+
subject(:parameter_block) { packet.parameter_block }
|
25
|
+
|
26
|
+
it 'is a standard Trans ParameterBlock' do
|
27
|
+
expect(parameter_block).to be_a RubySMB::SMB1::Packet::Trans::Response::ParameterBlock
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RubySMB::SMB1::Packet::Trans::Request do
|
4
|
+
subject(:packet) { described_class.new }
|
5
|
+
|
6
|
+
describe '#smb_header' do
|
7
|
+
subject(:header) { packet.smb_header }
|
8
|
+
|
9
|
+
it 'is a standard SMB Header' do
|
10
|
+
expect(header).to be_a RubySMB::SMB1::SMBHeader
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should have the command set to SMB_COM_NEGOTIATE' do
|
14
|
+
expect(header.command).to eq RubySMB::SMB1::Commands::SMB_COM_TRANSACTION
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should not have the response flag set' do
|
18
|
+
expect(header.flags.reply).to eq 0
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#parameter_block' do
|
23
|
+
subject(:parameter_block) { packet.parameter_block }
|
24
|
+
|
25
|
+
it 'is a standard ParameterBlock' do
|
26
|
+
expect(parameter_block).to be_a RubySMB::SMB1::ParameterBlock
|
27
|
+
end
|
28
|
+
|
29
|
+
it { is_expected.to respond_to :total_parameter_count }
|
30
|
+
it { is_expected.to respond_to :total_data_count }
|
31
|
+
it { is_expected.to respond_to :max_parameter_count }
|
32
|
+
it { is_expected.to respond_to :max_data_count }
|
33
|
+
it { is_expected.to respond_to :max_setup_count }
|
34
|
+
it { is_expected.to respond_to :timeout }
|
35
|
+
it { is_expected.to respond_to :parameter_count }
|
36
|
+
it { is_expected.to respond_to :parameter_offset }
|
37
|
+
it { is_expected.to respond_to :data_count }
|
38
|
+
it { is_expected.to respond_to :data_offset }
|
39
|
+
it { is_expected.to respond_to :setup_count }
|
40
|
+
|
41
|
+
describe 'flags' do
|
42
|
+
it 'is a trans_flags BitField' do
|
43
|
+
expect(parameter_block.flags).to be_a RubySMB::SMB1::BitField::TransFlags
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'parameter_count' do
|
48
|
+
it 'is a count of bytes in the data_block trans_parameters field' do
|
49
|
+
packet.data_block.trans_parameters = "\x00\x01\x02\x03"
|
50
|
+
expect(parameter_block.parameter_count).to eq 4
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe 'parameter_offset' do
|
55
|
+
it ' contains the absolute_offset to the data_block trans_parameters field' do
|
56
|
+
expect(parameter_block.parameter_offset).to eq packet.data_block.trans_parameters.abs_offset
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'data_count' do
|
61
|
+
it 'is a count of bytes in the data_block trans_data field' do
|
62
|
+
packet.data_block.trans_data = "\x00\x01\x02\x03"
|
63
|
+
expect(parameter_block.data_count).to eq 4
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe 'data_offset' do
|
68
|
+
it 'contains the absolute_offset to the data_block trans_data field' do
|
69
|
+
expect(parameter_block.data_offset).to eq packet.data_block.trans_data.abs_offset
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '#data_block' do
|
75
|
+
subject(:data_block) { packet.data_block }
|
76
|
+
|
77
|
+
it 'is a standard DataBlock' do
|
78
|
+
expect(data_block).to be_a RubySMB::SMB1::DataBlock
|
79
|
+
end
|
80
|
+
|
81
|
+
it { is_expected.to respond_to :name }
|
82
|
+
it { is_expected.to respond_to :trans_parameters }
|
83
|
+
it { is_expected.to respond_to :trans_data }
|
84
|
+
|
85
|
+
it 'should keep #trans_parameters 4-byte aligned' do
|
86
|
+
expect(data_block.trans_parameters.abs_offset % 4).to eq 0
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should keep #trans_data 4-byte aligned' do
|
90
|
+
data_block.trans_parameters = 'a'
|
91
|
+
expect(data_block.trans_data.abs_offset % 4).to eq 0
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|