-
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 all 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 |
---|---|---|
@@ -0,0 +1,112 @@ | ||
# encoding: ASCII-8BIT | ||
|
||
require 'rubyserial' | ||
|
||
require 'pwnlib/tubes/tube' | ||
|
||
module Pwnlib | ||
module Tubes | ||
# @!macro [new] raise_eof | ||
# @raise [Pwnlib::Errors::EndOfTubeError] | ||
# If the request is not satisfied when all data is received. | ||
|
||
# Serial Connections | ||
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 && [email protected]? | ||
@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 | ||
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(JonathanBeverley): should we reverse @convert_newlines here? | ||
return data | ||
rescue RubySerial::Error | ||
close | ||
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 |
---|---|---|
|
@@ -54,6 +54,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,165 @@ | ||
# encoding: ASCII-8BIT | ||
|
||
require 'open3' | ||
|
||
require 'test_helper' | ||
|
||
require 'pwnlib/tubes/serialtube' | ||
|
||
module Pwnlib | ||
module Tubes | ||
class SerialTube | ||
def break_encapsulation | ||
@conn.close | ||
end | ||
end | ||
end | ||
end | ||
|
||
class SerialTest < MiniTest::Test | ||
include ::Pwnlib::Tubes | ||
|
||
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 | ||
# To ensure socat have finished setup | ||
stderr.gets('starting data transfer loop') | ||
|
||
serial = SerialTube.new devs[1], convert_newlines: false | ||
|
||
begin | ||
File.open devs[0], 'r+' do |file| | ||
file.set_encoding 'default'.encoding | ||
yield file, serial, thread | ||
end | ||
ensure | ||
::Process.kill('SIGTERM', thread.pid) if thread.alive? | ||
end | ||
end | ||
end | ||
|
||
def random_string(length) | ||
Random.rand(36**length).to_s(36).rjust(length, '0') | ||
end | ||
|
||
def test_raise | ||
skip_windows | ||
open_pair do |_file, serial, thread| | ||
::Process.kill('SIGTERM', thread.pid) | ||
# ensure the process has been killed | ||
thread.value | ||
assert_raises(Pwnlib::Errors::EndOfTubeError) { serial.puts('a') } | ||
end | ||
open_pair do |_file, serial| | ||
serial.break_encapsulation | ||
assert_raises(Pwnlib::Errors::EndOfTubeError) { serial.recv(1, timeout: 2) } | ||
end | ||
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.
Yard doesn't support cross-file macro definition naturally
https://stackoverflow.com/questions/10324926/how-to-make-yard-macros-apply-to-multiple-files