ruby_smb 2.0.4 → 2.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.github/workflows/verify.yml +56 -0
  5. data/README.md +0 -1
  6. data/examples/delete_file.rb +0 -0
  7. data/examples/net_share_enum_all.rb +0 -0
  8. data/examples/pipes.rb +0 -0
  9. data/examples/rename_file.rb +0 -0
  10. data/lib/ruby_smb.rb +3 -2
  11. data/lib/ruby_smb/client.rb +25 -21
  12. data/lib/ruby_smb/client/negotiation.rb +10 -12
  13. data/lib/ruby_smb/compression.rb +7 -0
  14. data/lib/ruby_smb/compression/lznt1.rb +164 -0
  15. data/lib/ruby_smb/dcerpc.rb +3 -1
  16. data/lib/ruby_smb/dcerpc/ndr.rb +97 -0
  17. data/lib/ruby_smb/dcerpc/netlogon.rb +101 -0
  18. data/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_request.rb +37 -0
  19. data/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_response.rb +26 -0
  20. data/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_request.rb +37 -0
  21. data/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_response.rb +23 -0
  22. data/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_request.rb +32 -0
  23. data/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_response.rb +24 -0
  24. data/lib/ruby_smb/dcerpc/request.rb +6 -0
  25. data/lib/ruby_smb/dispatcher/socket.rb +1 -1
  26. data/lib/ruby_smb/smb1/file.rb +1 -1
  27. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +0 -1
  28. data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +0 -1
  29. data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +1 -2
  30. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +1 -13
  31. data/lib/ruby_smb/smb1/pipe.rb +4 -2
  32. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +4 -0
  33. data/lib/ruby_smb/smb2/pipe.rb +6 -4
  34. data/lib/ruby_smb/version.rb +1 -1
  35. data/spec/lib/ruby_smb/client_spec.rb +17 -7
  36. data/spec/lib/ruby_smb/compression/lznt1_spec.rb +32 -0
  37. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_request_spec.rb +69 -0
  38. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_response_spec.rb +53 -0
  39. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_request_spec.rb +69 -0
  40. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_response_spec.rb +37 -0
  41. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_request_spec.rb +45 -0
  42. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_response_spec.rb +37 -0
  43. data/spec/lib/ruby_smb/smb1/file_spec.rb +1 -1
  44. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +0 -1
  45. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +0 -1
  46. data/spec/lib/ruby_smb/smb1/packet/trans2/open2_response_spec.rb +0 -5
  47. data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb +0 -6
  48. data/spec/spec_helper.rb +1 -1
  49. metadata +42 -19
  50. metadata.gz.sig +0 -0
  51. data/.travis.yml +0 -6
@@ -44,8 +44,7 @@ module RubySMB
44
44
  class DataBlock < RubySMB::SMB1::Packet::Trans2::DataBlock
45
45
  string :pad1, length: -> { pad1_length }
46
46
  trans2_parameters :trans2_parameters, label: 'Trans2 Parameters'
47
- string :pad2, length: -> { pad2_length }
48
- string :trans2_data, label: 'Trans2 Data', length: 0
47
+ # trans2_data: No data is sent by this message.
49
48
  end
50
49
 
51
50
  smb_header :smb_header
@@ -23,23 +23,11 @@ module RubySMB
23
23
  end
24
24
  end
25
25
 
26
- # The Trans2 Data Block for this particular Subcommand
27
- class Trans2Data < BinData::Record
28
-
29
- # Returns the length of the Trans2Data struct
30
- # in number of bytes
31
- def length
32
- do_num_bytes
33
- end
34
- end
35
-
36
26
  # The {RubySMB::SMB1::DataBlock} specific to this packet type.
37
27
  class DataBlock < RubySMB::SMB1::Packet::Trans2::DataBlock
38
- uint8 :name, label: 'Name', initial_value: 0x00
39
28
  string :pad1, length: -> { pad1_length }
40
29
  trans2_parameters :trans2_parameters, label: 'Trans2 Parameters'
41
- string :pad2, length: -> { pad2_length }
42
- trans2_data :trans2_data, label: 'Trans2 Data'
30
+ # trans2_data: No data is sent by this message.
43
31
  end
44
32
 
45
33
  smb_header :smb_header
@@ -16,12 +16,14 @@ module RubySMB
16
16
  def initialize(tree:, response:, name:)
17
17
  raise ArgumentError, 'No Name Provided' if name.nil?
18
18
  case name
19
+ when 'netlogon', '\\netlogon'
20
+ extend RubySMB::Dcerpc::Netlogon
19
21
  when 'srvsvc', '\\srvsvc'
