-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/serialtube #90
Changes from 26 commits
b3fe465
ab1d1ee
5cd3a53
e1a674a
20e6a38
31b57b3
7dfa4f4
7cfc0fc
ad30289
f1930b7
55cc048
ba86f2c
cbffd90
39a3fbc
1da19e8
3a3661b
7b84223
94c3b08
4772e6a
78a21a4
867b892
b6077ae
ffdee6f
625e103
6b61cb1
ebfa490
4fc138e
b04dbff
4ac3f86
79a8b33
33b2d21
f6a724d
cf5f22e
3add469
910c0dc
55a87c9
589dd09
764ada5
bda6f8b
0f68f83
8257d80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,8 +54,9 @@ def countdown(timeout = nil) | |
begin | ||
yield | ||
ensure | ||
raise ::Pwnlib::Errors::TimeoutError unless active? | ||
was_active = active? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for pointing out this, |
||
@deadline = nil | ||
raise ::Pwnlib::Errors::TimeoutError unless was_active | ||
end | ||
end | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
# encoding: ASCII-8BIT | ||
|
||
require 'rubyserial' | ||
|
||
require 'pwnlib/tubes/tube' | ||
|
||
module Pwnlib | ||
module Tubes | ||
# Serial Connections | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. change here to # @!macro [new] raise_eof
# @raise [Pwnlib::Errors::EndOfTubeError]
# If the request is not satisfied when all data is received.
# Serial Connections
class SerialTube < Tube otherwise YARD will have wrong result |
||
# @!macro [new] raise_eof | ||
# @raise [Pwnlib::Errors::EndOfTubeError] | ||
# If the request is not satisfied when all data is received. | ||
class SerialTube < Tube | ||
# Instantiate a {Pwnlib::Tubes::SerialTube} object. | ||
# | ||
# @param [String] port | ||
# A device name for rubyserial to open, e.g. /dev/ttypUSB0 | ||
# @param [Integer] baudrate | ||
# Baud rate. | ||
# @param [Boolean] convert_newlines | ||
# If +true+, convert any +context.newline+s to +"\\r\\n"+ before | ||
# sending to remote. Has no effect on bytes received. | ||
# @param [Integer] bytesize | ||
# Serial character byte size. The '8' in '8N1'. | ||
# @param [Symbol] parity | ||
# Serial character parity. The 'N' in '8N1'. | ||
def initialize(port = nil, baudrate: 115_200, | ||
convert_newlines: true, | ||
bytesize: 8, parity: :none) | ||
super() | ||
|
||
# go hunting for a port | ||
port ||= Dir.glob('/dev/tty.usbserial*').first | ||
port ||= '/dev/ttyUSB0' | ||
|
||
@convert_newlines = convert_newlines | ||
@conn = Serial.new(port, baudrate, bytesize, parity) | ||
@serial_timer = Timer.new | ||
end | ||
|
||
# Closes the active connection | ||
def close | ||
@conn.close if @conn | ||
@conn = nil | ||
end | ||
|
||
# Implementation of the methods required for tube | ||
private | ||
|
||
# Gets bytes over the serial connection until some bytes are received, or | ||
# +@timeout+ has passed. It is an error for it to return no data in less | ||
# than +@timeout+ seconds. It is ok for it to return some data in less | ||
# time. | ||
# | ||
# @param [Integer] numbytes | ||
# An upper limit on the number of bytes to get. | ||
# | ||
# @return [String] | ||
# A string containing read bytes. | ||
# | ||
# @!macro raise_eof | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yard doesn't support cross-file macro definition naturally |
||
def recv_raw(numbytes) | ||
raise ::Pwnlib::Errors::EndOfTubeError if @conn.nil? | ||
|
||
@serial_timer.countdown do | ||
data = '' | ||
begin | ||
while @serial_timer.active? | ||
data += @conn.read(numbytes - data.length) | ||
break unless data.empty? | ||
sleep 0.1 | ||
end | ||
# XXX(JonathanBeverey): should we reverse @convert_newlines here? | ||
return data | ||
rescue RubySerial::Error | ||
close | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Current tests don't cover this rescue There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't see any way to do this. open(2) checks for all the same errors as read(2). Nothing I've done will cause an error in the right place, without writing bad code. If you know a way the test code can break Ruby's encapsulation model and directly invoke SerialTube.conn.close(), that could do it. But I really don't want to write a method to allow that, and honestly, if I did, I would then tighten up the error checking around @conn and close off that avenue as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found a way. |
||
raise ::Pwnlib::Errors::EndOfTubeError | ||
end | ||
end | ||
end | ||
|
||
# Sends bytes over the serial connection. This call will block until all the bytes are sent or an error occurs. | ||
# | ||
# @param [String] data | ||
# A string of the bytes to send. | ||
# | ||
# @return [Integer] | ||
# The number of bytes successfully written. | ||
# | ||
# @!macro raise_eof | ||
def send_raw(data) | ||
raise ::Pwnlib::Errors::EndOfTubeError if @conn.nil? | ||
|
||
data.gsub!(context.newline, "\r\n") if @convert_newlines | ||
begin | ||
return @conn.write(data) | ||
rescue RubySerial::Error | ||
close | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||
raise ::Pwnlib::Errors::EndOfTubeError | ||
end | ||
end | ||
|
||
# Sets the +timeout+ to use for subsequent +recv_raw+ calls. | ||
# | ||
# @param [Float] timeout | ||
def timeout_raw=(timeout) | ||
@serial_timer.timeout = timeout | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,6 +51,9 @@ def initialize(timeout: nil) | |
# @return [String] | ||
# A string contains bytes received from the tube, or +''+ if a timeout occurred while | ||
# waiting. | ||
# | ||
# @!macro raise_eof | ||
# @!macro raise_timeout | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one isn't cross-file. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. my fault |
||
def recv(num_bytes = nil, timeout: nil) | ||
return '' if @buffer.empty? && !fillbuffer(timeout: timeout) | ||
@buffer.get(num_bytes) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
# encoding: ASCII-8BIT | ||
|
||
require 'open3' | ||
|
||
require 'test_helper' | ||
|
||
require 'pwnlib/tubes/serialtube' | ||
|
||
class SerialTest < MiniTest::Test | ||
include ::Pwnlib::Tubes | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the serial tube intend to work on macOS? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And skip on Windows as well There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't have access to a mac to test it on. It seems likely that it will work off-the-shelf. rubyserial is supported on Windows, and so it's possible that serialtube will work there. I don't have a Windows/ruby setup in-place, nor do I have any idea how to test it there. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To install socat, simply add Is that possible to test serialtube on Windows? I said skip on Windows because the tests you added ( If serialtube is supposed to support on Windows, maybe you should try another testing method to make it work on Windows as well. |
||
def skip_windows | ||
skip 'Not test tube/serialtube on Windows' if TTY::Platform.new.windows? | ||
end | ||
|
||
def open_pair | ||
Open3.popen3('socat -d -d pty,raw,echo=0 pty,raw,echo=0') do |_i, _o, stderr, thread| | ||
devs = [] | ||
2.times do | ||
devs << stderr.readline.chomp.split.last | ||
# First pattern matches Linux, second is macOS | ||
raise IOError, 'Could not create serial crosslink' if devs.last !~ %r{^(/dev/pts/[0-9]+|/dev/ttys[0-9]+)$} | ||
end | ||
|
||
serial = SerialTube.new devs[1], convert_newlines: false | ||
|
||
begin | ||
File.open devs[0], 'r+' do |file| | ||
file.set_encoding 'default'.encoding | ||
yield file, serial | ||
end | ||
ensure | ||
Process.kill('SIGTERM', thread.pid) | ||
end | ||
end | ||
end | ||
|
||
def random_string(length) | ||
Random.rand(36**length).to_s(36).encode('default'.encoding) | ||
end | ||
|
||
def test_recv | ||
skip_windows | ||
open_pair do |file, serial| | ||
# recv, recvline | ||
rs = random_string 24 | ||
file.puts rs | ||
result = serial.recv 8, timeout: 1 | ||
|
||
assert_equal(rs[0...8], result) | ||
result = serial.recv 8 | ||
assert_equal(rs[8...16], result) | ||
result = serial.recvline.chomp | ||
assert_equal(rs[16..-1], result) | ||
|
||
assert_raises(Pwnlib::Errors::TimeoutError) { serial.recv(1, timeout: 0.2) } | ||
|
||
# recvpred | ||
rs = random_string 12 | ||
file.print rs | ||
result = serial.recvpred do |data| | ||
data[-6..-1] == rs[-6..-1] | ||
end | ||
assert_equal rs, result | ||
|
||
assert_raises(Pwnlib::Errors::TimeoutError) { serial.recv(1, timeout: 0.2) } | ||
|
||
# recvn | ||
rs = random_string 6 | ||
file.print rs | ||
result = '' | ||
assert_raises(Pwnlib::Errors::TimeoutError) do | ||
result = serial.recvn 120, timeout: 1 | ||
end | ||
assert_empty result | ||
file.print rs | ||
result = serial.recvn 12 | ||
assert_equal rs * 2, result | ||
|
||
assert_raises(Pwnlib::Errors::TimeoutError) { serial.recv(1, timeout: 0.2) } | ||
|
||
# recvuntil | ||
rs = random_string 12 | ||
file.print rs + '|' | ||
result = serial.recvuntil('|').chomp('|') | ||
assert_equal rs, result | ||
|
||
assert_raises(Pwnlib::Errors::TimeoutError) { serial.recv(1, timeout: 0.2) } | ||
|
||
# gets | ||
rs = random_string 24 | ||
file.puts rs | ||
result = serial.gets 12 | ||
assert_equal rs[0...12], result | ||
result = serial.gets.chomp | ||
assert_equal rs[12..-1], result | ||
|
||
assert_raises(Pwnlib::Errors::TimeoutError) { serial.recv(1, timeout: 0.2) } | ||
end | ||
end | ||
|
||
def test_send | ||
skip_windows | ||
open_pair do |file, serial| | ||
# send, sendline | ||
rs = random_string 24 | ||
# rubocop:disable Style/Send | ||
# Justification: This isn't Object#send, false positive. | ||
serial.send rs[0...12] | ||
# rubocop:enable Style/Send | ||
serial.sendline rs[12...24] | ||
result = file.readline.chomp | ||
assert_equal rs, result | ||
|
||
# puts | ||
r1 = random_string 4 | ||
r2 = random_string 4 | ||
r3 = random_string 4 | ||
serial.puts r1, r2, r3 | ||
result = '' | ||
3.times do | ||
result += file.readline.chomp | ||
end | ||
assert_equal r1 + r2 + r3, result | ||
end | ||
end | ||
|
||
def test_close | ||
skip_windows | ||
open_pair do |_file, serial| | ||
serial.close | ||
assert_raises(::Pwnlib::Errors::EndOfTubeError) { serial.puts(514) } | ||
assert_raises(::Pwnlib::Errors::EndOfTubeError) { serial.puts(514) } | ||
assert_raises(::Pwnlib::Errors::EndOfTubeError) { serial.recv } | ||
assert_raises(::Pwnlib::Errors::EndOfTubeError) { serial.recv } | ||
assert_raises(ArgumentError) { serial.close(:hh) } | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put this line before 'pwnlib/tubes/sock'