Skip to content

Instantly share code, notes, and snippets.

@darkl
Created December 16, 2019 11:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save darkl/3acc48adb6d7094826a7566aeca32b59 to your computer and use it in GitHub Desktop.
Save darkl/3acc48adb6d7094826a7566aeca32b59 to your computer and use it in GitHub Desktop.
WAMP parser
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {
Component,
createFactory,
} = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const { div } = dom;
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const Services = require("Services");
const { L10N } = require("devtools/client/netmonitor/src/utils/l10n.js");
const {
getFramePayload,
isJSON,
} = require("devtools/client/netmonitor/src/utils/request-utils.js");
const {
getFormattedSize,
} = require("devtools/client/netmonitor/src/utils/format-utils.js");
const MESSAGE_DATA_LIMIT = Services.prefs.getIntPref(
"devtools.netmonitor.ws.messageDataLimit"
);
const MESSAGE_DATA_TRUNCATED = L10N.getStr("messageDataTruncated");
const SocketIODecoder = require("devtools/client/netmonitor/src/components/websockets/parsers/socket-io/index.js");
const {
JsonHubProtocol,
HandshakeProtocol,
} = require("devtools/client/netmonitor/src/components/websockets/parsers/signalr/index.js");
const {
parseSockJS,
} = require("devtools/client/netmonitor/src/components/websockets/parsers/sockjs/index.js");
const {
parseWampMessage,
} = require("devtools/client/netmonitor/src/components/websockets/parsers/wamp/index.js");
// Components
const Accordion = createFactory(
require("devtools/client/shared/components/Accordion")
);
const RawData = createFactory(require("./RawData"));
loader.lazyGetter(this, "JSONPreview", function() {
return createFactory(
require("devtools/client/netmonitor/src/components/JSONPreview")
);
});
/**
* Shows the full payload of a WebSocket frame.
* The payload is unwrapped from the LongStringActor object.
*/
class FramePayload extends Component {
static get propTypes() {
return {
connector: PropTypes.object.isRequired,
selectedFrame: PropTypes.object,
};
}
constructor(props) {
super(props);
this.state = {
payload: "",
isFormattedData: false,
formattedData: {},
formattedDataTitle: "",
};
}
componentDidMount() {
this.updateFramePayload();
}
componentDidUpdate(prevProps) {
if (this.props.selectedFrame !== prevProps.selectedFrame) {
this.updateFramePayload();
}
}
updateFramePayload() {
const { selectedFrame, connector } = this.props;
getFramePayload(selectedFrame.payload, connector.getLongString).then(
payload => {
const { formattedData, formattedDataTitle } = this.parsePayload(
payload
);
this.setState({
payload,
isFormattedData: !!formattedData,
formattedData,
formattedDataTitle,
});
}
);
}
parsePayload(payload) {
// socket.io payload
const socketIOPayload = this.parseSocketIOPayload(payload);
if (socketIOPayload) {
return {
formattedData: socketIOPayload,
formattedDataTitle: "Socket.IO",
};
}
// sockjs payload
const sockJSPayload = parseSockJS(payload);
if (sockJSPayload) {
return {
formattedData: sockJSPayload,
formattedDataTitle: "SockJS",
};
}
// signalr payload
const signalRPayload = this.parseSignalR(payload);
if (signalRPayload) {
return {
formattedData: signalRPayload,
formattedDataTitle: "SignalR",
};
}
// wamp payload
const wampPayload = this.parseWamp(payload);
if (wampPayload) {
return {
formattedData: wampPayload,
formattedDataTitle: "WAMP",
};
}
// json payload
const { json } = isJSON(payload);
if (json) {
return {
formattedData: json,
formattedDataTitle: "JSON",
};
}
return {
formattedData: null,
formattedDataTitle: "",
};
}
parseSocketIOPayload(payload) {
let result;
// Try decoding socket.io frames
try {
const decoder = new SocketIODecoder();
decoder.on("decoded", decodedPacket => {
if (
decodedPacket &&
!decodedPacket.data.includes("parser error") &&
decodedPacket.type
) {
result = decodedPacket;
}
});
decoder.add(payload);
return result;
} catch (err) {
// Ignore errors
}
return null;
}
parseSignalR(payload) {
// attempt to parse as HandshakeResponseMessage
let decoder;
try {
decoder = new HandshakeProtocol();
const [remainingData, responseMessage] = decoder.parseHandshakeResponse(
payload
);
if (responseMessage) {
return {
handshakeResponse: responseMessage,
remainingData: this.parseSignalR(remainingData),
};
}
} catch (err) {
// ignore errors;
}
// attempt to parse as JsonHubProtocolMessage
try {
decoder = new JsonHubProtocol();
const msgs = decoder.parseMessages(payload, null);
if (msgs && msgs.length) {
return msgs;
}
} catch (err) {
// ignore errors;
}
// MVP Signalr
if (payload.endsWith("\u001e")) {
const { json } = isJSON(payload.slice(0, -1));
if (json) {
return json;
}
}
return null;
}
parseWampMessage(payload) {
try {
var array = JSON.parse(payload);
return parseWampMessage(array);
}
catch (err) {
// ignore errors;
}
return null;
}
render() {
let payload = this.state.payload;
let isTruncated = false;
if (this.state.payload.length >= MESSAGE_DATA_LIMIT) {
payload = payload.substring(0, MESSAGE_DATA_LIMIT);
isTruncated = true;
}
const items = [
{
className: "rawData",
component: RawData,
componentProps: { payload },
header: L10N.getFormatStrWithNumbers(
"netmonitor.ws.rawData.header",
getFormattedSize(this.state.payload.length)
),
id: "ws-frame-rawData",
opened: true,
},
];
if (!isTruncated && this.state.isFormattedData) {
/**
* Push the JSON section (formatted data) at the begging of the array
* before the raw data section. Note that the JSON section will be
* auto-expanded while the raw data auto-collapsed.
*/
items.unshift({
className: "formattedData",
component: JSONPreview,
componentProps: {
object: this.state.formattedData,
columns: [
{
id: "value",
width: "100%",
},
],
},
header: `${this.state.formattedDataTitle} (${getFormattedSize(
this.state.payload.length
)})`,
id: "ws-frame-formattedData",
opened: true,
});
}
return div(
{
className: "ws-frame-payload",
},
isTruncated &&
div(
{
className: "truncated-data-message",
},
MESSAGE_DATA_TRUNCATED
),
Accordion({
items,
})
);
}
}
module.exports = FramePayload;
// parsers/wamp/index.js
var WampMessageType;
(function (WampMessageType) {
WampMessageType[WampMessageType["Hello"] = 1] = "Hello";
WampMessageType[WampMessageType["Welcome"] = 2] = "Welcome";
WampMessageType[WampMessageType["Abort"] = 3] = "Abort";
WampMessageType[WampMessageType["Challenge"] = 4] = "Challenge";
WampMessageType[WampMessageType["Authenticate"] = 5] = "Authenticate";
WampMessageType[WampMessageType["Goodbye"] = 6] = "Goodbye";
WampMessageType[WampMessageType["Error"] = 8] = "Error";
WampMessageType[WampMessageType["Publish"] = 16] = "Publish";
WampMessageType[WampMessageType["Published"] = 17] = "Published";
WampMessageType[WampMessageType["Subscribe"] = 32] = "Subscribe";
WampMessageType[WampMessageType["Subscribed"] = 33] = "Subscribed";
WampMessageType[WampMessageType["Unsubscribe"] = 34] = "Unsubscribe";
WampMessageType[WampMessageType["Unsubscribed"] = 35] = "Unsubscribed";
WampMessageType[WampMessageType["Event"] = 36] = "Event";
WampMessageType[WampMessageType["Call"] = 48] = "Call";
WampMessageType[WampMessageType["Cancel"] = 49] = "Cancel";
WampMessageType[WampMessageType["Result"] = 50] = "Result";
WampMessageType[WampMessageType["Register"] = 64] = "Register";
WampMessageType[WampMessageType["Registered"] = 65] = "Registered";
WampMessageType[WampMessageType["Unregister"] = 66] = "Unregister";
WampMessageType[WampMessageType["Unregistered"] = 67] = "Unregistered";
WampMessageType[WampMessageType["Invocation"] = 68] = "Invocation";
WampMessageType[WampMessageType["Interrupt"] = 69] = "Interrupt";
WampMessageType[WampMessageType["Yield"] = 70] = "Yield";
})(WampMessageType || (WampMessageType = {}));
function parseWampMessage(messageArray) {
var messageType;
var arguments;
[messageType, ...arguments] = messageArray;
switch (messageType) {
case WampMessageType.Hello:
return new HelloMessage(arguments);
case WampMessageType.Welcome:
return new WelcomeMessage(arguments);
case WampMessageType.Abort:
return new AbortMessage(arguments);
case WampMessageType.Challenge:
return new ChallengeMessage(arguments);
case WampMessageType.Authenticate:
return new AuthenticateMessage(arguments);
case WampMessageType.Goodbye:
return new GoodbyeMessage(arguments);
case WampMessageType.Error:
return new ErrorMessage(arguments);
case WampMessageType.Publish:
return new PublishMessage(arguments);
case WampMessageType.Published:
return new PublishedMessage(arguments);
case WampMessageType.Subscribe:
return new SubscribeMessage(arguments);
case WampMessageType.Subscribed:
return new SubscribedMessage(arguments);
case WampMessageType.Unsubscribe:
return new UnsubscribeMessage(arguments);
case WampMessageType.Unsubscribed:
return new UnsubscribedMessage(arguments);
case WampMessageType.Event:
return new EventMessage(arguments);
case WampMessageType.Call:
return new CallMessage(arguments);
case WampMessageType.Cancel:
return new CancelMessage(arguments);
case WampMessageType.Result:
return new ResultMessage(arguments);
case WampMessageType.Register:
return new RegisterMessage(arguments);
case WampMessageType.Registered:
return new RegisteredMessage(arguments);
case WampMessageType.Unregister:
return new UnregisterMessage(arguments);
case WampMessageType.Unregistered:
return new UnregisteredMessage(arguments);
case WampMessageType.Invocation:
return new InvocationMessage(arguments);
case WampMessageType.Interrupt:
return new InterruptMessage(arguments);
case WampMessageType.Yield:
return new YieldMessage(arguments);
default:
return null;
}
}
class HelloMessage {
constructor(messageArguments) {
[this._realm, this._details] = messageArguments;
}
get messageCode() { return WampMessageType.Hello; }
get messageName() { return "HELLO"; }
get realm() { return this._realm; }
get details() { return this._details; }
}
class WelcomeMessage {
constructor(messageArguments) {
[this._session, this._details] = messageArguments;
}
get messageCode() { return WampMessageType.Welcome; }
get messageName() { return "WELCOME"; }
get session() { return this._session; }
get details() { return this._details; }
}
class AbortMessage {
constructor(messageArguments) {
[this._details, this._reason] = messageArguments;
}
get messageCode() { return WampMessageType.Abort; }
get messageName() { return "ABORT"; }
get details() { return this._details; }
get reason() { return this._reason; }
}
class ChallengeMessage {
constructor(messageArguments) {
[this._authMethod, this._extra] = messageArguments;
}
get messageCode() { return WampMessageType.Challenge; }
get messageName() { return "CHALLENGE"; }
get authMethod() { return this._authMethod; }
get extra() { return this._extra; }
}
class AuthenticateMessage {
constructor(messageArguments) {
[this._signature, this._extra] = messageArguments;
}
get messageCode() { return WampMessageType.Authenticate; }
get messageName() { return "AUTHENTICATE"; }
get signature() { return this._signature; }
get extra() { return this._extra; }
}
class GoodbyeMessage {
constructor(messageArguments) {
[this._details, this._reason] = messageArguments;
}
get messageCode() { return WampMessageType.Goodbye; }
get messageName() { return "GOODBYE"; }
get details() { return this._details; }
get reason() { return this._reason; }
}
class ErrorMessage {
constructor(messageArguments) {
[this._type, this._request, this._details, this._error, this._arguments, this._argumentsKw] = messageArguments;
}
get messageCode() { return WampMessageType.Error; }
get messageName() { return "ERROR"; }
get type() { return this._type; }
get request() { return this._request; }
get details() { return this._details; }
get error() { return this._error; }
get arguments() { return this._arguments; }
get argumentsKw() { return this._argumentsKw; }
}
class PublishMessage {
constructor(messageArguments) {
[this._request, this._options, this._topic, this._arguments, this._argumentsKw] = messageArguments;
}
get messageCode() { return WampMessageType.Publish; }
get messageName() { return "PUBLISH"; }
get request() { return this._request; }
get options() { return this._options; }
get topic() { return this._topic; }
get arguments() { return this._arguments; }
get argumentsKw() { return this._argumentsKw; }
}
class PublishedMessage {
constructor(messageArguments) {
[this._request, this._publication] = messageArguments;
}
get messageCode() { return WampMessageType.Published; }
get messageName() { return "PUBLISHED"; }
get request() { return this._request; }
get publication() { return this._publication; }
}
class SubscribeMessage {
constructor(messageArguments) {
[this._request, this._options, this._topic] = messageArguments;
}
get messageCode() { return WampMessageType.Subscribe; }
get messageName() { return "SUBSCRIBE"; }
get request() { return this._request; }
get options() { return this._options; }
get topic() { return this._topic; }
}
class SubscribedMessage {
constructor(messageArguments) {
[this._request, this._subscription] = messageArguments;
}
get messageCode() { return WampMessageType.Subscribed; }
get messageName() { return "SUBSCRIBED"; }
get request() { return this._request; }
get subscription() { return this._subscription; }
}
class UnsubscribeMessage {
constructor(messageArguments) {
[this._request, this._subscription] = messageArguments;
}
get messageCode() { return WampMessageType.Unsubscribe; }
get messageName() { return "UNSUBSCRIBE"; }
get request() { return this._request; }
get subscription() { return this._subscription; }
}
class UnsubscribedMessage {
constructor(messageArguments) {
[this._request] = messageArguments;
}
get messageCode() { return WampMessageType.Unsubscribed; }
get messageName() { return "UNSUBSCRIBED"; }
get request() { return this._request; }
}
class EventMessage {
constructor(messageArguments) {
[this._subscription, this._publication, this._details, this._arguments, this._argumentsKw] = messageArguments;
}
get messageCode() { return WampMessageType.Event; }
get messageName() { return "EVENT"; }
get subscription() { return this._subscription; }
get publication() { return this._publication; }
get details() { return this._details; }
get arguments() { return this._arguments; }
get argumentsKw() { return this._argumentsKw; }
}
class CallMessage {
constructor(messageArguments) {
[this._request, this._options, this._procedure, this._arguments, this._argumentsKw] = messageArguments;
}
get messageCode() { return WampMessageType.Call; }
get messageName() { return "CALL"; }
get request() { return this._request; }
get options() { return this._options; }
get procedure() { return this._procedure; }
get arguments() { return this._arguments; }
get argumentsKw() { return this._argumentsKw; }
}
class CancelMessage {
constructor(messageArguments) {
[this._request, this._options] = messageArguments;
}
get messageCode() { return WampMessageType.Cancel; }
get messageName() { return "CANCEL"; }
get request() { return this._request; }
get options() { return this._options; }
}
class ResultMessage {
constructor(messageArguments) {
[this._request, this._details, this._arguments, this._argumentsKw] = messageArguments;
}
get messageCode() { return WampMessageType.Result; }
get messageName() { return "RESULT"; }
get request() { return this._request; }
get details() { return this._details; }
get arguments() { return this._arguments; }
get argumentsKw() { return this._argumentsKw; }
}
class RegisterMessage {
constructor(messageArguments) {
[this._request, this._options, this._procedure] = messageArguments;
}
get messageCode() { return WampMessageType.Register; }
get messageName() { return "REGISTER"; }
get request() { return this._request; }
get options() { return this._options; }
get procedure() { return this._procedure; }
}
class RegisteredMessage {
constructor(messageArguments) {
[this._request, this._registration] = messageArguments;
}
get messageCode() { return WampMessageType.Registered; }
get messageName() { return "REGISTERED"; }
get request() { return this._request; }
get registration() { return this._registration; }
}
class UnregisterMessage {
constructor(messageArguments) {
[this._request, this._registration] = messageArguments;
}
get messageCode() { return WampMessageType.Unregister; }
get messageName() { return "UNREGISTER"; }
get request() { return this._request; }
get registration() { return this._registration; }
}
class UnregisteredMessage {
constructor(messageArguments) {
[this._request] = messageArguments;
}
get messageCode() { return WampMessageType.Unregistered; }
get messageName() { return "UNREGISTERED"; }
get request() { return this._request; }
}
class InvocationMessage {
constructor(messageArguments) {
[this._request, this._registration, this._details, this._arguments, this._argumentsKw] = messageArguments;
}
get messageCode() { return WampMessageType.Invocation; }
get messageName() { return "INVOCATION"; }
get request() { return this._request; }
get registration() { return this._registration; }
get details() { return this._details; }
get arguments() { return this._arguments; }
get argumentsKw() { return this._argumentsKw; }
}
class InterruptMessage {
constructor(messageArguments) {
[this._request, this._options] = messageArguments;
}
get messageCode() { return WampMessageType.Interrupt; }
get messageName() { return "INTERRUPT"; }
get request() { return this._request; }
get options() { return this._options; }
}
class YieldMessage {
constructor(messageArguments) {
[this._request, this._options, this._arguments, this._argumentsKw] = messageArguments;
}
get messageCode() { return WampMessageType.Yield; }
get messageName() { return "YIELD"; }
get request() { return this._request; }
get options() { return this._options; }
get arguments() { return this._arguments; }
get argumentsKw() { return this._argumentsKw; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment