radare/radare2-webui

View on GitHub
www/m/js/modules/disasm/DisassemblyNavigator.js

Summary

Maintainability
C
1 day
Test Coverage
import {BlockNavigator} from '../../core/BlockNavigator';
import {ChunkStatus} from '../../core/ChunkStatus';

// Should refactor with HexPairNav and go/get methods
/**
 * DisasmNavigator
 * Based on non-fixed size of "chunk"
 * will use:
 *    this.navigationData, as dictionnary [offset => {size, callback, data}]
 *    this.navigationOffsets, for all ordered [offset]
 *     this.currentlyShown, as currently shown [offset]
 */

function line2offset(line) {
    return line * 2;
}

export class DisassemblyNavigator extends BlockNavigator {

    constructor(howManyLines, startOffset) {
        super();

        this.currentOffset = startOffset;
        this.howManyLines = howManyLines;
        this.gap = this.howManyLines * 2;

        this.providerWorker = new Worker('disasmProvider.js');

        this.optimalLines = this.howManyLines * 3;
        this.MINFILL = this.optimalLines * 0.8;

        this.items = [];

        this.init();
    }

    init() {
        super.init()
        this.currentlyShown = [];
        this.populateFirst();
    }

    configureWorker_() {
        var _this = this;
        this.providerWorker.onmessage = function(e) {
            var item;
            for (var i = 0 ; i < _this.items.length ; i++) {
                if (_this.items[i].offset === e.data.offset &&
                    _this.items[i].size === e.data.size) {
                    item = _this.items[i];
                }
            }

            if (typeof item === 'undefined') {
                console.log('Unable to find origin item');
                return;
            }

            item.data = e.data.data;
            item.status = ChunkStatus.COMPLETED;
            for (var i = 0 ; i < item.callback.length ; i++) {
                item.callback[i](item);
            }
            item.callback = [];
        };
    }

    cleanOldData() {
        for (var i = 0 ; i < this.items.length ; i++) {
            delete this.items[i].data;
            delete this.items[i].status;
        }
    }

    crunchingData(onReadyCallback) {
        var initWorker = new Worker('disasmNavProvider.js');
        var _this = this;

        initWorker.onmessage = function(e) {
            _this.navigationData = e.data;
            _this.navigationOffsets = Object.keys(e.data);
            _this.navigationOffsets.sort();
            initWorker.terminate();
            onReadyCallback();
        };

        initWorker.postMessage(true);
    }

    getOverlappingIntervals(start, end) {
        let intervals = [];
        for (let offset in this.navigationData) {
            const startInterval = offset;
            const endInterval = offset + this.navigationData[offset].size;
            if (startInterval <= end && start <= endInterval) {
                intervals.push(offset);
            }
        }
        return intervals;
    }

    populateFirst() {
        return this.populateFrom(this.currentOffset);
    }

    /**
     * Create block between [start;end[
     */
    fillGap(start, end, artifical) {
        var curSize = end - start;
        // FIX, can't cut everywhere: byte alignment (invalid lines)
        return [{offset: start, size: curSize, artifical: artifical}];
        if (curSize > this.howManyLines) {
            var half = Math.round(end / 2);
            return [{
                offset: start,
                size: Math.round(curSize / 2),
                artifical: artifical
            }].concat(this.fillGap(start + Math.round(curSize / 2), end));
        } else {
            return [{
                offset: start,
                size: curSize,
                artifical: artifical
            }];
        }
    }

