twib 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: be083ce5e43cdb183a7548ce6ad0b2eabc77e425
4
+ data.tar.gz: 58e462f9150b324ac65acdaa12dbc1fb10dc4df9
5
+ SHA512:
6
+ metadata.gz: a58ca6fa102c71854d30b3b2bb4338e6425f59fd05be3392ad660816a617fb82866a5508ff3b50c542b29125ff1c1d76b6a67cee14f8d1e737780304e2835d5a
7
+ data.tar.gz: f77ef0b936b82b29b2d5e48bc803b85d37e077a7222118ee4d2c6b070f0368331744ce6ef862525b8f7746cb5a1cc75d654f606674959ec9e49cc48906c581a2
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ *~
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.1
5
+ before_install: gem install bundler -v 1.16.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in twib.gemspec
6
+ gemspec
@@ -0,0 +1,37 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ twib (0.1.0)
5
+ msgpack
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.3)
11
+ msgpack (1.2.4)
12
+ rake (10.5.0)
13
+ rspec (3.8.0)
14
+ rspec-core (~> 3.8.0)
15
+ rspec-expectations (~> 3.8.0)
16
+ rspec-mocks (~> 3.8.0)
17
+ rspec-core (3.8.0)
18
+ rspec-support (~> 3.8.0)
19
+ rspec-expectations (3.8.1)
20
+ diff-lcs (>= 1.2.0, < 2.0)
21
+ rspec-support (~> 3.8.0)
22
+ rspec-mocks (3.8.0)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.8.0)
25
+ rspec-support (3.8.0)
26
+
27
+ PLATFORMS
28
+ ruby
29
+
30
+ DEPENDENCIES
31
+ bundler (~> 1.16)
32
+ rake (~> 10.0)
33
+ rspec (~> 3.0)
34
+ twib!
35
+
36
+ BUNDLED WITH
37
+ 1.16.1
@@ -0,0 +1,51 @@
1
+ # ruby-twib
2
+
3
+ [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
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'twib'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install twib
20
+
21
+ ## Usage
22
+
23
+ See the instructions on the [Twili repository](https://github.com/misson20000/twili#twili) for how to set up Twili and twibd.
24
+
25
+ First, create a Twib::TwibConnection. There is a convenience method `Twib::TwibConnection::connect_unix`
26
+ ```ruby
27
+ > tc = Twib::TwibConnection.connect_unix
28
+ => #<Twib::TwibConnection:...>
29
+ ```
30
+
31
+ After that, you can list the available devices with `TwibConnection#list_devices`, which returns an array of hashes identifying each device.
32
+ ```ruby
33
+ > tc.list_devices
34
+ [{"device_id"=>507914862, ...}, ...]
35
+ ```
36
+
37
+ Use `TwibConnection#open_device` to get a device's `ITwibDeviceInterface`.
38
+ ```ruby
39
+ > tc.open_device(507914862)
40
+ => #<Twib::Interfaces::ITwibDeviceInterface:...>
41
+ ```
42
+
43
+ ## Development
44
+
45
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
46
+
47
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
48
+
49
+ ## Contributing
50
+
51
+ Bug reports and pull requests are welcome on GitHub at https://github.com/misson20000/ruby-twib.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "twib"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,152 @@
1
+ require "socket"
2
+ require "thread"
3
+
4
+ require "twib/version"
5
+ require "twib/interfaces/ITwibMetaInterface"
6
+ require "twib/interfaces/ITwibDeviceInterface"
7
+
8
+ module Twib
9
+ class Error < RuntimeError
10
+ end
11
+
12
+ class ResultError < Error
13
+ def initialize(code)
14
+ super("0x#{code.to_s(16)}")
15
+ @code = code
16
+ end
17
+ attr_reader :code
18
+ end
19
+
20
+ class Response
21
+ def initialize(device_id, object_id, result_code, tag, payload, object_ids)
22
+ @device_id = device_id
23
+ @object_id = object_id
24
+ @result_code = result_code
25
+ @tag = tag
26
+ @payload = payload
27
+ @object_ids = object_ids
28
+ end
29
+
30
+ attr_reader :device_id, :object_id, :result_code, :tag, :payload, :object_ids
31
+
32
+ def assert_ok
33
+ if @result_code != 0 then
34
+ raise ResultError.new(@result_code)
35
+ end
36
+ return self
37
+ end
38
+ end
39
+
40
+ class ActiveRequest
41
+ def initialize(tc, device_id, object_id, command_id, tag, payload, &block)
42
+ @tc = tc
43
+ @device_id = device_id
44
+ @object_id = object_id
45
+ @command_id = command_id
46
+ @tag = tag
47
+ @payload = payload
48
+ @condvar = ConditionVariable.new
49
+ @cb = block
50
+ @response = nil
51
+ end
52
+
53
+ def respond(response) # expects to be synchronized by @tc.mutex
54
+ @response = response
55
+ @condvar.broadcast
56
+ if @cb then
57
+ @tc.cb_queue.push([@cb, response])
58
+ end
59
+ end
60
+
61
+ def wait
62
+ @tc.mutex.synchronize do
63
+ while @response == nil do
64
+ @condvar.wait(@tc.mutex)
65
+ end
66
+ end
67
+ return @response
68
+ end
69
+
70
+ def wait_ok
71
+ wait.assert_ok
72
+ end
73
+ end
74
+
75
+ class TwibConnection
76
+ def self.connect_unix
77
+ return self.new(UNIXSocket.new("/var/run/twibd.sock"))
78
+ end
79
+
80
+ def initialize(socket)
81
+ @socket = socket
82
+ @itmi = Interfaces::ITwibMetaInterface.new(self, 0, 0)
83
+ @alive = true
84
+ @active_requests = {}
85
+ @mutex = Mutex.new
86
+ @thread = Thread.new do
87
+ loop do
88
+ header = @socket.recv(32)
89
+ header = header.unpack("L<L<L<L<Q<L<")
90
+
91
+ payload = String.new
92
+ object_ids = []
93
+ if header[4] > 0 then
94
+ payload = @socket.recv(header[4]) # payload size
95
+ end
96
+ if header[5] > 0 then
97
+ object_ids = @socket.recv(header[5] * 4).unpack("L<*") # object IDs
98
+ end
99
+
100
+ rs = Response.new(header[0], header[1], header[2], header[3], payload, object_ids)
101
+ @mutex.synchronize do
102
+ rq = @active_requests.delete(rs.tag)
103
+ if !rq then
104
+ puts "WARNING: got response for bad tag"
105
+ end
106
+ rq.respond(rs)
107
+ end
108
+ end
109
+ end
110
+
111
+ @cb_queue = Queue.new
112
+ @cb_thread = Thread.new do # to avoid deadlocks on I/O thread, we execute callbacks here
113
+ while @alive do
114
+ cb, response = @cb_queue.pop
115
+ cb.call(response)
116
+ end
117
+ end
118
+ end
119
+
120
+ attr_reader :mutex
121
+ attr_reader :cb_queue
122
+
123
+ def close
124
+ @alive = false
125
+ @socket.close
126
+ @thread.join
127
+ end
128
+
129
+ def send(device_id, object_id, command_id, payload, &block)
130
+ tag = rand(0xffffffff)
131
+
132
+ rq = ActiveRequest.new(self, device_id, object_id, command_id, tag, payload, &block)
133
+ @active_requests[tag] = rq
134
+
135
+ message = [device_id, object_id, command_id, tag, payload.size, 0, 0].pack("L<L<L<L<Q<L<L<") + payload
136
+ sz = @socket.send(message, 0)
137
+ if sz < message.size then
138
+ raise "couldn't send entire message"
139
+ end
140
+
141
+ return rq
142
+ end
143
+
144
+ def list_devices
145
+ @itmi.list_devices
146
+ end
147
+
148
+ def open_device(device_id)
149
+ return Interfaces::ITwibDeviceInterface.new(self, device_id, 0)
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,23 @@
1
+ module Twib
2
+ class Interface
3
+ def initialize(connection, device_id, object_id)
4
+ @connection = connection
5
+ @device_id = device_id
6
+ @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
+ end
18
+
19
+ def send(command_id, payload=String.new, &block)
20
+ @connection.send(@device_id, @object_id, command_id, payload, &block)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,83 @@
1
+ require "twib/interface.rb"
2
+ require "twib/switch/debug.rb"
3
+
4
+ module Twib
5
+ module Interfaces
6
+ class ITwibDebugger < Interface
7
+ module Command
8
+ QUERY_MEMORY = 10
9
+ READ_MEMORY = 11
10
+ WRITE_MEMORY = 12
11
+ LIST_THREADS = 13
12
+ GET_DEBUG_EVENT = 14
13
+ GET_THREAD_CONTEXT = 15
14
+ BREAK_PROCESS = 16
15
+ CONTINUE_DEBUG_EVENT = 17
16
+ SET_THREAD_CONTEXT = 18
17
+ GET_NSO_INFOS = 19
18
+ WAIT_EVENT = 20
19
+ end
20
+
21
+ def query_memory(addr)
22
+ Hash[
23
+ [:base, :size, :memory_type, :memory_attribute,
24
+ :permission, :device_ref_count, :ipc_ref_count].zip(
25
+ send(Command::QUERY_MEMORY, [addr].pack("Q<")).wait_ok.payload.unpack("Q<Q<L<L<L<L<L<"))]
26
+ end
27
+
28
+ def read_memory(addr, size)
29
+ send(Command::READ_MEMORY, [addr, size].pack("Q<Q<")).wait_ok.payload
30
+ end
31
+
32
+ def write_memory(addr, string)
33
+ send(Command::WRITE_MEMORY, [addr].pack("Q<") + string).wait_ok
34
+ end
35
+
36
+ def list_threads
37
+ raise "nyi"
38
+ end
39
+
40
+ def get_debug_event
41
+ rs = send(Command::GET_DEBUG_EVENT).wait
42
+ if rs.result_code == 0x8c01 then # no debug events left
43
+ return nil
44
+ end
45
+ return Switch::Debug::Event::Event.unpack(rs.payload)
46
+ end
47
+
48
+ def get_thread_context(thread_id)
49
+ raise "nyi"
50
+ end
51
+
52
+ def break_process
53
+ raise "nyi"
54
+ end
55
+
56
+ def continue_debug_event(flags, thread_ids=[])
57
+ send(Command::CONTINUE_DEBUG_EVENT, ([flags] + thread_ids).pack("L<Q<*")).wait_ok
58
+ nil
59
+ end
60
+
61
+ def set_thread_context(thread_id)
62
+ raise "nyi"
63
+ end
64
+
65
+ def get_nso_infos
66
+ response = send(Command::GET_NSO_INFOS).wait_ok.payload
67
+ count = response.unpack("Q<")[0]
68
+ count.times.map do |i|
69
+ Hash[
70
+ [:base, :size, :build_id].zip(response[8 + 0x30 * i, 0x30].unpack("Q<Q<a32"))]
71
+ end
72
+ end
73
+
74
+ def wait_event_async(&block)
75
+ send(Command::WAIT_EVENT, String.new, &block)
76
+ end
77
+
78
+ def wait_event
79
+ send(Command::WAIT_EVENT).wait_ok
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,25 @@
1
+ require "twib/interface.rb"
2
+
3
+ require "twib/interfaces/ITwibDebugger.rb"
4
+
5
+ module Twib
6
+ module Interfaces
7
+ class ITwibDeviceInterface < Interface
8
+ module Command
9
+ RUN = 10
10
+ REBOOT = 11
11
+ COREDUMP = 12
12
+ TERMINATE = 13
13
+ LIST_PROCESSES = 14
14
+ UPGRADE_TWILI = 15
15
+ IDENTIFY = 16
16
+ LIST_NAMED_PIPES = 17
17
+ OPEN_NAMED_PIPE = 18
18
+ OPEN_ACTIVE_DEBUGGER = 19
19
+ end
20
+ def open_active_debugger(pid)
21
+ ITwibDebugger.new(@connection, @device_id, send(Command::OPEN_ACTIVE_DEBUGGER, [pid].pack("Q<")).wait_ok.object_ids[0])
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ require "msgpack"
2
+
3
+ require "twib/interface.rb"
4
+
5
+ module Twib
6
+ module Interfaces
7
+ class ITwibMetaInterface < Interface
8
+ module Command
9
+ LIST_DEVICES = 10
10
+ end
11
+ def list_devices
12
+ MessagePack.unpack(send(Command::LIST_DEVICES).wait_ok.payload)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,89 @@
1
+ module Twib
2
+ module Switch
3
+ module Debug
4
+ module Event
5
+ class Event
6
+ def self.unpack(pack)
7
+ event_type, flags, thread_id, specific = pack.unpack("L<L<Q<a*")
8
+ case event_type
9
+ when AttachProcess::TYPE
10
+ return AttachProcess.new(flags, thread_id, specific)
11
+ when AttachThread::TYPE
12
+ return AttachThread.new(flags, thread_id, specific)
13
+ when ExitProcess::TYPE
14
+ return ExitProcess.new(flags, thread_id, specific)
15
+ when ExitThread::TYPE
16
+ return ExitThread.new(flags, thread_id, specific)
17
+ when Exception::TYPE
18
+ return Exception.new(flags, thread_id, specific)
19
+ else
20
+ raise "unknown debug event type: #{event_type}"
21
+ end
22
+ end
23
+ def initialize(flags, thread_id, specific)
24
+ @flags = flags
25
+ @thread_id = thread_id
26
+ unpack_specific(specific)
27
+ end
28
+ attr_reader :flags
29
+ attr_reader :thread_id
30
+ end
31
+
32
+ class AttachProcess < Event
33
+ TYPE = 0
34
+ def unpack_specific(pack)
35
+ @title_id, @process_id, @process_name, @mmu_flags = pack.unpack("Q<Q<Z12L<")
36
+ end
37
+ def event_type
38
+ :attach_process
39
+ end
40
+ attr_reader :title_id, :process_id, :process_name, :mmu_flags
41
+ end
42
+
43
+ class AttachThread < Event
44
+ TYPE = 1
45
+ def unpack_specific(pack)
46
+ @thread_id, @tls, @entrypoint = pack.unpack("Q<Q<Q<")
47
+ end
48
+ def event_type
49
+ :attach_thread
50
+ end
51
+ attr_reader :thread_id, :tls, :entrypoint
52
+ end
53
+
54
+ class ExitProcess < Event
55
+ TYPE = 2
56
+ def unpack_specific(pack)
57
+ @type = pack.unpack("L<")
58
+ end
59
+ def event_type
60
+ :exit_process
61
+ end
62
+ attr_reader :type
63
+ end
64
+
65
+ class ExitThread < Event
66
+ TYPE = 3
67
+ def unpack_specific(pack)
68
+ @type = pack.unpack("L<")
69
+ end
70
+ def event_type
71
+ :exit_thread
72
+ end
73
+ attr_reader :type
74
+ end
75
+
76
+ class Exception < Event
77
+ TYPE = 4
78
+ def unpack_specific(pack)
79
+ @type, @fault_register, @specific = pack.unpack("L<Q<a*")
80
+ end
81
+ def event_type
82
+ :exception
83
+ end
84
+ attr_reader :type, :fault_register, :specific
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,3 @@
1
+ module Twib
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,25 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "twib/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "twib"
7
+ spec.version = Twib::VERSION
8
+ spec.authors = ["misson20000"]
9
+ spec.email = ["xenotoad@xenotoad.net"]
10
+
11
+ spec.summary = "Twili bridge client for Ruby"
12
+ spec.homepage = "https://github.com/misson20000/ruby-twib/"
13
+
14
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
15
+ f.match(%r{^(test|spec|features)/})
16
+ end
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.16"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec", "~> 3.0"
24
+ spec.add_dependency "msgpack"
25
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: twib
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - misson20000
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-09-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: msgpack
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
70
+ email:
71
+ - xenotoad@xenotoad.net
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".rspec"
78
+ - ".travis.yml"
79
+ - Gemfile
80
+ - Gemfile.lock
81
+ - README.md
82
+ - Rakefile
83
+ - bin/console
84
+ - bin/setup
85
+ - lib/twib.rb
86
+ - lib/twib/interface.rb
87
+ - lib/twib/interfaces/ITwibDebugger.rb
88
+ - lib/twib/interfaces/ITwibDeviceInterface.rb
89
+ - lib/twib/interfaces/ITwibMetaInterface.rb
90
+ - lib/twib/switch/debug.rb
91
+ - lib/twib/version.rb
92
+ - twib.gemspec
93
+ homepage: https://github.com/misson20000/ruby-twib/
94
+ licenses: []
95
+ metadata: {}
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubyforge_project:
112
+ rubygems_version: 2.6.12
113
+ signing_key:
114
+ specification_version: 4
115
+ summary: Twili bridge client for Ruby
116
+ test_files: []