pwntools 0.1.0 → 1.0.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.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +88 -11
  3. data/Rakefile +5 -1
  4. data/lib/pwn.rb +9 -7
  5. data/lib/pwnlib/abi.rb +60 -0
  6. data/lib/pwnlib/asm.rb +146 -0
  7. data/lib/pwnlib/constants/constant.rb +16 -2
  8. data/lib/pwnlib/constants/constants.rb +35 -19
  9. data/lib/pwnlib/constants/linux/amd64.rb +30 -1
  10. data/lib/pwnlib/context.rb +25 -17
  11. data/lib/pwnlib/dynelf.rb +117 -54
  12. data/lib/pwnlib/elf/elf.rb +267 -0
  13. data/lib/pwnlib/ext/helper.rb +4 -4
  14. data/lib/pwnlib/logger.rb +87 -0
  15. data/lib/pwnlib/memleak.rb +58 -29
  16. data/lib/pwnlib/pwn.rb +19 -8
  17. data/lib/pwnlib/reg_sort.rb +102 -108
  18. data/lib/pwnlib/shellcraft/generators/amd64/common/common.rb +14 -0
  19. data/lib/pwnlib/shellcraft/generators/amd64/common/infloop.rb +17 -0
  20. data/lib/pwnlib/shellcraft/generators/amd64/common/memcpy.rb +31 -0
  21. data/lib/pwnlib/shellcraft/generators/amd64/common/mov.rb +127 -0
  22. data/lib/pwnlib/shellcraft/generators/amd64/common/nop.rb +16 -0
  23. data/lib/pwnlib/shellcraft/generators/amd64/common/popad.rb +27 -0
  24. data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr.rb +64 -0
  25. data/lib/pwnlib/shellcraft/generators/amd64/common/pushstr_array.rb +19 -0
  26. data/lib/pwnlib/shellcraft/generators/amd64/common/ret.rb +32 -0
  27. data/lib/pwnlib/shellcraft/generators/amd64/common/setregs.rb +19 -0
  28. data/lib/pwnlib/shellcraft/generators/amd64/linux/execve.rb +21 -0
  29. data/lib/pwnlib/shellcraft/generators/amd64/linux/linux.rb +14 -0
  30. data/lib/pwnlib/shellcraft/generators/amd64/linux/ls.rb +19 -0
  31. data/lib/pwnlib/shellcraft/generators/amd64/linux/sh.rb +19 -0
  32. data/lib/pwnlib/shellcraft/generators/amd64/linux/syscall.rb +21 -0
  33. data/lib/pwnlib/shellcraft/generators/helper.rb +106 -0
  34. data/lib/pwnlib/shellcraft/generators/i386/common/common.rb +14 -0
  35. data/lib/pwnlib/shellcraft/generators/i386/common/infloop.rb +17 -0
  36. data/lib/pwnlib/shellcraft/generators/i386/common/mov.rb +90 -0
  37. data/lib/pwnlib/shellcraft/generators/i386/common/nop.rb +16 -0
  38. data/lib/pwnlib/shellcraft/generators/i386/common/pushstr.rb +39 -0
  39. data/lib/pwnlib/shellcraft/generators/i386/common/pushstr_array.rb +19 -0
  40. data/lib/pwnlib/shellcraft/generators/i386/common/setregs.rb +19 -0
  41. data/lib/pwnlib/shellcraft/generators/i386/linux/execve.rb +19 -0
  42. data/lib/pwnlib/shellcraft/generators/i386/linux/linux.rb +14 -0
  43. data/lib/pwnlib/shellcraft/generators/i386/linux/ls.rb +19 -0
  44. data/lib/pwnlib/shellcraft/generators/i386/linux/sh.rb +19 -0
  45. data/lib/pwnlib/shellcraft/generators/i386/linux/syscall.rb +19 -0
  46. data/lib/pwnlib/shellcraft/generators/x86/common/common.rb +26 -0
  47. data/lib/pwnlib/shellcraft/generators/x86/common/infloop.rb +22 -0
  48. data/lib/pwnlib/shellcraft/generators/x86/common/mov.rb +15 -0
  49. data/lib/pwnlib/shellcraft/generators/x86/common/pushstr.rb +15 -0
  50. data/lib/pwnlib/shellcraft/generators/x86/common/pushstr_array.rb +85 -0
  51. data/lib/pwnlib/shellcraft/generators/x86/common/setregs.rb +82 -0
  52. data/lib/pwnlib/shellcraft/generators/x86/linux/execve.rb +69 -0
  53. data/lib/pwnlib/shellcraft/generators/x86/linux/linux.rb +14 -0
  54. data/lib/pwnlib/shellcraft/generators/x86/linux/ls.rb +66 -0
  55. data/lib/pwnlib/shellcraft/generators/x86/linux/sh.rb +52 -0
  56. data/lib/pwnlib/shellcraft/generators/x86/linux/syscall.rb +52 -0
  57. data/lib/pwnlib/shellcraft/registers.rb +145 -0
  58. data/lib/pwnlib/shellcraft/shellcraft.rb +67 -0
  59. data/lib/pwnlib/timer.rb +60 -0
  60. data/lib/pwnlib/tubes/buffer.rb +96 -0
  61. data/lib/pwnlib/tubes/sock.rb +95 -0
  62. data/lib/pwnlib/tubes/tube.rb +270 -0
  63. data/lib/pwnlib/util/cyclic.rb +95 -94
  64. data/lib/pwnlib/util/fiddling.rb +256 -220
  65. data/lib/pwnlib/util/getdents.rb +83 -0
  66. data/lib/pwnlib/util/hexdump.rb +109 -108
  67. data/lib/pwnlib/util/lists.rb +55 -0
  68. data/lib/pwnlib/util/packing.rb +226 -228
  69. data/lib/pwnlib/util/ruby.rb +18 -0
  70. data/lib/pwnlib/version.rb +2 -1
  71. data/test/abi_test.rb +21 -0
  72. data/test/asm_test.rb +104 -0
  73. data/test/constants/constant_test.rb +1 -0
  74. data/test/constants/constants_test.rb +4 -2
  75. data/test/context_test.rb +1 -0
  76. data/test/data/echo.rb +20 -0
  77. data/test/data/elfs/Makefile +22 -0
  78. data/test/data/elfs/amd64.frelro.elf +0 -0
  79. data/test/data/elfs/amd64.frelro.pie.elf +0 -0
  80. data/test/data/elfs/amd64.nrelro.elf +0 -0
  81. data/test/data/elfs/amd64.prelro.elf +0 -0
  82. data/test/data/elfs/i386.frelro.pie.elf +0 -0
  83. data/test/data/elfs/i386.prelro.elf +0 -0
  84. data/test/data/elfs/source.cpp +19 -0
  85. data/test/data/flag +1 -0
  86. data/test/data/lib32/ld.so.2 +0 -0
  87. data/test/data/lib32/libc.so.6 +0 -0
  88. data/test/data/lib64/ld.so.2 +0 -0
  89. data/test/data/lib64/libc.so.6 +0 -0
  90. data/test/dynelf_test.rb +59 -24
  91. data/test/elf/elf_test.rb +120 -0
  92. data/test/ext_test.rb +3 -2
  93. data/test/files/use_pwnlib.rb +1 -1
  94. data/test/logger_test.rb +61 -0
  95. data/test/memleak_test.rb +4 -33
  96. data/test/reg_sort_test.rb +3 -1
  97. data/test/shellcraft/infloop_test.rb +26 -0
  98. data/test/shellcraft/linux/ls_test.rb +108 -0
  99. data/test/shellcraft/linux/sh_test.rb +119 -0
  100. data/test/shellcraft/linux/syscalls/execve_test.rb +136 -0
  101. data/test/shellcraft/linux/syscalls/syscall_test.rb +83 -0
  102. data/test/shellcraft/memcpy_test.rb +35 -0
  103. data/test/shellcraft/mov_test.rb +98 -0
  104. data/test/shellcraft/nop_test.rb +26 -0
  105. data/test/shellcraft/popad_test.rb +29 -0
  106. data/test/shellcraft/pushstr_array_test.rb +91 -0
  107. data/test/shellcraft/pushstr_test.rb +108 -0
  108. data/test/shellcraft/registers_test.rb +32 -0
  109. data/test/shellcraft/ret_test.rb +30 -0
  110. data/test/shellcraft/setregs_test.rb +62 -0
  111. data/test/shellcraft/shellcraft_test.rb +28 -0
  112. data/test/test_helper.rb +12 -1
  113. data/test/timer_test.rb +23 -0
  114. data/test/tubes/buffer_test.rb +45 -0
  115. data/test/tubes/sock_test.rb +68 -0
  116. data/test/tubes/tube_test.rb +241 -0
  117. data/test/util/cyclic_test.rb +2 -1
  118. data/test/util/fiddling_test.rb +2 -1
  119. data/test/util/getdents_test.rb +32 -0
  120. data/test/util/hexdump_test.rb +7 -9
  121. data/test/util/lists_test.rb +21 -0
  122. data/test/util/packing_test.rb +4 -3
  123. metadata +215 -25
