From a1987f927ff042622013ac92b4cb8fd5323b57a2 Mon Sep 17 00:00:00 2001 From: pancake Date: Fri, 5 Apr 2024 19:10:26 +0200 Subject: [PATCH] r2pipe-ts: Add some: documentation, cleanup, support Bun + Node --- typescript/r2pipe/http.ts | 29 +++++++-- typescript/r2pipe/local.ts | 6 ++ typescript/r2pipe/queue.ts | 11 +++- typescript/r2pipe/spawn.ts | 125 ++++++++++++++++++++++++------------- typescript/test-http.ts | 5 +- typescript/tsconfig.json | 4 +- 6 files changed, 124 insertions(+), 56 deletions(-) diff --git a/typescript/r2pipe/http.ts b/typescript/r2pipe/http.ts index 9c57cc0..4e62570 100644 --- a/typescript/r2pipe/http.ts +++ b/typescript/r2pipe/http.ts @@ -1,25 +1,42 @@ import * as http from "http"; -import {R2PipeBase} from "./base.js"; +import { R2PipeBase } from "./base.js"; +/** + * Extends the `R2PipeBase` class to provide an HTTP-based implementation of the r2pipe protocol. + */ export class R2PipeHttp extends R2PipeBase { private baseUrl: string; - constructor(url: string) { + /** + * Initializes a new instance of the `R2PipeHttp` class with the specified base URL. + * @param url - The base URL for the HTTP-based r2pipe protocol. f.ex: `http://host:port` + */ + constructor(baseUrl: string) { super(); - this.baseUrl = url; + this.baseUrl = baseUrl; } + + /** + * Executes the given r2 command and returns the response as a string. + * @param command - The r2 command to execute. + * @returns The response from the r2 command as a string. + */ async cmd(command: string): Promise { return this.httpCmd(this.baseUrl, command); } + + /** + * Closes the connection to the r2 process and returns a boolean indicating whether the operation was successful. + * @returns `true` if the connection was closed successfully, `false` otherwise. + */ async quit(): Promise { - // nothing + // do nothing return true; } - ///////////////////////// + private async httpCmd(uri: string, cmd: string): Promise { return new Promise((resolve, reject) => { const url = `${uri}/cmd/${cmd}`; - // console.error("==> " + url); http.get(url, (res: http.IncomingMessage) => { if (res.statusCode !== 200) { reject(new Error(`Request Failed. Status Code: ${res.statusCode}`)); diff --git a/typescript/r2pipe/local.ts b/typescript/r2pipe/local.ts index 3f31fa3..fc0d16e 100644 --- a/typescript/r2pipe/local.ts +++ b/typescript/r2pipe/local.ts @@ -31,6 +31,12 @@ export class R2PipeLocal extends R2PipeBase { } } +/** + * Executes a command in the radare2 pipe and returns the output as a Promise. + * + * @param command - The command to execute in the radare2 pipe. + * @returns A Promise that resolves with the output of the command, or rejects with an error. + */ async cmd(command: string): Promise { return new Promise((resolve, reject) => { this.stream.cmd(command, (error, res) => { diff --git a/typescript/r2pipe/queue.ts b/typescript/r2pipe/queue.ts index d8beaa2..37c40ae 100644 --- a/typescript/r2pipe/queue.ts +++ b/typescript/r2pipe/queue.ts @@ -7,12 +7,21 @@ interface R2PipeQueueItem { error: Error; } +/** + * Manages a queue of R2Pipe commands and their corresponding results. + * + * The `R2PipeQueue` class is responsible for sending commands to the + * R2Pipe input stream and handling the responses from the output stream. + * + * It maintains a queue of pending commands and their associated callbacks, + * and processes the responses in the order they were sent. + */ export class R2PipeQueue { private pipeQueue: R2PipeQueueItem[]; private output: fs.ReadStream; private input: fs.WriteStream; - constructor(input:fs.WriteStream, output:fs.ReadStream) { + constructor(input: fs.WriteStream, output: fs.ReadStream) { this.pipeQueue = []; this.input = input; this.output = output; diff --git a/typescript/r2pipe/spawn.ts b/typescript/r2pipe/spawn.ts index fd9d8b9..96f91e0 100644 --- a/typescript/r2pipe/spawn.ts +++ b/typescript/r2pipe/spawn.ts @@ -1,18 +1,37 @@ import * as proc from "child_process"; import { R2PipeBase } from "./base.js"; +/** + * Provides an interface to run commands on an spawned instance of the radare2. + * + * The `R2PipeSpawn` class extends the `R2PipeBase` class and provides a way to + * spawn a new radare2 process and interact with it through a pipe-based + * interface. + * + * The constructor takes the path to the file to be analyzed and an optional + * path to the radare2 executable. The `cmd` method can be used to execute + * radare2 commands and retrieve the output. The `quit` method can be used to + * terminate the radare2 process. + */ export class R2PipeSpawn extends R2PipeBase { + /** + * The path to the file loaded by the spawned r2 instance + */ private filePath: string; + + /** + * The path to the `radare2` executable + */ private r2Path: string; private r2cb: any; constructor(filePath: string, r2path: string = "radare2") { super(); this.filePath = filePath; - // this.r2Path = "/usr/local/bin/radare2"; this.r2Path = r2path; this.pipeSpawn(this.filePath, []); } + async cmd(command: string): Promise { return new Promise((resolve, reject) => { this.r2cb.cmd(command, (error, res) => { @@ -23,64 +42,65 @@ export class R2PipeSpawn extends R2PipeBase { } }); }); - //return this.httpCmd(this.filePath, command); } + async quit(): Promise { this.r2cb.quit(); return true; } + private pipeSpawn(filePath: string, opts: any) { const args = ['-q0'].concat(opts).concat(filePath); const child = proc.spawn(this.r2Path, args); - this.r2cb = r2bind(child, () => { }, 'pipe'); + this.r2cb = r2bind(child, () => { console.log("nothing"); }, 'pipe'); + // this.r2cb = r2bind(child, () => { }, 'pipe'); } } -function pipeCmd(proc, cmd, cb) { - this.pipeQueue.push({ - cmd: cmd, - cb: cb, - result: '', - error: null - }); - if (this.pipeQueue.length === 1) { - proc.stdin.write(cmd + '\n'); - } -} - -function pipeCmdOutput(proc, data, cb) { +function pipeCmdOutput(r2, proc, data) { + r2.running = true; let len = data.length; - if (this.pipeQueue.length < 1) { - return cb(new Error('r2pipe error: No pending commands for incomming data')); + if (r2.pipeQueue.length < 1) { + return new Error('r2pipe error: No pending commands for incomming data'); } + if (data.length > 1 && data[0] === 0x00) { + data = data.slice(1); + } if (data[len - 1] !== 0x00) { - this.pipeQueue[0].result += data.toString(); - return this.pipeQueue[0].result; + r2.pipeQueue[0].result += data.toString(); + data = ""; + // return; + /// return r2.pipeQueue[0].result; } while (data[len - 1] == 0x00) { len--; } + if (len == 0) { + r2.running = false; + return; + } - this.pipeQueue[0].result += data.slice(0, len).toString(); - this.pipeQueue[0].cb(this.pipeQueue[0].error, this.pipeQueue[0].result); - this.pipeQueue.splice(0, 1); + r2.pipeQueue[0].result += data.slice(0, len).toString(); + r2.pipeQueue[0].cb(r2.pipeQueue[0].error, r2.pipeQueue[0].result); + r2.pipeQueue.splice(0, 1); - if (this.pipeQueue.length > 0) { + if (r2.pipeQueue.length > 0) { try { - proc.stdin.write(this.pipeQueue[0].cmd + '\n'); + proc.stdin.write(r2.pipeQueue[0].cmd + '\n'); } catch (e) { - return cb(e); + r2.pipeQueue[0].cb(e, null); } } + r2.running = false; } function r2bind(child, cb, r2cmd) { - let running = false; let errmsg = ''; const r2 = { + running: false, pipeQueue: [], call: (s, cb2) => { this.cmd("'" + s, cb2); @@ -97,13 +117,19 @@ function r2bind(child, cb, r2cmd) { this.cmd(s + "@0x" + Number(addr).toString(16), cb2); }, /* Run cmd and return plaintext output */ - cmd: function (s, cb2) { - pipeCmd.bind(r2)(child, s, cb2); + cmd: (command, commandCallback) => { // s = util.cleanCmd(s); - // r2cmd(child.cmdparm, s, cb2); + r2.pipeQueue.push({ + cmd: command, + cb: commandCallback, + result: '', + error: null + }); + if (r2.pipeQueue.length === 1) { + child.stdin.write(command + '\n'); + } }, - /* Quit CMD */ quit: function (callback) { if (typeof child.stdin === 'object' && typeof child.stdin.end === 'function') { @@ -126,8 +152,8 @@ function r2bind(child, cb, r2cmd) { errmsg = null; } } - if (!running && (typeof r2cmd !== 'string')) { - running = true; + if (!r2.running && (typeof r2cmd !== 'string')) { + r2.running = true; if (typeof cb === 'function') { cb(null, r2); } else { @@ -140,15 +166,24 @@ function r2bind(child, cb, r2cmd) { /* handle STDOUT nessages */ if (child.stdout !== null) { child.stdout.on('data', data => { - /* Set as running for pipe method */ - if (running) { - if (typeof r2cmd === 'string') { - pipeCmdOutput.bind(r2)(child, data, cb); - } - } else { - running = true; - cb(null, r2); + if (r2.running) { + console.error("race"); } + pipeCmdOutput(r2, child, data); + /* + // console.log("received data: " + data); + // Set as running for pipe method + if (running) { + console.log("RUNING"); + if (typeof r2cmd === 'string') { + pipeCmdOutput.bind(r2)(child, data, cb); + } + } else { + console.log("not RUNING"); + running = true; + cb(null, r2); + } + */ }); } else { cb(null, r2); // Callback for connect @@ -157,12 +192,12 @@ function r2bind(child, cb, r2cmd) { /* Proccess event handling only for methods using childs */ if (typeof child.on === 'function') { child.on('error', function (err) { - running = false; + r2.running = false; console.log(err); }); child.on('close', function (code, signal) { - running = false; + r2.running = false; const error = errmsg ? errmsg : ''; if (signal) { cb(new Error('Child received signal ' + signal + '\n' + error)); @@ -174,8 +209,8 @@ function r2bind(child, cb, r2cmd) { /* lpipe (rlangpipe) is ready from the start and does not * require to wait for any input from stdin or stdout */ - if (!running && (r2cmd === 'lpipe')) { - running = true; + if (!r2.running && (r2cmd === 'lpipe')) { + r2.running = true; cb(null, r2); } return r2; diff --git a/typescript/test-http.ts b/typescript/test-http.ts index 8f4377f..0653f0c 100644 --- a/typescript/test-http.ts +++ b/typescript/test-http.ts @@ -4,12 +4,13 @@ import * as r2pipe from "./dist/index.js"; // import * as r2pipe from "r2pipe-ts"; -async function main() { +async function main() : Promise { console.log("Hello R2Pipe for TypeScript"); const r2 = await r2pipe.open("http://127.0.0.1:9090"); const res = await r2.cmd("?E Hello TypeScript"); console.log(res); await r2.quit(); + return "Done"; } -main().then((x)=>{}).catch(console.error); +main().then(console.log).catch(console.error); diff --git a/typescript/tsconfig.json b/typescript/tsconfig.json index d85c3ff..af36d83 100644 --- a/typescript/tsconfig.json +++ b/typescript/tsconfig.json @@ -2,11 +2,11 @@ "compilerOptions": { "baseUrl": "r2pipe", "moduleResolution": "nodenext", - "target": "es5", + "target": "esnext", "esModuleInterop": true, "resolveJsonModule": true, "module": "nodenext", - "lib": ["es2017"], + "lib": ["esnext"], "declaration": true, "strict": false, "skipLibCheck": true,