twib 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: be083ce5e43cdb183a7548ce6ad0b2eabc77e425
4
- data.tar.gz: 58e462f9150b324ac65acdaa12dbc1fb10dc4df9
3
+ metadata.gz: b9015c1f3220bfaeda563a3f8c1cc515b29379dd
4
+ data.tar.gz: 35e11df95809753993e09f2374e710bdb0459d09
5
5
  SHA512:
6
- metadata.gz: a58ca6fa102c71854d30b3b2bb4338e6425f59fd05be3392ad660816a617fb82866a5508ff3b50c542b29125ff1c1d76b6a67cee14f8d1e737780304e2835d5a
7
- data.tar.gz: f77ef0b936b82b29b2d5e48bc803b85d37e077a7222118ee4d2c6b070f0368331744ce6ef862525b8f7746cb5a1cc75d654f606674959ec9e49cc48906c581a2
6
+ metadata.gz: 7924978593a63d765464260639f158aa4cc1b3690851635e77b7b2fd1bf367aef5c6d0944453bf195a21a1268fddb5fe246b8e57b038facd5024c5c8f307815a
7
+ data.tar.gz: def10e5a85b65b522bdd401718c12df4ac01fc37e457a1207b929068dddbe307fe474be82ef1c632575294a254cef8e09737c868065e0586875a6367b2685cb1
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- twib (0.1.0)
4
+ twib (0.1.1)
5
5
  msgpack
6
6
 
7
7
  GEM
