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