20
22
  extend RubySMB::Dcerpc::Srvsvc
21
- when 'winreg', '\\winreg'
22
- extend RubySMB::Dcerpc::Winreg
23
23
  when 'svcctl', '\\svcctl'
24
24
  extend RubySMB::Dcerpc::Svcctl
25
+ when 'winreg', '\\winreg'
26
+ extend RubySMB::Dcerpc::Winreg
25
27
  end
26
28
  super(tree: tree, response: response, name: name)
27
29
  end
@@ -3,6 +3,9 @@ module RubySMB
3
3
  module Packet
4
4
  # An SMB2 COMPRESSION_TRANSFORM_HEADER Packet as defined in
5
5
  # [2.2.42 SMB2 COMPRESSION_TRANSFORM_HEADER](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/1d435f21-9a21-4f4c-828e-624a176cf2a0)
6
+ # NOTE: On 2021-04-06 the official documentation split the definition of COMPRESSION_TRANSFORM_HEADER into the following two variants:
7
+ # * SMB2_COMPRESSION_TRANSFORM_HEADER_CHAINED
8
+ # * SMB2_COMPRESSION_TRANSFORM_HEADER_UNCHAINED
6
9
  class CompressionTransformHeader < BinData::Record
7
10
  endian :little
8
11
 
@@ -11,6 +14,7 @@ module RubySMB
11
14
  uint16 :compression_algorithm, label: 'Compression Algorithm'
12
15
  uint16 :flags, label: 'Flags'
13
16
  uint32 :offset, label: 'Offset / Length'
17
+ array :compressed_data, label: 'Compressed Data', type: :uint8, read_until: :eof
14
18
  end
15
19
 
16
20
  # An SMB2 SMB2_COMPRESSION_TRANSFORM_HEADER_PAYLOAD Packet as defined in
@@ -13,12 +13,14 @@ module RubySMB
13
13
  def initialize(tree:, response:, name:)
14
14
  raise ArgumentError, 'No Name Provided' if name.nil?
15
15
  case name
16
- when 'srvsvc'
16
+ when 'netlogon', '\\netlogon'
17
+ extend RubySMB::Dcerpc::Netlogon
18
+ when 'srvsvc', '\\srvsvc'
17
19
  extend RubySMB::Dcerpc::Srvsvc
18
- when 'winreg'
19
- extend RubySMB::Dcerpc::Winreg
20
- when 'svcctl'
20
+ when 'svcctl', '\\svcctl'
21
21
  extend RubySMB::Dcerpc::Svcctl
22
+ when 'winreg', '\\winreg'
23
+ extend RubySMB::Dcerpc::Winreg
22
24
  end
23
25
  super(tree: tree, response: response, name: name)
24
26
  end
@@ -1,3 +1,3 @@
1
1
  module RubySMB
2
- VERSION = '2.0.4'.freeze
2
+ VERSION = '2.0.9'.freeze
3
3
  end
@@ -809,6 +809,18 @@ RSpec.describe RubySMB::Client do
809
809
  smb1_extended_response.to_binary_s
810
810
  }
811
811
 
812
+ let(:smb1_response) {
813
+ packet = RubySMB::SMB1::Packet::NegotiateResponse.new
814
+ smb1_capabilities_dup = smb1_capabilities.dup
815
+ smb1_capabilities_dup[:extended_security] = 0
816
+
817
+ packet.parameter_block.capabilities = smb1_capabilities_dup
818
+ packet
819
+ }
820
+ let(:smb1_response_raw) {
821
+ smb1_response.to_binary_s
822
+ }
823
+
812
824
  let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x200) }
813
825
  let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x300) }
814
826
 
@@ -997,10 +1009,14 @@ RSpec.describe RubySMB::Client do
997
1009
 
998
1010
  describe '#negotiate_response' do
999
1011
  context 'with only SMB1' do
1000
- it 'returns a properly formed packet' do
1012
+ it 'returns a properly formed NegotiateResponseExtended packet if extended_security is set as 1' do
1001
1013
  expect(smb1_client.negotiate_response(smb1_extended_response_raw)).to eq smb1_extended_response
1002
1014
  end
1003
1015
 
1016
+ it 'returns a properly formed NegotiateResponse packet if extended_security is set as 0' do
1017
+ expect(smb1_client.negotiate_response(smb1_response_raw)).to eq smb1_response
1018
+ end
1019
+
1004
1020
  it 'raises an exception if the response is not a SMB packet' do