@@ -0,0 +1,52 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'pwnlib/shellcraft/generators/x86/common/setregs'
4
+ require 'pwnlib/shellcraft/generators/x86/linux/linux'
5
+
6
+ module Pwnlib
7
+ module Shellcraft
8
+ module Generators
9
+ module X86
10
+ module Linux
11
+ # Assembly of +syscall+.
12
+ #
13
+ # @example
14
+ # context.arch = 'i386'
15
+ # puts shellcraft.syscall('SYS_open', 'esp', 0, 0)
16
+ # # /* call open("esp", 0, 0) */
17
+ # # push 5 /* (SYS_open) */
18
+ # # pop eax
19
+ # # mov ebx, esp
20
+ # # xor ecx, ecx /* 0 */
21
+ # # cdq /* edx=0 */
22
+ # # int 0x80
23
+ # #=> nil
24
+ def syscall(*arguments)
25
+ abi = ::Pwnlib::ABI::ABI.syscall
26
+ syscall, arg0, arg1, arg2, arg3, arg4, arg5 = arguments
27
+ if syscall.respond_to?(:to_s) && syscall.to_s.start_with?('SYS_')
28
+ syscall_repr = syscall.to_s[4..-1] + '(%s)'
29
+ args = []
30
+ else
31
+ syscall_repr = 'syscall(%s)'
32
+ args = [syscall ? syscall.inspect : '?']
33
+ end
34
+ # arg0 to arg5
35
+ 1.upto(6) do |i|
36
+ args.push(arguments[i] ? arguments[i].inspect : '?')
37
+ end
38
+
39
+ args.pop while args.last == '?'
40
+ syscall_repr = format(syscall_repr, args.join(', '))
41
+ registers = abi.register_arguments
42
+ arguments = [syscall, arg0, arg1, arg2, arg3, arg4, arg5]
43
+ reg_ctx = registers.zip(arguments).to_h
44
+ cat "/* call #{syscall_repr} */"
45
+ cat Common.setregs(reg_ctx) if arguments.any? { |v| !v.nil? }
46
+ cat abi.syscall_str
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,145 @@
1
+ require 'pwnlib/context'
2
+
3
+ module Pwnlib
4
+ module Shellcraft
5
+ # Define register names and methods for shellcode generators.
6
+ module Registers
7
+ X86_BASEREGS = %w(ax cx dx bx sp bp si di ip).freeze
8
+
9
+ I386 = (X86_BASEREGS.map { |r| "e#{r}" } +
10
+ X86_BASEREGS +
11
+ %w(eflags cs ss ds es fs gs)).freeze
12
+
13
+ AMD64 = (X86_BASEREGS.map { |r| "r#{r}" } +
14
+ (8..15).map { |r| "r#{r}" } +
15
+ (8..15).map { |r| "r#{r}d" } +
16
+ I386).freeze
17
+
18
+ # x86 registers in decreasing size
19
+ X86_ORDERED = ([
20
+ %w(rax eax ax al),
21
+ %w(rbx ebx bx bl),
22
+ %w(rcx ecx cx cl),
23
+ %w(rdx edx dx dl),
24
+ %w(rdi edi di),
25
+ %w(rsi esi si),
26
+ %w(rbp ebp bp),
27
+ %w(rsp esp sp)
28
+ ] + (8..15).map { |r| ['', 'd', 'w', 'b'].map { |t| "r#{r}#{t}" } }).freeze
29
+
30
+ # class Register, currently only supports i386 and amd64.
31
+ class Register
32
+ # @return [String]
33
+ # Register's name.
34
+ attr_reader :name
35
+ attr_reader :bigger, :smaller, :ff00, :is64bit, :native64, :native32, :xor
36
+ attr_reader :size, :sizes
37
+
38
+ # Instantiate a {Register} object.
39
+ #
40
+ # Create a register by its name and size (in bits) for fetching other information. For example, for register
41
+ # 'ax', +#bigger+ contains 'rax' and 'eax'.
42
+ #
43
+ # Normally you don't need to create any {Register} object, use {.get_register} to get register by name.
44
+ #
45
+ # @param [String] name
46
+ # Register's name.
47
+ # @param [Integer] size
48
+ # Register size in bits.
49
+ #
50
+ # @example
51
+ # Register.new('rax', 64)
52
+ # Register.new('bx', 16)
53
+ def initialize(name, size)
54
+ @name = name
55
+ @size = size
56
+ X86_ORDERED.each do |row|
57
+ next unless row.include?(name)
58
+ @bigger = row[0, row.index(name)]
59
+ @smaller = row[(row.index(name) + 1)..-1]
60
+ @native64 = row[0]
61
+ @native32 = row[1]
62
+ @sizes = row.each_with_object({}).with_index { |(r, h), i| h[64 >> i] = r }
63
+ @xor = @sizes[[size, 32].min]
64
+ break
65
+ end
66
+ @ff00 = name[1] + 'h' if @size >= 32 && @name.end_with?('x')
67
+ @is64bit = true if @name.start_with?('r')
68
+ end
69
+
70
+ def bits
71
+ size
72
+ end
73
+
74
+ def bytes
75
+ bits / 8
76
+ end
77
+
78
+ def fits(value)
79
+ size >= Registers.bits_required(value)
80
+ end
81
+
82
+ def to_s
83
+ name
84
+ end
85
+
86
+ def inspect
87
+ format('Register(%s)', name)
88
+ end
89
+ end
90
+
91
+ module_function
92
+
93
+ def registers
94
+ {
95
+ [32, 'i386', 'linux'] => ::Pwnlib::Shellcraft::Registers::I386,
96
+ [64, 'amd64', 'linux'] => ::Pwnlib::Shellcraft::Registers::AMD64
97
+ }[[context.bits, context.arch, context.os]]
98
+ end
99
+
100
+ INTEL = (X86_ORDERED.each_with_object({}) do |row, obj|
101
+ row.each_with_index do |reg, i|
102
+ obj[reg] = Register.new(reg, 64 >> i)
103
+ end
104
+ end).freeze
105
+
106
+ # Get a {Register} object by name.
107
+ #
108
+ # @param [String, Symbol, Register] name
109
+ # The name of register.
110
+ # If +name+ is already a {Register} object, +name+ itself will be returned.
111
+ #
112
+ # @return [Register?]
113
+ # Get the register with name +name+.
114
+ #
115
+ # @example
116
+ # Registers.get_register('eax')
117
+ # #=> Register(eax)
118
+ # Registers.get_register(:ebx)
119
+ # #=> Register(ebx)
120
+ # Registers.get_register('xdd')
121
+ # #=> nil
122
+ def get_register(name)
123
+ return name if name.instance_of?(Register)
124
+ return INTEL[name.to_s] if name.instance_of?(String) || name.instance_of?(Symbol)
125
+ nil
126
+ end
127
+
128
+ def register?(obj)
129
+ !get_register(obj).nil?
130
+ end
131
+
132
+ def bits_required(value)
133
+ bits = 0
134
+ value = value.abs
135
+ while value > 0
136
+ bits += 8
137
+ value >>= 8
138
+ end
139
+ bits
140
+ end
141
+
142
+ include ::Pwnlib::Context
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,67 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'singleton'
4
+
5
+ require 'pwnlib/context'
6
+ require 'pwnlib/logger'
7
+
8
+ module Pwnlib
9
+ # Implement shellcraft!
10
+ #
11
+ # All shellcode generators are defined under generators/*.
12
+ # While typing +Shellcraft::Generators::I386::Linux.sh+ is too annoying, we define an instance +shellcraft+ in this
13
+ # module, which let user invoke +shellcraft.sh+ directly.
14
+ module Shellcraft
15
+ # Singleton class.
16
+ class Shellcraft
17
+ include ::Singleton
18
+
19
+ # All files under generators/ will be required.
20
+ def initialize
21
+ Dir[File.join(__dir__, 'generators', '**', '*.rb')].each do |f|
22
+ require f
23
+ end
24
+ end
25
+
26
+ # Will search modules/methods under {Shellcraft::Generators} according to current arch and os.
27
+ # i.e. +Shellcraft::Generators::${arch}::<Common|${os}>.${method}+.
28
+ #
29
+ # With this method, +context.local(arch: 'amd64') { shellcraft.sh }+ will invoke
30
+ # {Shellcraft::Generators::Amd64::Linux#sh}.
31
+ def method_missing(method, *args, &block)
32
+ mod = find_module_for(method)
33
+ return super if mod.nil?
34
+ mod.public_send(method, *args, &block)
35
+ end
36
+
37
+ # For +respond_to?+.
38
+ def respond_to_missing?(method, include_private = false)
39
+ return true if find_module_for(method)
40
+ super
41
+ end
42
+
43
+ private
44
+
45
+ # @return [Module?]
46
+ # +nil+ for not found.
47
+ def find_module_for(method)
48
+ begin
49
+ arch_module = ::Pwnlib::Shellcraft::Generators.const_get(context.arch.capitalize)
50
+ rescue NameError
51
+ log.error("Can't use shellcraft under architecture #{context.arch.inspect}.")
52
+ return nil
53
+ end
54
+ # try search in Common module
55
+ common_module = arch_module.const_get(:Common)
56
+ return common_module if common_module.singleton_methods.include?(method)
57
+ # search in ${os} module
58
+ os_module = arch_module.const_get(context.os.capitalize)
59
+ return os_module if os_module.singleton_methods.include?(method)
60
+ nil
61
+ end
62
+
63
+ include ::Pwnlib::Context
64
+ include ::Pwnlib::Logger
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,60 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'time'
4
+
5
+ require 'pwnlib/context'
6
+
7
+ module Pwnlib
8
+ # A simple timer class.
9
+ # TODO(Darkpi): Python pwntools seems to have many unreasonable codes in this class,
10
+ # not sure of the use case of this, check if everything is coded as
11
+ # intended after we have some use cases. (e.g. sock)
12
+ # NOTE(Darkpi): This class is actually quite weird, and expected to be used only in tubes.
13
+ class Timer
14
+ # @diff We just use nil for default and :forever for forever.
15
+
16
+ def initialize(timeout = nil)
17
+ @deadline = nil
18
+ @timeout = timeout
19
+ end
20
+
21
+ def started?
22
+ @deadline
23
+ end
24
+
25
+ def active?
26
+ started? && (@deadline == :forever || Time.now < @deadline)
27
+ end
28
+
29
+ def timeout
30
+ return @timeout || Pwnlib::Context.context.timeout unless started?
31
+ @deadline == :forever ? :forever : [@deadline - Time.now, 0].max
32
+ end
33
+
34
+ def timeout=(timeout)
35
+ raise "Can't change timeout when countdown" if started?
36
+ @timeout = timeout
37
+ end
38
+
39
+ # @diff We do NOT allow nested countdown with non-default value. This simplifies thing a lot.
40
+ # NOTE(Darkpi): timeout = nil means default value for the first time, and nop after that.
41
+ def countdown(timeout = nil)
42
+ raise ArgumentError, 'Need a block for countdown' unless block_given?
43
+ if started?
44
+ return yield if timeout.nil?
45
+ raise 'Nested countdown not permitted'
46
+ end
47
+
48
+ timeout ||= @timeout
49
+ timeout ||= Pwnlib::Context.context.timeout
50
+
51
+ @deadline = timeout == :forever ? :forever : Time.now + timeout
52
+
53
+ begin
54
+ yield
55
+ ensure
56
+ @deadline = nil
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,96 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ module Pwnlib
4
+ module Tubes
5
+ # Buffer that support deque-like string operations.
6
+ class Buffer
7
+ # Instantiate a {Pwnlib::Tubes::Buffer} object.
8
+ def initialize
9
+ @data = []
10
+ @size = 0
11
+ end
12
+
13
+ attr_reader :size
14
+ alias length size
15
+
16
+ # Check whether the buffer is empty.
17
+ #
18
+ # @return [Boalean]
19
+ # Returns true if contains no elements.
20
+ def empty?
21
+ size.zero?
22
+ end
23
+
24
+ # Python __contains__ and index is only correct with single-char input, which doesn't seems to
25
+ # be useful, and they're not used anywhere either. Just ignore them for now.
26
+
27
+ # Adds data to the buffer.
28
+ #
29
+ # @param [String] data
30
+ # Data to add.
31
+ def add(data)
32
+ case data
33
+ when Buffer
34
+ @data.concat(data.data)
35
+ else
36
+ data = data.to_s.dup
37
+ return if data.empty?
38
+ @data << data
39
+ end
40
+ @size += data.size
41
+ self
42
+ end
43
+ alias << add
44
+
45
+ # Places data at the front of the buffer.
46
+ #
47
+ # @param [String] data
48
+ # Data to place at the beginning of the buffer.
49
+ def unget(data)
50
+ case data
51
+ when Buffer
52
+ @data.unshift(*data.data)
53
+ else
54
+ data = data.to_s.dup
55
+ return if data.empty?
56
+ @data.unshift(data)
57
+ end
58
+ @size += data.size
59
+ self
60
+ end
61
+
62
+ # Retrieves bytes from the buffer.
63
+ #
64
+ # @param [Integer] n
65
+ # Maximum number of bytes to fetch.
66
+ #
67
+ # @return [String]
68
+ # Data as string.
69
+ def get(n = nil)
70
+ if n.nil? || n >= size
71
+ data = @data.join
72
+ @size = 0
73
+ @data = []
74
+ else
75
+ have = 0
76
+ idx = 0
77
+ while have < n
78
+ have += @data[idx].size
79
+ idx += 1
80
+ end
81
+ data = @data.slice!(0...idx).join
82
+ if have > n
83
+ extra = data.slice!(n..-1)
84
+ @data.unshift(extra)
85
+ end
86
+ @size -= n
87
+ end
88
+ data
89
+ end
90
+
91
+ protected
92
+
93
+ attr_reader :data
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,95 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ require 'socket'
4
+
5
+ require 'pwnlib/tubes/tube'
6
+
7
+ module Pwnlib
8
+ module Tubes
9
+ # Socket!
10
+ class Sock < Tube
11
+ # Instantiate a {Pwnlib::Tubes::Sock} object.
12
+ #
13
+ # @param [String] host
14
+ # The host to connect.
15
+ # @param [Integer] port
16
+ # The port to connect.
17
+ def initialize(host, port)
18
+ super()
19
+ @sock = TCPSocket.new(host, port)
20
+ @sock.binmode
21
+ @timeout = nil
22
+ @closed = { recv: false, send: false }
23
+ end
24
+
25
+ def io
26
+ @sock
27
+ end
28
+ alias sock io
29
+
30
+ # Close the TCPSocket if no arguments passed.
31
+ # Or close the direction in +sock+.
32
+ #
33
+ # @param [:both, :recv, :read, :send, :write] direction
34
+ # * Close the TCPSocket if +:both+ or no arguments passed.
35
+ # * Disallow further read in +sock+ if +:recv+ or +:read+ passed.
36
+ # * Disallow further write in +sock+ if +:send+ or +:write+ passed.
37
+ #
38
+ # @diff In pwntools-python, method +shutdown(direction)+ is for closing socket one side,
39
+ # +close()+ is for closing both side. We merge these two methods into one here.
40
+ def close(direction = :both)
41
+ case direction
42
+ when :both
43
+ return if @sock.closed?
44
+ @closed[:recv] = @closed[:send] = true
45
+ @sock.close
46
+ when :recv, :read
47
+ shutdown(:recv)
48
+ when :send, :write
49
+ shutdown(:send)
50
+ else
51
+ raise ArgumentError, 'Only allow :both, :recv, :read, :send and :write passed'
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def shutdown(direction)
58
+ return if @closed[direction]
59
+ @closed[direction] = true
60
+
61
+ if direction.equal?(:recv)
62
+ @sock.close_read
63
+ elsif direction.equal?(:send)
64
+ @sock.close_write
65
+ end
66
+ end
67
+
68
+ def timeout_raw=(timeout)
69
+ @timeout = timeout == :forever ? nil : timeout
70
+ end
71
+
72
+ def send_raw(data)
73
+ raise EOFError if @closed[:send]
74
+ begin
75
+ @sock.write(data)
76
+ rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED
77
+ shutdown(:send)
78
+ raise EOFError
79
+ end
80
+ end
81
+
82
+ def recv_raw(size)
83
+ raise EOFError if @closed[:recv]
84
+ begin
85
+ rs, = IO.select([@sock], [], [], @timeout)
86
+ return if rs.nil?
87
+ return @sock.readpartial(size)
88
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED, EOFError
89
+ shutdown(:recv)
90
+ raise EOFError
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end