1 /**
2  * Some common stuff for replaying PortForward replay logs.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.demo.portforward.replay;
15 
16 import std.stdio;
17 import std.conv;
18 import std.string;
19 import std.datetime;
20 import std.exception;
21 
22 import ae.utils.text;
23 
24 class Replayer
25 {
26 private:
27 	File f;
28 	string line;
29 
30 	/// Consume and return next space-delimited word from line.
31 	string getWord()
32 	{
33 		auto p = line.indexOf(' ');
34 		string word;
35 		if (p<0)
36 			word = line,
37 			line = null;
38 		else
39 			word = line[0..p],
40 			line = line[p+1..$];
41 		return word;
42 	}
43 
44 	/// Consume and return the rest of the line.
45 	string getData()
46 	{
47 		string data = line;
48 		line = null;
49 		return data;
50 	}
51 
52 public:
53 	this(string fn)
54 	{
55 		f.open(fn, "rb");
56 
57 		nextLine();
58 	}
59 
60 protected:
61 	void nextLine()
62 	{
63 		line = f.readln().chomp();
64 		if (line=="" && f.eof())
65 			return;
66 
67 		auto recordTime = SysTime(to!long(getWord()));
68 		string commandStr = getWord();
69 		enforce(commandStr.length==1, "Invalid command");
70 		char command = commandStr[0];
71 		bool proceed;
72 		switch (command)
73 		{
74 		case 'S':
75 			proceed = handleStart(recordTime);
76 			break;
77 		case 'L':
78 			proceed = handleListen(recordTime, to!ushort(getWord()));
79 			break;
80 		case 'A':
81 			proceed = handleAccept(recordTime, to!uint(getWord()), to!ushort(getWord()));
82 			break;
83 		case 'C':
84 		{
85 			uint index = to!uint(getWord());
86 			string addr = getWord();
87 			auto p = addr.lastIndexOf(':');
88 			proceed = handleConnect(recordTime, index, addr[0..p], to!ushort(addr[p+1..$]));
89 			break;
90 		}
91 		case '<':
92 			proceed = handleIncomingData(recordTime, to!uint(getWord()), stringUnescape(getData()));
93 			break;
94 		case '>':
95 			proceed = handleOutgoingData(recordTime, to!uint(getWord()), stringUnescape(getData()));
96 			break;
97 		case '[':
98 			proceed = handleIncomingDisconnect(recordTime, to!uint(getWord()), getData());
99 			break;
100 		case ']':
101 			proceed = handleOutgoingDisconnect(recordTime, to!uint(getWord()), getData());
102 			break;
103 		default:
104 			throw new Exception("Unknown command: " ~ command);
105 		}
106 
107 		enforce(line.length==0, "Unexpected data at the end of line: " ~ line);
108 
109 		if (proceed)
110 			nextLine();
111 	}
112 
113 	bool handleStart             (SysTime time)                                       { return true; }
114 	bool handleListen            (SysTime time, ushort port)                          { return true; }
115 	bool handleAccept            (SysTime time, uint index, ushort port)              { return true; }
116 	bool handleConnect           (SysTime time, uint index, string host, ushort port) { return true; }
117 	bool handleIncomingData      (SysTime time, uint index, void[] data)              { return true; }
118 	bool handleOutgoingData      (SysTime time, uint index, void[] data)              { return true; }
119 	bool handleIncomingDisconnect(SysTime time, uint index, string reason)            { return true; }
120 	bool handleOutgoingDisconnect(SysTime time, uint index, string reason)            { return true; }
121 }
122 
123 ubyte[] stringUnescape(string s)
124 {
125 	ubyte[] r;
126 	for (int i=0; i<s.length; i++)
127 		if (s[i]=='\\')
128 			r ~= fromHex!ubyte(s[i+1..i+3]),
129 			i+=2;
130 		else
131 			r ~= s[i];
132 	return r;
133 }