    populateFrom(offset) {
        // From currentOffset
        // I want at least 80% of 3 screens

        // go up of 1 screen, take first in order

        var fromOffset = offset - line2offset(this.howManyLines);
        var endOffset = fromOffset + (3 * line2offset(this.howManyLines));

        var existingIntervals = this.getOverlappingIntervals(fromOffset, endOffset);

        var requestedIntervals = []; // {offset, size}

        // If they overlap between them, we merge
        for (var i = 0 ; i < existingIntervals.length - 1 ; i++) {
            var endCurrent = existingIntervals[i];
            var startNext = existingIntervals[i + 1];
            if (startNext < endCurrent) {
                if (endNext <= endCurrent) { // inclusive
                    requestedIntervals.push({
                        offset: existingIntervals[i],
                        size: this.navigationData[existingIntervals[i]].size
                    });
                } else {
                    var endNext = startNext + this.navigationData[startNext].size;
                    requestedIntervals.push({
                        offset: existingIntervals[i],
                        size: endNext - existingIntervals[i]
                    });
                }
            }
        }

        if (requestedIntervals.length > 0) {
            // If there is gap before
            if (requestedIntervals[0].offset !== fromOffset) {
                requestedIntervals = requestedIntervals.concat(this.fillGap(fromOffset, requestedIntervals[0].offset));
            }

            // If there is a gap after
            var lastInterval = requestedIntervals[requestedIntervals.length - 1];
            var lastOffsetInterval = (lastInterval.offset + lastInterval.size);
            if (lastOffsetInterval !== endOffset) {
                requestedIntervals = requestedIntervals.concat(this.fillGap(lastOffsetInterval + 1, endOffset));
            }

            // If there is a gap between
            for (var i = 0 ; i < requestedIntervals.length - 1 ; i++) {
                var endCurrent = existingIntervals[i];
                var startNext = existingIntervals[i + 1];

                if (startNext - endCurrent > 1) {
                    requestedIntervals = requestedIntervals.concat(this.fillGap(endCurrent + 1, startNext));
                }
            }
        } else {
            requestedIntervals = this.fillGap(fromOffset, endOffset, true);
        }

        this.currentlyShown = requestedIntervals;

        /****
        TODO: check if existing (data field), if not, ask provider
        don't care about total length, but need to find approx. the line requested:
            which interval, starting at? +lineHeight*diff
        *****/
    }

    /**
     * Returns the current chunks to display
     * Will be conciliated with offset (key)
     */
    getShownOffset() {
        return this.currentlyShown;
    }

    getSize(offset) {
        for (var i = 0 ; i < this.currentlyShown.length ; i++) {
            if (this.currentlyShown[i].offset === offset) {
                return this.currentlyShown[i].size;
            }
        }
        return -1;
    }

    getChunkPositionFor(offset) {
        for (var i = 0 ; i < this.currentlyShown.length ; i++) {
            if (offset >= this.currentlyShown[i].offset &&
                offset < this.currentlyShown[i].offset + this.currentlyShown[i].size) {
                return i;
            }
        }

        return -1;
    }

    get(offset, size, callback) {
        // TODO: retrieve data (async) and call
        var item;
        for (var i = 0 ; i < this.items.length ; i++) {
            if (this.items[i].offset === offset &&
                this.items[i].size === size) {
                item = this.items[i];
            }
        }

        if (typeof item === 'undefined') {
            item = {
                offset: offset,
                size: size
            };
            this.items.push(item);
        }

        if (typeof item.data !== 'undefined') {
            return callback(item);
        } else { // Not currently here
            if (typeof item.callback === 'undefined') {
                item.callback = [];
            }
            // Store in callback, could be retrieving or we will start it
            item.callback.push(callback);
            if (item.status !== ChunkStatus.LAUNCHED) { // Need to be retrieved
                item.status = ChunkStatus.LAUNCHED;
                this.providerWorker.postMessage({
                    offset: item.offset,
                    size: item.size
                });
            }
        }
    }

    go(dir) {
        this.currentOffset += dir * (this.howManyLines * 2);
        this.populateFrom(this.currentOffset);
    }

    refreshCurrentOffset() {
        var _this = this;
        r2.cmd('s', function(offset) {
            _this.currentOffset = parseInt(offset, 16);
        });
    }

    getSeekOffset() {
        return this.currentOffset;
    }

}