import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.math.*;
import javafx.application.*;
import javafx.stage.*;
import javafx.scene.control.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import javafx.scene.layout.*;
import javafx.scene.input.*;
import javafx.scene.*;
import javafx.util.*;
import javafx.beans.property.*;
import javafx.event.*;
import javafx.geometry.*;
import javafx.collections.*;
public class Asn1Viewer extends Application {
TextArea hexArea, valueArea;
TreeTableView<Pair<Asn1Object, Integer>> treeTableView;
GridPane gridPane;
@Override
public void start(Stage stage) {
BorderPane layout = new BorderPane();
stage.setScene(new Scene(layout, 800, 500));
BorderPane cont = new BorderPane();
hexArea = new TextArea();
valueArea = new TextArea();
valueArea.setEditable(false);
valueArea.setWrapText(true);
valueArea.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.TAB && !keyEvent.isControlDown()) {
KeyEvent newEvent = new KeyEvent(keyEvent.getEventType(), keyEvent.getCharacter(),
keyEvent.getText(), keyEvent.getCode(), keyEvent.isShiftDown(),
!keyEvent.isControlDown(), keyEvent.isAltDown(), keyEvent.isMetaDown());
Event.fireEvent(keyEvent.getTarget(), newEvent);
keyEvent.consume();
}
});
treeTableView = createTreeTableView();
gridPane = new GridPane();
ScrollPane sp = new ScrollPane();
sp.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
sp.setContent(gridPane);
sp.setFocusTraversable(true);
SplitPane splitPane = new SplitPane(treeTableView, valueArea);
SplitPane.setResizableWithParent(valueArea, false);
splitPane.setOrientation(Orientation.VERTICAL);
splitPane.setDividerPositions(1d);
cont.setCenter(splitPane);
cont.setRight(sp);
layout.setCenter(cont);
Button button = new Button("Load");
button.setOnAction(event -> { load(stage); treeTableView.requestFocus(); });
HBox hb = new HBox();
hb.getChildren().addAll(button);
layout.setBottom(hb);
BorderPane.setMargin(hb, new Insets(2));
setContent(createDemoAsn1Obj());
stage.show();
}
void setContent(Asn1Object root) {
TreeItem<Pair<Asn1Object, Integer>> tiRoot = createTreeItem(root);
treeTableView.setRoot(tiRoot);
treeTableView.getSelectionModel().selectFirst();
}
void load(Stage stage) {
Stage newStage = new Stage();
newStage.initOwner(stage);
newStage.initModality(Modality.APPLICATION_MODAL);
BorderPane layout = new BorderPane();
newStage.setScene(new Scene(layout, 700, 300));
newStage.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.ESCAPE) newStage.close();
});
hexArea.setFont(Font.font("Monospaced"));
layout.setCenter(hexArea);
Button button1 = new Button("File");
button1.setOnAction((event) -> { fromFile(newStage); newStage.requestFocus(); hexArea.requestFocus(); });
Button button2 = new Button("Clear");
button2.setOnAction((event) -> { hexArea.clear(); hexArea.requestFocus(); });
Button button3 = new Button("Paste");
button3.setOnAction((event) -> { hexArea.paste(); hexArea.requestFocus(); });
Button button4 = new Button("Ok");
button4.setOnAction((event) -> { if (loadDump(newStage)) newStage.close(); hexArea.requestFocus(); });
HBox hb = new HBox(4);
hb.getChildren().addAll(button1, button2, button3, button4);
layout.setBottom(hb);
BorderPane.setMargin(hb, new Insets(2));
newStage.show();
}
void fromFile(Stage stage) {
FileChooser fileChooser = new FileChooser();
File file = fileChooser.showOpenDialog(stage);
if (file != null) {
try (RandomAccessFile fl = new RandomAccessFile(file, "r")) {
StringBuilder sb = new StringBuilder();
boolean bin = false;
int b = -1;
while ((b = fl.read()) != -1) {
if ((b < 32 || b > 127) && b != 9 && b != 10 && b != 13) { bin = true; break; }
sb.append((char)b);
}
if (bin) {
sb.setLength(0);
fl.seek(0);
for (int i = 0; (b = fl.read()) != -1; i++) {
sb.append(String.format("%02X", b));
sb.append(i % 16 == 15 ? "\n" : i % 16 == 7 ? " " : " ");
}
}
hexArea.setText(sb.toString());
} catch (Exception e) { alertError(stage, e.toString()); }
}
}
void alertError(Window window, String s) {
Alert alert = new Alert(Alert.AlertType.ERROR, s, ButtonType.OK);
alert.initOwner(window);
alert.setHeaderText(null);
alert.showAndWait();
}
boolean loadDump(Stage stage) {
Exception e1 = null;
try {
byte[] source = {};
String s = hexArea.getText().replaceAll("-----BEGIN [^-]+-----\\s+|-----END [^-]+-----|begin-base64[^\\n]+\\s+|====", "");
if (hexArea.getText().length() == s.length()) {
try {
s = hexArea.getText().replaceAll("\\s", "");
source = new byte[s.length() / 2];
for (int i = 0; i < source.length; i++) {
int index = i * 2;
int v = Integer.parseInt(s.substring(index, index + 2), 16);
source[i] = (byte)v;
}
try {
Asn1Object o = new Asn1Object(source);
setContent(o);
return true;
} catch (Exception e) { e1 = e; }
} catch (Exception e) {}
}
source = Base64.getMimeDecoder().decode(s);
Asn1Object o = new Asn1Object(source);
setContent(o);
} catch (Exception e) {
alertError(stage, (e1 != null ? e1 : e).toString());
return false;
}
return true;
}
TreeTableView<Pair<Asn1Object, Integer>> createTreeTableView() {
TreeTableView<Pair<Asn1Object, Integer>> treeTableView = new TreeTableView<>();
TreeTableColumn<Pair<Asn1Object, Integer>, Pair<Asn1Object, Integer>> column = new TreeTableColumn<>();
column.setCellValueFactory(
(TreeTableColumn.CellDataFeatures<Pair<Asn1Object, Integer>, Pair<Asn1Object, Integer>> cellData) ->
new ReadOnlyObjectWrapper<Pair<Asn1Object, Integer>>(cellData.getValue().getValue())
);
column.setCellFactory(c -> {
TreeTableCell<Pair<Asn1Object, Integer>, Pair<Asn1Object, Integer>> cell = new TreeTableCell<Pair<Asn1Object, Integer>, Pair<Asn1Object, Integer>>() {
@Override
protected void updateItem(Pair<Asn1Object, Integer> item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null || item.getValue() == null || item.getKey() == null) {
setGraphic(null);
} else {
Text text1 = new Text(item.getKey().toString() +" Offset: "+item.getValue().intValue());
Text text2 = new Text(" " + item.getKey().valueAsString());
text2.setFill(Color.BLUE);
setGraphic(new Group(new TextFlow(text1, text2)));
}
}
};
return cell;
});
treeTableView.getColumns().add(column);
treeTableView.setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
treeTableView.widthProperty().addListener((ov, t, t1) -> {
Pane header = (Pane)treeTableView.lookup("TableHeaderRow");
if (header != null && header.isVisible()) {
header.setMaxHeight(0); header.setMinHeight(0); header.setPrefHeight(0);
header.setVisible(false); header.setManaged(false);
}
});
treeTableView.getFocusModel().focusedItemProperty().addListener((observable, oldValue, newValue) -> {
if (gridPane == null || gridPane.getChildren().isEmpty()) return;
if (oldValue != null) showTag(oldValue.getValue(), false);
if (newValue != null) showTag(newValue.getValue(), true);
if (newValue != null && newValue.getValue() != null && newValue.getValue().getKey() != null) {
String s = newValue.getValue().getKey().valueAsString(Asn1Object.STVALUE);
valueArea.setText(s);
} else valueArea.clear();
});
return treeTableView;
}
void showTag(Pair<Asn1Object, Integer> value, boolean sel) {
if (value != null && value.getValue() != null && value.getKey() != null) {
Asn1Object o = value.getKey();
int offset = value.getValue().intValue();
Background bg1 = new Background(new BackgroundFill(Color.web("C0C0C0"), CornerRadii.EMPTY, Insets.EMPTY));
Background bg2 = new Background(new BackgroundFill(Color.web("DCDCDC"), CornerRadii.EMPTY, Insets.EMPTY));
ObservableList<Node> ol = gridPane.getChildren();
for(int i = 0, hl = o.headLength(); i < hl + o.getLength() && offset + i < ol.size(); i++) {
((StackPane)ol.get(offset + i)).setBackground(sel ? i < hl ? bg1 : bg2 : Background.EMPTY);
}
}
}
Font font;
int offset, n;
TreeItem<Pair<Asn1Object, Integer>> createTreeItem(Asn1Object root) {
if (root == null) return null;
gridPane.getChildren().clear();
font = Font.font("Monospaced");
offset = n = 0;
try {
return walkTree(root);
} catch (Exception e) { return null; }
}
TreeItem<Pair<Asn1Object, Integer>> walkTree(Asn1Object o) throws IOException {
Iterator<Asn1Object> iter = o.getSubElementsIterator();
TreeItem<Pair<Asn1Object, Integer>> ti = new TreeItem<>();
ti.setValue(new Pair<Asn1Object, Integer>(o, Integer.valueOf(offset)));
offset += o.effHeadLength();
ti.setExpanded(true);
ByteArrayOutputStream out = new ByteArrayOutputStream();
o.writeObj(out);
byte[] bytes = out.toByteArray();
for (int hl = o.headLength(), i = 0; i < bytes.length; i++, n++) {
String s = String.format("%02X", bytes[i]);
Text text = new Text(s + (n % 16 == 15 ? "" : n % 16 == 7 ? " " : " "));
text.setFont(font);
text.setFill(i < hl ? Color.BLACK : Color.BLUE);
gridPane.add(new StackPane(text), n % 16, n / 16);
text.setOnMousePressed(event -> { goToItem(ti); event.consume(); });
}
while (iter.hasNext()) ti.getChildren().add(walkTree(iter.next()));
return ti;
}
void goToItem(TreeItem<Pair<Asn1Object, Integer>> ti) {
if (ti != null) {
treeTableView.getSelectionModel().select(ti);
treeTableView.requestFocus();
}
}
Asn1Object createDemoAsn1Obj() {
Asn1Object o1 =
new Asn1Object(Asn1Object.CONSTRUCTED, Asn1Object.SEQUENCE).addAll(
new Asn1Object(Asn1Object.UNIVERSAL, Asn1Object.BOOLEAN, Boolean.valueOf(true)),
new Asn1Object(Asn1Object.UNIVERSAL, Asn1Object.UTC_TIME, new Date())
);
Asn1Object o2 =
new Asn1Object(Asn1Object.CONSTRUCTED, Asn1Object.SEQUENCE).addAll(
new Asn1Object(Asn1Object.CONSTRUCTED, Asn1Object.SET).addAll(
new Asn1Object(Asn1Object.CONSTRUCTED, Asn1Object.SEQUENCE).addAll(
new Asn1Object(Asn1Object.UNIVERSAL, Asn1Object.OBJECT_IDENTIFIER, "2.12345.12345.12345"),
new Asn1Object(Asn1Object.UNIVERSAL, Asn1Object.UTF8_STRING, "qwertyu"),
new Asn1Object(Asn1Object.UNIVERSAL, Asn1Object.OCTET_STRING, "octet".getBytes()),
new Asn1Object(Asn1Object.UNIVERSAL, Asn1Object.BIT_STRING, new Object[] { Integer.valueOf(4), "bit".getBytes() }),
new Asn1Object(Asn1Object.UNIVERSAL, Asn1Object.OCTET_STRING, o1.getBytes()),
new Asn1Object(Asn1Object.UNIVERSAL, Asn1Object.BIT_STRING, new Object[] { Integer.valueOf(0), o1.getBytes() })
),
new Asn1Object(Asn1Object.CONSTRUCTED, Asn1Object.SEQUENCE).setIndefinite(true).addAll(
new Asn1Object(Asn1Object.UNIVERSAL, Asn1Object.INTEGER, new BigInteger("12345678901234567890")),
new Asn1Object(Asn1Object.UNIVERSAL, Asn1Object.INTEGER, Integer.valueOf(-1234567890)),
new Asn1Object(Asn1Object.EOC, Asn1Object.EOC)
),
new Asn1Object(Asn1Object.CONTEXT, 0xABCD, "abcd".getBytes())
),
new Asn1Object(Asn1Object.UNIVERSAL, Asn1Object.NULL)
);
//try (OutputStream os = Base64.getMimeEncoder().wrap(new FileOutputStream("demo.base64"))) {
// o2.write(os);
//} catch (Exception ex) { }
return o2;
}
} // End of class Asn1Viewer