AMPSharp/0000755000175000017500000000000011613431120012737 5ustar teratornteratornAMPSharp/command.cs0000644000175000017500000000746411613431120014717 0ustar teratornteratorn/* Copyright (c) 2008-2011 - Eric P. Mangold * Released under the terms of the MIT/X11 license - see LICENSE.txt */ using System; using System.Collections; using System.Collections.Generic; namespace AMP { internal class Parameter { internal string name; internal IAmpType type; public Parameter(string name, IAmpType ampType) { this.name = name; type = ampType; } } public class Command { // The return value of Responder must be in the form of Object since we allow you // to return a Msg (if responding synchronously) or a DeferredResponse (if you need to // respond asyncronously). public delegate Object Responder(Msg msg, Object state); private static readonly log4net.ILog log = log4net.LogManager.GetLogger("AMP"); public bool RequiresAnswer { get; set; } private string name; internal List arguments; internal List response; internal Dictionary errors = new Dictionary(); public string Name { get { return name; } } public Command() { arguments = new List(); response = new List(); // These are the errors that every Command will know about, since they // are part of the AMP protocol. errors.Add(typeof(Error.UnknownRemoteError), Error.Codes.UNKNOWN_ERROR_CODE); errors.Add(typeof(Error.UnhandledCommand), Error.Codes.UNHANDLED_ERROR_CODE); RequiresAnswer = true; // default. changeable. } internal Command(List args) : this() { arguments = args; } public Command(string name) : this() { this.name = name; } public void AddArgument(string keyName, IAmpType type) { arguments.Add(new Parameter(keyName, type)); } public void AddResponse(string keyName, IAmpType type) { response.Add(new Parameter(keyName, type)); } public void AddError(System.Type errorClass, byte[] errorCode) { errors.Add(errorClass, errorCode); } internal Msg_Raw ToRawMessage(Msg typed_msg, MsgType msgType) { List paramList = (msgType == MsgType.REQUEST ? arguments : response); var result = new Msg_Raw(); foreach (Parameter param in paramList) { if (!typed_msg.ContainsKey(param.name)) { log.ErrorFormat("Command {0}: paramter \"{1}\" not found in msg!", name, param.name); throw new Error.ParameterNotFound(); } result.Add(param.name, param.type.ToAmpBytes(typed_msg[param.name])); } return result; } internal Msg ToTypedMessage(Msg_Raw raw_msg, MsgType msgType) { List paramList = (msgType == MsgType.REQUEST ? arguments : response); var result = new Msg(); foreach (Parameter param in paramList) { if (!raw_msg.ContainsKey(param.name)) { log.ErrorFormat("Command {0}: paramter \"{1}\" not found in msg!", name, param.name); throw new Error.ParameterNotFound(); } result.Add(param.name, param.type.FromAmpBytes(raw_msg[param.name])); } return result; } } internal enum MsgType { REQUEST, RESPONSE } } AMPSharp/deferred.cs0000644000175000017500000000341711613431120015053 0ustar teratornteratorn/* Copyright (c) 2008-2011 - Eric P. Mangold * Released under the terms of the MIT/X11 license - see LICENSE.txt */ using System; namespace AMP { /// /// This is the most brain-dead, simple abstraction for a deferred result possible. /// Inside your AMP Command responder method you may return one of these, /// instead of returning an AMP.Msg. Then later, when the result you wish to return /// to the caller of your Command is ready, you will call respond(AMP.Msg) on the /// DeferredResponse you returned earlier. /// /// If an error occurred producing the result you should call respond() and pass it /// an Exception object, instead of an AMP.Msg. /// If the type of the Exception object has been registered on the Command that was /// invoked (with Command.AddError(...)) then the appropriate AMP error code will /// be sent on the wire, otherwise the generic error code will be used. /// public class DeferredResponse { public delegate void Delegate(Object AmpMessageOrException); private DeferredResponse.Delegate theCallback; private Object savedResult; internal void setCallback(DeferredResponse.Delegate callback) { if (savedResult != null) { // run callback synchronously callback(savedResult); } else { theCallback = callback; } } public void respond(Object result) { if (theCallback == null) { savedResult = result; } else { theCallback(result); } } } } AMPSharp/AMPSharp.nuspec0000644000175000017500000000144111613431120015571 0ustar teratornteratorn $id$ $version$ Eric P. Mangold Eric P. Mangold http://www.opensource.org/licenses/mit-license.php http://amp-protocol.net/AmpSharp false $description$ amp twisted asynchronous rpc AMPSharp/AMPSharp.csproj0000644000175000017500000000617111613431120015601 0ustar teratornteratorn Debug AnyCPU 9.0.21022 2.0 {E57E8D4F-56CD-4630-BDCC-97E44FAC4ABC} Library Properties AMPSharp AMPSharp v2.0 512 true full false bin\Debug\ TRACE;DEBUG prompt 4 Library AMPSharp true pdbonly true bin\Release\ TRACE prompt 4 Library true AMPSharp true False PreserveNewest AMPSharp/data.cs0000644000175000017500000000450111613431120014177 0ustar teratornteratorn/* Copyright (c) 2008-2011 - Eric P. Mangold * Released under the terms of the MIT/X11 license - see LICENSE.txt */ using System; using System.Collections.Generic; namespace AMP { // Container class for a TYPED AMP message. The actual values will be instances of the specific // types that have been defined for the fields. This class is what client code will use to handle // AMP requests and responses. public class Msg : Dictionary { } // Container class for a raw AMP "box"... this always maps field names to byte arrays. // We convert back and forth between this class and the TYPED version of an AMP box, Msg, // using the Command class which knows how to map fields to types. internal class Msg_Raw : Dictionary { internal static void printBytes(byte[] bytes) { foreach (byte b in bytes) { Console.Write(System.String.Format("\\x{0:x2}", b)); } Console.WriteLine(); } public override bool Equals(object obj) { if (!(obj is Msg_Raw)) { return false; } return this == (Msg_Raw)obj; } public static bool operator ==(Msg_Raw a, Msg_Raw b) { if (a.Count != b.Count) { return false; } foreach (string key in a.Keys) { if (!b.ContainsKey(key)) { return false; } if (a[key].Length != b[key].Length) return false; for (int i = 0; i < a[key].Length; i++) { if (a[key][i] != b[key][i]) return false; } } return true; } public static bool operator !=(Msg_Raw a, Msg_Raw b) { return !(a == b); } } internal struct Ask_Info { public int askKey; public Command cmd; public SimpleAsyncResult ar; public Ask_Info(int askKey, Command cmd, SimpleAsyncResult ar) { this.askKey = askKey; this.cmd = cmd; this.ar = ar; } } } AMPSharp/error.cs0000644000175000017500000000406111613431120014420 0ustar teratornteratorn/* Copyright (c) 2008-2011 - Eric P. Mangold * Released under the terms of the MIT/X11 license - see LICENSE.txt */ using System; using System.Net.Sockets; using System.Text; namespace AMP.Error { public class Codes { public static byte[] UNKNOWN_ERROR_CODE = Encoding.UTF8.GetBytes("UNKNOWN"); public static byte[] UNHANDLED_ERROR_CODE = Encoding.UTF8.GetBytes("UNHANDLED"); } public class StreamClosedException : SocketException { } public class AmpError : Exception { public AmpError(string description) : base(description) { } public AmpError() { } } public class ProtocolError : AmpError { } public class MessageEmptyError : AmpError { } public class InvalidKeySizeError : AmpError { } public class InvalidValueSizeError : AmpError { } public class ParameterNotFound : AmpError { } public class TypeDecodeError : AmpError { } public class RemoteAmpError : AmpError { public byte[] errorCode; public RemoteAmpError() { } public RemoteAmpError(byte[] errorCode, string description) : base(description) { this.errorCode = errorCode; } } public class UnknownRemoteError : RemoteAmpError { public UnknownRemoteError(byte[] errorCode, string description) : base(errorCode, description) { } } public class UnhandledCommand : RemoteAmpError { public UnhandledCommand(byte[] errorCode, string description) : base(errorCode, description) { } } public class ServerTimeout : AmpError { } public class BadResponderReturnValue : AmpError { public override string ToString() { return "BadResponderReturnValue: Your responder method must return an AMP.Msg or an AMP.DeferredResponse."; } } } AMPSharp/types.cs0000644000175000017500000004304111613431120014434 0ustar teratornteratorn/* Copyright (c) 2008-2011 - Eric P. Mangold * Released under the terms of the MIT/X11 license - see LICENSE.txt */ using System; using System.Text; using System.Collections; using System.Collections.Generic; namespace AMP { // TODO implement AMPList public interface IAmpType { byte[] ToAmpBytes(Object obj); Object FromAmpBytes(byte[] bytes); } namespace Type { /// /// Encoder/Decoder for .NET `string' type /// public class String : IAmpType { public byte[] ToAmpBytes(Object obj) { return Encoding.UTF8.GetBytes((string)obj); } public Object FromAmpBytes(byte[] bytes) { return Encoding.UTF8.GetString(bytes); } } /// /// Encoder/Decoder for .NET `byte[]' type /// public class Bytes : IAmpType { public byte[] ToAmpBytes(Object obj) { return (byte[])obj; } public Object FromAmpBytes(byte[] bytes) { return bytes; } } /// /// Encoder/Decoder for .NET `bool' type /// public class Bool : IAmpType { private byte[] TRUE = Encoding.UTF8.GetBytes("True"); private byte[] FALSE = Encoding.UTF8.GetBytes("False"); public byte[] ToAmpBytes(Object obj) { return (bool)obj ? TRUE : FALSE; } public Object FromAmpBytes(byte[] bytes) { if (BytesUtil.AreEqual(TRUE, bytes)) { return true; } if (BytesUtil.AreEqual(FALSE, bytes)) { return false; } /// XXX If this throws it blows up when passing this error across thread boundaries /// with SerializationException. test_AMP_error will demonstrate if you /// change this method to always throw throw new Error.TypeDecodeError(); } } /// /// Encoder/Decoder for .NET `int' type /// public class Int32 : IAmpType { public byte[] ToAmpBytes(Object obj) { return Encoding.UTF8.GetBytes(((int)obj).ToString()); } public Object FromAmpBytes(byte[] bytes) { return System.Int32.Parse(Encoding.UTF8.GetString(bytes)); } } /// /// Encoder/Decoder for .NET `uint' type /// public class UInt32 : IAmpType { public byte[] ToAmpBytes(Object obj) { return Encoding.UTF8.GetBytes(((uint)obj).ToString()); } public Object FromAmpBytes(byte[] bytes) { return System.UInt32.Parse(Encoding.UTF8.GetString(bytes)); } } /// /// Encoder/Decoder for .NET `long' type /// public class Int64 : IAmpType { public byte[] ToAmpBytes(Object obj) { return Encoding.UTF8.GetBytes(((long)obj).ToString()); } public Object FromAmpBytes(byte[] bytes) { return System.Int64.Parse(Encoding.UTF8.GetString(bytes)); } } /// /// Encoder/Decoder for .NET `ulong' type /// public class UInt64 : IAmpType { public byte[] ToAmpBytes(Object obj) { return Encoding.UTF8.GetBytes(((ulong)obj).ToString()); } public Object FromAmpBytes(byte[] bytes) { return System.UInt64.Parse(Encoding.UTF8.GetString(bytes)); } } /// /// Encoder/Decoder for .NET `decimal' type /// public class Decimal : IAmpType { public byte[] ToAmpBytes(Object obj) { return Encoding.UTF8.GetBytes(((decimal)obj).ToString()); } public Object FromAmpBytes(byte[] bytes) { string s = Encoding.UTF8.GetString(bytes); string[] parts = s.Split('E'); // might use Engineering notation if (parts.Length == 1) { return decimal.Parse(s); } else if (parts.Length == 2) { decimal d = decimal.Parse(parts[0]); int i = int.Parse(parts[1]); if (i != 0) { decimal factor = (i > 0) ? 10m : 0.1m; for (int j = Math.Abs(i); j > 0; j--) { d *= factor; } } return d; } else { throw new Error.TypeDecodeError(); } } } /// /// Encoder/Decoder for .NET `double' type /// public class Double : IAmpType { public static byte[] NAN = Encoding.UTF8.GetBytes("nan"); public static byte[] POSINF = Encoding.UTF8.GetBytes("inf"); public static byte[] NEGINF = Encoding.UTF8.GetBytes("-inf"); static bool arraysEqual(byte[] a, byte[] b) { if (a.Length != b.Length) return false; int i; for (i = 0; i < a.Length; i++) { if (a[i] != b[i]) return false; } return true; } public byte[] ToAmpBytes(Object obj) { double d = (double)obj; if (double.IsNaN(d)) { return NAN; } else if (double.IsPositiveInfinity(d)) { return POSINF; } else if (double.IsNegativeInfinity(d)) { return NEGINF; } else { return Encoding.UTF8.GetBytes(d.ToString()); } } public Object FromAmpBytes(byte[] bytes) { if (arraysEqual(bytes, NAN)) { return double.NaN; } else if (arraysEqual(bytes, POSINF)) { return double.PositiveInfinity; } else if (arraysEqual(bytes, NEGINF)) { return double.NegativeInfinity; } else { return double.Parse(Encoding.UTF8.GetString(bytes)); } } } /// /// Encoder/Decoder for .NET `DateTimeOffset' type, using the standard AMP `DateTime' format. /// If you wish to send .NET `DateTime' instances over the wire, convert them to a `DateTimeOffset' first. /// public class DateTimeOffset : IAmpType { public byte[] ToAmpBytes(Object obj) { var dt = (System.DateTimeOffset)obj; var fmt = new System.Globalization.DateTimeFormatInfo(); fmt.TimeSeparator = ":"; string s = dt.ToString("yyyy-MM-ddTHH:mm:ss.ffffffzzz", fmt); System.Diagnostics.Debug.Assert(s.Length == 32); return Encoding.UTF8.GetBytes(s); } public Object FromAmpBytes(byte[] bytes) { // "2012-01-23T12:34:56.054321+01:23" string s = Encoding.UTF8.GetString(bytes); System.Diagnostics.Debug.Assert(s.Length == 32); int year = int.Parse(s.Substring(0, 4)); int month = int.Parse(s.Substring(5, 2)); int day = int.Parse(s.Substring(8, 2)); int hour = int.Parse(s.Substring(11, 2)); int min = int.Parse(s.Substring(14, 2)); int sec = int.Parse(s.Substring(17, 2)); // The AMP spec specifies 6 decimal places for the fractional portion of the seconds. // But a DateTimeOffset can only hold miliseconds (3 decimal places), so we just // take the first 3 and ignore the rest. int miliseconds = int.Parse(s.Substring(20, 3)); string offsetDirection = s.Substring(26, 1); int offsetHour = int.Parse(s.Substring(27, 2)); int offsetMinute = int.Parse(s.Substring(30, 2)); TimeSpan offset; if (offsetDirection == "+") { offset = new TimeSpan(offsetHour, offsetMinute, 0); } else if (offsetDirection == "-") { offset = new TimeSpan(offsetHour, offsetMinute, 0).Negate(); } else { throw new AMP.Error.TypeDecodeError(); } return new System.DateTimeOffset(year, month, day, hour, min, sec, miliseconds, offset); } } /// /// Encoder/Decoder for .NET `DateTime' type. Encodes and decodes as UTC - timezone information is not retained. /// **DEPRECATED** - use Amp.Type.DateTimeOffset /// public class TimeArgument : IAmpType { public byte[] ToAmpBytes(Object obj) { DateTime dt = ((DateTime)obj).ToUniversalTime(); string s = dt.ToString("yyyy/M/d HH:mm:ss"); return Encoding.UTF8.GetBytes(s); } public Object FromAmpBytes(byte[] bytes) { string s = Encoding.UTF8.GetString(bytes); string[] parts = s.Split(' '); string[] date_parts = parts[0].Split('/'); string[] time_parts = parts[1].Split(':'); int year = int.Parse(date_parts[0]); int month = int.Parse(date_parts[1]); int day = int.Parse(date_parts[2]); int hour = int.Parse(time_parts[0]); int min = int.Parse(time_parts[1]); int sec = int.Parse(time_parts[2]); var dt = new DateTime(year, month, day, hour, min, sec, DateTimeKind.Utc); return dt; } } /// /// Encoder/Decoder for a List of one of the other IAmpType classes /// e.g. new ListOf(new AMP.Type.String()) /// public class ListOf : IAmpType { private IAmpType listType; public ListOf(IAmpType type) { listType = type; } public byte[] ToAmpBytes(Object obj) { var chunks = new List(); byte[] chunkLen; byte[] chunk; byte[] result; int totalLen = 0; if (BitConverter.IsLittleEndian) { foreach (Object item in (IEnumerable)obj) { chunk = listType.ToAmpBytes(item); totalLen += chunk.Length + 2; chunkLen = BitConverter.GetBytes((UInt16)chunk.Length); // XOR SWAP the bytes because we're on little-endian but the value // needs to be in big-endian order. chunkLen[0] ^= chunkLen[1]; chunkLen[1] ^= chunkLen[0]; chunkLen[0] ^= chunkLen[1]; chunks.Add(chunkLen); chunks.Add(chunk); } } else { foreach (Object item in (IEnumerable)obj) { chunk = listType.ToAmpBytes(item); totalLen += chunk.Length + 2; chunkLen = BitConverter.GetBytes((UInt16)chunk.Length); chunks.Add(chunkLen); chunks.Add(chunk); } } result = new byte[totalLen]; // blit each sub-array to the result array int i = 0; foreach (byte[] c in chunks) { c.CopyTo(result, i); i += c.Length; } return result; } public Object FromAmpBytes(byte[] bytes) { int idx = 0; byte[] chunkLen = new byte[2]; int chunkLenInt; var result = new List(); while (idx < bytes.Length) { // got 2 bytes remaining at least? if (bytes.Length - idx < 2) { throw new Error.TypeDecodeError(); } // read the length prefix chunkLen[0] = bytes[idx]; chunkLen[1] = bytes[idx + 1]; idx += 2; if (BitConverter.IsLittleEndian) { // on a little-endian machine, but value is in big-endian, so // XOR SWAP bytes before decoding integer chunkLen[0] ^= chunkLen[1]; chunkLen[1] ^= chunkLen[0]; chunkLen[0] ^= chunkLen[1]; } chunkLenInt = BitConverter.ToUInt16(chunkLen, 0); // We *should* have at least chunkLenInt bytes remaining in the array... if (bytes.Length - idx < chunkLenInt) { throw new Error.TypeDecodeError(); } var tmp = new byte[chunkLenInt]; Array.Copy(bytes, idx, tmp, 0, chunkLenInt); idx += chunkLenInt; result.Add(listType.FromAmpBytes(tmp)); } return result; } } /// /// Encoder/Decoder for a List of AMP messages. E.g. /// /// var aList = new AMP.Type.AmpList(); /// aList.AddParameter("full_name", new AMP.Type.String()); /// aList.AddParameter("age", new AMP.Type.Int32()); /// /// Then each item of the List that you send or receive using aList /// will be an AMP.Msg containing a "full_name" and an "age" key /// with values of the appropriate type (though casted to Object for /// storage in an AMP.Msg) /// public class AmpList : IAmpType { // A customized Protocol for parsing and accumulating AMP boxes private class SubParser : Protocol { private List msgs; public SubParser(List msgs) : base() { this.msgs = msgs; } internal override void ProcessFullMessage(Msg_Raw raw_msg) { msgs.Add(raw_msg); } } private Command cmd; public AmpList() { // The Command class wasn't really meant to help us parse nested AMP messages // but it's flexible enough to do so without being refactored, so who am I to argue? cmd = new Command(); } public void AddParameter(string keyName, IAmpType type) { cmd.AddArgument(keyName, type); } public byte[] ToAmpBytes(Object obj) { var chunks = new List(); byte[] chunk; Msg_Raw raw_msg; int totalLen = 0; var msgs = (IEnumerable)obj; foreach (Msg msg in msgs) { raw_msg = cmd.ToRawMessage(msg, MsgType.REQUEST); chunk = Protocol.BuildAMPWireCommand(raw_msg); totalLen += chunk.Length; chunks.Add(chunk); } var result = new byte[totalLen]; // blit each sub-array to the result array int i = 0; foreach (byte[] c in chunks) { c.CopyTo(result, i); i += c.Length; } return result; } public Object FromAmpBytes(byte[] bytes) { var msgs = new List(); var subParser = new SubParser(msgs); var result = new List(); subParser.DataReceived(bytes); Msg typedMsg; foreach (Msg_Raw raw in msgs) { typedMsg = cmd.ToTypedMessage(raw, MsgType.REQUEST); result.Add(typedMsg); } return result; } } } } AMPSharp/protocol.cs0000644000175000017500000007542211613431120015141 0ustar teratornteratorn/* Copyright (c) 2008-2011 - Eric P. Mangold * Released under the terms of the MIT/X11 license - see LICENSE.txt */ /* TODO: * - Lose the TCP connection when fatal errors occur... follow the semantics of Twisted's implementation. * we currently lose the connection in certain cases, but not others. It's all very ambiguous. */ using System; using System.Text; using System.Collections; using System.Collections.Generic; using System.Threading; using System.Runtime.CompilerServices; using System.Windows.Forms; using System.Net.Sockets; using Timer=System.Threading.Timer; // Configure logging for this assembly using the AMPSharp.dll.log4net file [assembly: log4net.Config.XmlConfigurator(ConfigFileExtension="log4net", Watch=true)] namespace AMP { public interface IAmpProtocol { Msg CallRemote(Command cmd, Msg msg); IAsyncResult BeginCallRemote(Command cmd, Msg msg, AsyncCallback callback, Object cbState); IAsyncResult BeginCallRemote(Command cmd, Msg msg, AsyncCallback callback); Msg EndCallRemote(IAsyncResult ar); void RegisterResponder(Command cmd, Command.Responder responder, Object callbackState); void RegisterResponder(Command cmd, Command.Responder responder); void RegisterResponder(Command cmd, Command.Responder responder, Object callbackState, SynchronizationContext callbackContext); void RegisterResponder(Command cmd, Command.Responder responder, SynchronizationContext callbackContext); void RegisterResponder(Command cmd, Command.Responder responder, Object callbackState, Control callbackControl); void RegisterResponder(Command cmd, Command.Responder responder, Control callbackControl); void DataReceived(byte[] bytes, int len); void ConnectionLost(Exception reason); void StartReading(); } /// /// AMP protocol-parsing and state-keeping class. /// Instantiate it with a System.IO.Stream (such as System.IO.NetworkStream) /// that is already be connected to the remote peer. /// Be sure to call StartReading() after you've registered any responders. /// Provides CallRemote() and BeginCallRemote(), for making remote AMP calls. /// Provides RegisterResponder() for registering delegates that will handle /// the AMP commands that we support on this side of the connection. Responders /// may be either synchrounous or asynchronous. /// public class Protocol: IAmpProtocol { private static readonly log4net.ILog log = log4net.LogManager.GetLogger("AMP"); private enum states { KEY_LEN_READ, KEY_DATA_READ, VAL_LEN_READ, VAL_DATA_READ, FULL_MESSAGE_FINISHED }; private states state; private ArrayList key_len; private ArrayList key_data; private ArrayList val_len; private int val_len_int; private byte[] val_data; internal Msg_Raw msg_raw; private int lastAskKey; private Dictionary askInfoDict; private readonly Dictionary registeredCommands = new Dictionary(); private IAMPTransport transport; private System.IO.Stream stream; public System.IO.Stream Stream { get { return stream; } } public Protocol() { state = states.KEY_LEN_READ; key_len = new ArrayList(); key_data = new ArrayList(); val_len = new ArrayList(); val_data = new byte[0]; msg_raw = new Msg_Raw(); askInfoDict = new Dictionary(); } public Protocol(System.IO.Stream stream) : this() { this.stream = stream; transport = new StreamTransport(stream); transport.RegisterProtocol(this); } public void StartReading() { transport.StartReading(); } // Hold a reference to an Responder, the Command for this responder and the responder state object private struct responderStruct { public Command.Responder responder; public Command command; public Object state; // may be null public SynchronizationContext callbackContext; // may be null public Control callbackControl; // may be null } public class CommandAlreadyRegistered: Exception { }; public void RegisterResponder(Command cmd, Command.Responder responder) { _RegisterResponder(cmd, responder, null, null, null); } public void RegisterResponder(Command cmd, Command.Responder responder, object callbackState, SynchronizationContext callbackContext) { _RegisterResponder(cmd, responder, callbackState, callbackContext, null); } public void RegisterResponder(Command cmd, Command.Responder responder, SynchronizationContext callbackContext) { _RegisterResponder(cmd, responder, null, callbackContext, null); } public void RegisterResponder(Command cmd, Command.Responder responder, object callbackState, Control callbackControl) { _RegisterResponder(cmd, responder, callbackState, null, callbackControl); } public void RegisterResponder(Command cmd, Command.Responder responder, Control callbackControl) { _RegisterResponder(cmd, responder, null, null, callbackControl); } public void RegisterResponder(Command cmd, Command.Responder responder, Object callbackState) { _RegisterResponder(cmd, responder, callbackState, null, null); } private void _RegisterResponder(Command cmd, Command.Responder responder, Object callbackState, SynchronizationContext callbackContext, Control callbackControl) { if (registeredCommands.ContainsKey(cmd.Name)) { throw new CommandAlreadyRegistered(); } var respRef = new responderStruct {responder = responder, command = cmd, state = callbackState, callbackContext = callbackContext, callbackControl = callbackControl}; registeredCommands.Add(cmd.Name, respRef); } public virtual void ConnectionLost(Exception reason) { foreach (Ask_Info info in askInfoDict.Values) { info.ar.error = reason; try { info.ar.doCallback(); } catch (Exception e) { log.Error("Exception in user-defined AMP callback while failing outstanding requests:", e); } } // dont bother to do this if we are gonna be closing the stream associated with // this protocol.. askInfoDict.Clear(); } public void DataReceived(byte[] buf) { DataReceived(buf, buf.Length); } [MethodImpl(MethodImplOptions.Synchronized)] public void DataReceived(byte[] buf, int len) { int idx = 0; while (idx < len) { int bytes_left = len - idx; int bytes_needed; switch (state) { case states.KEY_LEN_READ: if (key_len.Count == 0 && buf[idx] != 0) { // The 1st byte of the key length must always be \x00 throw new Error.ProtocolError(); } key_len.Add(buf[idx]); idx++; if (key_len.Count == 2) { // got the 2 bytes for a key. transition state. // if these 2 bytes are both null, then that should signal the // end of a full AMP message. if ((byte)key_len[0] == 0 && (byte)key_len[1] == 0) { if (msg_raw.Count == 0) { // we haven't received any key/values yet so there shouldn't be a // double-NULL here throw new Error.ProtocolError(); } // We got the empty-key terminator on a full, valid message, yay! key_len.Clear(); // forget the 2 null bytes ProcessFullMessage(msg_raw); state = states.FULL_MESSAGE_FINISHED; // transition } else { state = states.KEY_DATA_READ; // transition } } // else state stays the same since we've only read 1 byte of the 2-byte key length break; case states.KEY_DATA_READ: // the key length prefix is restricted to 1 byte (8 bits) on the wire, even though it is // represented by two bytes. Thus, because it is big-endian, the first byte will always be zero // and the 2nd byte is the only one we are interested in. int key_len_int = (byte)key_len[1]; bytes_needed = key_len_int - key_data.Count; byte[] tmp; if (bytes_left < bytes_needed) { tmp = new byte[bytes_left]; Array.Copy(buf, idx, tmp, 0, bytes_left); key_data.AddRange(tmp); idx += bytes_left; // state stays the same } else if (bytes_left >= bytes_needed) { tmp = new byte[bytes_needed]; Array.Copy(buf, idx, tmp, 0, bytes_needed); key_data.AddRange(tmp); idx += bytes_needed; state = states.VAL_LEN_READ; // transition } break; case states.VAL_LEN_READ: val_len.Add(buf[idx]); idx++; if (val_len.Count == 2) { // TODO use a XOR SWAP instead of a separate tmp_bytes array // got the 2 bytes for a value length. var tmp_bytes = new byte[2]; if (BitConverter.IsLittleEndian) { // reverse bytes before casting because this is a little-endian computer, but the bytes // we read are in network byte order (big-endian) tmp_bytes[0] = (byte)val_len[1]; tmp_bytes[1] = (byte)val_len[0]; } else { tmp_bytes[0] = (byte)val_len[0]; tmp_bytes[1] = (byte)val_len[1]; } val_len_int = BitConverter.ToUInt16(tmp_bytes, 0); state = states.VAL_DATA_READ; // transition } // else the state stays the same since we've only read 1 byte of the 2-byte value length break; case states.VAL_DATA_READ: bytes_needed = val_len_int - val_data.Length; if (bytes_left < bytes_needed) { var tmp_bytes = new byte[val_data.Length+bytes_left]; Array.Copy(val_data, tmp_bytes, val_data.Length); Array.Copy(buf, idx, tmp_bytes, val_data.Length, bytes_left); val_data = tmp_bytes; idx += bytes_left; // state stays the same } else if (bytes_left >= bytes_needed) { var tmp_bytes = new byte[val_data.Length+bytes_needed]; Array.Copy(val_data, tmp_bytes, val_data.Length); Array.Copy(buf, idx, tmp_bytes, val_data.Length, bytes_needed); val_data = tmp_bytes; idx += bytes_needed; state = states.KEY_LEN_READ; // transition ProcessCurrentKeyValuePair(); } break; case states.FULL_MESSAGE_FINISHED: // clear out the msg_raw map so we can fill it w/ the next one msg_raw = new Msg_Raw(); state = states.KEY_LEN_READ; break; } } } internal virtual void ProcessFullMessage(Msg_Raw raw_msg) { if ( raw_msg.ContainsKey("_command") ) { string command = Encoding.UTF8.GetString(raw_msg["_command"]); ProcessCommand(command, raw_msg); } else if ( raw_msg.ContainsKey("_answer") ) { ProcessAnswer(raw_msg); } else if ( raw_msg.ContainsKey("_error") ) { ProcessError(raw_msg); } else { log.Error("Got AMP message w/o a _command, _answer, or _error key!"); return; } } private void ProcessCommand(string cmdName, Msg_Raw raw_msg) { log.Debug("Processing AMP command: " + cmdName); if (registeredCommands.ContainsKey(cmdName)) { // YES, THIS COMMAND HAS A REGISTERED RESPONDER. responderStruct respRef = registeredCommands[cmdName]; Msg requestMsg = respRef.command.ToTypedMessage(raw_msg, MsgType.REQUEST); try { Object returnVal = null; if (respRef.callbackContext != null) { respRef.callbackContext.Send(delegate { returnVal = respRef.responder(requestMsg, respRef.state); }, null); } else if (respRef.callbackControl != null) { returnVal = respRef.callbackControl.Invoke(respRef.responder, requestMsg, respRef.state); } else { returnVal = respRef.responder(requestMsg, respRef.state); } if (!raw_msg.ContainsKey("_ask")) return; var responseMsg = returnVal as Msg; if (responseMsg != null) { SendAMPResponse(responseMsg, respRef.command, raw_msg["_ask"]); } else { var deferred = returnVal as DeferredResponse; if (deferred != null) { byte[] ask_key = raw_msg["_ask"]; deferred.setCallback(response => ProcessDeferredAMPResponse(response, respRef.command, ask_key)); } else { throw new Error.BadResponderReturnValue(); } } } catch (Exception e) { // TODO FIXME - SendAMPError does error logging, but we should still // log the error even if no _ask key. if (raw_msg.ContainsKey("_ask")) { SendAMPError(e, respRef.command, raw_msg["_ask"]); } } } else { // NO, THERE ISN'T ANY REGISTERED RESPONDER FOR THIS COMMAND log.Error("No such AMP command: " + cmdName); if (raw_msg.ContainsKey("_ask")) { var response = new Msg_Raw { {"_error", raw_msg["_ask"]}, {"_error_code", Error.Codes.UNHANDLED_ERROR_CODE}, { "_error_description", Encoding.UTF8.GetBytes(System.String.Format("Unhandled Command: {0}", cmdName)) } }; SendMessage(response); } return; } } private void SendAMPError(Exception e, Command cmd, byte[] ask_key) { var response = new Msg_Raw {{"_error", ask_key}}; byte[] error_code; byte[] descr; if (cmd.errors.ContainsKey(e.GetType())) { error_code = cmd.errors[e.GetType()]; descr = Encoding.UTF8.GetBytes(e.ToString()); } else { log.Error("Exception occured in command handler that was not defined on Command object:"); log.Error(e.ToString()); error_code = Error.Codes.UNKNOWN_ERROR_CODE; descr = Encoding.UTF8.GetBytes("Unknown Error"); } response.Add("_error_code", error_code); response.Add("_error_description", descr); SendMessage(response); } private void SendAMPResponse(Msg msg, Command cmd, byte[] answer_key) { // XXX TODO - should log a helpful error message if this raises InvalidCastException // because it's very easy for the user to have put a wrong type in to the Msg instance. Msg_Raw responseMsg = cmd.ToRawMessage(msg, MsgType.RESPONSE); responseMsg["_answer"] = answer_key; SendMessage(responseMsg); } internal virtual void SendMessage(Msg_Raw raw_msg) { transport.Write(BuildAMPWireCommand(raw_msg)); } private void ProcessDeferredAMPResponse(Object response, Command cmd, byte[] answer_key) { try { try { var msg = (Msg)response; SendAMPResponse(msg, cmd, answer_key); } catch (InvalidCastException) { // assume it's an exception var err = (Exception) response; SendAMPError(err, cmd, answer_key); } } catch (Exception e) { SendAMPError(e, cmd, answer_key); } } private void ProcessAnswer(Msg_Raw raw_msg) { int answerKey = Int32.Parse(Encoding.UTF8.GetString(raw_msg["_answer"])); log.Debug("Processing AMP answer for request " + answerKey); if ( ! askInfoDict.ContainsKey(answerKey) ) { log.ErrorFormat("Got AMP answer message w/o an unknown key {0}! (Perhaps we already timed out?)", answerKey); return; } Ask_Info askInfo = askInfoDict[answerKey]; askInfo.ar.result = askInfo.cmd.ToTypedMessage(raw_msg, MsgType.RESPONSE); try { //askInfo.ar._callback(askInfo.ar); askInfo.ar.doCallback(); } catch (Exception e) { // XXX TODO should this catch exceptions? We aren't really writing a *framework*, a la Twisted, here. So since we're just // writing a *library*, maybe it should be up to the client code to deal with exceptions. // NOTE: We can't catch errors anyway in the case that doCallback posts the callback on the SyncronizationContext log.Error("Exception raised in user-defined AMP result callback:", e); } finally { askInfoDict.Remove(answerKey); } } private void ProcessError(Msg_Raw raw_msg) { // According to the AMP spec an error packet should contain all 3 of these keys int msgKey = Int32.Parse(Encoding.UTF8.GetString(raw_msg["_error"])); byte[] errorCode = raw_msg["_error_code"]; string description = Encoding.UTF8.GetString(raw_msg["_error_description"]); log.DebugFormat("Procesing AMP error for request {0} code: {1} descr: {2}", msgKey, BytesUtil.ToHexStr(errorCode), description); if ( ! askInfoDict.ContainsKey(msgKey) ) { log.ErrorFormat("Got AMP error message w/ an unknown key {0}! (Perhaps we already timed out?)", msgKey); return; } Ask_Info askInfo = askInfoDict[msgKey]; System.Type excType = typeof(Error.UnknownRemoteError); // default if we don't find the _error_code below foreach (var kvp in askInfo.cmd.errors) { if (BytesUtil.AreEqual(kvp.Value, errorCode)) { excType = kvp.Key; } } Exception exc = null; try { // If excType is a subclass of RemoteAmpError then we should find a // matching constructor here. exc = (Exception)Activator.CreateInstance(excType, errorCode, description); } catch (MissingMethodException) { // Otherwise any type of Exception should at least have a void constructor exc = (Exception)Activator.CreateInstance(excType); } askInfo.ar.error = exc; try { askInfo.ar.doCallback(); } catch (Exception e) { // XXX TODO should this catch exceptions? We aren't really writing a *framework*, a la Twisted, here. So since we're just // writing a *library*, maybe it should be up to the client code to deal with exceptions. log.Error("AMP error returned, and exception raised in user-defined callback:", e); } finally { askInfoDict.Remove(msgKey); } } private void ProcessCurrentKeyValuePair() { var key_data_array = (byte[])key_data.ToArray(typeof(byte)); msg_raw.Add(Encoding.UTF8.GetString(key_data_array), val_data); key_len.Clear(); key_data.Clear(); val_len.Clear(); val_data = new byte[0]; val_len_int = 0; } // TODO Benchmark this method and experiment with different algorithms internal static byte[] BuildAMPWireCommand(Msg_Raw msg) { var tmp_result = new ArrayList(); byte[] value; if (msg.Count == 0) { throw new Error.MessageEmptyError(); } if (BitConverter.IsLittleEndian) { foreach (string key in msg.Keys) { value = msg[key]; if (key.Length == 0 || key.Length > 255) throw new Error.InvalidKeySizeError(); if (value.Length > 65535) throw new Error.InvalidValueSizeError(); tmp_result.Add((byte)0x00); // first byte of key length // use the first byte of the 2-byte sequence, and store that as the 2nd byte in the tmp_result // this is because the host computer is little-endien, but we're encoding wire values in network byte order (big-endien) tmp_result.Add(BitConverter.GetBytes((UInt16)key.Length)[0]); tmp_result.AddRange(Encoding.UTF8.GetBytes(key)); byte[] tmp = BitConverter.GetBytes((UInt16)value.Length); tmp_result.Add(tmp[1]); tmp_result.Add(tmp[0]); tmp_result.AddRange(value); } } else { foreach (string key in msg.Keys) { value = msg[key]; if (key.Length == 0 || key.Length > 255) throw new Error.InvalidKeySizeError(); if (value.Length > 65535) throw new Error.InvalidValueSizeError(); tmp_result.Add((byte)0x00); // first byte of key length tmp_result.Add(BitConverter.GetBytes((UInt16)key.Length)[1]); tmp_result.AddRange(Encoding.UTF8.GetBytes(key)); byte[] tmp = BitConverter.GetBytes((UInt16)value.Length); tmp_result.Add(tmp[0]); tmp_result.Add(tmp[1]); tmp_result.AddRange(value); } } // terminated w/ an empty key tmp_result.Add( (byte)0x00 ); tmp_result.Add( (byte)0x00 ); //byte[] result = {1}; //return (byte[])tmp_result; //return tmp_result.ToArr var result = (byte[])tmp_result.ToArray(typeof(byte)); return result; } public Msg CallRemote(Command cmd, Msg msg) { IAsyncResult ar = BeginCallRemote(cmd, msg, null, null); ar.AsyncWaitHandle.WaitOne(); return EndCallRemote(ar); } public IAsyncResult BeginCallRemote(Command cmd, Msg msg, AsyncCallback callback) { return BeginCallRemote(cmd, msg, callback, null); } [MethodImpl(MethodImplOptions.Synchronized)] public IAsyncResult BeginCallRemote(Command cmd, Msg msg, AsyncCallback callback, Object cbState) { var ar = new SimpleAsyncResult {callback = callback, asyncState = cbState}; Msg_Raw raw_msg = cmd.ToRawMessage(msg, MsgType.REQUEST); raw_msg.Add("_command", Encoding.UTF8.GetBytes(cmd.Name)); if (cmd.RequiresAnswer) { int currentKey = ++lastAskKey; askInfoDict.Add(currentKey, new Ask_Info(currentKey, cmd, ar)); raw_msg.Add("_ask", Encoding.UTF8.GetBytes(currentKey.ToString())); log.DebugFormat("Calling remote AMP Command: {0} with key {1}", cmd.Name, currentKey); SendMessage(raw_msg); } else { log.DebugFormat("Calling remote AMP Command: {0}", cmd.Name); SendMessage(raw_msg); ar.doCallback(); } return ar; } public Msg EndCallRemote(IAsyncResult ar) { SimpleAsyncResult result = ThrowIfError(ar); // No error, we must have a result from the server available, then // If RequiresAnswer was false on the Command that was called, then // result should simply be null here. return (Msg)result.result; } private static SimpleAsyncResult ThrowIfError(IAsyncResult ar) { var result = (SimpleAsyncResult)ar; if (result.error != null) { throw result.error; } return result; } } public class SimpleAsyncResult : IAsyncResult { internal object result; internal Exception error; // TODO make priviate/internal (needed by tests, though) public AsyncCallback callback; internal SynchronizationContext savedContext; public SimpleAsyncResult() { savedContext = SynchronizationContext.Current; waitHandle = new ManualResetEvent(false); } public void ThrowIfError() { if (error != null) { throw error; } } // TODO make priviate (needed by tests, though) public void doCallback() { isCompleted = true; try { if (callback != null) { if (savedContext != null) { // Post the callback to the context // (Callback will happen asynchronously in the saved context's thread) // XXX TODO shouldn't we pass _asyncState to the delegate here, and also // pass it to the callback in the case where we run the callback synchronously (below) ? // maybe not, because AsyncCallback is only defined to take a single argument: IAsyncResult savedContext.Post(delegate { callback(this); }, null); } else { // Just callback synchronously callback(this); } } } finally { waitHandle.Set(); } } // BEGIN IAsyncResult properties // TODO these really should be defined 'internal' public bool isCompleted; public Object asyncState; public bool completedSynchronously; public ManualResetEvent waitHandle; public Object AsyncState { get { return asyncState; } } public WaitHandle AsyncWaitHandle { get { return waitHandle; } } public bool IsCompleted { get { return isCompleted; } } public bool CompletedSynchronously { get { return completedSynchronously; } } // END IAsyncResult properties } } AMPSharp/ampsharp.snk0000644000175000017500000000112411613431120015265 0ustar teratornteratorn$RSA2w5>ni9.gfKqR\C@n)PK&T^d"0d XaKbD{{&/k-ol!@d9ދ":y[JV0:d9&-I2zgh>>;ARj Ao#Lwר;uP?֔]h}tπI4#!k>NajMՃ}h݂ZA]{3?$4a5p@0qpcùC2&=qBuҗ4"i:$f0Dc15?(S-1rx,z6f_]_dF*2JCsV%˨KzRbTa l[xEdI"Rf?LdFeP7LJC_T:ɣ vLu/sF`;k- u֪,0K{>w7;i:(P b4{?y~{aodѯn_W >wb[AMPSharp/util.cs0000644000175000017500000000221611613431120014244 0ustar teratornteratorn/* Copyright (c) 2008-2011 - Eric P. Mangold * Released under the terms of the MIT/X11 license - see LICENSE.txt */ using System; using System.Text; namespace AMP { internal class BytesUtil { public static bool AreEqual(byte[] x, byte[] y) { if (x.Length != y.Length) return false; for (int i = 0; i < x.Length; i++) { if (x[i] != y[i]) return false; } return true; } public static string ToHexStr(byte[] bytes) { var sb = new StringBuilder(); foreach (byte b in bytes) { if (b < 32 || b > 126) { sb.AppendFormat("\\x{0:x2}", b); } else { if (b == '\\') { sb.Append("\\\\"); } else { sb.Append((char)b); } } } return sb.ToString(); } } } AMPSharp/transport.cs0000644000175000017500000000745311613431120015333 0ustar teratornteratorn/* Copyright (c) 2008-2011 - Eric P. Mangold * Released under the terms of the MIT/X11 license - see LICENSE.txt */ using System; namespace AMP { internal interface IAMPTransport { void RegisterProtocol(IAmpProtocol proto); void StartReading(); void Write(byte[] bytes); } internal class StreamTransport : IAMPTransport { private static readonly log4net.ILog log = log4net.LogManager.GetLogger("AMP"); private const int readBufSize = 32768; private readonly byte[] readBuf = new byte[readBufSize]; private System.IO.Stream stream; private IAmpProtocol proto; public StreamTransport(System.IO.Stream stream) { this.stream = stream; } public System.IO.Stream Stream { get { return stream; } } public void StartReading() { stream.BeginRead(readBuf, 0, readBuf.Length, new AsyncCallback(AsyncDataReceived), stream); } public void Write(byte[] bytes) { try { stream.BeginWrite(bytes, 0, bytes.Length, new AsyncCallback(AsyncDataSent), stream); } catch (Exception e) { HandleFatalNetworkError(e); } } private void AsyncDataSent(IAsyncResult ar) { var s = (System.IO.Stream)ar.AsyncState; try { s.EndWrite(ar); } catch (Exception e) { log.Error("Error sending to Server:", e); HandleFatalNetworkError(e); return; } } public void RegisterProtocol(IAmpProtocol proto) { this.proto = proto; } private void AsyncDataReceived(IAsyncResult ar) { // XXX TODO I'm not 100% sure but it might be possible that this callback gets called // even after this stream has been discarded. Which means we will // have failed any outstanding callRemote's that these bytes might be related too.. // We should ignore these bytes in that case. // // UPDATE: Yes, it's definately possible. int bytesRead; var s = (System.IO.Stream)ar.AsyncState; try { bytesRead = s.EndRead(ar); } catch (System.IO.IOException e) { log.Error("Connection to peer closed uncleanly."); HandleFatalNetworkError(e); return; } catch (Exception e) { log.Error("Error reading from socket:", e); HandleFatalNetworkError(e); return; } if (bytesRead == 0) { log.Debug("Connection to peer closed cleanly."); proto.ConnectionLost(new Error.StreamClosedException()); } else { proto.DataReceived(readBuf, bytesRead); try { s.BeginRead(readBuf, 0, readBuf.Length, new AsyncCallback(AsyncDataReceived), s); } catch (Exception e) { log.Error("Error calling BeginRead:", e); HandleFatalNetworkError(e); } } } private void HandleFatalNetworkError(Exception err) { // XXX TODO we need to be double-plus sure that we // don't call ConnectionLost more than once. proto.ConnectionLost(err); } } } AMPSharp/AMPSharp.dll.log4net0000644000175000017500000000110411613431120016416 0ustar teratornteratorn AMPSharp/Properties/0000755000175000017500000000000011613431120015073 5ustar teratornteratornAMPSharp/Properties/AssemblyInfo.cs0000644000175000017500000000505011613431120020015 0ustar teratornteratorn/* Copyright (c) 2008-2011 - Eric P. Mangold * Released under the terms of the MIT/X11 license - see LICENSE.txt */ using System.Reflection; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("AMPSharp")] [assembly: AssemblyDescription("A client/server implementation of AMP, the Asynchronous Messaging Protocol, for .NET. ")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("AMPSharp")] [assembly: AssemblyCopyright("Copyright © 2008-2011, Eric P. Mangold ")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("ce8a23dd-62f6-4234-9028-262cbb0729c3")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("2.0.4")] [assembly: AssemblyFileVersion("2.0.4")] [assembly: AssemblyKeyFileAttribute("ampsharp.snk")] // Make `internal' types visibile to the Tests assembly [assembly: InternalsVisibleTo("Tests, PublicKey=002400000480000094000000060200000024000" + "05253413100040000110000002b7425c119b7fc" + "48b02d2339b40e844ac04275f2c64d70600a218" + "04671ec589cb58033d67632f96a66b2b4e2d2b4" + "ee32d9ef443fcef3770db0da85836746b2f1f0f" + "fff5438846af2ecd7d69bbabb247731083c160d" + "7ea9344562764d96ee1d759ba560571e6d028c1" + "aef494d3c0e4e7c3dc2789a8393f5824d0fa4ec" + "82ebcfca")] AMPSharp.sln0000644000175000017500000000546311613431120013465 0ustar teratornteratorn Microsoft Visual Studio Solution File, Format Version 10.00 # Visual Studio 2008 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AMPSharp", "AMPSharp\AMPSharp.csproj", "{E57E8D4F-56CD-4630-BDCC-97E44FAC4ABC}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MathClient", "doc\examples\MathClient\MathClient.csproj", "{FF922190-D1A5-4176-A994-CBAFC2B2F8CD}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MathServer", "doc\examples\MathServer\MathServer.csproj", "{70AFB378-CCE4-40B3-8602-5B46A4A7F098}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{EF0B4953-2C57-42D5-987E-DAC8F0710365}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "Tests\Benchmarks\Benchmarks.csproj", "{5ACA924D-7792-48A6-8BD4-FC28D0E81318}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {E57E8D4F-56CD-4630-BDCC-97E44FAC4ABC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E57E8D4F-56CD-4630-BDCC-97E44FAC4ABC}.Debug|Any CPU.Build.0 = Debug|Any CPU {E57E8D4F-56CD-4630-BDCC-97E44FAC4ABC}.Release|Any CPU.ActiveCfg = Release|Any CPU {E57E8D4F-56CD-4630-BDCC-97E44FAC4ABC}.Release|Any CPU.Build.0 = Release|Any CPU {FF922190-D1A5-4176-A994-CBAFC2B2F8CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FF922190-D1A5-4176-A994-CBAFC2B2F8CD}.Debug|Any CPU.Build.0 = Debug|Any CPU {FF922190-D1A5-4176-A994-CBAFC2B2F8CD}.Release|Any CPU.ActiveCfg = Release|Any CPU {FF922190-D1A5-4176-A994-CBAFC2B2F8CD}.Release|Any CPU.Build.0 = Release|Any CPU {70AFB378-CCE4-40B3-8602-5B46A4A7F098}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {70AFB378-CCE4-40B3-8602-5B46A4A7F098}.Debug|Any CPU.Build.0 = Debug|Any CPU {70AFB378-CCE4-40B3-8602-5B46A4A7F098}.Release|Any CPU.ActiveCfg = Release|Any CPU {70AFB378-CCE4-40B3-8602-5B46A4A7F098}.Release|Any CPU.Build.0 = Release|Any CPU {EF0B4953-2C57-42D5-987E-DAC8F0710365}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EF0B4953-2C57-42D5-987E-DAC8F0710365}.Debug|Any CPU.Build.0 = Debug|Any CPU {EF0B4953-2C57-42D5-987E-DAC8F0710365}.Release|Any CPU.ActiveCfg = Release|Any CPU {EF0B4953-2C57-42D5-987E-DAC8F0710365}.Release|Any CPU.Build.0 = Release|Any CPU {5ACA924D-7792-48A6-8BD4-FC28D0E81318}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5ACA924D-7792-48A6-8BD4-FC28D0E81318}.Debug|Any CPU.Build.0 = Debug|Any CPU {5ACA924D-7792-48A6-8BD4-FC28D0E81318}.Release|Any CPU.ActiveCfg = Release|Any CPU {5ACA924D-7792-48A6-8BD4-FC28D0E81318}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal doc/0000755000175000017500000000000011613431120012071 5ustar teratornteratorndoc/examples/0000755000175000017500000000000011613431120013707 5ustar teratornteratorndoc/examples/MathServer/0000755000175000017500000000000011613431120015767 5ustar teratornteratorndoc/examples/MathServer/Server.cs0000644000175000017500000000550611613431120017572 0ustar teratornteratorn/* Copyright (c) 2008-2011 - Eric P. Mangold * Released under the terms of the MIT/X11 license - see LICENSE.txt */ using System; using System.Net; using System.Net.Sockets; using System.Text; namespace MathServer { class Server { static void Main(string[] args) { int port = 1234; var listener = new TcpListener(port); listener.Start(5); Console.WriteLine("Listening on port {0}", port); TcpClient client; AMP.Protocol protocol; // the AMP.Command's we're going to respond to var sum = new Sum(); var div = new Divide(); while (true) { client = listener.AcceptTcpClient(); protocol = new AMP.Protocol(client.GetStream()); // we could also pass a 'state' argument to RegisterResponder // which becomes the 'state' argument in the responders (below). // Since we aren't passing anything, then state will always be null protocol.RegisterResponder(sum, handleSum); protocol.RegisterResponder(div, handleDivide); protocol.StartReading(); // start the async data-reading loop } } static Object handleSum(AMP.Msg reqMsg, Object state) { int a = (int)reqMsg["a"]; int b = (int)reqMsg["b"]; int total = a + b; Console.WriteLine("Did a sum: {0} + {1} = {2}", a, b, total); return new AMP.Msg {{"total", total}}; } static Object handleDivide(AMP.Msg reqMsg, Object state) { long numerator = (int)reqMsg["numerator"]; long denominator = (int)reqMsg["denominator"]; decimal result = (decimal)numerator / denominator; Console.WriteLine("Did a division: {0} / {1} = {2}", numerator, denominator, result); return new AMP.Msg { { "result", result } }; } } public class Sum: AMP.Command { public Sum() : base("Sum") { AMP.IAmpType int32 = new AMP.Type.Int32(); AddArgument("a", int32); AddArgument("b", int32); AddResponse("total", int32); } } public class Divide : AMP.Command { public Divide() : base("Divide") { AMP.IAmpType int32 = new AMP.Type.Int32(); AMP.IAmpType dec = new AMP.Type.Decimal(); AddArgument("numerator", int32); AddArgument("denominator", int32); AddResponse("result", dec); AddError(typeof(DivideByZeroException), Encoding.UTF8.GetBytes("ZERO_DIVISION")); } } } doc/examples/MathServer/Properties/0000755000175000017500000000000011613431120020123 5ustar teratornteratorndoc/examples/MathServer/Properties/AssemblyInfo.cs0000644000175000017500000000302511613431120023045 0ustar teratornteratorn/* Copyright (c) 2008-2011 - Eric P. Mangold * Released under the terms of the MIT/X11 license - see LICENSE.txt */ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Simple_Server")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Simple_Server")] [assembly: AssemblyCopyright("Copyright © 2008")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("aeb72cfd-7854-4238-a25b-5e656e1c45a3")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] doc/examples/MathServer/MathServer.csproj0000644000175000017500000000431211613431120021271 0ustar teratornteratorn Debug AnyCPU 9.0.21022 2.0 {70AFB378-CCE4-40B3-8602-5B46A4A7F098} Exe Properties MathServer MathServer v2.0 512 true full false bin\Debug\ DEBUG;TRACE prompt 4 pdbonly true bin\Release\ TRACE prompt 4 {E57E8D4F-56CD-4630-BDCC-97E44FAC4ABC} AMPSharp doc/examples/MathClient/0000755000175000017500000000000011613431120015737 5ustar teratornteratorndoc/examples/MathClient/MathClient.csproj0000644000175000017500000000766511613431120021227 0ustar teratornteratorn Debug AnyCPU 9.0.21022 2.0 {FF922190-D1A5-4176-A994-CBAFC2B2F8CD} Exe Properties MathClient MathClient v2.0 512 publish\ true Disk false Foreground 7 Days false false true 0 1.0.0.%2a false false true true full false bin\Debug\ DEBUG;TRACE prompt 4 pdbonly true bin\Release\ TRACE prompt 4 {E57E8D4F-56CD-4630-BDCC-97E44FAC4ABC} AMPSharp {70AFB378-CCE4-40B3-8602-5B46A4A7F098} SumServer False .NET Framework 2.0 %28x86%29 false False .NET Framework 3.0 %28x86%29 false False .NET Framework 3.5 true False Windows Installer 3.1 true doc/examples/MathClient/Client.cs0000644000175000017500000000601211613431120017503 0ustar teratornteratorn/* Copyright (c) 2008-2011 - Eric P. Mangold * Released under the terms of the MIT/X11 license - see LICENSE.txt */ using System; using System.Net.Sockets; using System.Threading; namespace MathClient { class Client { static void Main(string[] args) { string hostname = "localhost"; int port = 1234; var client = new TcpClient(hostname, port); var protocol = new AMP.Protocol(client.GetStream()); protocol.StartReading(); // start the async data-reading loop // Instantiate the Commands we're going to call AMP.Command sum = new MathServer.Sum(); AMP.Command div = new MathServer.Divide(); // Make one remote call synchronously. CallRemote() will raise an exception if // anything goes wrong making the call. AMP.Msg msg = protocol.CallRemote(sum, new AMP.Msg { { "a", 5 }, { "b", 7 } }); Console.WriteLine("Got sum result: {0}", (int)msg["total"]); Thread.Sleep(2000); // Now make some asynchronous calls in a loop int count = 3; while (count-- > 0) { // pass the AMP.Protocol object though as the `state' parameter since we need it in the callback protocol.BeginCallRemote(sum, new AMP.Msg { { "a", 13 }, { "b", 81 } }, CBGotSumResponse, protocol); Thread.Sleep(2000); } // Do a division try { msg = protocol.CallRemote(div, new AMP.Msg { { "numerator", 10 }, { "denominator", 3 } }); } catch (DivideByZeroException) { System.Console.WriteLine("Shouldn't have caused zero division here!"); return; } Console.WriteLine("Got division result: {0}", (decimal)msg["result"]); // Now do it again, but this time cause a DivideByZeroException on the remote end, // which will result in a DivideByZeroException being raised here, because the // Divide command has been implemented to handle this particular Exception. try { msg = protocol.CallRemote(div, new AMP.Msg { { "numerator", 10 }, { "denominator", 0 } }); } catch (DivideByZeroException) { System.Console.WriteLine("Got DivideByZeroException as expected."); } Console.ReadKey(); // Press any key to continue... } // Call-back method for receiving result from server asynchronously static void CBGotSumResponse(IAsyncResult ar) { var protocol = (AMP.Protocol)ar.AsyncState; // EndCallRemote() will raise if any error occured during the remote call AMP.Msg respMsg = protocol.EndCallRemote(ar); Console.WriteLine("Got sum: {0}", (int)respMsg["total"]); } } } doc/examples/MathClient/Properties/0000755000175000017500000000000011613431120020073 5ustar teratornteratorndoc/examples/MathClient/Properties/AssemblyInfo.cs0000644000175000017500000000302511613431120023015 0ustar teratornteratorn/* Copyright (c) 2008-2011 - Eric P. Mangold * Released under the terms of the MIT/X11 license - see LICENSE.txt */ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Simple_Client")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Simple_Client")] [assembly: AssemblyCopyright("Copyright © 2008")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("b9cd3696-d353-46cb-841b-5ebb1762b257")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] LICENSE.txt0000644000175000017500000000236711613431120013157 0ustar teratornteratornCopyright (c) 2008-2011 Eric P. Mangold All code and supporting files are released under the Expat license (virtually identical to versions of the MIT license). The full text of the license follows: -= EXPAT LICENSE =- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Makefile0000644000175000017500000000021511613431120012762 0ustar teratornteratorndefault: gmcs -t:library -out:AMPSharp.dll -keyfile:AMPSharp/ampsharp.snk -r:System.Windows.Forms -pkg:log4net `find AMPSharp -name "*.cs"` README.txt0000644000175000017500000000404511613431120013025 0ustar teratornteratornAMP# - The client/server AMP library for all .NET runtimes. AMP Website: http://amp-protocol.net AMP# Website: http://amp-protocol.net/Ampsharp Code is hosted on Launchpad: https://launchpad.net/ampsharp The solution file should open in Visual Studio or Mono Develop. Author: Eric P. Mangold - teratorn@gmail.com - teratorn on Freenode IRC === LOGGING === A file 'AMPSharp.dll.log4net' should be placed in the build directory along side AMPSharp.dll. Edit this file to configure logging options. By default it has the logging level set to DEBUG which will produce *a lot* of log output... probably a lot more than you want for production. Set the log level to something sensible like ERROR for production. (Currently, the code only uses two levels, DEBUG and ERROR) === DOCS === Check out the example programs in doc/examples See the AMP docs on amp-protocol.net Read the source code :) === BUILDING === You will need log4net installed to build the AMPSharp project. MonoDevelop or Visual Studio should build the whole Solution or individual Projects just fine. Alternatively use Mono's `xbuild` utility to do it on the command-line: xbuild AMPSharp.csproj Or build the whole solution: xbuild AMPSharp.sln === BUILDING WITH MONO ON THE COMMAND-LINE === To build the assembly using Mono, just type `make'. === BUILDING .DEB PACKAGES === On Debian, Ubuntu or other derivatives, type a command like this: fakeroot debian/rules binary You may be missing some build dependencies - to be sure you have them all installed type `sudo mk-build-deps -i' === UNIT TESTS === To build the Tests project you will need NUnit installed. NUnit 2.5 or higher is required. NOTE: Tests/Tests.csproj references nunit.framework 2.5.10.11092 *specifically*. Change this to match your installed version of nunit 2.5. Find it with: gacutil -l nunit.framework After compiling the Tests.dll assembly it may be "run" with any of the NUnit test runners as usual. =============== Have fun! Tests/0000755000175000017500000000000011613431120012426 5ustar teratornteratornTests/test_amp.cs0000644000175000017500000022337411613431120014604 0ustar teratornteratorn/* Copyright (c) 2008-2011 - Eric P. Mangold * Released under the terms of the MIT/X11 license - see LICENSE.txt */ using System.Net; namespace AMP.Tests { using System; using NUnit.Framework; using System.Collections; using System.Collections.Generic; using System.Text; using System.Windows.Forms; using System.Threading; using System.Net.Sockets; public class MySillyForm : Form { public int formThreadId; public int callbackThreadId; public ManualResetEvent waitHandle; public IAsyncResult ar; public MySillyForm(ManualResetEvent waitHandle) { this.waitHandle = waitHandle; Load += MySillyForm_Load; } public void MySillyForm_Load(object sender, EventArgs e) { formThreadId = Thread.CurrentThread.ManagedThreadId; Console.WriteLine("Form thread {0}", formThreadId); Console.WriteLine(String.Format("Form ctx: {0}", SynchronizationContext.Current)); // when we supply any delegates to an AMP method the current // synchronization context will be captured. This means that // calls to run those delegates will be posted to the synchronization // context's message loop, and will thus execute in the same thread // that the original AMP call was made in. var result = new SimpleAsyncResult(); result.callback = delegate { callbackThreadId = Thread.CurrentThread.ManagedThreadId; waitHandle.Set(); }; // run the callback on a foreign thread, as it would be if this callback were // the result of receiving network traffic var t = new Thread(result.doCallback); t.Start(); } } public class B { public static byte[] b(string foo) { return Encoding.UTF8.GetBytes(foo); } } public static class ByteArrayExtensions { public static string ToHexString(this byte[] bytes) { StringBuilder sb = new StringBuilder(); foreach (byte b in bytes) { sb.Append("\\x" + b.ToString("X2")); } return sb.ToString(); } } [TestFixture] public class StringDicts { internal AMP.Msg_Raw a; internal AMP.Msg_Raw b; [SetUp] public void Init() { a = new AMP.Msg_Raw(); b = new AMP.Msg_Raw(); } [Test] public void empty() { Assert.That(a, Is.EqualTo(b)); } [Test] public void diff_size() { // a stays empty b.Add("key", B.b("value")); Assert.That(a, Is.Not.EqualTo(b)); } [Test] public void diff_keys() { a.Add("key", B.b("value")); b.Add("key2", B.b("value")); Assert.That(a, Is.Not.EqualTo(b)); } [Test] public void diff_values() { a.Add("key", B.b("value")); b.Add("key", B.b("value2")); Assert.That(a, Is.Not.EqualTo(b)); } [Test] public void equal_size_1() { a.Add("key", B.b("value")); b.Add("key", B.b("value")); Assert.That(a, Is.EqualTo(b)); } [Test] public void equal_size_2() { a.Add("key", B.b("value")); a.Add("key2", B.b("value2")); b.Add("key", B.b("value")); b.Add("key2", B.b("value2")); Assert.That(a, Is.EqualTo(b)); } } public class Test_AMP_Protocol : AMP.Protocol { internal List messages = new List(); internal override void SendMessage(AMP.Msg_Raw raw_msg) { messages.Add(raw_msg); } } namespace Protocol { [TestFixture] public class BuildAMPWireCommand { [Test] [ExpectedException(typeof(AMP.Error.MessageEmptyError))] public void build_msg_empty() { AMP.Protocol.BuildAMPWireCommand(new AMP.Msg_Raw()); } [Test] [ExpectedException(typeof(AMP.Error.InvalidKeySizeError))] public void build_msg_null_key() { var amp_msg = new AMP.Msg_Raw { { "", B.b("ignored") } }; AMP.Protocol.BuildAMPWireCommand(amp_msg); } [Test] [ExpectedException(typeof(AMP.Error.InvalidKeySizeError))] public void build_msg_key_too_long() { var amp_msg = new AMP.Msg_Raw(); const string chunk = "0123456789ABCDEF"; string key = ""; for (int i = 0; i < 16; i++) { // 16 bytes 16 times is 256 bytes which is 1 byte too long key += chunk; } Assert.That(key.Length, Is.EqualTo(256)); amp_msg.Add(key, B.b("ignored")); AMP.Protocol.BuildAMPWireCommand(amp_msg); } [Test] public void build_msg_max_key_len() // this also doubles as a test for checking that empty // values in the StringDict are handled OK { var amp_msg = new AMP.Msg_Raw(); const string chunk = "0123456789ABCDEF"; string key = ""; for (int i = 0; i < 15; i++) { // 16 bytes 15 times is 240 bytes key += chunk; } key += "0123456789ABCDE"; // 15 more bytes gives us 255 Assert.That(key.Length, Is.EqualTo(255)); amp_msg.Add(key, B.b("")); byte[] result = AMP.Protocol.BuildAMPWireCommand(amp_msg); var expected_list = new ArrayList { (byte)0x00, (byte)0xff }; expected_list.AddRange(Encoding.UTF8.GetBytes(key)); expected_list.Add((byte)0x00); expected_list.Add((byte)0x00); expected_list.Add((byte)0x00); expected_list.Add((byte)0x00); var expected = (byte[])expected_list.ToArray(typeof(byte)); Assert.That(expected, Is.EqualTo(result)); } [Test] [ExpectedException(typeof(AMP.Error.InvalidValueSizeError))] public void build_msg_value_too_long() { var amp_msg = new AMP.Msg_Raw(); const string chunk = "0123456789ABCDEF"; string value = ""; for (int i = 0; i < 4096; i++) { // 16 bytes 4096 times is 65536 bytes (1 byte too long) value += chunk; } Assert.That(value.Length, Is.EqualTo(65536)); amp_msg.Add("key", B.b(value)); AMP.Protocol.BuildAMPWireCommand(amp_msg); } [Test] public void build_msg_max_value_len() { var amp_msg = new AMP.Msg_Raw(); const string chunk = "0123456789ABCDEF"; string value = ""; for (int i = 0; i < 4095; i++) { // 16 bytes 15 times is 65520 bytes value += chunk; } value += "0123456789ABCDE"; // 15 more bytes gives us 65535 Assert.That(value.Length, Is.EqualTo(65535)); amp_msg.Add("k", B.b(value)); byte[] result = AMP.Protocol.BuildAMPWireCommand(amp_msg); var expected_list = new ArrayList { (byte)0x00, (byte)0x01, (byte)0x6B, (byte)0xff, (byte)0xff }; expected_list.AddRange(Encoding.UTF8.GetBytes(value)); expected_list.Add((byte)0x00); expected_list.Add((byte)0x00); var expected = (byte[])expected_list.ToArray(typeof(byte)); Assert.That(expected, Is.EqualTo(result)); } } /// /// Test the actual AMP wire-protcol parsing and dispatching. /// XXX TODO need to test that processCommand() processAnswer() and processError() is called /// correctly... we probably want to override these method and simply capture the arguments /// they are called with. /// [TestFixture] public class Parser { internal AMP.Protocol proto; [SetUp] public void Init() { proto = new Test_AMP_Protocol(); } [Test] [ExpectedException(typeof(AMP.Error.ProtocolError))] public void parse_initial_null() { byte[] chunk = { 0, 0 }; proto.DataReceived(chunk); } [Test] [ExpectedException(typeof(AMP.Error.ProtocolError))] public void parse_key_too_big() { byte[] chunk = { 1, 0 }; proto.DataReceived(chunk); } [Test] public void parse_one_byte_key_value() { var amp_msg = new AMP.Msg_Raw { { "x", B.b("y") } }; byte[] bytes = AMP.Protocol.BuildAMPWireCommand(amp_msg); proto.DataReceived(bytes); Assert.That(amp_msg, Is.EqualTo(proto.msg_raw)); } [Test] public void parse_one_byte_key_empty_value() { var amp_msg = new AMP.Msg_Raw { { "x", B.b("") } }; byte[] bytes = AMP.Protocol.BuildAMPWireCommand(amp_msg); proto.DataReceived(bytes); foreach (byte b in bytes) { Console.Write(String.Format("\\x{0:x2}", b)); } Console.WriteLine(); // NUnit kinda sucks a lot. This doesn't work at all for no apparent reason. Assert.That(amp_msg, Is.EqualTo(proto.msg_raw)); } [Test] public void parse_simple_msg() { var amp_msg = new AMP.Msg_Raw { {"_ask", B.b("14253")}, {"_command", B.b("Sum")}, {"x", B.b("15")}, {"y", B.b("45")} }; byte[] bytes = AMP.Protocol.BuildAMPWireCommand(amp_msg); proto.DataReceived(bytes); Assert.That(amp_msg, Is.EqualTo(proto.msg_raw)); } [Test] public void parse_simple_msg_byte_at_a_time() { var amp_msg = new AMP.Msg_Raw { {"_ask", B.b("14253")}, {"_command", B.b("Sum")}, {"x", B.b("15")}, {"y", B.b("45")} }; byte[] bytes = AMP.Protocol.BuildAMPWireCommand(amp_msg); var buf = new byte[1]; foreach (byte b in bytes) { buf[0] = b; proto.DataReceived(buf); } Assert.That(amp_msg, Is.EqualTo(proto.msg_raw)); } [Test] public void parse_double_msg() { var amp_msg = new AMP.Msg_Raw { {"_ask", B.b("14253")}, {"_command", B.b("Sum")}, {"x", B.b("15")}, {"y", B.b("45")} }; var amp_msg2 = new AMP.Msg_Raw { {"_ask", B.b("321")}, {"_command", B.b("Power")}, {"x", B.b("51")}, {"y", B.b("54")} }; byte[] bytes = AMP.Protocol.BuildAMPWireCommand(amp_msg); byte[] bytes2 = AMP.Protocol.BuildAMPWireCommand(amp_msg2); proto.DataReceived(bytes); Assert.That(amp_msg, Is.EqualTo(proto.msg_raw)); proto.DataReceived(bytes2); Assert.That(amp_msg2, Is.EqualTo(proto.msg_raw)); var bytes3 = new byte[bytes.Length + bytes2.Length]; bytes2.CopyTo(bytes3, 0); bytes.CopyTo(bytes3, bytes2.Length); proto.DataReceived(bytes3); Assert.That(amp_msg, Is.EqualTo(proto.msg_raw)); } } } internal class AMP_TEST_Command : AMP.Command { public AMP_TEST_Command() : base("test") { AddArgument("one", new AMP.Type.String()); AddArgument("two", new AMP.Type.String()); AddResponse("result", new AMP.Type.String()); } } [TestFixture] public class Command { internal AMP.Command cmd; internal AMP.Command test_cmd; [SetUp] public void Init() { cmd = new AMP.Command("cmd"); test_cmd = new AMP_TEST_Command(); } [Test] public void toRawMessage_request_empty() { var amp_typed_msg = new AMP.Msg(); AMP.Msg_Raw result = cmd.ToRawMessage(amp_typed_msg, AMP.MsgType.REQUEST); Assert.That(result.Count, Is.EqualTo(0)); } [Test] public void toRawMessage_response_empty() { var amp_typed_msg = new AMP.Msg(); AMP.Msg_Raw result = cmd.ToRawMessage(amp_typed_msg, AMP.MsgType.RESPONSE); Assert.That(result.Count, Is.EqualTo(0)); } [Test] [ExpectedException(typeof(AMP.Error.ParameterNotFound))] public void toRawMessage_request_missing_parameter() { var amp_typed_msg = new AMP.Msg(); test_cmd.ToRawMessage(amp_typed_msg, AMP.MsgType.REQUEST); } [Test] [ExpectedException(typeof(AMP.Error.ParameterNotFound))] public void toRawMessage_response_missing_parameter() { var amp_typed_msg = new AMP.Msg(); test_cmd.ToRawMessage(amp_typed_msg, AMP.MsgType.RESPONSE); } [Test] public void toRawMessage_normal() { var amp_typed_msg = new AMP.Msg { { "one", (object)"one_value" }, { "two", (object)"two_value" } }; AMP.Msg_Raw result = test_cmd.ToRawMessage(amp_typed_msg, AMP.MsgType.REQUEST); Assert.That(result.Count, Is.EqualTo(2)); Assert.That(B.b("one_value"), Is.EqualTo(result["one"])); Assert.That(B.b("two_value"), Is.EqualTo(result["two"])); amp_typed_msg = new AMP.Msg { { "result", (object)"one_valuetwo_value" } }; result = test_cmd.ToRawMessage(amp_typed_msg, AMP.MsgType.RESPONSE); Assert.That(result.Count, Is.EqualTo(1)); Assert.That(B.b("one_valuetwo_value"), Is.EqualTo(result["result"])); } [Test] public void toTypedMessage_request_empty() { var amp_raw_msg = new AMP.Msg_Raw(); AMP.Msg result = cmd.ToTypedMessage(amp_raw_msg, AMP.MsgType.REQUEST); Assert.That(result.Count, Is.EqualTo(0)); } [Test] public void toTypedMessage_response_empty() { var amp_raw_msg = new AMP.Msg_Raw(); AMP.Msg result = cmd.ToTypedMessage(amp_raw_msg, AMP.MsgType.RESPONSE); Assert.That(result.Count, Is.EqualTo(0)); } [Test] [ExpectedException(typeof(AMP.Error.ParameterNotFound))] public void toTypedMessage_request_missing_parameter() { var amp_raw_msg = new AMP.Msg_Raw(); test_cmd.ToTypedMessage(amp_raw_msg, AMP.MsgType.REQUEST); } [Test] [ExpectedException(typeof(AMP.Error.ParameterNotFound))] public void toTypedMessage_response_missing_parameter() { var amp_raw_msg = new AMP.Msg_Raw(); test_cmd.ToTypedMessage(amp_raw_msg, AMP.MsgType.RESPONSE); } [Test] public void toTypedMessage_normal() { var amp_raw_msg = new AMP.Msg_Raw { { "one", B.b("one_value") }, { "two", B.b("two_value") } }; AMP.Msg result = test_cmd.ToTypedMessage(amp_raw_msg, AMP.MsgType.REQUEST); Assert.That(result.Count, Is.EqualTo(2)); Assert.That("one_value", Is.EqualTo(result["one"])); Assert.That("two_value", Is.EqualTo(result["two"])); amp_raw_msg = new AMP.Msg_Raw { { "result", B.b("one_valuetwo_value") } }; result = test_cmd.ToTypedMessage(amp_raw_msg, AMP.MsgType.RESPONSE); Assert.That(result.Count, Is.EqualTo(1)); Assert.That("one_valuetwo_value", Is.EqualTo(result["result"])); } } [TestFixture] public class Callbacks { public MySillyForm theForm; [SetUp] public void Init() { } // Test that the SimpleAsyncResult class captures the current synchronization context // when it is created, and dispatches to the context's thread at callback-time [Test] public void form_synchronize() { int testThreadId = Thread.CurrentThread.ManagedThreadId; int callbackThreadId = 0; // assigned in callback var result = new SimpleAsyncResult(); result.callback = delegate { callbackThreadId = Thread.CurrentThread.ManagedThreadId; }; // run the callback on a foreign thread, as it would be if this callback were // the result of receiving network traffic var tmpWaitHandle = new ManualResetEvent(false); var t = new Thread(() => { result.doCallback(); tmpWaitHandle.Set(); }); t.Start(); tmpWaitHandle.WaitOne(); Console.WriteLine("Test thread : {0}", testThreadId); Console.WriteLine("Callback thread : {0}", callbackThreadId); // Assert our callback actually ran and saved its thread ID Assert.That(callbackThreadId, Is.Not.EqualTo(0)); // Assert the callback did happen on a foreign thread Assert.That(callbackThreadId, Is.Not.EqualTo(testThreadId)); // NOW, we're going to run a Form on another thread, and have that Form's code create the // SimpleAsyncResult. We should see that the login callback happens ON THE FORM'S THREAD, which // is quite different than the semantics we see above. //System.Console.WriteLine("Test thread {0}", Thread.CurrentThread.ManagedThreadId); Assert.That(SynchronizationContext.Current, Is.Null); var waitHandle = new ManualResetEvent(false); var t2 = new Thread(() => _doForm(waitHandle)); t2.Start(); // The form will Set() the waitHandle once it is done. waitHandle.WaitOne(); Assert.That(theForm.formThreadId, Is.EqualTo(theForm.callbackThreadId)); theForm.Invoke(new MethodInvoker(theForm.Close)); } public void _doForm(ManualResetEvent waitHandle) { theForm = new MySillyForm(waitHandle); theForm.ShowDialog(); } } /* public class DummyTransport: IAMPTransport { public void RegisterProtocol(IAMPProtocol proto) { } public void Start() { } public void Write(byte[] bytes) { } } [TestFixture] public class AMP_Responder_Form_Tests { public ResponderTestForm theForm; public static byte[] B.b(string foo) { return Encoding.UTF8.GetBytes(foo); } /// /// Test that registering a responder captures the current /// synchronization context and that subsequent calls to the Responder /// are posted to context's message loop /// [Test] public void test_responder_form_synchronize() { var proto = new AMP.Protocol(new DummyTransport()); theForm = new ResponderTestForm(proto); var t = new Thread(() => theForm.ShowDialog()); t.Start(); // wait till the Form has loaded and registered the responder theForm.onLoadHandle.WaitOne(); // give the protocol some commands to process, so that it // calls the responders that the Form has registered var raw_msg = new AMP.Msg_Raw { {"_command", B.b("SimpleCmd")}, {"_ask", B.b("1")}, {"test_val", B.b("test")}, }; proto.DataReceived(AMP.Protocol.buildAMPWireCommand(raw_msg)); raw_msg["_command"] = B.b("TestCmd"); proto.DataReceived(AMP.Protocol.buildAMPWireCommand(raw_msg)); // The form will Set() these wait handles when the responders runs. theForm.formRespondedHandle.WaitOne(); theForm.syncCtxRespondedHandle.WaitOne(); // verify that the responders ran on the Form's thread, not this one Assert.That(theForm.formThreadId, Is.EqualTo(theForm.formResponderThreadId)); Assert.That(theForm.formThreadId, Is.EqualTo(theForm.syncCtxResponderThreadId)); // close the Form theForm.Invoke(new MethodInvoker(theForm.Close)); } public void _doForm(ManualResetEvent waitHandle) { } } public class ResponderTestForm : Form { public int formThreadId; public int formResponderThreadId; public int syncCtxResponderThreadId; public ManualResetEvent onLoadHandle = new ManualResetEvent(false); public ManualResetEvent formRespondedHandle = new ManualResetEvent(false); public ManualResetEvent syncCtxRespondedHandle = new ManualResetEvent(false); private AMP.Protocol proto; public ResponderTestForm(AMP.Protocol proto) { this.proto = proto; Load += ResponderTestForm_Load; } public void ResponderTestForm_Load(object sender, EventArgs e) { formThreadId = Thread.CurrentThread.ManagedThreadId; Console.WriteLine("Form thread {0}", formThreadId); Console.WriteLine(String.Format("Form ctx: {0}", SynchronizationContext.Current)); // passing 'this' causes calls to the responder use this.Invoke(...) proto.RegisterResponder(new SimpleCmd(), SimpleCmdResponder, this); // passing the current Synchronization Contextx causes calls to the responder // ctx.Send(...) proto.RegisterResponder(new TestCmd(), SimpleCmdResponder2, SynchronizationContext.Current); onLoadHandle.Set(); } private object SimpleCmdResponder(AMP.Msg msg, object state) { formResponderThreadId = Thread.CurrentThread.ManagedThreadId; formRespondedHandle.Set(); return new AMP.Msg {{"ret_val", "hello"}}; } private object SimpleCmdResponder2(AMP.Msg msg, object state) { syncCtxResponderThreadId = Thread.CurrentThread.ManagedThreadId; syncCtxRespondedHandle.Set(); return new AMP.Msg {{"ret_val", "hello"}}; } } */ public class TestCmd : AMP.Command { public TestCmd() : base("TestCmd") { AddArgument("test_val", new AMP.Type.String()); } } /// /// Test that processFullMessage() does the Right Thing under all possible conditions. /// processFullMessage() is the processing function that handles every AMP "box" that /// comes in off the wire. The "boxes" (represented by the AMP.Msg_Raw class) can /// represent 3 major catagories of things: /// - An AMP Command that the peer wishes to run. /// - A response to an AMP Command we called on the peer. /// - An error response to an AMP Command we called on the peer. /// public class AMP_Message_Dispatch_Base { // Set up a command that is used by several test cases public static string test_cmdName = "TestCmd"; public static string test_cmdNameNoAnswer = "TestCmdNoAnswer"; public class MyAMPError : AMP.Error.RemoteAmpError { public MyAMPError() { } public MyAMPError(byte[] errorCode, string description) : base(errorCode, description) { } } public class TestCmd : AMP.Command { public TestCmd() : base(test_cmdName) { AddArgument("test_val", new AMP.Type.String()); AddResponse("ret_val", new AMP.Type.String()); AddError(typeof(MyAMPError), B.b("MY_ERROR")); AddError(typeof(DivideByZeroException), B.b("ZERO_DIVISION")); } } /// /// TODO: write some tests that use this class. requiresAnswer is only a *hint* /// for the client-side portion of the protocol... /// public class TestCmdNoAnswer : AMP.Command { public TestCmdNoAnswer() : base(test_cmdNameNoAnswer) { AddArgument("test_val", new AMP.Type.String()); AddResponse("ret_val", new AMP.Type.String()); AddError(typeof(MyAMPError), B.b("MY_ERROR")); AddError(typeof(DivideByZeroException), B.b("ZERO_DIVISION")); RequiresAnswer = false; } } public static TestCmd test_cmd = new TestCmd(); public static TestCmdNoAnswer test_cmdNoAnswer = new TestCmdNoAnswer(); internal AMP.Msg_Raw test_msg; internal AMP.Msg_Raw test_msg_no_ask; internal AMP.Msg_Raw test_msgNoAnswer; public Test_AMP_Protocol proto; public AMP_Message_Dispatch_Base() { test_msg = new AMP.Msg_Raw(); test_msg["_command"] = B.b("TestCmd"); test_msg["_ask"] = B.b("25"); test_msg["test_val"] = B.b("Hello World!"); test_msg_no_ask = new AMP.Msg_Raw(); test_msg_no_ask["_command"] = B.b("TestCmd"); test_msg_no_ask["test_val"] = B.b("Hello World!"); test_msgNoAnswer = new AMP.Msg_Raw(); test_msgNoAnswer["_command"] = B.b("TestCmdNoAnswer"); test_msgNoAnswer["_ask"] = B.b("25"); test_msgNoAnswer["test_val"] = B.b("Hello World!"); } [SetUp] public void Init() { proto = new Test_AMP_Protocol(); } } [TestFixture] public class Command_Dispatch : AMP_Message_Dispatch_Base { /// /// Test what happens where there is no responder for the given command /// [Test] public void no_responder() { // dispatch a command for which we have not registered any responder var reqMsg = new AMP.Msg_Raw { { "_command", B.b("FOO") } }; proto.ProcessFullMessage(reqMsg); // no error should be written, because the request didn't contain an _ask key, // so it is not expecting any reply to this command. Assert.That(0, Is.EqualTo(proto.messages.Count)); //ok lets add an _ask key and do it again reqMsg.Add("_ask", B.b("77")); proto.ProcessFullMessage(reqMsg); // now we should find an error message has been sent out Assert.That(1, Is.EqualTo(proto.messages.Count)); AMP.Msg_Raw errMsg = proto.messages[0]; Assert.That(3, Is.EqualTo(errMsg.Count)); Assert.That(B.b("77"), Is.EqualTo(errMsg["_error"])); Assert.That(AMP.Error.Codes.UNHANDLED_ERROR_CODE, Is.EqualTo(errMsg["_error_code"])); Assert.That(errMsg.ContainsKey("_error_description")); } [Test] [ExpectedException(typeof(AMP.Error.ParameterNotFound))] public void deserialization_fails() { // that what happens when the command we receive doesn't match the schema of our local command definition. var oddball_msg = new AMP.Msg_Raw { {"_ask", B.b("1")}, {"_command", B.b("TestCmd")}, {"fubar", B.b("undefined field")} }; // register TestCmd w/ protocol proto.RegisterResponder(test_cmd, delegate { return new Object(); }); // OK, process the command.. this should raise an exception, which this test case expects. proto.ProcessFullMessage(oddball_msg); } [Test] public void responder_returns_amp_msg() { // Test a successful command request that results in a call to a responder that returns an AMP.Msg var responseMsg = new AMP.Msg { { "ret_val", "Return Value" } }; var requestMsg = new AMP.Msg(); Object responderState = "value_before"; proto.RegisterResponder(test_cmd, delegate(AMP.Msg reqMsg, Object state) { requestMsg = reqMsg; responderState = state; return responseMsg; }, "test_state"); // process request.. this should result in a successful response being written out proto.ProcessFullMessage(test_msg); Assert.That(1, Is.EqualTo(requestMsg.Count)); Assert.That(requestMsg["test_val"], Is.EqualTo("Hello World!")); Assert.That(responderState, Is.EqualTo("test_state")); Assert.That(1, Is.EqualTo(proto.messages.Count)); AMP.Msg_Raw respMsgRaw = proto.messages[0]; Assert.That(2, Is.EqualTo(respMsgRaw.Count)); Assert.That(B.b("25"), Is.EqualTo(respMsgRaw["_answer"])); Assert.That(B.b("Return Value"), Is.EqualTo(respMsgRaw["ret_val"])); } [Test] public void responder_returns_malformed_amp_msg() { // Test a responder that returns a malformed AMP.Msg var responseMsg = new AMP.Msg { { "wrong_key", "foo" } }; proto.RegisterResponder(test_cmd, delegate { return responseMsg; }); // process request.. this should result in an error being written out // because the responder returns an invalid value proto.ProcessFullMessage(test_msg); Assert.That(1, Is.EqualTo(proto.messages.Count)); AMP.Msg_Raw respMsgRaw = proto.messages[0]; Assert.That(3, Is.EqualTo(respMsgRaw.Count)); Assert.That(B.b("25"), Is.EqualTo(respMsgRaw["_error"])); Assert.That(B.b("UNKNOWN"), Is.EqualTo(respMsgRaw["_error_code"])); Assert.That(respMsgRaw.ContainsKey("_error_description")); } [Test] public void responder_returns_deferred_response() { // Test a successful command request that results in a call to a responder that returns a DeferredResponse var responseMsg = new AMP.Msg { { "ret_val", "Return Value" } }; var requestMsg = new AMP.Msg(); var deferred = new DeferredResponse(); // return DeferredResponse from responder proto.RegisterResponder(test_cmd, delegate(AMP.Msg reqMsg, Object state) { requestMsg = reqMsg; return deferred; }); // process request.. the proto should attach a callback to the DeferredResponse proto.ProcessFullMessage(test_msg); Assert.That(0, Is.EqualTo(proto.messages.Count)); Assert.That(1, Is.EqualTo(requestMsg.Count)); Assert.That(requestMsg["test_val"], Is.EqualTo("Hello World!")); // OK, lets fire the DeferredResponse callback, which should result in the AMP response being written out deferred.respond(responseMsg); Assert.That(1, Is.EqualTo(proto.messages.Count)); AMP.Msg_Raw respMsgRaw = proto.messages[0]; Assert.That(2, Is.EqualTo(respMsgRaw.Count)); Assert.That(B.b("25"), Is.EqualTo(respMsgRaw["_answer"])); Assert.That(B.b("Return Value"), Is.EqualTo(respMsgRaw["ret_val"])); } [Test] public void responder_returns_deferred_response_which_fires_with_exception() { var requestMsg = new AMP.Msg(); var deferred = new DeferredResponse(); // return DeferredResponse from responder proto.RegisterResponder(test_cmd, delegate(AMP.Msg reqMsg, Object state) { requestMsg = reqMsg; return deferred; }); // process request.. the proto should attach a callback to the DeferredResponse proto.ProcessFullMessage(test_msg); Assert.That(0, Is.EqualTo(proto.messages.Count)); Assert.That(1, Is.EqualTo(requestMsg.Count)); Assert.That(requestMsg["test_val"], Is.EqualTo("Hello World!")); // OK, lets fire the DeferredResponse callback with an exception, // which should result in an AMP error being written out deferred.respond(new Exception()); Assert.That(1, Is.EqualTo(proto.messages.Count)); AMP.Msg_Raw respMsgRaw = proto.messages[0]; Assert.That(3, Is.EqualTo(respMsgRaw.Count)); Assert.That(B.b("25"), Is.EqualTo(respMsgRaw["_error"])); Assert.That(B.b("UNKNOWN"), Is.EqualTo(respMsgRaw["_error_code"])); Assert.That(respMsgRaw.ContainsKey("_error_description")); } [Test] public void no_ask_key() { // test that we ignore the responder's return value if no _ask key was given var responseMsg = new AMP.Msg { { "ret_val", "Return Value" } }; proto.RegisterResponder(test_cmd, delegate { return responseMsg; }); // process request.. no response should be written because the peer didn't ask for one proto.ProcessFullMessage(test_msg_no_ask); Assert.That(0, Is.EqualTo(proto.messages.Count)); } [Test] public void responder_throws() { proto.RegisterResponder(test_cmd, delegate { throw new Exception(); }); proto.ProcessFullMessage(test_msg); Assert.That(1, Is.EqualTo(proto.messages.Count)); AMP.Msg_Raw respMsgRaw = proto.messages[0]; Assert.That(3, Is.EqualTo(respMsgRaw.Count)); Assert.That(B.b("25"), Is.EqualTo(respMsgRaw["_error"])); Assert.That(B.b("UNKNOWN"), Is.EqualTo(respMsgRaw["_error_code"])); Assert.That(respMsgRaw.ContainsKey("_error_description")); } [Test] public void responder_throws_but_no_ask_key_given() { // test that we don't send an error upon an exception in the responder if no _ask key is given proto.RegisterResponder(test_cmd, delegate { throw new Exception(); }); proto.ProcessFullMessage(test_msg_no_ask); // responder threw an exception, but no messages written out. Good. Assert.That(0, Is.EqualTo(proto.messages.Count)); } [Test] public void responder_throws_custom_exception() { // OK, now test having the responder throw a known exception that has a custom AMP error code proto.RegisterResponder(test_cmd, delegate { throw new MyAMPError(); }); proto.ProcessFullMessage(test_msg); Assert.That(1, Is.EqualTo(proto.messages.Count)); AMP.Msg_Raw respMsgRaw = proto.messages[0]; Assert.That(3, Is.EqualTo(respMsgRaw.Count)); Assert.That(B.b("25"), Is.EqualTo(respMsgRaw["_error"])); Assert.That(B.b("MY_ERROR"), Is.EqualTo(respMsgRaw["_error_code"])); Assert.That(Encoding.UTF8.GetString(respMsgRaw["_error_description"]).Contains("MyAMPError")); } [Test] public void responder_throws_normal_exception() { // Test having the responder throw a known exception that is just a normal // Exception subclass, not a RemoteAmpError proto.RegisterResponder(test_cmd, delegate { throw new DivideByZeroException(); }); proto.ProcessFullMessage(test_msg); Assert.That(1, Is.EqualTo(proto.messages.Count)); AMP.Msg_Raw respMsgRaw = proto.messages[0]; Assert.That(3, Is.EqualTo(respMsgRaw.Count)); Assert.That(B.b("25"), Is.EqualTo(respMsgRaw["_error"])); Assert.That(B.b("ZERO_DIVISION"), Is.EqualTo(respMsgRaw["_error_code"])); Assert.That(Encoding.UTF8.GetString(respMsgRaw["_error_description"]).Contains("DivideByZeroException")); } [Test] public void responder_returns_object_of_invalid_type() { proto.RegisterResponder(test_cmd, delegate { return new Object(); }); proto.ProcessFullMessage(test_msg); Assert.That(1, Is.EqualTo(proto.messages.Count)); AMP.Msg_Raw respMsgRaw = proto.messages[0]; Assert.That(3, Is.EqualTo(respMsgRaw.Count)); Assert.That(B.b("25"), Is.EqualTo(respMsgRaw["_error"])); Assert.That(B.b("UNKNOWN"), Is.EqualTo(respMsgRaw["_error_code"])); Assert.That(respMsgRaw.ContainsKey("_error_description")); } } public class SimpleCmd : AMP.Command { public SimpleCmd() : base("SimpleCmd") { AddArgument("test_val", new AMP.Type.String()); AddResponse("ret_val", new AMP.Type.String()); } } /// /// Test full end-to-end AMP functionality over a loopback socket /// [TestFixture] public class Loopback { private int port = 45343; private Socket listenSock; private Socket serverSock; private Socket clientSock; private NetworkStream serverStream; private AMP.Protocol server; private NetworkStream clientStream; private AMP.Protocol client; [SetUp] public void SetUp() { listenSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); listenSock.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), port)); listenSock.Listen(5); //Socket serverSock = null; var t = new Thread(() => { serverSock = listenSock.Accept(); }); t.Start(); clientSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); clientSock.Connect("localhost", port); t.Join(); //System.Console.WriteLine("serverSock: {0}", serverSock); //System.Console.WriteLine("clientSock: {0}", clientSock); serverStream = new NetworkStream(serverSock); server = new AMP.Protocol(serverStream); server.StartReading(); clientStream = new NetworkStream(clientSock); client = new AMP.Protocol(clientStream); client.StartReading(); } [TearDown] public void TearDown() { clientSock.Close(); serverSock.Close(); listenSock.Close(); } [Test] public void BeginCallRemote_loopback() { Console.WriteLine("Test thread : {0}", Thread.CurrentThread.ManagedThreadId); Console.WriteLine("test case syn ctx: {0}", SynchronizationContext.Current); AMP.Msg reqMsg = null; var _ret = new AMP.Msg {{"ret_val", "hello"}}; server.RegisterResponder(new SimpleCmd(), (msg, state) => { reqMsg = msg; return _ret; }); var _msg = new AMP.Msg {{"test_val", "value!"}}; AMP.Msg respMsg = null; IAsyncResult _ar = client.BeginCallRemote(new SimpleCmd(), _msg, delegate(IAsyncResult ar) { respMsg = client.EndCallRemote(ar); }, "state"); _ar.AsyncWaitHandle.WaitOne(); Assert.That("state", Is.EqualTo((string)_ar.AsyncState)); Assert.That("value!", Is.EqualTo((string)reqMsg["test_val"])); Assert.That(1, Is.EqualTo(reqMsg.Count)); Assert.That("hello", Is.EqualTo((string)respMsg["ret_val"])); Assert.That(1, Is.EqualTo(respMsg.Count)); // Also test that BeginCallRemote is callable without a state argument reqMsg = null; respMsg = null; _ar = client.BeginCallRemote(new SimpleCmd(), _msg, delegate(IAsyncResult ar) { respMsg = client.EndCallRemote(ar); }); _ar.AsyncWaitHandle.WaitOne(); Assert.That(_ar.AsyncState, Is.Null); Assert.That("value!", Is.EqualTo((string)reqMsg["test_val"])); Assert.That(1, Is.EqualTo(reqMsg.Count)); Assert.That("hello", Is.EqualTo((string)respMsg["ret_val"])); Assert.That(1, Is.EqualTo(respMsg.Count)); } [Test] public void CallRemote_loopback() { AMP.Msg reqMsg = null; var _ret = new AMP.Msg {{"ret_val", "hello"}}; server.RegisterResponder(new SimpleCmd(), (msg, state) => { reqMsg = msg; return _ret; }); var _msg = new AMP.Msg {{"test_val", "value!"}}; AMP.Msg respMsg = client.CallRemote(new SimpleCmd(), _msg); Assert.That("value!", Is.EqualTo((string)reqMsg["test_val"])); Assert.That(1, Is.EqualTo(reqMsg.Count)); Assert.That("hello", Is.EqualTo((string)respMsg["ret_val"])); Assert.That(1, Is.EqualTo(respMsg.Count)); } [Test] public void CallRemote_loopback_no_answer() { AMP.Command cmd = new SimpleCmd(); cmd.RequiresAnswer = false; AMP.Msg reqMsg = null; server.RegisterResponder(cmd, (msg, state) => { reqMsg = msg; return null; }); var _msg = new AMP.Msg {{"test_val", "value!"}}; // Since we're not requesting an answer, this call // does not block and instead returns null immediately. AMP.Msg respMsg = client.CallRemote(cmd, _msg); // Since the call above does not block, it is quite likely that we'll // reach these assertions before the server-side responder (above) // will have been called. Lets just busy-wait up to a second for it... double maxWaitSeconds = 1.0; int waitMs = 10; int waitCount = 0; while ((waitCount * waitMs / 1000.0) < maxWaitSeconds) { waitCount++; Thread.Sleep(waitMs); if (reqMsg != null) break; } Assert.That("value!", Is.EqualTo((string)reqMsg["test_val"])); Assert.That(1, Is.EqualTo(reqMsg.Count)); Assert.That(respMsg, Is.Null); } /// /// Test that registering a responder along with a Control, will actually work, /// and actually use that Control to run the responder in the correct thread. /// [Test] [Ignore("Intermittent failures. Not sure why yet. Seems to work if run by itself, but not in combination with other tests.")] public void responderWithStateAndForm() { // fire up a Form in a new thread and start it running Form theForm = null; var handle = new ManualResetEvent(false); var t = new Thread(new ThreadStart(delegate { theForm = new Form(); handle.Set(); theForm.ShowDialog(); })); t.Start(); handle.WaitOne(); // block until the Thread has started and theForm has been assigned var _ret = new AMP.Msg {{"ret_val", "hello"}}; Object _state = null; AMP.Msg _msg = null; int _threadID = 0; server.RegisterResponder(new SimpleCmd(), (msg, state) => { _msg = msg; _state = state; _threadID = Thread.CurrentThread.ManagedThreadId; return _ret; }, "state", theForm); // Responder should be invoked on theForm's thread AMP.Msg respMsg = client.CallRemote(new SimpleCmd(), new AMP.Msg {{"test_val", "value!"}}); Assert.That("value!", Is.EqualTo((string)_msg["test_val"])); Assert.That(1, Is.EqualTo(_msg.Count)); Assert.That("hello", Is.EqualTo((string)respMsg["ret_val"])); Assert.That(1, Is.EqualTo(respMsg.Count)); Assert.That("state", Is.EqualTo((string)_state)); Assert.That(t.ManagedThreadId, Is.EqualTo(_threadID)); theForm.Invoke(new MethodInvoker(theForm.Close)); } /// /// Ensure that the synchronous CallRemote API works OK when called from a form. /// [Test] public void CallRemoteFromForm() { var _ret = new AMP.Msg {{"ret_val", "hello"}}; server.RegisterResponder(new SimpleCmd(), (msg, state) => { return _ret; }); CallRemoteForm theForm = null; var t = new Thread(new ThreadStart(delegate { theForm = new CallRemoteForm(client); theForm.ShowDialog(); })); t.Start(); t.Join(); Assert.That("hello", Is.EqualTo((string)theForm.respMsg["ret_val"])); Assert.That(1, Is.EqualTo(theForm.respMsg.Count)); } // TODO also test registering a responder with an explicit SynchronizationContext } public class CallRemoteForm: Form { public AMP.Msg respMsg; private AMP.Protocol client; public CallRemoteForm(AMP.Protocol client) { this.client = client; Load += CallRemoteForm_OnLoad; } public void CallRemoteForm_OnLoad(Object sender, EventArgs e) { respMsg = client.CallRemote(new SimpleCmd(), new AMP.Msg{{"test_val", "value!"}}); Close(); } } namespace Types { [TestFixture] public class Int { [Test] public void Int32() { int i = System.Int32.MaxValue; AMP.IAmpType intType = new AMP.Type.Int32(); byte[] got = intType.ToAmpBytes(i); Assert.That(got, Is.EqualTo(B.b(System.Int32.MaxValue.ToString()))); Assert.That((int)intType.FromAmpBytes(got), Is.EqualTo(System.Int32.MaxValue)); i = System.Int32.MinValue; got = intType.ToAmpBytes(i); Assert.That(got, Is.EqualTo(B.b(System.Int32.MinValue.ToString()))); Assert.That((int)intType.FromAmpBytes(got), Is.EqualTo(System.Int32.MinValue)); i = 0; got = intType.ToAmpBytes(i); Assert.That(got, Is.EqualTo(B.b("0"))); Assert.That((int)intType.FromAmpBytes(got), Is.EqualTo(0)); } [Test] public void UInt32() { uint i = System.UInt32.MaxValue; AMP.IAmpType intType = new AMP.Type.UInt32(); byte[] got = intType.ToAmpBytes(i); Assert.That(got, Is.EqualTo(B.b(System.UInt32.MaxValue.ToString()))); Assert.That((uint)intType.FromAmpBytes(got), Is.EqualTo(System.UInt32.MaxValue)); i = 0; got = intType.ToAmpBytes(i); Assert.That(got, Is.EqualTo(B.b("0"))); Assert.That((uint)intType.FromAmpBytes(got), Is.EqualTo(0)); } [Test] public void Int64() { long i = System.Int64.MaxValue; AMP.IAmpType intType = new AMP.Type.Int64(); byte[] got = intType.ToAmpBytes(i); Assert.That(got, Is.EqualTo(B.b(System.Int64.MaxValue.ToString()))); Assert.That((long)intType.FromAmpBytes(got), Is.EqualTo(System.Int64.MaxValue)); i = System.Int64.MinValue; got = intType.ToAmpBytes(i); Assert.That(got, Is.EqualTo(B.b(System.Int64.MinValue.ToString()))); Assert.That((long)intType.FromAmpBytes(got), Is.EqualTo(System.Int64.MinValue)); i = 0; got = intType.ToAmpBytes(i); Assert.That(got, Is.EqualTo(B.b("0"))); Assert.That((long)intType.FromAmpBytes(got), Is.EqualTo(0)); } [Test] public void UInt64() { ulong i = System.UInt64.MaxValue; AMP.IAmpType intType = new AMP.Type.UInt64(); byte[] got = intType.ToAmpBytes(i); Assert.That(got, Is.EqualTo(B.b(System.UInt64.MaxValue.ToString()))); Assert.That((ulong)intType.FromAmpBytes(got), Is.EqualTo(System.UInt64.MaxValue)); i = 0; got = intType.ToAmpBytes(i); Assert.That(got, Is.EqualTo(B.b("0"))); Assert.That((ulong)intType.FromAmpBytes(got), Is.EqualTo(0)); } [Test] [ExpectedException(typeof(OverflowException))] public void Int32Overflow() { AMP.IAmpType intType = new AMP.Type.Int32(); intType.FromAmpBytes(B.b("999999999999999999999999")); } [Test] [ExpectedException(typeof(OverflowException))] public void UInt32Overflow() { AMP.IAmpType intType = new AMP.Type.UInt32(); intType.FromAmpBytes(B.b("999999999999999999999999")); } [Test] [ExpectedException(typeof(OverflowException))] public void Int64Overflow() { AMP.IAmpType intType = new AMP.Type.Int64(); intType.FromAmpBytes(B.b("999999999999999999999999")); } [Test] [ExpectedException(typeof(OverflowException))] public void UInt64Overflow() { AMP.IAmpType intType = new AMP.Type.UInt64(); intType.FromAmpBytes(B.b("999999999999999999999999")); } } [TestFixture] public class Decimal { [Test] public void Decimal_ToBytes_1() { AMP.IAmpType d = new AMP.Type.Decimal(); byte[] got = d.ToAmpBytes(1.5m); Assert.That(got, Is.EqualTo(B.b("1.5"))); } [Test] public void Decimal_ToBytes_2() { AMP.IAmpType d = new AMP.Type.Decimal(); byte[] got = d.ToAmpBytes(-0.050m); Assert.That(got, Is.EqualTo(B.b("-0.050"))); } [Test] public void Decimal_FromBytes_1() { AMP.IAmpType d = new AMP.Type.Decimal(); decimal got = (decimal)d.FromAmpBytes(B.b("1.5")); Assert.That(got, Is.EqualTo(1.5m)); } [Test] public void Decimal_FromBytes_2() { AMP.IAmpType d = new AMP.Type.Decimal(); decimal got = (decimal)d.FromAmpBytes(B.b("-0.050")); Assert.That(got, Is.EqualTo(-0.050m)); } [Test] public void Decimal_FromBytes_3() { AMP.IAmpType d = new AMP.Type.Decimal(); decimal got = (decimal)d.FromAmpBytes(B.b("1E+2")); Assert.That(got, Is.EqualTo(100m)); } [Test] public void Decimal_FromBytes_4() { AMP.IAmpType d = new AMP.Type.Decimal(); decimal got = (decimal)d.FromAmpBytes(B.b("1E-1")); Assert.That(got, Is.EqualTo(0.1m)); } [Test] public void Decimal_FromBytes_5() { AMP.IAmpType d = new AMP.Type.Decimal(); decimal got = (decimal)d.FromAmpBytes(B.b("10E+0")); Assert.That(got, Is.EqualTo(10m)); } [Test] public void Decimal_FromBytes_6() { AMP.IAmpType d = new AMP.Type.Decimal(); decimal got = (decimal)d.FromAmpBytes(B.b("1.5E+2")); Assert.That(got, Is.EqualTo(150m)); } [Test] [ExpectedException(typeof(FormatException))] public void Decimal_FromBytes_error_1() { AMP.IAmpType d = new AMP.Type.Decimal(); d.FromAmpBytes(B.b("1E")); } [Test] [ExpectedException(typeof(AMP.Error.TypeDecodeError))] public void Decimal_FromBytes_error_2() { AMP.IAmpType d = new AMP.Type.Decimal(); d.FromAmpBytes(B.b("1EE+1")); } } [TestFixture] public class ListOf { [Test] public void empty_ToBytes() { AMP.IAmpType lst = new AMP.Type.ListOf(new AMP.Type.Int32()); var empty = new List(); byte[] got = lst.ToAmpBytes(empty); byte[] expected = { }; Assert.That(got, Is.EqualTo(expected)); } [Test] public void int32_ToBytes() { AMP.IAmpType lst = new AMP.Type.ListOf(new AMP.Type.Int32()); var oneint = new List { 7 }; var ints = new List { 1, 2, 3 }; byte[] got = lst.ToAmpBytes(oneint); byte[] expected = new byte[] { 0, 1, 0x37 }; // 0x37 is hex for "7" in ASCII/UTF-8 Assert.That(got, Is.EqualTo(expected)); got = lst.ToAmpBytes(ints); expected = new byte[] { 0, 1, 0x31, 0, 1, 0x32, 0, 1, 0x33 }; // 0x31 is hex for "1" in ASCII/UTF-8 Assert.That(got, Is.EqualTo(expected)); } [Test] public void strings_ToBytes() { AMP.IAmpType lst = new AMP.Type.ListOf(new AMP.Type.String()); string test = "0123456789ABCDEF"; string chunk = ""; int i; for (i = 0; i < 15; i++) { chunk += test; } // chunk is now 16*15 == 240 chars long chunk += "0123456789ABCDE"; // chunk is now 255 chars long var strings = new List { chunk, chunk, chunk }; byte[] got = lst.ToAmpBytes(strings); Assert.That(got.Length, Is.EqualTo((2 + 255) * 3)); System.Console.WriteLine("{0} {1}", got[0], got[1]); var _tmp = new byte[2] { got[0], got[1] }; Assert.That(_tmp, Is.EqualTo(new byte[] { 0x00, 0xff })); chunk += "Z"; strings = new List { chunk }; got = lst.ToAmpBytes(strings); System.Console.WriteLine("{0} {1}", got[0], got[1]); _tmp = new byte[2] { got[0], got[1] }; Assert.That(_tmp, Is.EqualTo(new byte[] { 0x01, 0x00 })); } [Test] public void empty_FromBytes() { AMP.IAmpType lst = new AMP.Type.ListOf(new AMP.Type.Int32()); var empty = (List)lst.FromAmpBytes(new byte[] { }); Assert.That(empty.Count, Is.EqualTo(0)); } [Test] public void int32_FromBytes() { AMP.IAmpType lst = new AMP.Type.ListOf(new AMP.Type.Int32()); var res = (List)lst.FromAmpBytes(new byte[] { 0x00, 0x03, 0x31, 0x32, 0x33 }); Assert.That(res.Count, Is.EqualTo(1)); Assert.That((int)res[0], Is.EqualTo(123)); } [Test] public void empty_string_FromBytes() { AMP.IAmpType lst = new AMP.Type.ListOf(new AMP.Type.String()); var res = (List)lst.FromAmpBytes(new byte[] { 0x00, 0x00, 0x00, 0x00 }); Assert.That(res.Count, Is.EqualTo(2)); Assert.That((string)res[0], Is.EqualTo("")); Assert.That((string)res[1], Is.EqualTo("")); } [Test] [ExpectedException(typeof(AMP.Error.TypeDecodeError))] public void int32_error() { AMP.IAmpType lst = new AMP.Type.ListOf(new AMP.Type.Int32()); lst.FromAmpBytes(new byte[] { 0x00 }); } [Test] [ExpectedException(typeof(AMP.Error.TypeDecodeError))] public void int32_error2() { AMP.IAmpType lst = new AMP.Type.ListOf(new AMP.Type.Int32()); lst.FromAmpBytes(new byte[] { 0x00, 0x01 }); } } [TestFixture] public class AmpList { [Test] public void empty_ToBytes() { var alist = new AMP.Type.AmpList(); alist.AddParameter("a", new AMP.Type.String()); alist.AddParameter("b", new AMP.Type.Int32()); byte[] got = alist.ToAmpBytes(new List()); Assert.That(got.Length, Is.EqualTo(0)); } [Test] public void ToBytes() { var alist = new AMP.Type.AmpList(); alist.AddParameter("a", new AMP.Type.String()); alist.AddParameter("b", new AMP.Type.Int32()); var msgs = new List { new AMP.Msg { { "a", "AAA" }, { "b", 7 } } }; byte[] got = alist.ToAmpBytes(msgs); //System.Console.WriteLine("got {0}", got.ToHexString()); var expected = new byte[] { 0x00, 0x01, // key of length 1 follows 0x61, // 'a' 0x00, 0x03, // value of length 3 follows 0x41, 0x41, 0x41, // "AAA" 0x00, 0x01, // key of length 1 follows 0x62, // 'b' 0x00, 0x01, // value of length 1 follows 0x37, // '7' 0x00, 0x00 // key of length 0 follows. end of message. }; Assert.That(got, Is.EqualTo(expected)); msgs.Add(new AMP.Msg { { "a", "aaa" }, { "b", 3 } }); got = alist.ToAmpBytes(msgs); expected = new byte[] { 0x00, 0x01, // key of length 1 follows 0x61, // 'a' 0x00, 0x03, // value of length 3 follows 0x41, 0x41, 0x41, // "AAA" 0x00, 0x01, // key of length 1 follows 0x62, // 'b' 0x00, 0x01, // value of length 1 follows 0x37, // '7' 0x00, 0x00, // null key. end of message. 0x00, 0x01, // key of length 1 follows 0x61, // 'a' 0x00, 0x03, // value of length 3 follows 0x61, 0x61, 0x61, // "aaa" 0x00, 0x01, // key of length 1 follows 0x62, // 'b' 0x00, 0x01, // value of length 1 follows 0x33, // '3' 0x00, 0x00 // null key. end of message. }; Assert.That(got, Is.EqualTo(expected)); } [Test] public void empty_FromBytes() { var alist = new AMP.Type.AmpList(); alist.AddParameter("a", new AMP.Type.String()); alist.AddParameter("b", new AMP.Type.Int32()); var msgs = (List)alist.FromAmpBytes(new byte[] { }); Assert.That(msgs.Count, Is.EqualTo(0)); } [Test] public void FromBytes() { var alist = new AMP.Type.AmpList(); alist.AddParameter("a", new AMP.Type.String()); alist.AddParameter("b", new AMP.Type.Int32()); var bytes = new byte[] { 0x00, 0x01, // key of length 1 follows 0x61, // 'a' 0x00, 0x03, // value of length 3 follows 0x41, 0x41, 0x41, // "AAA" 0x00, 0x01, // key of length 1 follows 0x62, // 'b' 0x00, 0x01, // value of length 1 follows 0x37, // '7' 0x00, 0x00 // key of length 0 follows. end of message. }; var msgs = (List)alist.FromAmpBytes(bytes); Assert.That(msgs.Count, Is.EqualTo(1)); Assert.That(msgs[0].Count, Is.EqualTo(2)); Assert.That((string)msgs[0]["a"], Is.EqualTo("AAA")); Assert.That((int)msgs[0]["b"], Is.EqualTo(7)); bytes = new byte[] { 0x00, 0x01, // key of length 1 follows 0x61, // 'a' 0x00, 0x03, // value of length 3 follows 0x41, 0x41, 0x41, // "AAA" 0x00, 0x01, // key of length 1 follows 0x62, // 'b' 0x00, 0x01, // value of length 1 follows 0x37, // '7' 0x00, 0x00, // null key. end of message. 0x00, 0x01, // key of length 1 follows 0x61, // 'a' 0x00, 0x03, // value of length 3 follows 0x61, 0x61, 0x61, // "aaa" 0x00, 0x01, // key of length 1 follows 0x62, // 'b' 0x00, 0x01, // value of length 1 follows 0x33, // '3' 0x00, 0x00 // null key. end of message. }; msgs = (List)alist.FromAmpBytes(bytes); Assert.That(msgs.Count, Is.EqualTo(2)); Assert.That(msgs[0].Count, Is.EqualTo(2)); Assert.That((string)msgs[0]["a"], Is.EqualTo("AAA")); Assert.That((int)msgs[0]["b"], Is.EqualTo(7)); Assert.That(msgs[1].Count, Is.EqualTo(2)); Assert.That((string)msgs[1]["a"], Is.EqualTo("aaa")); Assert.That((int)msgs[1]["b"], Is.EqualTo(3)); } [Test] [ExpectedException(typeof(AMP.Error.ProtocolError))] public void FromBytes_error() { var alist = new AMP.Type.AmpList(); var bytes = new byte[] { 0x66, 0x01 }; // bogus key length alist.FromAmpBytes(bytes); } [Test] [ExpectedException(typeof(AMP.Error.ParameterNotFound))] public void FromBytes_missing_keys() { var alist = new AMP.Type.AmpList(); alist.AddParameter("a", new AMP.Type.String()); alist.AddParameter("b", new AMP.Type.Int32()); var bytes = new byte[] { 0x00, 0x01, // key of length 1 follows 0x61, // 'a' 0x00, 0x03, // value of length 3 follows 0x41, 0x41, 0x41, // "AAA" // NOTE missing 'b' key 0x00, 0x00 // key of length 0 follows. end of message. }; alist.FromAmpBytes(bytes); } [Test] public void FromBytes_extra_keys() { var alist = new AMP.Type.AmpList(); alist.AddParameter("a", new AMP.Type.String()); alist.AddParameter("b", new AMP.Type.Int32()); var bytes = new byte[] { 0x00, 0x01, // key of length 1 follows 0x61, // 'a' 0x00, 0x03, // value of length 3 follows 0x41, 0x41, 0x41, // "AAA" 0x00, 0x01, // key of length 1 follows 0x62, // 'b' 0x00, 0x01, // value of length 1 follows 0x37, // '7' 0x00, 0x01, // key of length 1 follows 0x63, // 'c' NOTE 'c' key is not part of the AmpList definition 0x00, 0x01, // value of length 1 follows 0x43, // 'C' 0x00, 0x00 // key of length 0 follows. end of message. }; var msgs = (List)alist.FromAmpBytes(bytes); // result should only contain keys defined in the AmpList Assert.That(msgs.Count, Is.EqualTo(1)); Assert.That(msgs[0].Count, Is.EqualTo(2)); // only "a" and "b" Assert.That((string)msgs[0]["a"], Is.EqualTo("AAA")); Assert.That((int)msgs[0]["b"], Is.EqualTo(7)); } } [TestFixture] public class TimeArgument { [Test] public void ToBytes_from_utc() { var atr = new AMP.Type.TimeArgument(); var dt = new DateTime(2008, 04, 16, 12, 30, 15, 200, DateTimeKind.Utc); byte[] value = atr.ToAmpBytes(dt); Assert.That(value, Is.EqualTo(B.b("2008/4/16 12:30:15"))); } [Test] public void ToBytes_from_local() { var atr = new AMP.Type.TimeArgument(); var dt = new DateTime(2008, 04, 16, 12, 30, 15, 200, DateTimeKind.Local); byte[] value = atr.ToAmpBytes(dt); byte[] expected = B.b(dt.ToUniversalTime().ToString("yyyy/M/d HH:mm:ss")); Assert.That(value, Is.EqualTo(expected)); } // TODO FromBytes test for TimeArgument } [TestFixture] public class Double { AMP.IAmpType d; [SetUp] public void SetUp() { d = new AMP.Type.Double(); } [Test] public void ToBytes_normal() { var s = Encoding.UTF8.GetString(d.ToAmpBytes(3.141592654)); Assert.That(s, Is.StringStarting("3.141592654")); s = Encoding.UTF8.GetString(d.ToAmpBytes(-2.718281828)); Assert.That(s, Is.StringStarting("-2.718281828")); } [Test] public void ToBytes_NaN() { // Not-a-number var s = Encoding.UTF8.GetString(d.ToAmpBytes(double.NaN)); Assert.That(s, Is.EqualTo("nan")); } [Test] public void ToBytes_pos_infinity() { // Positive Infinity var s = Encoding.UTF8.GetString(d.ToAmpBytes(double.PositiveInfinity)); Assert.That(s, Is.EqualTo("inf")); } [Test] public void ToBytes_neg_infinity() { // Negative Infinity var s = Encoding.UTF8.GetString(d.ToAmpBytes(double.NegativeInfinity)); Assert.That(s, Is.EqualTo("-inf")); } [Test] public void FromBytes_normal() { var num = (double)d.FromAmpBytes(Encoding.UTF8.GetBytes("3.141592654")); Assert.That(num, Is.InRange(3.1415926, 3.1415927)); num = (double)d.FromAmpBytes(Encoding.UTF8.GetBytes("-2.718281828")); Assert.That(num, Is.InRange(-2.7182819, -2.7182818)); } [Test] public void FromBytes_NaN() { // Not-a-number var num = (double)d.FromAmpBytes(Encoding.UTF8.GetBytes("nan")); Assert.That(num, Is.EqualTo(double.NaN)); } [Test] public void FromBytes_pos_infinity() { // Positive Infinity var num = (double)d.FromAmpBytes(Encoding.UTF8.GetBytes("inf")); Assert.That(num, Is.EqualTo(double.PositiveInfinity)); } [Test] public void FromBytes_neg_infinity() { // Negative Infinity var num = (double)d.FromAmpBytes(Encoding.UTF8.GetBytes("-inf")); Assert.That(num, Is.EqualTo(double.NegativeInfinity)); } } [TestFixture] public class DateTimeOffset { AMP.Type.DateTimeOffset d; [SetUp] public void SetUp() { d = new AMP.Type.DateTimeOffset(); } [Test] public void ToBytes_normal() { // set the current culture to use a TimeSeperator that is different than the // seperator we expect in the AMP DateTime string. This ensures that we // aren't relying on the current culture to encode the DateTimeOffset properly. var ci = new System.Globalization.CultureInfo("en-US"); var dtfi = new System.Globalization.DateTimeFormatInfo(); dtfi.TimeSeparator = "JUNK"; ci.DateTimeFormat = dtfi; Thread.CurrentThread.CurrentCulture = ci; var dt = new System.DateTimeOffset(2012, 01, 23, 12, 34, 56, 054, new TimeSpan(1, 23, 0)); var s = Encoding.UTF8.GetString(d.ToAmpBytes(dt)); Assert.That(s, Is.EqualTo("2012-01-23T12:34:56.054000+01:23")); dt = new System.DateTimeOffset(2012, 01, 23, 18, 34, 56, 123, new TimeSpan(1, 23, 0).Negate()); s = Encoding.UTF8.GetString(d.ToAmpBytes(dt)); Assert.That(s, Is.EqualTo("2012-01-23T18:34:56.123000-01:23")); } [Test] public void FromBytes_normal() { byte[] bytes = Encoding.UTF8.GetBytes("2012-01-23T12:34:56.054000+01:23"); var dt = (System.DateTimeOffset)d.FromAmpBytes(bytes); Assert.That(dt, Is.EqualTo(new System.DateTimeOffset(2012, 01, 23, 12, 34, 56, 054, new TimeSpan(1, 23, 0)))); bytes = Encoding.UTF8.GetBytes("2012-01-23T18:34:56.123000-01:23"); dt = (System.DateTimeOffset)d.FromAmpBytes(bytes); Assert.That(dt, Is.EqualTo(new System.DateTimeOffset(2012, 01, 23, 18, 34, 56, 123, new TimeSpan(1, 23, 0).Negate()))); } } } } Tests/Benchmarks/0000755000175000017500000000000011613431120014503 5ustar teratornteratornTests/Benchmarks/Benchmarks.csproj0000644000175000017500000000442711613431120020011 0ustar teratornteratorn Debug AnyCPU 9.0.21022 2.0 {5ACA924D-7792-48A6-8BD4-FC28D0E81318} Exe Properties Benchmarks Benchmarks v2.0 512 true full false bin\Debug\ DEBUG;TRACE prompt 4 pdbonly true bin\Release\ TRACE prompt 4 {E57E8D4F-56CD-4630-BDCC-97E44FAC4ABC} AMPSharp Tests/Benchmarks/bench.cs0000644000175000017500000000521611613431120016115 0ustar teratornteratorn/* Copyright (c) 2008-2011 - Eric P. Mangold * Released under the terms of the MIT/X11 license - see LICENSE.txt */ using System; using System.Collections.Generic; using System.Text; using System.Net; using System.Threading; using System.Net.Sockets; namespace Benchmarks { class EmptyCommand: AMP.Command { public EmptyCommand() : base("EmptyCommand") {} } class bench { static AMP.Command cmd; static int port; static TcpListener listener; static void Main(string[] args) { cmd = new EmptyCommand(); listener = new TcpListener(0); listener.Start(5); port = ((IPEndPoint)listener.LocalEndpoint).Port; Console.WriteLine("Listening on port {0}", port); var s = new Thread(Server); s.Start(); Client(); } static void Server() { TcpClient client; AMP.Protocol protocol; // the AMP.Command's we're going to respond to while (true) { client = listener.AcceptTcpClient(); protocol = new AMP.Protocol(client.GetStream()); // we could also pass a 'state' argument to RegisterResponder // which becomes the 'state' argument in the responders (below). // Since we aren't passing anything, then state will always be null protocol.RegisterResponder(cmd, handleCmd); protocol.StartReading(); // start the async data-reading loop } } static Object handleCmd(AMP.Msg msg, Object state) { return new AMP.Msg {}; } static void Client() { var client = new TcpClient("localhost", port); var protocol = new AMP.Protocol(client.GetStream()); protocol.StartReading(); // start the async data-reading loop int block = 10000; int count = 0; var start = DateTime.Now; double howLong; var msg = new AMP.Msg { }; while (true) { protocol.CallRemote(cmd, msg); count++; if (count == block) { count = 0; howLong = (DateTime.Now - start).TotalSeconds; System.Console.WriteLine("{0:0} AMP/s", block / howLong); start = DateTime.Now; } } } } } Tests/Benchmarks/Properties/0000755000175000017500000000000011613431120016637 5ustar teratornteratornTests/Benchmarks/Properties/AssemblyInfo.cs0000644000175000017500000000304111613431120021557 0ustar teratornteratorn/* Copyright (c) 2008-2011 - Eric P. Mangold * Released under the terms of the MIT/X11 license - see LICENSE.txt */ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Benchmarks")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Microsoft")] [assembly: AssemblyProduct("Benchmarks")] [assembly: AssemblyCopyright("Copyright © Microsoft 2011")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("fd70f95b-2303-45c0-b7d9-b9ca1df614e4")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] Tests/Properties/0000755000175000017500000000000011613431120014562 5ustar teratornteratornTests/Properties/AssemblyInfo.cs0000644000175000017500000000311211613431120017501 0ustar teratornteratorn/* Copyright (c) 2008-2011 - Eric P. Mangold * Released under the terms of the MIT/X11 license - see LICENSE.txt */ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Tests")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Microsoft")] [assembly: AssemblyProduct("Tests")] [assembly: AssemblyCopyright("Copyright © Microsoft 2010")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("7f25e373-4b12-4880-bcc7-7b7b9de32346")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyKeyFileAttribute("tests.snk")] Tests/test_deferred.cs0000644000175000017500000000231011613431120015570 0ustar teratornteratorn/* Copyright (c) 2008-2011 - Eric P. Mangold * Released under the terms of the MIT/X11 license - see LICENSE.txt */ namespace AMP.Tests { using System; using NUnit.Framework; using NUnit.Framework.Constraints; using System.Text; [TestFixture] public class DeferredResponseTests { AMP.DeferredResponse deferred; [SetUp] public void Init() { deferred = new AMP.DeferredResponse(); } [Test] public void set_then_callback() { Object callbackArg = null; deferred.setCallback(obj => { callbackArg = obj; }); Assert.That(callbackArg, Is.Null); deferred.respond((Object)"hello"); Assert.That("hello", Is.EqualTo((string)callbackArg)); } [Test] public void callback_then_set() { Object callbackArg = null; deferred.respond((Object)"hello"); Assert.That(callbackArg, Is.Null); deferred.setCallback(obj => { callbackArg = obj; }); Assert.That("hello", Is.EqualTo((string)callbackArg)); } } } Tests/Tests.csproj0000644000175000017500000000540611613431120014757 0ustar teratornteratorn Debug AnyCPU 9.0.21022 2.0 {EF0B4953-2C57-42D5-987E-DAC8F0710365} Library Properties Tests Tests v3.5 512 true full false bin\Debug\ DEBUG;TRACE prompt 4 pdbonly true bin\Release\ TRACE prompt 4 True True 3.5 Form {E57E8D4F-56CD-4630-BDCC-97E44FAC4ABC} AMPSharp Tests/tests.snk0000644000175000017500000000112411613431120014303 0ustar teratornteratorn$RSA2+t%H-#9JBuMp` !FqX3v2jfҴ2D?w څgFT8j֛$w1< ~4EbvMu`WmIM<N|=xMN E@odI |9UVyĊ< Z[ ^Lv57,d>Z~hq7!8&$BRC C<>e,8