1005
1021
  expect { smb1_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
1006
1022
  end
@@ -1015,12 +1031,6 @@ RSpec.describe RubySMB::Client do
1015
1031
  bogus_response.smb_header.command = 0xff
1016
1032
  expect { smb1_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
1017
1033
  end
1018
-
1019
- it 'considers the response invalid if Extended Security is not enabled' do
1020
- bogus_response = smb1_extended_response
1021
- bogus_response.parameter_block.capabilities.extended_security = 0
1022
- expect { smb1_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
1023
- end
1024
1034
  end
1025
1035
 
1026
1036
  context 'with only SMB2' do
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe RubySMB::Compression::LZNT1 do
4
+ describe '.compress' do
5
+ it 'generates an empty blob when provided an empty blob' do
6
+ expected = "".b
7
+ expect(described_class.compress('')).to eq(expected)
8
+ end
9
+
10
+ it 'generates a compressed blob when provided a string with non-reoccurring characters' do
11
+ expect(described_class.compress('RubySMB')).to eq("\x060RubySMB".b)
12
+ end
13
+
14
+ it 'generates a compressed blob when provided a string of reoccurring characters' do
15
+ expect(described_class.compress("\x01" * 0x200)).to eq("\x03\xB0\x02\x01\xFC\x01".b)
16
+ end
17
+ end
18
+
19
+ describe '.decompress' do
20
+ it 'generates a decompressed blob for a string with non-reoccurring characters' do
21
+ expect(described_class.decompress("\x060RubySMB".b)).to eq('RubySMB')
22
+ end
23
+
24
+ it 'generates a decompressed blob for a string of reoccurring characters' do
25
+ expect(described_class.decompress("\x03\xB0\x02\x01\xFC\x01".b)).to eq("\x01" * 0x200)
26
+ end
27
+
28
+ it 'raises an EncodingError when the length is invalid' do
29
+ expect { described_class.decompress("\x010".b) }.to raise_error(EncodingError)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,69 @@
1
+ RSpec.describe RubySMB::Dcerpc::Netlogon::NetrServerAuthenticate3Request do
2
+ subject(:packet) { described_class.new }
3
+
4
+ it { is_expected.to respond_to :primary_name }
5
+ it { is_expected.to respond_to :account_name }
6
+ it { is_expected.to respond_to :secure_channel_type }
7
+ it { is_expected.to respond_to :computer_name }
8
+ it { is_expected.to respond_to :client_credential }
9
+ it { is_expected.to respond_to :flags }
10
+
11
+ it 'is little endian' do
12
+ expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
13
+ end
14
+
15
+ describe '#primary_name' do
16
+ it 'is a LogonsrvHandle structure' do
17
+ expect(packet.primary_name).to be_a RubySMB::Dcerpc::Netlogon::LogonsrvHandle
18
+ end
19
+ end
20
+
21
+ describe '#account_name' do
22
+ it 'is a NdrString structure' do
23
+ expect(packet.account_name).to be_a RubySMB::Dcerpc::Ndr::NdrString
24
+ end
25
+ end
26
+
27
+ describe '#secure_channel_type' do
28
+ it 'is a NetlogonSecureChannelType enum' do
29
+ expect(packet.secure_channel_type).to be_a RubySMB::Dcerpc::Netlogon::NetlogonSecureChannelType
30
+ end
31
+ end
32
+
33
+ describe '#computer_name' do
34
+ it 'is a NdrString structure' do
35
+ expect(packet.computer_name).to be_a RubySMB::Dcerpc::Ndr::NdrString
36
+ end
37
+ end
38
+
39
+ describe '#client_credential' do
40
+ it 'is a NetlogonCredential structure' do
41
+ expect(packet.client_credential).to be_a RubySMB::Dcerpc::Netlogon::NetlogonCredential
42
+ end
43
+ end
44
+
45
+ describe '#flags' do
46
+ it 'is a 32-bit unsigned integer' do
47
+ expect(packet.flags).to be_a BinData::Uint32le
48
+ end
49
+ end
50
+
51
+ describe '#initialize_instance' do
52
+ it 'sets #opnum to NETR_SERVER_AUTHENTICATE3 constant' do
53
+ expect(packet.opnum).to eq(RubySMB::Dcerpc::Netlogon::NETR_SERVER_AUTHENTICATE3)
54
+ end
55
+ end
56
+
57
+ it 'reads its own binary representation and outputs the same packet' do
58
+ packet = described_class.new(
59
+ primary_name: 'primary_name',
60
+ account_name: 'account_name',
61
+ secure_channel_type: 0,
62
+ computer_name: 'computer_name',
63
+ client_credential: "\x00" * 8,
64
+ flags: rand(0xffffffff)
65
+ )
66
+ binary = packet.to_binary_s
67
+ expect(described_class.read(binary)).to eq(packet)
68
+ end
69
+ end
@@ -0,0 +1,53 @@
1
+ RSpec.describe RubySMB::Dcerpc::Netlogon::NetrServerAuthenticate3Response do
2
+ subject(:packet) { described_class.new }
3
+
4
+ it { is_expected.to respond_to :server_credential }
5
+ it { is_expected.to respond_to :negotiate_flags }
6
+ it { is_expected.to respond_to :account_rid }
7
+ it { is_expected.to respond_to :error_status }
8
+
9
+ it 'is little endian' do
10
+ expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
11
+ end
12
+
13
+ describe '#server_credential' do
14
+ it 'is a NetlogonCredential structure' do
15
+ expect(packet.server_credential).to be_a RubySMB::Dcerpc::Netlogon::NetlogonCredential
16
+ end
17
+ end
18
+
19
+ describe '#negotiate_flags' do
20
+ it 'is a 32-bit unsigned integer' do
21
+ expect(packet.negotiate_flags).to be_a BinData::Uint32le
22
+ end
23
+ end
24
+
25
+ describe '#account_rid' do
26
+ it 'is a 32-bit unsigned integer' do
27
+ expect(packet.account_rid).to be_a BinData::Uint32le
28
+ end
29
+ end
30
+
31
+ describe '#error_status' do
32
+ it 'is a 32-bit unsigned integer' do
33
+ expect(packet.error_status).to be_a BinData::Uint32le
34
+ end
35
+ end
36
+
37
+ describe '#initialize_instance' do
38
+ it 'sets #opnum to NETR_SERVER_AUTHENTICATE3 constant' do
39
+ expect(packet.opnum).to eq(RubySMB::Dcerpc::Netlogon::NETR_SERVER_AUTHENTICATE3)
40
+ end
41
+ end
42
+
43
+ it 'reads its own binary representation and outputs the same packet' do
44
+ packet = described_class.new(
45
+ server_credential: "\x00" * 8,
46
+ negotiate_flags: rand(0xffffffff),
47
+ account_rid: rand(0xffffffff),
48
+ error_status: rand(0xffffffff)
49
+ )
50
+ binary = packet.to_binary_s
51
+ expect(described_class.read(binary)).to eq(packet)
52
+ end
53
+ end
@@ -0,0 +1,69 @@
1
+ RSpec.describe RubySMB::Dcerpc::Netlogon::NetrServerPasswordSet2Request do
2
+ subject(:packet) { described_class.new }
3
+
4
+ it { is_expected.to respond_to :primary_name }
5
+ it { is_expected.to respond_to :account_name }
6
+ it { is_expected.to respond_to :secure_channel_type }
7
+ it { is_expected.to respond_to :computer_name }
8
+ it { is_expected.to respond_to :authenticator }
9
+ it { is_expected.to respond_to :clear_new_password }
10
+
11
+ it 'is little endian' do
12
+ expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
13
+ end
14
+
15
+ describe '#primary_name' do
16
+ it 'is a LogonsrvHandle structure' do
17
+ expect(packet.primary_name).to be_a RubySMB::Dcerpc::Netlogon::LogonsrvHandle
18
+ end
19
+ end
20
+
21
+ describe '#account_name' do
22
+ it 'is a NdrString structure' do
23
+ expect(packet.account_name).to be_a RubySMB::Dcerpc::Ndr::NdrString
24
+ end
25
+ end
26
+
27
+ describe '#secure_channel_type' do
28
+ it 'is a NetlogonSecureChannelType enum' do
29
+ expect(packet.secure_channel_type).to be_a RubySMB::Dcerpc::Netlogon::NetlogonSecureChannelType
30
+ end
31
+ end
32
+
33
+ describe '#computer_name' do
34
+ it 'is a NdrString structure' do
35
+ expect(packet.computer_name).to be_a RubySMB::Dcerpc::Ndr::NdrString
36
+ end
37
+ end
38
+
39
+ describe '#authenticator' do
40
+ it 'is a NetlogonAuthenticator structure' do
41
+ expect(packet.authenticator).to be_a RubySMB::Dcerpc::Netlogon::NetlogonAuthenticator
42
+ end
43
+ end
44
+
45
+ describe '#clear_new_password' do
46
+ it 'is a NdrFixedByteArray structure' do
47
+ expect(packet.clear_new_password).to be_a RubySMB::Dcerpc::Ndr::NdrFixedByteArray
48
+ end
49
+ end
50
+
51
+ describe '#initialize_instance' do
52
+ it 'sets #opnum to NETR_SERVER_PASSWORD_SET2 constant' do
53
+ expect(packet.opnum).to eq(RubySMB::Dcerpc::Netlogon::NETR_SERVER_PASSWORD_SET2)
54
+ end
55
+ end
56
+
57
+ it 'reads its own binary representation and outputs the same packet' do
58
+ packet = described_class.new(
59
+ primary_name: 'primary_name',
60
+ account_name: 'account_name',
61
+ secure_channel_type: 0,
62
+ computer_name: 'computer_name',
63
+ authenticator: RubySMB::Dcerpc::Netlogon::NetlogonAuthenticator.new,
64
+ clear_new_password: "\x00" * 516
65
+ )
66
+ binary = packet.to_binary_s
67
+ expect(described_class.read(binary)).to eq(packet)
68
+ end
69
+ end
@@ -0,0 +1,37 @@
1
+ RSpec.describe RubySMB::Dcerpc::Netlogon::NetrServerPasswordSet2Response do
2
+ subject(:packet) { described_class.new }
3
+
4
+ it { is_expected.to respond_to :return_authenticator }
5
+ it { is_expected.to respond_to :error_status }
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 '#return_authenticator' do
12
+ it 'is a NetlogonAuthenticator structure' do
13
+ expect(packet.return_authenticator).to be_a RubySMB::Dcerpc::Netlogon::NetlogonAuthenticator
14
+ end
15
+ end
16
+
17
+ describe '#error_status' do
18
+ it 'is a 32-bit unsigned integer' do
19
+ expect(packet.error_status).to be_a BinData::Uint32le
20
+ end
21
+ end
22
+
23
+ describe '#initialize_instance' do
24
+ it 'sets #opnum to NETR_SERVER_PASSWORD_SET2 constant' do
25
+ expect(packet.opnum).to eq(RubySMB::Dcerpc::Netlogon::NETR_SERVER_PASSWORD_SET2)
26
+ end
27
+ end
28
+
29
+ it 'reads its own binary representation and outputs the same packet' do
30
+ packet = described_class.new(
31
+ return_authenticator: RubySMB::Dcerpc::Netlogon::NetlogonAuthenticator.new,
32
+ error_status: rand(0xffffffff)
33
+ )
34
+ binary = packet.to_binary_s
35
+ expect(described_class.read(binary)).to eq(packet)
36
+ end
37
+ end
@@ -0,0 +1,45 @@
1
+ RSpec.describe RubySMB::Dcerpc::Netlogon::NetrServerReqChallengeRequest do
2
+ subject(:packet) { described_class.new }
3
+
4
+ it { is_expected.to respond_to :primary_name }
5
+ it { is_expected.to respond_to :computer_name }
6
+ it { is_expected.to respond_to :client_challenge }
7
+
8
+ it 'is little endian' do
9
+ expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
10
+ end
11
+
12
+ describe '#primary_name' do
13
+ it 'is a LogonsrvHandle structure' do
14
+ expect(packet.primary_name).to be_a RubySMB::Dcerpc::Netlogon::LogonsrvHandle
15
+ end
16
+ end
17
+
18
+ describe '#computer_name' do
19
+ it 'is a NdrString structure' do
20
+ expect(packet.computer_name).to be_a RubySMB::Dcerpc::Ndr::NdrString
21
+ end
22
+ end
23
+
24
+ describe '#client_challenge' do
25
+ it 'is a NetlogonCredential structure' do
26
+ expect(packet.client_challenge).to be_a RubySMB::Dcerpc::Netlogon::NetlogonCredential
27
+ end
28
+ end
29
+
30
+ describe '#initialize_instance' do
31
+ it 'sets #opnum to NETR_SERVER_REQ_CHALLENGE constant' do
32
+ expect(packet.opnum).to eq(RubySMB::Dcerpc::Netlogon::NETR_SERVER_REQ_CHALLENGE)
33
+ end
34
+ end
35
+
36
+ it 'reads its own binary representation and outputs the same packet' do
37
+ packet = described_class.new(
38
+ primary_name: 'primary_name',
39
+ computer_name: 'computer_name',
40
+ client_challenge: "\x00" * 8,
41
+ )
42
+ binary = packet.to_binary_s
43
+ expect(described_class.read(binary)).to eq(packet)
44
+ end
45
+ end