Files
libremetaverse/Programs/GridProxy/Plugins/CapAnalyst.cs
2012-06-02 05:06:24 +01:00

875 lines
31 KiB
C#

/*
* Analyst.cs: proxy that makes packet inspection and modifcation interactive
* See the README for usage instructions.
*
* Copyright (c) 2006 Austin Jennings
* Modified by "qode" and "mcortez" on December 21st, 2006 to work with the new
* pregen
* All rights reserved.
*
* - Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Neither the name of the openmetaverse.org nor the names
* of its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using System.Reflection;
using System.Xml;
using Nwc.XmlRpc;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using OpenMetaverse.Packets;
using GridProxy;
public class CapAnalyst : ProxyPlugin
{
private ProxyFrame frame;
private Proxy proxy;
private HashSet<string> loggedCaps = new HashSet<string>();
private string logGrep = null;
private Dictionary<PacketType, Dictionary<BlockField, object>> modifiedPackets = new Dictionary<PacketType, Dictionary<BlockField, object>>();
private Assembly openmvAssembly;
private StreamWriter output;
//private PacketDecoder DecodePacket = new PacketDecoder();
public CapAnalyst(ProxyFrame frame)
{
this.frame = frame;
this.proxy = frame.proxy;
}
~CapAnalyst()
{
if (output != null)
output.Close();
}
public override void Init()
{
openmvAssembly = Assembly.Load("OpenMetaverse");
if (openmvAssembly == null) throw new Exception("Assembly load exception");
// build the table of /command delegates
InitializeCommandDelegates();
// handle command line arguments
foreach (string arg in frame.Args)
if (arg == "--log-all")
LogAll();
else if (arg.Contains("--log-whitelist="))
LogWhitelist(arg.Substring(arg.IndexOf('=') + 1));
else if (arg.Contains("--no-log-blacklist="))
NoLogBlacklist(arg.Substring(arg.IndexOf('=') + 1));
else if (arg.Contains("--output="))
SetOutput(arg.Substring(arg.IndexOf('=') + 1));
StartLogCap("FetchInventory2");
StartLogCap("FetchInventoryDescendents2");
Console.WriteLine("CapAnalyst loaded");
}
// InitializeCommandDelegates: configure Analyst's commands
private void InitializeCommandDelegates()
{
frame.AddCommand("/logcap", new ProxyFrame.CommandDelegate(CmdLog));
frame.AddCommand("/-logcap", new ProxyFrame.CommandDelegate(CmdNoLog));
// frame.AddCommand("/grep", new ProxyFrame.CommandDelegate(CmdGrep));
// frame.AddCommand("/drop", new ProxyFrame.CommandDelegate(CmdDrop));
// frame.AddCommand("/-drop", new ProxyFrame.CommandDelegate(CmdNoDrop));
// frame.AddCommand("/set", new ProxyFrame.CommandDelegate(CmdSet));
// frame.AddCommand("/-set", new ProxyFrame.CommandDelegate(CmdNoSet));
// frame.AddCommand("/inject", new ProxyFrame.CommandDelegate(CmdInject));
// frame.AddCommand("/in", new ProxyFrame.CommandDelegate(CmdInject));
}
// private static PacketType packetTypeFromName(string name)
// {
// Type packetTypeType = typeof(PacketType);
// System.Reflection.FieldInfo f = packetTypeType.GetField(name);
// if (f == null) throw new ArgumentException("Bad packet type");
// return (PacketType)Enum.ToObject(packetTypeType, (int)f.GetValue(packetTypeType));
// }
// CmdLog: handle a /log command
private void CmdLog(string[] words)
{
if (words.Length != 2)
SayToUser("Usage: /logcap <packet name>");
else if (words[1] == "*")
{
LogAll();
SayToUser("logging all caps");
}
else
{
// PacketType pType;
// try
// {
// pType = packetTypeFromName(words[1]);
// }
// catch (ArgumentException)
// {
// SayToUser("Bad cap name: " + words[1]);
// return;
// }
string capName = words[1];
StartLogCap(capName);
SayToUser("logging " + capName);
}
}
private void StartLogCap(string capName)
{
loggedCaps.Add(capName);
proxy.AddCapsDelegate(capName, LogCap);
}
// CmdNoLog: handle a /-log command
private void CmdNoLog(string[] words)
{
if (words.Length != 2)
SayToUser("Usage: /-logcap <cap name>");
else if (words[1] == "*")
{
NoLogAll();
SayToUser("stopped logging all caps");
}
else
{
// PacketType pType = packetTypeFromName(words[1]);
string capName = words[1];
loggedCaps.Remove(capName);
proxy.RemoveCapRequestDelegate(capName, LogCap);
SayToUser("stopped logging " + capName);
}
}
// CmdGrep: handle a /grep command
private void CmdGrep(string[] words)
{
if (words.Length == 1)
{
logGrep = null;
SayToUser("stopped filtering logs");
}
else
{
string[] regexArray = new string[words.Length - 1];
Array.Copy(words, 1, regexArray, 0, words.Length - 1);
logGrep = String.Join(" ", regexArray);
SayToUser("filtering log with " + logGrep);
}
}
private void CmdDrop(string[] words)
{
throw new NotImplementedException();
}
private void CmdNoDrop(string[] words)
{
throw new NotImplementedException();
}
// CmdSet: handle a /set command
private void CmdSet(string[] words)
{
// if (words.Length < 5)
// SayToUser("Usage: /set <packet name> <block> <field> <value>");
// else
// {
// PacketType pType;
// try
// {
// pType = packetTypeFromName(words[1]);
// }
// catch (ArgumentException)
// {
// SayToUser("Bad packet name: " + words[1]);
// return;
// }
//
// string[] valueArray = new string[words.Length - 4];
// Array.Copy(words, 4, valueArray, 0, words.Length - 4);
// string valueString = String.Join(" ", valueArray);
// object value;
// try
// {
// value = MagicCast(words[1], words[2], words[3], valueString);
// }
// catch (Exception e)
// {
// SayToUser(e.Message);
// return;
// }
//
// Dictionary<BlockField, object> fields;
// if (modifiedPackets.ContainsKey(pType))
// fields = (Dictionary<BlockField, object>)modifiedPackets[pType];
// else
// fields = new Dictionary<BlockField, object>();
//
// fields[new BlockField(words[2], words[3])] = value;
// modifiedPackets[pType] = fields;
//
// proxy.AddDelegate(pType, Direction.Incoming, new PacketDelegate(ModifyIn));
// proxy.AddDelegate(pType, Direction.Outgoing, new PacketDelegate(ModifyOut));
//
// SayToUser("setting " + words[1] + "." + words[2] + "." + words[3] + " = " + valueString);
// }
}
// CmdNoSet: handle a /-set command
private void CmdNoSet(string[] words)
{
// if (words.Length == 2 && words[1] == "*")
// {
// foreach (PacketType pType in modifiedPackets.Keys)
// {
// proxy.RemoveDelegate(pType, Direction.Incoming, new PacketDelegate(ModifyIn));
// proxy.RemoveDelegate(pType, Direction.Outgoing, new PacketDelegate(ModifyOut));
// }
// modifiedPackets = new Dictionary<PacketType, Dictionary<BlockField, object>>();
//
// SayToUser("stopped setting all fields");
// }
// else if (words.Length == 4)
// {
// PacketType pType;
// try
// {
// pType = packetTypeFromName(words[1]);
// }
// catch (ArgumentException)
// {
// SayToUser("Bad packet name: " + words[1]);
// return;
// }
//
//
// if (modifiedPackets.ContainsKey(pType))
// {
// Dictionary<BlockField, object> fields = modifiedPackets[pType];
// fields.Remove(new BlockField(words[2], words[3]));
//
// if (fields.Count == 0)
// {
// modifiedPackets.Remove(pType);
//
// proxy.RemoveDelegate(pType, Direction.Incoming, new PacketDelegate(ModifyIn));
// proxy.RemoveDelegate(pType, Direction.Outgoing, new PacketDelegate(ModifyOut));
// }
// }
//
// SayToUser("stopped setting " + words[1] + "." + words[2] + "." + words[3]);
// }
// else
// SayToUser("Usage: /-set <packet name> <block> <field>");
}
// CmdInject: handle an /inject command
private void CmdInject(string[] words)
{
// if (words.Length < 2)
// SayToUser("Usage: /inject <packet file> [value]");
// else
// {
// string[] valueArray = new string[words.Length - 2];
// Array.Copy(words, 2, valueArray, 0, words.Length - 2);
// string value = String.Join(" ", valueArray);
//
// FileStream fs = null;
// StreamReader sr = null;
// Direction direction = Direction.Incoming;
// string name = null;
// string block = null;
// object blockObj = null;
// Type packetClass = null;
// Packet packet = null;
//
// try
// {
// fs = File.OpenRead(words[1] + ".packet");
// sr = new StreamReader(fs);
//
// string line;
// while ((line = sr.ReadLine()) != null)
// {
// Match match;
//
// if (name == null)
// {
// match = (new Regex(@"^\s*(in|out)\s+(\w+)\s*$")).Match(line);
// if (!match.Success)
// {
// SayToUser("expecting direction and packet name, got: " + line);
// return;
// }
//
// string lineDir = match.Groups[1].Captures[0].ToString();
// string lineName = match.Groups[2].Captures[0].ToString();
//
// if (lineDir == "in")
// direction = Direction.Incoming;
// else if (lineDir == "out")
// direction = Direction.Outgoing;
// else
// {
// SayToUser("expecting 'in' or 'out', got: " + line);
// return;
// }
//
// name = lineName;
// packetClass = openmvAssembly.GetType("OpenMetaverse.Packets." + name + "Packet");
// if (packetClass == null) throw new Exception("Couldn't get class " + name + "Packet");
// ConstructorInfo ctr = packetClass.GetConstructor(new Type[] { });
// if (ctr == null) throw new Exception("Couldn't get suitable constructor for " + name + "Packet");
// packet = (Packet)ctr.Invoke(new object[] { });
// //Console.WriteLine("Created new " + name + "Packet");
// }
// else
// {
// match = (new Regex(@"^\s*\[(\w+)\]\s*$")).Match(line);
// if (match.Success)
// {
// block = match.Groups[1].Captures[0].ToString();
// FieldInfo blockField = packetClass.GetField(block);
// if (blockField == null) throw new Exception("Couldn't get " + name + "Packet." + block);
// Type blockClass = blockField.FieldType;
// if (blockClass.IsArray)
// {
// blockClass = blockClass.GetElementType();
// ConstructorInfo ctr = blockClass.GetConstructor(new Type[] { });
// if (ctr == null) throw new Exception("Couldn't get suitable constructor for " + blockClass.Name);
// blockObj = ctr.Invoke(new object[] { });
// object[] arr = (object[])blockField.GetValue(packet);
// object[] narr = (object[])Array.CreateInstance(blockClass, arr.Length + 1);
// Array.Copy(arr, narr, arr.Length);
// narr[arr.Length] = blockObj;
// blockField.SetValue(packet, narr);
// //Console.WriteLine("Added block "+block);
// }
// else
// {
// blockObj = blockField.GetValue(packet);
// }
// if (blockObj == null) throw new Exception("Got " + name + "Packet." + block + " == null");
// //Console.WriteLine("Got block " + name + "Packet." + block);
//
// continue;
// }
//
// if (block == null)
// {
// SayToUser("expecting block name, got: " + line);
// return;
// }
//
// match = (new Regex(@"^\s*(\w+)\s*=\s*(.*)$")).Match(line);
// if (match.Success)
// {
// string lineField = match.Groups[1].Captures[0].ToString();
// string lineValue = match.Groups[2].Captures[0].ToString();
// object fval;
//
// //FIXME: use of MagicCast inefficient
// if (lineValue == "$Value")
// fval = MagicCast(name, block, lineField, value);
// else if (lineValue == "$UUID")
// fval = UUID.Random();
// else if (lineValue == "$AgentID")
// fval = frame.AgentID;
// else if (lineValue == "$SessionID")
// fval = frame.SessionID;
// else
// fval = MagicCast(name, block, lineField, lineValue);
//
// MagicSetField(blockObj, lineField, fval);
// continue;
// }
//
// SayToUser("expecting block name or field, got: " + line);
// return;
// }
// }
//
// if (name == null)
// {
// SayToUser("expecting direction and packet name, got EOF");
// return;
// }
//
// packet.Header.Reliable = true;
// //if (protocolManager.Command(name).Encoded)
// // packet.Header.Zerocoded = true;
// proxy.InjectPacket(packet, direction);
//
// SayToUser("injected " + words[1]);
// }
// catch (Exception e)
// {
// SayToUser("failed to inject " + words[1] + ": " + e.Message);
// Console.WriteLine("failed to inject " + words[1] + ": " + e.Message + "\n" + e.StackTrace);
// }
// finally
// {
// if (fs != null)
// fs.Close();
// if (sr != null)
// sr.Close();
// }
// }
}
// SayToUser: send a message to the user as in-world chat
private void SayToUser(string message)
{
ChatFromSimulatorPacket packet = new ChatFromSimulatorPacket();
packet.ChatData.FromName = Utils.StringToBytes("Analyst");
packet.ChatData.SourceID = UUID.Random();
packet.ChatData.OwnerID = frame.AgentID;
packet.ChatData.SourceType = (byte)2;
packet.ChatData.ChatType = (byte)1;
packet.ChatData.Audible = (byte)1;
packet.ChatData.Position = new Vector3(0, 0, 0);
packet.ChatData.Message = Utils.StringToBytes(message);
proxy.InjectPacket(packet, Direction.Incoming);
}
// BlockField: product type for a block name and field name
private struct BlockField
{
public string block;
public string field;
public BlockField(string block, string field)
{
this.block = block;
this.field = field;
}
}
private static void MagicSetField(object obj, string field, object val)
{
Type cls = obj.GetType();
FieldInfo fieldInf = cls.GetField(field);
if (fieldInf == null)
{
PropertyInfo prop = cls.GetProperty(field);
if (prop == null) throw new Exception("Couldn't find field " + cls.Name + "." + field);
prop.SetValue(obj, val, null);
//throw new Exception("FIXME: can't set properties");
}
else
{
fieldInf.SetValue(obj, val);
}
}
// MagicCast: given a packet/block/field name and a string, convert the string to a value of the appropriate type
private object MagicCast(string name, string block, string field, string value)
{
Type packetClass = openmvAssembly.GetType("OpenMetaverse.Packets." + name + "Packet");
if (packetClass == null) throw new Exception("Couldn't get class " + name + "Packet");
FieldInfo blockField = packetClass.GetField(block);
if (blockField == null) throw new Exception("Couldn't get " + name + "Packet." + block);
Type blockClass = blockField.FieldType;
if (blockClass.IsArray) blockClass = blockClass.GetElementType();
// Console.WriteLine("DEBUG: " + blockClass.Name);
FieldInfo fieldField = blockClass.GetField(field); PropertyInfo fieldProp = null;
Type fieldClass = null;
if (fieldField == null)
{
fieldProp = blockClass.GetProperty(field);
if (fieldProp == null) throw new Exception("Couldn't get " + name + "Packet." + block + "." + field);
fieldClass = fieldProp.PropertyType;
}
else
{
fieldClass = fieldField.FieldType;
}
try
{
if (fieldClass == typeof(byte))
{
return Convert.ToByte(value);
}
else if (fieldClass == typeof(ushort))
{
return Convert.ToUInt16(value);
}
else if (fieldClass == typeof(uint))
{
return Convert.ToUInt32(value);
}
else if (fieldClass == typeof(ulong))
{
return Convert.ToUInt64(value);
}
else if (fieldClass == typeof(sbyte))
{
return Convert.ToSByte(value);
}
else if (fieldClass == typeof(short))
{
return Convert.ToInt16(value);
}
else if (fieldClass == typeof(int))
{
return Convert.ToInt32(value);
}
else if (fieldClass == typeof(long))
{
return Convert.ToInt64(value);
}
else if (fieldClass == typeof(float))
{
return Convert.ToSingle(value);
}
else if (fieldClass == typeof(double))
{
return Convert.ToDouble(value);
}
else if (fieldClass == typeof(UUID))
{
return new UUID(value);
}
else if (fieldClass == typeof(bool))
{
if (value.ToLower() == "true")
return true;
else if (value.ToLower() == "false")
return false;
else
throw new Exception();
}
else if (fieldClass == typeof(byte[]))
{
return Utils.StringToBytes(value);
}
else if (fieldClass == typeof(Vector3))
{
Vector3 result;
if (Vector3.TryParse(value, out result))
return result;
else
throw new Exception();
}
else if (fieldClass == typeof(Vector3d))
{
Vector3d result;
if (Vector3d.TryParse(value, out result))
return result;
else
throw new Exception();
}
else if (fieldClass == typeof(Vector4))
{
Vector4 result;
if (Vector4.TryParse(value, out result))
return result;
else
throw new Exception();
}
else if (fieldClass == typeof(Quaternion))
{
Quaternion result;
if (Quaternion.TryParse(value, out result))
return result;
else
throw new Exception();
}
else
{
throw new Exception("unsupported field type " + fieldClass);
}
}
catch
{
throw new Exception("unable to interpret " + value + " as " + fieldClass);
}
}
// ModifyIn: modify an incoming packet
private Packet ModifyIn(Packet packet, IPEndPoint endPoint)
{
return Modify(packet, endPoint, Direction.Incoming);
}
// ModifyOut: modify an outgoing packet
private Packet ModifyOut(Packet packet, IPEndPoint endPoint)
{
return Modify(packet, endPoint, Direction.Outgoing);
}
// Modify: modify a packet
private Packet Modify(Packet packet, IPEndPoint endPoint, Direction direction)
{
if (modifiedPackets.ContainsKey(packet.Type))
{
try
{
Dictionary<BlockField, object> changes = modifiedPackets[packet.Type];
Type packetClass = packet.GetType();
foreach (KeyValuePair<BlockField, object> change in changes)
{
BlockField bf = change.Key;
FieldInfo blockField = packetClass.GetField(bf.block);
if (blockField.FieldType.IsArray) // We're modifying a variable block.
{
// Modify each block in the variable block identically.
// This is really simple, can probably be improved.
object[] blockArray = (object[])blockField.GetValue(packet);
foreach (object blockElement in blockArray)
{
MagicSetField(blockElement, bf.field, change.Value);
}
}
else
{
//Type blockClass = blockField.FieldType;
object blockObject = blockField.GetValue(packet);
MagicSetField(blockObject, bf.field, change.Value);
}
}
}
catch (Exception e)
{
Console.WriteLine("failed to modify " + packet.Type + ": " + e.Message);
Console.WriteLine(e.StackTrace);
}
}
return packet;
}
// LogAll: register logging delegates for all packets
private void LogAll()
{
// Type packetTypeType = typeof(PacketType);
// System.Reflection.MemberInfo[] packetTypes = packetTypeType.GetMembers();
//
// for (int i = 0; i < packetTypes.Length; i++)
// {
// if (packetTypes[i].MemberType == System.Reflection.MemberTypes.Field && packetTypes[i].DeclaringType == packetTypeType)
// {
// string name = packetTypes[i].Name;
// PacketType pType;
//
// try
// {
// pType = packetTypeFromName(name);
// }
// catch (Exception)
// {
// continue;
// }
//
// loggedCaps[pType] = null;
//
// proxy.AddDelegate(pType, Direction.Incoming, new PacketDelegate(LogPacketIn));
// proxy.AddDelegate(pType, Direction.Outgoing, new PacketDelegate(LogPacketOut));
// }
// }
}
private void LogWhitelist(string whitelistFile)
{
// try
// {
// string[] lines = File.ReadAllLines(whitelistFile);
// int count = 0;
//
// for (int i = 0; i < lines.Length; i++)
// {
// string line = lines[i].Trim();
// if (line.Length == 0)
// continue;
//
// PacketType pType;
//
// try
// {
// pType = packetTypeFromName(line);
// proxy.AddDelegate(pType, Direction.Incoming, new PacketDelegate(LogPacketIn));
// proxy.AddDelegate(pType, Direction.Outgoing, new PacketDelegate(LogPacketOut));
// ++count;
// }
// catch (ArgumentException)
// {
// Console.WriteLine("Bad packet name: " + line);
// }
// }
//
// Console.WriteLine(String.Format("Logging {0} packet types loaded from whitelist", count));
// }
// catch (Exception)
// {
// Console.WriteLine("Failed to load packet whitelist from " + whitelistFile);
// }
}
private void NoLogBlacklist(string blacklistFile)
{
// try
// {
// string[] lines = File.ReadAllLines(blacklistFile);
// int count = 0;
//
// for (int i = 0; i < lines.Length; i++)
// {
// string line = lines[i].Trim();
// if (line.Length == 0)
// continue;
//
// PacketType pType;
//
// try
// {
// pType = packetTypeFromName(line);
// string[] noLogStr = new string[] {"/-log", line};
// CmdNoLog(noLogStr);
// ++count;
// }
// catch (ArgumentException)
// {
// Console.WriteLine("Bad packet name: " + line);
// }
// }
//
// Console.WriteLine(String.Format("Not logging {0} packet types loaded from blacklist", count));
// }
// catch (Exception)
// {
// Console.WriteLine("Failed to load packet blacklist from " + blacklistFile);
// }
}
private void SetOutput(string outputFile)
{
try
{
output = new StreamWriter(outputFile, false);
Console.WriteLine("Logging packets to " + outputFile);
}
catch (Exception)
{
Console.WriteLine(String.Format("Failed to open {0} for logging", outputFile));
}
}
// NoLogAll: unregister logging delegates for all packets
private void NoLogAll()
{
// Type packetTypeType = typeof(PacketType);
// System.Reflection.MemberInfo[] packetTypes = packetTypeType.GetMembers();
//
// for (int i = 0; i < packetTypes.Length; i++)
// {
// if (packetTypes[i].MemberType == System.Reflection.MemberTypes.Field && packetTypes[i].DeclaringType == packetTypeType)
// {
// string name = packetTypes[i].Name;
// PacketType pType;
//
// try
// {
// pType = packetTypeFromName(name);
// }
// catch (Exception)
// {
// continue;
// }
//
// loggedCaps.Remove(pType);
//
// proxy.RemoveDelegate(pType, Direction.Incoming, new PacketDelegate(LogPacketIn));
// proxy.RemoveDelegate(pType, Direction.Outgoing, new PacketDelegate(LogPacketOut));
// }
// }
}
private bool LogCap(CapsRequest req, CapsStage stage)
{
if (stage == CapsStage.Request)
return false;
using (StringWriter sw = new StringWriter())
{
using (XmlTextWriter xtw = new XmlTextWriter(sw))
{
xtw.Formatting = Formatting.Indented;
OSDParser.SerializeLLSDXmlElement(xtw, req.Request);
}
Console.WriteLine("REQUEST {0}", req.Info.CapType);
Console.WriteLine(sw.ToString());
}
using (StringWriter sw = new StringWriter())
{
using (XmlTextWriter xtw = new XmlTextWriter(sw))
{
xtw.Formatting = Formatting.Indented;
OSDParser.SerializeLLSDXmlElement(xtw, req.Response);
}
Console.WriteLine("RESPONSE {0}", req.Info.CapType);
Console.WriteLine(sw.ToString());
}
Console.WriteLine("------------------------------");
// We don't want to stop any other delegates from executing.
return false;
}
// InterpretOptions: produce a string representing a packet's header options
private static string InterpretOptions(Header header)
{
return "["
+ (header.AppendedAcks ? "Ack" : " ")
+ " "
+ (header.Resent ? "Res" : " ")
+ " "
+ (header.Reliable ? "Rel" : " ")
+ " "
+ (header.Zerocoded ? "Zer" : " ")
+ "]"
;
}
}