pwntools 0.1.0 → 1.0.0

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