Skip to content

Commit

Permalink
r2pipe-ts: Add some: documentation, cleanup, support Bun + Node
Browse files Browse the repository at this point in the history
  • Loading branch information
radare committed Apr 5, 2024
1 parent cc07c49 commit a1987f9
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 56 deletions.
29 changes: 23 additions & 6 deletions typescript/r2pipe/http.ts
Original file line number Diff line number Diff line change
@@ -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<string> {
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<boolean> {
// nothing
// do nothing
return true;
}
/////////////////////////

private async httpCmd(uri: string, cmd: string): Promise<string> {
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}`));
Expand Down
6 changes: 6 additions & 0 deletions typescript/r2pipe/local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
return new Promise((resolve, reject) => {
this.stream.cmd(command, (error, res) => {
Expand Down
11 changes: 10 additions & 1 deletion typescript/r2pipe/queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
125 changes: 80 additions & 45 deletions typescript/r2pipe/spawn.ts
Original file line number Diff line number Diff line change
@@ -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<string> {
return new Promise((resolve, reject) => {
this.r2cb.cmd(command, (error, res) => {
Expand All @@ -23,64 +42,65 @@ export class R2PipeSpawn extends R2PipeBase {
}
});
});
//return this.httpCmd(this.filePath, command);
}

async quit(): Promise<boolean> {
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);
Expand All @@ -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') {
Expand All @@ -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 {
Expand All @@ -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
Expand All @@ -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));
Expand All @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions typescript/test-http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
import * as r2pipe from "./dist/index.js";
// import * as r2pipe from "r2pipe-ts";

async function main() {
async function main() : Promise<string> {
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);
4 changes: 2 additions & 2 deletions typescript/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit a1987f9

Please sign in to comment.