blob: 19651966e6052f55ce170fed3e9498d7e18f7e3c [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ignore_for_file: implementation_imports, avoid_relative_lib_imports
import 'dart:convert' show utf8;
import 'dart:math' show min, max;
import 'dart:typed_data';
import 'package:fuchsia_inspect/src/vmo/block.dart';
import 'package:fuchsia_inspect/src/vmo/vmo_fields.dart';
import 'package:fuchsia_inspect/src/vmo/vmo_holder.dart';
import 'package:fuchsia_inspect/src/vmo/vmo_writer.dart';
String _utf8ToString(ByteData bytes) {
return utf8.decode(bytes.buffer
.asUint8ClampedList(bytes.offsetInBytes, bytes.lengthInBytes));
}
class _Metric {
num value;
_INode parent;
String _name;
String get name => _name;
BlockType _type;
BlockType get type => _type;
_Metric(Block metric) {
var nameBlock = Block.read(metric.vmo, metric.nameIndex);
_name = _utf8ToString(nameBlock.nameUtf8);
_type = metric.type;
switch (metric.type) {
case BlockType.doubleValue:
value = metric.doubleValue;
break;
case BlockType.intValue:
value = metric.intValue;
break;
case BlockType.uintValue:
throw StateError('Dart does not expect uint metrics.');
default:
throw StateError('Type was ${metric.type.name} not numeric.');
}
}
@override
String toString() =>
'${_type == BlockType.intValue ? 'Int' : 'Double'}Property "$_name": $value';
}
class _Property {
_INode parent;
String _name;
String get name => _name;
final int bytesToPrint = 20;
bool isString;
int payloadLength;
ByteData bytes;
_Property(Block property) {
var nameBlock = Block.read(property.vmo, property.nameIndex);
_name = _utf8ToString(nameBlock.nameUtf8);
isString = property.propertyFlags == propertyUtf8Flag;
payloadLength = property.propertyTotalLength;
bytes = ByteData(payloadLength);
int amountCopied = 0;
for (Block extentBlock =
Block.read(property.vmo, property.propertyExtentIndex);;
extentBlock = Block.read(property.vmo, extentBlock.nextExtent)) {
int copyEnd =
min(payloadLength, amountCopied + extentBlock.payloadSpaceBytes);
bytes.buffer.asUint8List().setRange(
amountCopied, copyEnd, extentBlock.payloadBytes.buffer.asUint8List());
if (extentBlock.nextExtent == invalidIndex) {
break;
}
}
}
@override
String toString() {
if (isString) {
return 'StringProperty "$name": "${_utf8ToString(bytes)}"';
} else {
var buffer = StringBuffer('ByteDataProperty "$name":');
for (int i = 0; i < bytesToPrint && i < payloadLength; i++) {
buffer.write(' ${bytes.getUint8(i).toRadixString(16).padLeft(2, '0')}');
}
return buffer.toString();
}
}
}
class _INode {
_INode parent;
List<_INode> children = <_INode>[];
List<_Metric> metrics = <_Metric>[];
List<_Property> properties = <_Property>[];
String _name;
String get name => _name;
void setFrom(Block block) {
var nameBlock = Block.read(block.vmo, block.nameIndex);
_name = _utf8ToString(nameBlock.nameUtf8);
}
@override
String toString() => 'Node: "$_name"';
String treeToString(String indentStep, [String currentIndent = '']) {
var buffer = StringBuffer()
..write(currentIndent)
..writeln(this);
String nextIndent = '$currentIndent$indentStep';
for (var metric in metrics) {
buffer
..write(nextIndent)
..writeln(metric);
}
for (var property in properties) {
buffer
..write(nextIndent)
..writeln(property);
}
for (var node in children) {
buffer.write(node.treeToString(indentStep, nextIndent));
}
return buffer.toString();
}
}
class _NodeTree {
final _nodes = <int, _INode>{};
final VmoHolder vmo;
_NodeTree(this.vmo);
_INode node(int index) {
if (!_nodes.containsKey(index)) {
_nodes[index] = _INode();
}
return _nodes[index];
}
}
class VmoReader {
final _NodeTree _nodes;
VmoReader(VmoHolder vmo) : _nodes = _NodeTree(vmo) {
for (int readPosBytes = 0; readPosBytes < vmo.size;) {
var block = Block.read(vmo, readPosBytes ~/ bytesPerIndex);
switch (block.type) {
case BlockType.nodeValue:
_INode parentNode = _nodes.node(block.parentIndex);
_INode node = _nodes.node(block.index)
..setFrom(block)
..parent = parentNode;
parentNode.children.add(node);
break;
case BlockType.propertyValue:
_INode parentNode = _nodes.node(block.parentIndex);
var property = _Property(block);
property.parent = parentNode;
parentNode.properties.add(property);
break;
case BlockType.intValue:
case BlockType.doubleValue:
_INode parentNode = _nodes.node(block.parentIndex);
var metric = _Metric(block);
metric.parent = parentNode;
parentNode.metrics.add(metric);
break;
}
readPosBytes += max(block.size, 16);
}
}
_INode get _rootNode => _nodes.node(rootNodeIndex);
@override
String toString() => _rootNode.treeToString('>> ', '<> ');
}