data/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ Copyright 2018 misson20000
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
4
+
5
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # ruby-twib
2
+ [![Gem Version](https://badge.fury.io/rb/twib.svg)](https://badge.fury.io/rb/twib)
2
3
 
3
4
  [Twili](https://github.com/misson20000/twili) bridge client for Ruby, providing a way for Ruby applications to send requests to a Twili device via twibd.
4
5
 
@@ -22,19 +23,21 @@ Or install it yourself as:
22
23
 
23
24
  See the instructions on the [Twili repository](https://github.com/misson20000/twili#twili) for how to set up Twili and twibd.
24
25
 
25
- First, create a Twib::TwibConnection. There is a convenience method `Twib::TwibConnection::connect_unix`
26
+ First, create a [Twib::TwibConnection](https://www.rubydoc.info/gems/twib/Twib/TwibConnection). There is a convenience method [Twib::TwibConnection::connect_unix](https://www.rubydoc.info/gems/twib/Twib/TwibConnection#connect_unix-class_method).
26
27
  ```ruby
28
+ > require "twib"
29
+ => true
27
30
  > tc = Twib::TwibConnection.connect_unix
28
31
  => #<Twib::TwibConnection:...>
29
32
  ```
30
33
 
31
- After that, you can list the available devices with `TwibConnection#list_devices`, which returns an array of hashes identifying each device.
34
+ After that, you can list the available devices with [TwibConnection#list_devices](https://www.rubydoc.info/gems/twib/Twib/TwibConnection#list_devices-instance_method), which returns an array of hashes identifying each device.
32
35
  ```ruby
33
36
  > tc.list_devices
34
37
  [{"device_id"=>507914862, ...}, ...]
35
38
  ```
36
39
 
37
- Use `TwibConnection#open_device` to get a device's `ITwibDeviceInterface`.
40
+ Use [TwibConnection#open_device](https://www.rubydoc.info/gems/twib/Twib/TwibConnection#open_device-instance_method) to get a device's [ITwibDeviceInterface](https://www.rubydoc.info/gems/twib/Twib/Interfaces/ITwibDeviceInterface).
38
41
  ```ruby
39
42
  > tc.open_device(507914862)
40
43
  => #<Twib::Interfaces::ITwibDeviceInterface:...>
@@ -49,3 +52,13 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
49
52
  ## Contributing
50
53
 
51
54
  Bug reports and pull requests are welcome on GitHub at https://github.com/misson20000/ruby-twib.
55
+
56
+ ## License
57
+
58
+ ```
59
+ Copyright 2018 misson20000
60
+
61
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
62
+
63
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
64
+ ```
@@ -8,16 +8,26 @@ require "twib/interfaces/ITwibDeviceInterface"
8
8
  module Twib
9
9
  class Error < RuntimeError
10
10
  end
11
-
11
+
12
+ # An error originating from either twibd or a remote device.
12
13
  class ResultError < Error
13
14
  def initialize(code)
14
- super("0x#{code.to_s(16)}")
15
- @code = code
15
+ super("0x#{code.to_i.to_s(16)}")
16
+ @code = code.to_i
16
17
  end
18
+
19
+ # @return [Integer] the bad result code that caused this error
17
20
  attr_reader :code
18
21
  end
19
-
22
+
23
+ # A response from either twibd or a remote device.
20
24
  class Response
25
+ # @param device_id [Integer] ID of the device that this response originated from.
26
+ # @param object_id [Integer] ID of the bridge object that this response originated from.
27
+ # @param result_code [Integer] Result code
28
+ # @param tag [Integer] Tag corresponding to the request that prompted this response.
29
+ # @param payload [String] Raw data associated with the response
30
+ # @param object_ids [Array<Integer>] Object IDs sent with the response
21
31
  def initialize(device_id, object_id, result_code, tag, payload, object_ids)
22
32
  @device_id = device_id
23
33
  @object_id = object_id
@@ -28,7 +38,10 @@ module Twib
28
38
  end
29
39
 
30
40
  attr_reader :device_id, :object_id, :result_code, :tag, :payload, :object_ids
31
-
41
+
42
+ # Raises a {ResultError} if the {#result_code} is not OK.
43
+ # @raise [ResultError]
44
+ # @return [self]
32
45
  def assert_ok
33
46
  if @result_code != 0 then
34
47
  raise ResultError.new(@result_code)
@@ -36,7 +49,8 @@ module Twib
36
49
  return self
37
50
  end
38
51
  end
39
-
52
+
53
+ # A request to be sent to a remote device.
40
54
  class ActiveRequest
41
55
  def initialize(tc, device_id, object_id, command_id, tag, payload, &block)
42
56
  @tc = tc
@@ -50,6 +64,7 @@ module Twib
50
64
  @response = nil
51
65
  end
52
66
 
67
+ # @api private
53
68
  def respond(response) # expects to be synchronized by @tc.mutex
54
69
  @response = response
55
70
  @condvar.broadcast
@@ -58,6 +73,8 @@ module Twib
58
73
  end
59
74
  end
60
75
 
76
+ # Waits for this request to receive a response.
77
+ # @return [Response]
61
78
  def wait
62
79
  @tc.mutex.synchronize do
63
80
  while @response == nil do
@@ -67,16 +84,24 @@ module Twib
67
84
  return @response
68
85
  end
69
86
 
87
+ # Waits for this request to receive a response, and raises if the response is not OK.
88
+ # @raise [ResultError]
89
+ # @return [Response]
70
90
  def wait_ok
71
91
  wait.assert_ok
72
92
  end
73
93
  end
74
-
94
+
95
+ # A connection to twibd.
75
96
  class TwibConnection
97
+ # Connects to twibd using the standard UNIX socket address.
98
+ # @return [TwibConnection]
76
99
  def self.connect_unix
77
100
  return self.new(UNIXSocket.new("/var/run/twibd.sock"))
78
101
  end
79
-
102
+
103
+ # Creates a TwibConnection using the specified socket
104
+ # @param [BasicSocket] socket
80
105
  def initialize(socket)
81
106
  @socket = socket
82
107
  @itmi = Interfaces::ITwibMetaInterface.new(self, 0, 0)
@@ -112,20 +137,29 @@ module Twib
112
137
  @cb_thread = Thread.new do # to avoid deadlocks on I/O thread, we execute callbacks here
113
138
  while @alive do
114
139
  cb, response = @cb_queue.pop
115
- cb.call(response)
140
+ if cb then
141
+ cb.call(response)
142
+ end
116
143
  end
117
144
  end
118
145
  end
119
146
 
147
+ # @api private
120
148
  attr_reader :mutex
149
+ # @api private
121
150
  attr_reader :cb_queue
122
-
151
+
152
+ # Closes the socket and stops internal threads
123
153
  def close
124
154
  @alive = false
125
155
  @socket.close
126
156
  @thread.join
157
+ @cb_queue.close
158
+ @cb_thread.join
127
159
  end
128
160
 
161
+ # @api private
162
+ # @return [ActiveRequest]
129
163
  def send(device_id, object_id, command_id, payload, &block)
130
164
  tag = rand(0xffffffff)
131
165
 
@@ -140,11 +174,21 @@ module Twib
140
174
 
141
175
  return rq
142
176
  end
143
-
177
+
178
+ # Returns a list of devices connected to twibd.
179
+ #
180
+ # tc.list_devices
181
+ # # => [{"device_id"=>507914862, "identification"=>{...}}]
182
+ #
183
+ # Use {#open_device} to connect to one.
184
+ #
185
+ # @return [Array<Hash>]
144
186
  def list_devices
145
187
  @itmi.list_devices
146
188
  end
147
189
 
190
+ # Opens a device specified by one of the device IDs returned from {#list_devices}.
191
+ # @return [Interfaces::ITwibDeviceInterface]
148
192
  def open_device(device_id)
149
193
  return Interfaces::ITwibDeviceInterface.new(self, device_id, 0)
150
194
  end
@@ -1,21 +1,35 @@
1
1
  module Twib
2
+ # Base class for Ruby bindings to a remote interface.
3
+ # An instance of this class represents a remote bridge object.
4
+ #
5
+ # This class and any subclasses should not typically be instantiated directly.
6
+ # Rather, instances should be returned from either {TwibConnection#open_device}
7
+ # or a remote command binding.
2
8
  class Interface
9
+
10
+ # @param connection [TwibConnection] Twib connection to use for transport
11
+ # @param device_id [Integer] ID of the device this object exists on
12
+ # @param object_id [Integer] ID of the remote object this object is bound to
3
13
  def initialize(connection, device_id, object_id)
4
14
  @connection = connection
5
15
  @device_id = device_id
6
16
  @object_id = object_id
7
-
8
- # object id 0 is special
9
- #if @object_id != 0 then
10
- # ObjectSpace.define_finalizer(self, self.class.finalize(connection, device_id, object_id))
11
- #end
12
- end
13
-
14
- def self.finalize(connection, device_id, object_id)
15
- # send close request
16
- #connection.send_sync(device_id, object_id, 0xffffffff, String.new)
17
17
  end
18
18
 
19
+ # Sends a request to the remote object this instance is bound to.
20
+ #
21
+ # object.send(10, [1234].pack("L<")) do |rs|
22
+ # puts "got back: " + rs.assert_ok.payload
23
+ # end
24
+ #
25
+ # object.send(10, [1234].pack("L<")).wait_ok
26
+ # # => #<Twib::TwibConnection::Response>
27
+ #
28
+ # @param command_id [Integer] ID of remtoe command to invoke
29
+ # @param payload [String] Data to send along with the request
30
+ # @yield [rs] Calls the block (in a separate thread) when a response is received, if present.
31
+ # @yieldparam rs [Response]
32
+ # @return [ActiveRequest]
19
33
  def send(command_id, payload=String.new, &block)
20
34
  @connection.send(@device_id, @object_id, command_id, payload, &block)
21
35
  end
@@ -3,7 +3,9 @@ require "twib/switch/debug.rb"
3
3
 
4
4
  module Twib
5
5
  module Interfaces
6
+ # Debug interface bound to a specific process.
6
7
  class ITwibDebugger < Interface
8
+ # @api private
7
9
  module Command
8
10
  QUERY_MEMORY = 10
9
11
  READ_MEMORY = 11
@@ -17,51 +19,86 @@ module Twib
17
19
  GET_NSO_INFOS = 19
18
20
  WAIT_EVENT = 20
19
21
  end
20
-
22
+
23
+ # Queries process segment information at the given address.
24
+ #
25
+ # debug.query_memory(0)
26
+ # # => {:base=>0, :size=>62308483072, :memory_type=>0,
27
+ # # :memory_attribute=>0, :permission=>0,
28
+ # # :device_ref_count=>0, :ipc_ref_count=>0}
29
+ #
30
+ # @param addr [Integer] Address to query
31
+ # @return [Hash]
21
32
  def query_memory(addr)
22
33
  Hash[
23
34
  [:base, :size, :memory_type, :memory_attribute,
24
35
  :permission, :device_ref_count, :ipc_ref_count].zip(
25
36
  send(Command::QUERY_MEMORY, [addr].pack("Q<")).wait_ok.payload.unpack("Q<Q<L<L<L<L<L<"))]
26
37
  end
27
-
38
+
39
+ # Reads from process memory at the given address.
40
+ # @param addr [Integer] Address to read from
41
+ # @param size [Integer] How many bytes to read
42
+ # @return [String]
28
43
  def read_memory(addr, size)
29
44
  send(Command::READ_MEMORY, [addr, size].pack("Q<Q<")).wait_ok.payload
30
45
  end
31
-
46
+
47
+ # Writes to process memory at the given address.
48
+ # @param addr [Integer] Address to write to
49
+ # @param string [String] Data to write
50
+ # @return [String]
32
51
  def write_memory(addr, string)
33
52
  send(Command::WRITE_MEMORY, [addr].pack("Q<") + string).wait_ok
53
+ string
34
54
  end
35
-
55
+
56
+ # Lists threads in the target process.
57
+ # @return [self]
36
58
  def list_threads
37
59
  raise "nyi"
38
60
  end
39
-
61
+
62
+ # Gets a debug event from the target process.
63
+ # @return [Switch::Debug::Event, nil] A debug event, or nil if none were left
40
64
  def get_debug_event
41
65
  rs = send(Command::GET_DEBUG_EVENT).wait
42
66
  if rs.result_code == 0x8c01 then # no debug events left
43
67
  return nil
68
+ else
69
+ rs.assert_ok
44
70
  end
45
71
  return Switch::Debug::Event::Event.unpack(rs.payload)
46
72
  end
47
-
73
+
74
+ # Gets a thread's context.
75
+ # @return [self]
48
76
  def get_thread_context(thread_id)
49
77
  raise "nyi"
50
78
  end
51
-
79
+
80
+ # Breaks the target process.
81
+ # @return [self]
52
82
  def break_process
53
83
  raise "nyi"
54
84
  end
55
-
85
+
86
+ # Continues the target process.
87
+ # @param flags [Integer] See http://www.switchbrew.org/index.php?title=SVC#ContinueDebugFlagsOld
88
+ # @return [self]
56
89
  def continue_debug_event(flags, thread_ids=[])
57
90
  send(Command::CONTINUE_DEBUG_EVENT, ([flags] + thread_ids).pack("L<Q<*")).wait_ok
58
- nil
91
+ self
59
92
  end
60
-
93
+
94
+ # Sets a thread's context.
95
+ # @return [self]
61
96
  def set_thread_context(thread_id)
62
97
  raise "nyi"
63
98
  end
64
-
99
+
100
+ # Queries NSO info for the target process.
101
+ # @return [Array<Hash>]
65
102
  def get_nso_infos
66
103
  response = send(Command::GET_NSO_INFOS).wait_ok.payload
67
104
  count = response.unpack("Q<")[0]
@@ -70,13 +107,19 @@ module Twib
70
107
  [:base, :size, :build_id].zip(response[8 + 0x30 * i, 0x30].unpack("Q<Q<a32"))]
71
108
  end
72
109
  end
73
-
110
+
111
+ # Yields from a separate thread when a debug event is available.
112
+ # @return [self]
74
113
  def wait_event_async(&block)
75
114
  send(Command::WAIT_EVENT, String.new, &block)
115
+ self
76
116
  end
77
-
117
+
118
+ # Waits for a debug event to become available.
119
+ # @return [self]
78
120
  def wait_event
79
121
  send(Command::WAIT_EVENT).wait_ok
122
+ self
80
123
  end
81
124
  end
82
125
  end
@@ -4,7 +4,9 @@ require "twib/interfaces/ITwibDebugger.rb"
4
4
 
5
5
  module Twib
6
6
  module Interfaces
7
+ # Main interface exposed by a device.
7
8
  class ITwibDeviceInterface < Interface
9
+ # @api private
8
10
  module Command
9
11
  RUN = 10
10
12
  REBOOT = 11
@@ -17,6 +19,10 @@ module Twib
17
19
  OPEN_NAMED_PIPE = 18
18
20
  OPEN_ACTIVE_DEBUGGER = 19
19
21
  end
22
+
23
+ # Opens an active debugger for the process with the given PID.
24
+ # @param pid [Integer] Process ID
25
+ # @return [ITwibDebugger] Debugger interface
20
26
  def open_active_debugger(pid)
21
27
  ITwibDebugger.new(@connection, @device_id, send(Command::OPEN_ACTIVE_DEBUGGER, [pid].pack("Q<")).wait_ok.object_ids[0])
22
28
  end
@@ -4,10 +4,15 @@ require "twib/interface.rb"
4
4
 
5
5
  module Twib
6
6
  module Interfaces
7
+ # Exposed by twibd
7
8
  class ITwibMetaInterface < Interface
9
+ # @api private
8
10
  module Command
9
11
  LIST_DEVICES = 10
10
12
  end
13
+
14
+ # Lists devices known to twibd.
15
+ # @return [Array<Hash>]
11
16
  def list_devices
12
17
  MessagePack.unpack(send(Command::LIST_DEVICES).wait_ok.payload)
13
18
  end
@@ -1,3 +1,3 @@
1
1
  module Twib
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -18,6 +18,8 @@ Gem::Specification.new do |spec|
18
18
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
19
  spec.require_paths = ["lib"]
20
20
 
21
+ spec.license = 'ISC'
22
+
21
23
  spec.add_development_dependency "bundler", "~> 1.16"
22
24
  spec.add_development_dependency "rake", "~> 10.0"
23
25
  spec.add_development_dependency "rspec", "~> 3.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twib
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - misson20000
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-09-02 00:00:00.000000000 Z
11
+ date: 2018-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -78,6 +78,7 @@ files:
78
78
  - ".travis.yml"
79
79
  - Gemfile
80
80
  - Gemfile.lock
81
+ - LICENSE
81
82
  - README.md
82
83
  - Rakefile
83
84
  - bin/console
@@ -91,7 +92,8 @@ files:
91
92
  - lib/twib/version.rb
92
93
  - twib.gemspec
93
94
  homepage: https://github.com/misson20000/ruby-twib/
94
- licenses: []
95
+ licenses:
96
+ - ISC
95
97
  metadata: {}
96
98
  post_install_message:
97
99
  rdoc_options: []