@phala/dstack-sdk
Version:
DStack SDK
256 lines • 9.69 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TappdClient = exports.__version__ = void 0;
exports.to_hex = to_hex;
exports.send_rpc_request = send_rpc_request;
const net_1 = __importDefault(require("net"));
const crypto_1 = __importDefault(require("crypto"));
const http_1 = __importDefault(require("http"));
const https_1 = __importDefault(require("https"));
const url_1 = require("url");
exports.__version__ = "0.2.1";
function to_hex(data) {
if (typeof data === 'string') {
return Buffer.from(data).toString('hex');
}
if (data instanceof Uint8Array) {
return Buffer.from(data).toString('hex');
}
return data.toString('hex');
}
function x509key_to_uint8array(pem, max_length) {
const content = pem.replace(/-----BEGIN PRIVATE KEY-----/, '')
.replace(/-----END PRIVATE KEY-----/, '')
.replace(/\n/g, '');
const binaryDer = atob(content);
if (!max_length) {
max_length = binaryDer.length;
}
const result = new Uint8Array(max_length);
for (let i = 0; i < max_length; i++) {
result[i] = binaryDer.charCodeAt(i);
}
return result;
}
function replay_rtmr(history) {
const INIT_MR = "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
if (history.length === 0) {
return INIT_MR;
}
let mr = Buffer.from(INIT_MR, 'hex');
for (const content of history) {
// Convert hex string to buffer
let contentBuffer = Buffer.from(content, 'hex');
// Pad content with zeros if shorter than 48 bytes
if (contentBuffer.length < 48) {
const padding = Buffer.alloc(48 - contentBuffer.length, 0);
contentBuffer = Buffer.concat([contentBuffer, padding]);
}
mr = crypto_1.default.createHash('sha384')
.update(Buffer.concat([mr, contentBuffer]))
.digest();
}
return mr.toString('hex');
}
function reply_rtmrs(event_log) {
const rtmrs = [];
for (let idx = 0; idx < 4; idx++) {
const history = event_log
.filter(event => event.imr === idx)
.map(event => event.digest);
rtmrs[idx] = replay_rtmr(history);
}
return rtmrs;
}
function send_rpc_request(endpoint, path, payload, timeoutMs) {
return new Promise((resolve, reject) => {
const abortController = new AbortController();
let isCompleted = false;
const safeReject = (error) => {
if (!isCompleted) {
isCompleted = true;
reject(error);
}
};
const safeResolve = (result) => {
if (!isCompleted) {
isCompleted = true;
resolve(result);
}
};
const timeout = setTimeout(() => {
abortController.abort();
safeReject(new Error('request timed out'));
}, timeoutMs || 30000); // Default 30 seconds timeout
const cleanup = () => {
clearTimeout(timeout);
abortController.signal.removeEventListener('abort', onAbort);
};
const onAbort = () => {
cleanup();
safeReject(new Error('request aborted'));
};
abortController.signal.addEventListener('abort', onAbort);
const isHttp = endpoint.startsWith('http://') || endpoint.startsWith('https://');
if (isHttp) {
const url = new url_1.URL(path, endpoint);
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload),
'User-Agent': `dstack-sdk-js/${exports.__version__}`,
},
};
const req = (url.protocol === 'https:' ? https_1.default : http_1.default).request(url, options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
cleanup();
try {
const result = JSON.parse(data);
safeResolve(result);
}
catch (error) {
safeReject(new Error('failed to parse response'));
}
});
});
req.on('error', (error) => {
cleanup();
safeReject(error);
});
abortController.signal.addEventListener('abort', () => {
req.destroy();
});
req.write(payload);
req.end();
}
else {
const client = net_1.default.createConnection({ path: endpoint }, () => {
client.write(`POST ${path} HTTP/1.1\r\n`);
client.write(`Host: localhost\r\n`);
client.write(`Content-Type: application/json\r\n`);
client.write(`Content-Length: ${payload.length}\r\n`);
client.write('\r\n');
client.write(payload);
});
let data = '';
let headers = {};
let headersParsed = false;
let contentLength = 0;
let bodyData = '';
client.on('data', (chunk) => {
data += chunk;
if (!headersParsed) {
const headerEndIndex = data.indexOf('\r\n\r\n');
if (headerEndIndex !== -1) {
const headerLines = data.slice(0, headerEndIndex).split('\r\n');
headerLines.forEach(line => {
const [key, value] = line.split(': ');
if (key && value) {
headers[key.toLowerCase()] = value;
}
});
headersParsed = true;
contentLength = parseInt(headers['content-length'] || '0', 10);
bodyData = data.slice(headerEndIndex + 4);
}
}
else {
bodyData += chunk;
}
if (headersParsed && bodyData.length >= contentLength) {
client.end();
}
});
client.on('end', () => {
cleanup();
try {
const result = JSON.parse(bodyData.slice(0, contentLength));
safeResolve(result);
}
catch (error) {
safeReject(new Error('failed to parse response'));
}
});
client.on('error', (error) => {
cleanup();
safeReject(error);
});
abortController.signal.addEventListener('abort', () => {
client.destroy();
});
}
});
}
class TappdClient {
constructor(endpoint = '/var/run/tappd.sock') {
if (process.env.DSTACK_SIMULATOR_ENDPOINT) {
console.debug(`Using simulator endpoint: ${process.env.DSTACK_SIMULATOR_ENDPOINT}`);
endpoint = process.env.DSTACK_SIMULATOR_ENDPOINT;
}
this.endpoint = endpoint;
}
async deriveKey(path, subject, alt_names) {
let raw = { path: path || '', subject: subject || path || '' };
if (alt_names && alt_names.length) {
raw['alt_names'] = alt_names;
}
const payload = JSON.stringify(raw);
const result = await send_rpc_request(this.endpoint, '/prpc/Tappd.DeriveKey', payload);
Object.defineProperty(result, 'asUint8Array', {
get: () => (length) => x509key_to_uint8array(result.key, length),
enumerable: true,
configurable: false,
});
return Object.freeze(result);
}
async tdxQuote(report_data, hash_algorithm) {
let hex = to_hex(report_data);
if (hash_algorithm === 'raw') {
if (hex.length > 128) {
throw new Error(`Report data is too large, it should less then 64 bytes when hash_algorithm is raw.`);
}
if (hex.length < 128) {
hex = hex.padStart(128, '0');
}
}
const payload = JSON.stringify({ report_data: hex, hash_algorithm });
const result = await send_rpc_request(this.endpoint, '/prpc/Tappd.TdxQuote', payload);
if ('error' in result) {
const err = result['error'];
throw new Error(err);
}
Object.defineProperty(result, 'replayRtmrs', {
get: () => () => reply_rtmrs(JSON.parse(result.event_log)),
enumerable: true,
configurable: false,
});
return Object.freeze(result);
}
async info() {
const result = await send_rpc_request(this.endpoint, '/prpc/Tappd.Info', '{}');
return Object.freeze({
...result,
tcb_info: JSON.parse(result.tcb_info),
});
}
async isReachable() {
try {
// Use info endpoint to test connectivity with 500ms timeout
await send_rpc_request(this.endpoint, '/prpc/Tappd.Info', '{}', 500);
return true;
}
catch (error) {
return false;
}
}
}
exports.TappdClient = TappdClient;
//# sourceMappingURL=index.js.map