1// ASN.1 JavaScript decoder
2// Copyright (c) 2008 Lapo Luchini <lapo@lapo.it>
3
4// Permission to use, copy, modify, and/or distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16import { ASN1 } from './asn1.js';
17import { oids } from './oids.js';
18import { bindContextMenu } from './context.js';
19
20const
21 lineLength = 80,
22 contentLength = 8 * lineLength,
23 DOM = {
24 ellipsis: '\u2026',
25 tag: function (tagName, className, text) {
26 let t = document.createElement(tagName);
27 if (className) t.className = className;
28 if (text) t.innerText = text;
29 return t;
30 },
31 text: function (str) {
32 return document.createTextNode(str);
33 },
34 space: function () {
35 return DOM.tag('span', 'spaces', ' ');
36 },
37 breakLines: function (str, length) {
38 let lines = str.split(/\r?\n/),
39 o = '';
40 for (let i = 0; i < lines.length; ++i) {
41 let line = lines[i];
42 if (i > 0) o += '\n';
43 while (line.length > length) {
44 o += line.substring(0, length);
45 o += '\n';
46 line = line.substring(length);
47 }
48 o += line;
49 }
50 return o;
51 },
52 };
53
54export class ASN1DOM extends ASN1 {
55
56 toDOM(spaces) {
57 spaces = spaces || '';
58 let isOID = (typeof oids === 'object') && (this.tag.isUniversal() && (this.tag.tagNumber == 0x06) || (this.tag.tagNumber == 0x0D));
59 let node = DOM.tag('li');
60 node.asn1 = this;
61 let head = DOM.tag('span', 'head');
62 const typeName = this.typeName().replace(/_/g, ' ');
63 if (this.def) {
64 if (this.def.id) {
65 head.appendChild(DOM.tag('span', 'name id', this.def.id));
66 head.appendChild(DOM.space());
67 }
68 if (this.def.name && this.def.name != typeName) {
69 head.appendChild(DOM.tag('span', 'name type', this.def.name));
70 head.appendChild(DOM.space());
71 }
72 if (this.def.mismatch) {
73 head.appendChild(DOM.tag('span', 'name type', '[?]'));
74 head.appendChild(DOM.space());
75 }
76 }
77 head.appendChild(DOM.text(typeName));
78 let content;
79 try {
80 content = this.content(contentLength);
81 } catch (e) {
82 content = 'Cannot decode: ' + e;
83 }
84 let oid;
85 if (content !== null) {
86 let preview = DOM.tag('span', 'preview'),
87 shortContent;
88 if (isOID)
89 content = content.split('\n', 1)[0];
90 shortContent = (content.length > lineLength) ? content.substring(0, lineLength) + DOM.ellipsis : content;
91 preview.appendChild(DOM.space());
92 preview.appendChild(DOM.text(shortContent));
93 if (isOID) {
94 oid = oids[content];
95 if (oid) {
96 if (oid.d) {
97 preview.appendChild(DOM.space());
98 let oidd = DOM.tag('span', 'oid description', oid.d);
99 preview.appendChild(oidd);
100 }
101 if (oid.c) {
102 preview.appendChild(DOM.space());
103 let oidc = DOM.tag('span', 'oid comment', '(' + oid.c + ')');
104 preview.appendChild(oidc);
105 }
106 }
107 }
108 head.appendChild(preview);
109 content = DOM.breakLines(content, lineLength);
110 content = content.replace(/</g, '<');
111 content = content.replace(/\n/g, '<br>');
112 }
113 // add the li and details section for this node
114 let contentNode;
115 let childNode;
116 if (this.sub !== null) {
117 let details = DOM.tag('details');
118 details.setAttribute('open', '');
119 node.appendChild(details);
120 let summary = DOM.tag('summary', 'node');
121 details.appendChild(summary);
122 summary.appendChild(head);
123 contentNode = summary;
124 childNode = details;
125 } else {
126 contentNode = node;
127 contentNode.classList.add('node');
128 contentNode.appendChild(head);
129 }
130 this.node = contentNode;
131 this.head = head;
132 let value = DOM.tag('div', 'value');
133 let s = 'Offset: ' + this.stream.pos + '<br>';
134 s += 'Length: ' + this.header + '+';
135 if (this.length >= 0)
136 s += this.length;
137 else
138 s += (-this.length) + ' (undefined)';
139 if (this.tag.tagConstructed)
140 s += '<br>(constructed)';
141 else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null))
142 s += '<br>(encapsulates)';
143 //TODO if (this.tag.isUniversal() && this.tag.tagNumber == 0x03) s += "Unused bits: "
144 if (content !== null) {
145 s += '<br>Value:<br><b>' + content + '</b>';
146 if (isOID && oid) {
147 if (oid.d) s += '<br>' + oid.d;
148 if (oid.c) s += '<br>' + oid.c;
149 if (oid.w) s += '<br>(warning!)';
150 }
151 }
152 value.innerHTML = s;
153 contentNode.appendChild(value);
154 if (this.sub !== null) {
155 let sub = DOM.tag('ul');
156 childNode.appendChild(sub);
157 spaces += '\xA0 ';
158 for (let i = 0, max = this.sub.length; i < max; ++i)
159 sub.appendChild(this.sub[i].toDOM(spaces));
160 }
161 bindContextMenu(node);
162 return node;
163 }
164 fakeHover(current) {
165 this.node.classList.add('hover');
166 if (current)
167 this.head.classList.add('hover');
168 }
169 fakeOut(current) {
170 this.node.classList.remove('hover');
171 if (current)
172 this.head.classList.remove('hover');
173 }
174 toHexDOM_sub(node, className, stream, start, end) {
175 if (start >= end)
176 return;
177 let sub = DOM.tag('span', className, stream.hexDump(start, end));
178 node.appendChild(sub);
179 }
180 toHexDOM(root, trim=true) {
181 let node = DOM.tag('span', 'hex');
182 if (root === undefined) root = node;
183 this.head.hexNode = node;
184 this.head.onmouseover = function () { this.hexNode.className = 'hexCurrent'; };
185 this.head.onmouseout = function () { this.hexNode.className = 'hex'; };
186 node.asn1 = this;
187 node.onmouseover = function (event) {
188 let current = !root.selected;
189 if (current) {
190 root.selected = this.asn1;
191 this.className = 'hexCurrent';
192 }
193 this.asn1.fakeHover(current);
194 event.stopPropagation();
195 };
196 node.onmouseout = function () {
197 let current = (root.selected == this.asn1);
198 this.asn1.fakeOut(current);
199 if (current) {
200 root.selected = null;
201 this.className = 'hex';
202 }
203 };
204 bindContextMenu(node);
205 if (root == node) {
206 let lineStart = this.posStart() & 0xF;
207 if (lineStart != 0) {
208 let skip = DOM.tag('span', 'skip');
209 let skipStr = '';
210 for (let j = lineStart; j > 0; --j)
211 skipStr += ' ';
212 if (lineStart >= 8)
213 skipStr += ' ';
214 skip.innerText = skipStr;
215 node.appendChild(skip);
216 }
217 }
218 this.toHexDOM_sub(node, 'tag', this.stream, this.posStart(), this.posLen());
219 this.toHexDOM_sub(node, (this.length >= 0) ? 'dlen' : 'ulen', this.stream, this.posLen(), this.posContent());
220 if (this.sub === null) {
221 let start = this.posContent();
222 let end = this.posEnd();
223 if (!trim || end - start < 10 * 16)
224 node.appendChild(DOM.text(
225 this.stream.hexDump(start, end)));
226 else {
227 let end1 = start + 5 * 16 - (start & 0xF);
228 let start2 = end - 16 - (end & 0xF);
229 node.appendChild(DOM.text(this.stream.hexDump(start, end1)));
230 node.appendChild(DOM.tag('span', 'skip', '\u2026 skipping ' + (start2 - end1) + ' bytes \u2026\n'));
231 node.appendChild(DOM.text(this.stream.hexDump(start2, end)));
232 }
233 } else if (this.sub.length > 0) {
234 let first = this.sub[0];
235 let last = this.sub[this.sub.length - 1];
236 this.toHexDOM_sub(node, 'intro', this.stream, this.posContent(), first.posStart());
237 for (let i = 0, max = this.sub.length; i < max; ++i)
238 node.appendChild(this.sub[i].toHexDOM(root, trim));
239 this.toHexDOM_sub(node, 'outro', this.stream, last.posEnd(), this.posEnd());
240 } else
241 this.toHexDOM_sub(node, 'outro', this.stream, this.posContent(), this.posEnd());
242 return node;
243 }
244 static decode(stream, offset) {
245 return ASN1.decode(stream, offset, ASN1DOM);
246 }
247
248}