nonnative 2.11.0 → 2.12.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 304dcea40b84eda206fe9964fe1479a54e0188f52f2a5300dbcf992101cab2b3
4
- data.tar.gz: a6adb1b816c424709ab337d1b845dd106ff5141ddb6f26bfe73318c4f51bed23
3
+ metadata.gz: 37bb994055052e4b4e901466b837ba3b74944340a32abf820cebe66dc8fd7f37
4
+ data.tar.gz: 5c99b070397381627c5543189f9cc107c089a1b886a3144423a083c77e13960a
5
5
  SHA512:
6
- metadata.gz: a7d8afad5ad7ff9b550c67fe73a683da85ae1bcfa611e0b25b0d570f44ebd5beba47ab50287d58206a51d6d61722bf42ae893929d2c4a99d5ea53b2e033fae91
7
- data.tar.gz: 506907d924a16c49d583a91e78a7c6e17bb03e3e5d879f00f43e8d4313fb57957bf545538d2953d056ab0c3eecb94ff29ab9a1af22547c890c1a1e56e3f0335c
6
+ metadata.gz: f1f3a920b31c3d77b16b73e6c63bd8adc0f1b2029db821186879ea6fb25daff094970f9dccdea8285c23bbf074cf081626d51847b447255c910a79049dec6965
7
+ data.tar.gz: 93c9efe976dea298fbfd0e187b125991945e715d59fc55c05d7f692c38f90fa8e0e60fa2283cfdec7f89e580253824bae6139e37ddbcf519581ea25979c9e050
data/.circleci/config.yml CHANGED
@@ -25,8 +25,8 @@ jobs:
25
25
  - vendor
26
26
  - run: make lint
27
27
  - run: make sec
28
- - run: COVERAGE_NAME="Features" make features
29
- - run: COVERAGE_NAME="Benchmarks" make benchmarks
28
+ - run: make features
29
+ - run: make benchmarks
30
30
  - store_test_results:
31
31
  path: test/reports
32
32
  - store_artifacts:
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nonnative (2.11.0)
4
+ nonnative (2.12.0)
5
5
  concurrent-ruby (>= 1, < 2)
6
6
  config (>= 5, < 6)
7
7
  cucumber (>= 7, < 11)
@@ -3,8 +3,8 @@
3
3
  module Nonnative
4
4
  # Socket-pair variant used by the fault-injection proxy to simulate corrupted/incoherent traffic.
5
5
  #
6
- # When active, data written to the upstream socket is corrupted by shuffling the payload bytes
7
- # before forwarding.
6
+ # When active, client requests still pass through unchanged, but responses flowing back from the
7
+ # upstream socket are corrupted before they reach the client.
8
8
  #
9
9
  # This behavior is enabled by calling {Nonnative::FaultInjectionProxy#invalid_data}.
10
10
  #
@@ -12,16 +12,28 @@ module Nonnative
12
12
  # @see Nonnative::SocketPairFactory
13
13
  # @see Nonnative::SocketPair
14
14
  class InvalidDataSocketPair < SocketPair
15
- # Writes corrupted data to the socket by shuffling bytes.
15
+ LINE_DELIMITERS = ["\r\n", "\n", "\r"].freeze
16
+
17
+ # Track the accepted client socket so we can corrupt only the response path.
18
+ def connect(local_socket)
19
+ @local_socket = local_socket
20
+
21
+ super
22
+ ensure
23
+ @local_socket = nil
24
+ end
25
+
26
+ # Writes corrupted data to the socket by mutating payload bytes.
16
27
  #
17
- # The payload must always change, otherwise short or repetitive inputs such as "test" can
18
- # occasionally pass through unchanged and make fault-injection scenarios flaky.
28
+ # Client requests are forwarded unchanged so the upstream service can still parse them.
29
+ # Responses flowing back to the client are corrupted in-place, which keeps line-based clients
30
+ # from hanging while ensuring echoed data does not come back unchanged.
19
31
  #
20
32
  # @param socket [IO] the socket to write to
21
33
  # @param data [String] the original payload
22
34
  # @return [Integer] number of bytes written
23
35
  def write(socket, data)
24
- Nonnative.logger.info "shuffling socket data '#{socket.inspect}' for 'invalid_data' pair"
36
+ return super unless socket.equal?(@local_socket)
25
37
 
26
38
  super(socket, corrupt(data))
27
39
  end
@@ -29,40 +41,18 @@ module Nonnative
29
41
  private
30
42
 
31
43
  def corrupt(data)
32
- payload, delimiter = split_trailing_delimiter(data)
33
- return "\0#{delimiter}" if payload.empty?
34
-
35
- "#{corrupt_payload(payload)}#{delimiter}"
36
- end
37
-
38
- def split_trailing_delimiter(data)
39
- index = trailing_delimiter_start(data)
40
- payload = data.byteslice(0, index)
41
- delimiter = data.byteslice(index, data.bytesize - index) || ''
44
+ # Preserve a final line ending so line-oriented clients still finish their reads.
45
+ delimiter = LINE_DELIMITERS.find { |candidate| data.end_with?(candidate) } || ''
46
+ payload = data.delete_suffix(delimiter)
42
47
 
43
- [payload, delimiter]
44
- end
45
-
46
- def trailing_delimiter_start(data)
47
- index = data.bytesize
48
-
49
- while index.positive?
50
- byte = data.getbyte(index - 1)
51
- break unless [10, 13].include?(byte)
52
-
53
- index -= 1
54
- end
55
-
56
- index
57
- end
48
+ # A delimiter-only response still needs one byte we can corrupt without losing the terminator.
49
+ payload = "\0" if payload.empty?
58
50
 
59
- def corrupt_payload(payload)
60
- bytes = payload.bytes
61
- corrupted = bytes.shuffle
62
- return corrupted.pack('C*') unless corrupted == bytes
51
+ # Flip the first byte so the payload is definitely wrong without mangling the whole
52
+ # response structure and turning an invalid response into a timeout.
53
+ payload.setbyte(0, payload.getbyte(0).zero? ? 1 : 0)
63
54
 
64
- corrupted[0] = (corrupted[0] + 1) % 256
65
- corrupted.pack('C*')
55
+ "#{payload}#{delimiter}"
66
56
  end
67
57
  end
68
58
  end
@@ -4,5 +4,5 @@ module Nonnative
4
4
  # The current gem version.
5
5
  #
6
6
  # @return [String]
7
- VERSION = '2.11.0'
7
+ VERSION = '2.12.0'
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nonnative
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.11.0
4
+ version: 2.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alejandro Falkowski