diff --git a/apps/sam/csharp/src/I2P.SAM.Client/AssemblyInfo.cs b/apps/sam/csharp/src/I2P.SAM.Client/AssemblyInfo.cs new file mode 100644 index 000000000..f7d478392 --- /dev/null +++ b/apps/sam/csharp/src/I2P.SAM.Client/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following +// attributes. +// +// change them to the information which is associated with the assembly +// you compile. + +[assembly: AssemblyTitle("sam-sharp")] +[assembly: AssemblyDescription("Mono/.NET SAM client for I2P")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("sam-sharp")] +[assembly: AssemblyCopyright("Released into the Public Domain")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has following format : +// +// Major.Minor.Build.Revision +// +// You can specify all values by your own or you can build default build and revision +// numbers with the '*' character (the default): + +[assembly: AssemblyVersion("0.1")] + +// The following attributes specify the key for the sign of your assembly. See the +// .NET Framework documentation for more information about signing. +// This is not required, if you don't want signing let these attributes like they're. +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] diff --git a/apps/sam/csharp/src/I2P.SAM.Client/SamBridgeMessages.cs b/apps/sam/csharp/src/I2P.SAM.Client/SamBridgeMessages.cs new file mode 100644 index 000000000..df96c9643 --- /dev/null +++ b/apps/sam/csharp/src/I2P.SAM.Client/SamBridgeMessages.cs @@ -0,0 +1,26 @@ +namespace I2P.SAM.Client +{ + public struct SamBridgeMessages + { + public const string NAMING_REPLY_INVALID_KEY = "INVALID_KEY"; + public const string NAMING_REPLY_KEY_NOT_FOUND = "KEY_NOT_FOUND"; + public const string NAMING_REPLY_OK = "OK"; + + public const string SESSION_STATUS_DUPLICATE_DEST = "DUPLICATE_DEST"; + public const string SESSION_STATUS_I2P_ERROR = "I2P_ERROR"; + public const string SESSION_STATUS_INVALID_KEY = "INVALID_KEY"; + public const string SESSION_STATUS_OK = "OK"; + + public const string STREAM_CLOSED_CANT_REACH_PEER = "CANT_REACH_PEER"; + public const string STREAM_CLOSED_I2P_ERROR = "I2P_ERROR"; + public const string STREAM_CLOSED_PEER_NOT_FOUND = "PEER_NOT_FOUND"; + public const string STREAM_CLOSED_TIMEOUT = "CLOSED"; + public const string STREAM_CLOSED_OK = "OK"; + + public const string STREAM_STATUS_CANT_REACH_PEER = "CANT_REACH_PEER"; + public const string STREAM_STATUS_I2P_ERROR = "I2P_ERROR"; + public const string STREAM_STATUS_INVALID_KEY = "INVALID_KEY"; + public const string STREAM_STATUS_TIMEOUT = "TIMEOUT"; + public const string STREAM_STATUS_OK = "OK"; + } +} diff --git a/apps/sam/csharp/src/I2P.SAM.Client/SamClientEventListener.cs b/apps/sam/csharp/src/I2P.SAM.Client/SamClientEventListener.cs new file mode 100644 index 000000000..ab6459d33 --- /dev/null +++ b/apps/sam/csharp/src/I2P.SAM.Client/SamClientEventListener.cs @@ -0,0 +1,20 @@ +using System.Collections.Specialized; + +namespace I2P.SAM.Client +{ + /// + /// Async event notification interface for SAM clients. + /// + public interface SamClientEventListener + { + void HelloReplyReceived(bool ok); + void SessionStatusReceived(string result, string destination, string message); + void StreamStatusReceived(string result, int id, string message); + void StreamConnectedReceived(string remoteDestination, int id); + void StreamClosedReceived(string result, int id, string message); + void StreamDataReceived(int id, byte[] data, int offset, int length); + void NamingReplyReceived(string name, string result, string valueString, string message); + void DestReplyReceived(string publicKey, string privateKey); + void UnknownMessageReceived(string major, string minor, NameValueCollection parameters); + } +} \ No newline at end of file diff --git a/apps/sam/csharp/src/I2P.SAM.Client/SamClientEventListenerImpl.cs b/apps/sam/csharp/src/I2P.SAM.Client/SamClientEventListenerImpl.cs new file mode 100644 index 000000000..edd304558 --- /dev/null +++ b/apps/sam/csharp/src/I2P.SAM.Client/SamClientEventListenerImpl.cs @@ -0,0 +1,20 @@ +using System.Collections.Specialized; + +namespace I2P.SAM.Client +{ + /// + /// Basic noop client event listener. + /// + public class SamClientEventListenerImpl : SamClientEventListener + { + public virtual void DestReplyReceived(string publicKey, string privateKey) {} + public virtual void HelloReplyReceived(bool ok) {} + public virtual void NamingReplyReceived(string name, string result, string valueString, string message) {} + public virtual void SessionStatusReceived(string result, string destination, string message) {} + public virtual void StreamClosedReceived(string result, int id, string message) {} + public virtual void StreamConnectedReceived(string remoteDestination, int id) {} + public virtual void StreamDataReceived(int id, byte[] data, int offset, int length) {} + public virtual void StreamStatusReceived(string result, int id, string message) {} + public virtual void UnknownMessageReceived(string major, string minor, NameValueCollection parameters) {} + } +} diff --git a/apps/sam/csharp/src/I2P.SAM.Client/SamEventHandler.cs b/apps/sam/csharp/src/I2P.SAM.Client/SamEventHandler.cs new file mode 100644 index 000000000..0dfaf19c5 --- /dev/null +++ b/apps/sam/csharp/src/I2P.SAM.Client/SamEventHandler.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Specialized; +using System.Threading; + +namespace I2P.SAM.Client +{ + /// + /// Simple helper implementation of a SamClientEventListener. + /// + public class SamEventHandler : SamClientEventListenerImpl + { + private object _helloLock = new Object(); + private String _helloOk; + private NameValueCollection _namingReplies = new NameValueCollection(); + private object _namingReplyLock = new Object(); + private object _sessionCreateLock = new Object(); + private String _sessionCreateOk; + + public override void HelloReplyReceived(bool ok) { + lock (_helloLock) { + if (ok) + _helloOk = Boolean.TrueString; + else + _helloOk = Boolean.FalseString; + + Monitor.PulseAll(_helloLock); + } + } + + public override void NamingReplyReceived(string name, string result, string valueString, string message) { + lock (_namingReplyLock) { + if (result.Equals(SamBridgeMessages.NAMING_REPLY_OK)) + _namingReplies.Add(name, valueString); + else + _namingReplies.Add(name, result); + + Monitor.PulseAll(_namingReplyLock); + } + } + + public override void SessionStatusReceived(string result, string destination, string message) { + lock (_sessionCreateLock) { + if (result.Equals(SamBridgeMessages.SESSION_STATUS_OK)) + _sessionCreateOk = Boolean.TrueString; + else + _sessionCreateOk = Boolean.FalseString; + + Monitor.PulseAll(_sessionCreateLock); + } + } + + public override void UnknownMessageReceived(string major, string minor, NameValueCollection parameters) { + Console.WriteLine("wrt, [" + major + "] [" + minor + "] [" + parameters + "]"); + } + + /* + * Blocking lookup calls below. + */ + + /// + /// Wait for the connection to be established, returning true if + /// everything went ok. + /// + public bool WaitForHelloReply() { + while (true) { + lock (_helloLock) { + if (_helloOk == null) + Monitor.Wait(_helloLock); + else + return Boolean.Parse(_helloOk); + } + } + } + + /// + /// Return the destination found matching the name, or null if + /// the key was not able to be retrieved. + /// + /// The name to be looked for, or "ME". + public string WaitForNamingReply(string name) { + while (true) { + lock (_namingReplyLock) { + try { + string valueString = _namingReplies[name]; + _namingReplies.Remove(name); + + if (valueString.Equals(SamBridgeMessages.NAMING_REPLY_INVALID_KEY)) + return null; + else if (valueString.Equals(SamBridgeMessages.NAMING_REPLY_KEY_NOT_FOUND)) + return null; + else + return valueString; + + } catch (ArgumentNullException ane) { + Monitor.Wait(_namingReplyLock); + } + } + } + } + + /// + /// Wait for the session to be created, returning true if everything + /// went ok. + /// + public bool WaitForSessionCreateReply() { + while (true) { + lock (_sessionCreateLock) { + if (_sessionCreateOk == null) + Monitor.Wait(_sessionCreateLock); + else + return Boolean.Parse(_sessionCreateOk); + } + } + } + } +} \ No newline at end of file diff --git a/apps/sam/csharp/src/I2P.SAM.Client/SamReader.cs b/apps/sam/csharp/src/I2P.SAM.Client/SamReader.cs new file mode 100644 index 000000000..ac99d7d29 --- /dev/null +++ b/apps/sam/csharp/src/I2P.SAM.Client/SamReader.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections; +using System.Collections.Specialized; +using System.IO; +using System.Net.Sockets; +using System.Text; +using System.Threading; + +namespace I2P.SAM.Client +{ + /// + /// Read from a socket, producing events for any SAM message read. + /// + public class SamReader + { + private bool _isLive; + private SamClientEventListener _listener; + private NetworkStream _samStream; + private StreamReader _streamReader; + + public SamReader(NetworkStream samStream, SamClientEventListener listener) { + _samStream = samStream; + _listener = listener; + } + + public void RunThread() { + + NameValueCollection parameters = new NameValueCollection(); + + while (_isLive) { + + string line = null; + + _streamReader = new StreamReader(_samStream); + + try { + line = _streamReader.ReadLine(); + _streamReader.Close(); + } catch (IOException ioe) { + Console.Error.WriteLine("Error reading from SAM: {1}", ioe); + } catch (OutOfMemoryException oome) { + Console.Error.WriteLine("Out of memory while reading from SAM: {1}", oome); + return; + } + + if (line == null) { + break; + } + + string[] tokens = line.Split(new char[1] { ' ' }); + + if (tokens.Length < 2) { + Console.Error.WriteLine("Invalid SAM line: [" + line + "]"); + _isLive = false; + return; + } + + IEnumerator tokensEnumerator = tokens.GetEnumerator(); + tokensEnumerator.MoveNext(); + string major = (string) tokensEnumerator.Current; + tokensEnumerator.MoveNext(); + string minor = (string) tokensEnumerator.Current; + + parameters.Clear(); + + while (tokensEnumerator.MoveNext()) { + + string pair = (string) tokensEnumerator.Current; + int equalsPosition = pair.IndexOf('='); + + if ( (equalsPosition > 0) && (equalsPosition < pair.Length - 1) ) { + + string name = pair.Substring(0, equalsPosition); + string valueString = pair.Substring(equalsPosition + 1); + + while ( (valueString[0] == '\"') && (valueString.Length > 0) ) + valueString = valueString.Substring(1); + + while ( (valueString.Length > 0) && (valueString[valueString.Length - 1] == '\"') ) + valueString = valueString.Substring(0, valueString.Length - 1); + + parameters.Set(name, valueString); + } + } + + ProcessEvent(major, minor, parameters); + } + } + + private void ProcessEvent(string major, string minor, NameValueCollection parameters) { + + switch (major) + { + case "HELLO" : + + if (minor.Equals("REPLY")) { + + string result = parameters.Get("RESULT"); + + if (result.Equals("OK")) + _listener.HelloReplyReceived(true); + else + _listener.HelloReplyReceived(false); + } else { + _listener.UnknownMessageReceived(major, minor, parameters); + } + + break; + + case "SESSION" : + + if (minor.Equals("STATUS")) { + + string result = parameters.Get("RESULT"); + string destination = parameters.Get("DESTINATION"); + string message = parameters.Get("MESSAGE"); + + _listener.SessionStatusReceived(result, destination, message); + } else { + _listener.UnknownMessageReceived(major, minor, parameters); + } + + break; + + case "STREAM" : + + ProcessStream(major, minor, parameters); + break; + + case "NAMING" : + + if (minor.Equals("REPLY")) { + + string name = parameters.Get("NAME"); + string result = parameters.Get("RESULT"); + string valueString = parameters.Get("VALUE"); + string message = parameters.Get("MESSAGE"); + + _listener.NamingReplyReceived(name, result, valueString, message); + } else { + _listener.UnknownMessageReceived(major, minor, parameters); + } + + break; + + case "DEST" : + + if (minor.Equals("REPLY")) { + + string pub = parameters.Get("PUB"); + string priv = parameters.Get("PRIV"); + + _listener.DestReplyReceived(pub, priv); + } else { + _listener.UnknownMessageReceived(major, minor, parameters); + } + + break; + + default : + + _listener.UnknownMessageReceived(major, minor, parameters); + break; + } + } + + private void ProcessStream(string major, string minor, NameValueCollection parameters) { + + /* + * Would use another tidy switch() statement here but the Mono + * compiler presently gets variable scopes confused within nested + * switch() contexts. Broken with Mono/mcs 1.0.5, 1.1.3, and SVN + * head. + */ + if (minor.Equals("STATUS")) { + + string result = parameters.Get("RESULT"); + string id = parameters.Get("ID"); + string message = parameters.Get("MESSAGE"); + + try { + _listener.StreamStatusReceived(result, Int32.Parse(id), message); + } catch { + _listener.UnknownMessageReceived(major, minor, parameters); + } + + } else if (minor.Equals("CONNECTED")) { + + string destination = parameters.Get("DESTINATION"); + string id = parameters.Get("ID"); + + try { + _listener.StreamConnectedReceived(destination, Int32.Parse(id)); + } catch { + _listener.UnknownMessageReceived(major, minor, parameters); + } + + } else if (minor.Equals("CLOSED")) { + + string result = parameters.Get("RESULT"); + string id = parameters.Get("ID"); + string message = parameters.Get("MESSAGE"); + + try { + _listener.StreamClosedReceived(result, Int32.Parse(id), message); + } catch { + _listener.UnknownMessageReceived(major, minor, parameters); + } + + } else if (minor.Equals("RECEIVED")) { + + string id = parameters.Get("ID"); + string size = parameters.Get("SIZE"); + + if (id != null) { + try { + + int idValue = Int32.Parse(id); + int sizeValue = Int32.Parse(size); + byte[] data = new byte[sizeValue]; + int bytesRead; + + try { + bytesRead = _samStream.Read(data, 0, sizeValue); + + if (bytesRead != sizeValue) { + _listener.UnknownMessageReceived(major, minor, parameters); + return; + } + } catch { + _isLive = false; + _listener.UnknownMessageReceived(major, minor, parameters); + } + + _listener.StreamDataReceived(idValue, data, 0, sizeValue); + } catch (FormatException fe) { + _listener.UnknownMessageReceived(major, minor, parameters); + } + } else { + _listener.UnknownMessageReceived(major, minor, parameters); + } + } else { + _listener.UnknownMessageReceived(major, minor, parameters); + } + } + + public void StartReading() { + _isLive = true; + ThreadStart threadStart = new ThreadStart(RunThread); + Thread thread = new Thread(threadStart); + thread.Name = "SAM Reader"; + thread.Start(); + } + + public void StopReading() { + _isLive = false; + } + } +} diff --git a/apps/sam/csharp/src/SAM.NET/SAM.NET.Test/AssemblyInfo.cs b/apps/sam/csharp/src/SAM.NET/SAM.NET.Test/AssemblyInfo.cs deleted file mode 100644 index 9f89a3282..000000000 --- a/apps/sam/csharp/src/SAM.NET/SAM.NET.Test/AssemblyInfo.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -// -// 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("")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// -// 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 Revision and Build Numbers -// by using the '*' as shown below: - -[assembly: AssemblyVersion("1.0.*")] - -// -// In order to sign your assembly you must specify a key to use. Refer to the -// Microsoft .NET Framework documentation for more information on assembly signing. -// -// Use the attributes below to control which key is used for signing. -// -// Notes: -// (*) If no key is specified, the assembly is not signed. -// (*) KeyName refers to a key that has been installed in the Crypto Service -// Provider (CSP) on your machine. KeyFile refers to a file which contains -// a key. -// (*) If the KeyFile and the KeyName values are both specified, the -// following processing occurs: -// (1) If the KeyName can be found in the CSP, that key is used. -// (2) If the KeyName does not exist and the KeyFile does exist, the key -// in the KeyFile is installed into the CSP and used. -// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. -// When specifying the KeyFile, the location of the KeyFile should be -// relative to the project output directory which is -// %Project Directory%\obj\. For example, if your KeyFile is -// located in the project directory, you would specify the AssemblyKeyFile -// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] -// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework -// documentation for more information on this. -// -[assembly: AssemblyDelaySign(false)] -[assembly: AssemblyKeyFile("")] -[assembly: AssemblyKeyName("")] diff --git a/apps/sam/csharp/src/SAM.NET/SAM.NET.Test/SAM.NET.Test.cs b/apps/sam/csharp/src/SAM.NET/SAM.NET.Test/SAM.NET.Test.cs deleted file mode 100644 index e3805c7b8..000000000 --- a/apps/sam/csharp/src/SAM.NET/SAM.NET.Test/SAM.NET.Test.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Net; -using System.Threading; -using System.Text; -using System.Collections; - -namespace SAM.NET -{ - class SAMTester - { - [STAThread] - static void Main(string[] args) - { - new SAMTester(); - } - public SAMTester () - { - SAMConnection connection1 = new SAMConnection(IPAddress.Parse("127.0.0.1"),7656); - SAMSession session1 = new SAMSession(connection1,SAM.NET.SamSocketType.Stream,"alice"); - - SAMConnection connection2 = new SAMConnection(IPAddress.Parse("127.0.0.1"),7656); - SAMSession session2 = new SAMSession(connection2,SAM.NET.SamSocketType.Stream,"bob"); - - SAMStream stream1 = new SAMStream(connection1,session1,233); - stream1.Connect(session2.getKey()); - - //Wait till we are connected to destination - while (!stream1.isConnected) - Thread.Sleep(1000); - - //Send some bytes - stream1.Write(Encoding.ASCII.GetBytes(DateTime.Now.ToLongTimeString() + "Hi!!!!!!")); - - //Wait till a stream magically appears on the other side - while (session2.getStreams().Count == 0) Thread.Sleep(1000); - - Thread.Sleep(1000); - foreach (SAMStream stream in session2.getStreams().Values) - { - Console.WriteLine("Text received on " + stream.getID() + " at " + DateTime.Now.ToLongTimeString()); - Console.WriteLine(Encoding.ASCII.GetString(stream.ReadToEnd())); - stream.Close(); - } - - stream1.Close(); - connection1.Close(); - connection2.Close(); - } - } -} diff --git a/apps/sam/csharp/src/SAM.NET/SAM.NET/AssemblyInfo.cs b/apps/sam/csharp/src/SAM.NET/SAM.NET/AssemblyInfo.cs deleted file mode 100644 index 9f89a3282..000000000 --- a/apps/sam/csharp/src/SAM.NET/SAM.NET/AssemblyInfo.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -// -// 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("")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// -// 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 Revision and Build Numbers -// by using the '*' as shown below: - -[assembly: AssemblyVersion("1.0.*")] - -// -// In order to sign your assembly you must specify a key to use. Refer to the -// Microsoft .NET Framework documentation for more information on assembly signing. -// -// Use the attributes below to control which key is used for signing. -// -// Notes: -// (*) If no key is specified, the assembly is not signed. -// (*) KeyName refers to a key that has been installed in the Crypto Service -// Provider (CSP) on your machine. KeyFile refers to a file which contains -// a key. -// (*) If the KeyFile and the KeyName values are both specified, the -// following processing occurs: -// (1) If the KeyName can be found in the CSP, that key is used. -// (2) If the KeyName does not exist and the KeyFile does exist, the key -// in the KeyFile is installed into the CSP and used. -// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. -// When specifying the KeyFile, the location of the KeyFile should be -// relative to the project output directory which is -// %Project Directory%\obj\. For example, if your KeyFile is -// located in the project directory, you would specify the AssemblyKeyFile -// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] -// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework -// documentation for more information on this. -// -[assembly: AssemblyDelaySign(false)] -[assembly: AssemblyKeyFile("")] -[assembly: AssemblyKeyName("")] diff --git a/apps/sam/csharp/src/SAM.NET/SAM.NET/SAM.NET.cs b/apps/sam/csharp/src/SAM.NET/SAM.NET/SAM.NET.cs deleted file mode 100644 index 96ff4e09b..000000000 --- a/apps/sam/csharp/src/SAM.NET/SAM.NET/SAM.NET.cs +++ /dev/null @@ -1,271 +0,0 @@ -using System; -using System.Net.Sockets; -using System.Text; -using System.Net; -using System.IO; -using System.Collections; -using System.Threading; - -namespace SAM.NET -{ - public enum SamSocketType - { - Stream, - Datagram, - Raw - } - - public class SAMConnection - { - private const string propertyMinVersion = "1.0"; - private const string propertyMaxVersion = "1.0"; - - private Socket _sock; - private NetworkStream _sockStream; - private StreamReader _sockStreamIn; - private StreamWriter _sockStreamOut; - - public SAMConnection(IPAddress routerIP, int port) - { - _sock = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); - IPEndPoint rEP = new IPEndPoint(routerIP,port); - _sock.Connect(rEP); - _sockStream = new NetworkStream(_sock); - _sockStreamIn = new StreamReader(_sockStream); - _sockStreamOut = new StreamWriter(_sockStream); - try - { - sendVersion(propertyMinVersion,propertyMinVersion); - } - catch (Exception e) - { - _sock.Close(); - throw (new Exception("No SAM for you :(")); - } - - } - - private void sendVersion(string min, string max) - { - _sockStreamOut.WriteLine("HELLO VERSION MIN=" + propertyMinVersion + " MAX=" + propertyMaxVersion); - _sockStreamOut.Flush(); - Hashtable response = SAMUtil.parseKeyValues(_sockStreamIn.ReadLine(),2); - if (response["RESULT"].ToString() != "OK") throw (new Exception("Version mismatch")); - } - - public StreamWriter getOutputStream() - { - return _sockStreamOut; - } - - public StreamReader getInputStream() - { - return _sockStreamIn; - } - - public NetworkStream getStream() - { - return _sockStream; - } - - public void Close() - { - _sock.Close(); - } - } - - /* - * Creating a SAMSession object will automatically: - * 1) create a sesion on SAM - * 1) query for the base64key - * 2) start a listening thread to catch all stream commands - */ - public class SAMSession - { - private Hashtable _streams; - private string _sessionKey; - - public SAMSession (SAMConnection connection, SamSocketType type, string destination) - { - _streams = new Hashtable(); - StreamWriter writer = connection.getOutputStream(); - StreamReader reader = connection.getInputStream(); - writer.WriteLine("SESSION CREATE STYLE=STREAM DESTINATION=" + destination); - writer.Flush(); - Hashtable response = SAMUtil.parseKeyValues(reader.ReadLine(),2); - if (response["RESULT"].ToString() != "OK") - { - throw (new Exception(response["MESSAGE"].ToString())); - } - else - { - writer.WriteLine("NAMING LOOKUP NAME=ME"); - writer.Flush(); - response = SAMUtil.parseKeyValues(reader.ReadLine(),2); - _sessionKey = response["VALUE"].ToString(); - SAMSessionListener listener = new SAMSessionListener(connection,this,_streams); - new Thread(new ThreadStart(listener.startListening)).Start(); - } - } - public void addStream(SAMStream stream) - { - _streams.Add(stream.getID(),stream); - } - public string getKey() - { - return _sessionKey; - } - public Hashtable getStreams() - { - return _streams; - } - } - - public class SAMSessionListener - { - private Hashtable _streams; - private SAMConnection _connection; - private SAMSession _session; - private bool stayAlive = true; - - public SAMSessionListener(SAMConnection connection,SAMSession session, Hashtable streams) - { - _streams = streams; - _connection = connection; - _session = session; - } - public void startListening() - { - StreamReader reader = _connection.getInputStream(); - while (stayAlive) - { - string response = reader.ReadLine(); - if (response.StartsWith("STREAM STATUS")) - { - Hashtable values = SAMUtil.parseKeyValues(response,2); - SAMStream theStream = (SAMStream)_streams[int.Parse(values["ID"].ToString())]; - if (theStream != null) theStream.ReceivedStatus(values); - } - if (response.StartsWith("STREAM CONNECTED")) - { - Hashtable values = SAMUtil.parseKeyValues(response,2); - SAMStream theStream = (SAMStream)_streams[int.Parse(values["ID"].ToString())]; - if (theStream != null) theStream.isConnected = true; - } - if (response.StartsWith("STREAM RECEIVED")) - { - Hashtable values = SAMUtil.parseKeyValues(response,2); - int streamID = int.Parse(values["ID"].ToString()); - SAMStream theStream = (SAMStream)_streams[streamID]; - if (theStream == null) new SAMStream(_connection,_session,streamID); - theStream = (SAMStream)_streams[streamID]; - theStream.ReceivedData(int.Parse(values["SIZE"].ToString())); - } - if (response.StartsWith("STREAM CLOSE")) - { - Hashtable values = SAMUtil.parseKeyValues(response,2); - SAMStream theStream = (SAMStream)_streams[int.Parse(values["ID"].ToString())]; - if (theStream != null) theStream.isConnected = false; - } - } - } - } - - public class SAMStream - { - private int _ID; - private byte[] _data; - private int _position=0; - private int _size=0; - private SAMSession _session; - private SAMConnection _connection; - public bool isConnected=false; - - public SAMStream (SAMConnection connection,SAMSession session, int ID) - { - _data = new byte[100000]; //FIXME: change to non-static structure for storing stream data - _ID = ID; - _connection = connection; - _session = session; - _session.addStream(this); - } - - public void Connect(string destination) - { - StreamWriter writer = _connection.getOutputStream(); - writer.WriteLine("STREAM CONNECT ID=" + _ID.ToString() + " DESTINATION=" + destination); - writer.Flush(); - } - - public void ReceivedData(int size) //FIXME: WTF is going on when reading the payload here? All zeros and way too many of them. - { - NetworkStream stream = _connection.getStream(); - int bytesRead = stream.Read(_data,_size,size); - _size = _size + bytes; - } - - public void ReceivedStatus(Hashtable response) - { - if (response["RESULT"].ToString() != "OK") - { - throw (new Exception(response["RESULT"].ToString())); - } - else - { - isConnected = true; - } - } - - public int getID() {return _ID;} - - public bool DataAvailable() - { - return _position != _size; - } - - public void Write(byte[] buf) - { - NetworkStream stream = _connection.getStream(); - int sent = 0; - while (sent < buf.Length) - { - int toSend = Math.Min(buf.Length - sent,32768); - string header = "STREAM SEND ID=" + _ID.ToString() + " SIZE=" + toSend.ToString() + "\n"; - byte[] headerbytes = Encoding.ASCII.GetBytes(header); - stream.Write(headerbytes,0,headerbytes.Length); - stream.Write(buf,sent,toSend); - sent = sent + toSend; - } - } - - public byte[] ReadToEnd() - { - byte[] ret = new byte[_size - _position]; - Array.Copy(_data,_position,ret,0,_size - _position); - _position = _size; - return ret; - } - - public void Close() - { - StreamWriter writer = _connection.getOutputStream(); - writer.WriteLine("STREAM CLOSE " + _ID.ToString()); - writer.Flush(); - } - } - - public class SAMUtil - { - public static Hashtable parseKeyValues(string str, int startingWord) - { - Hashtable hash = new Hashtable(); - string strTruncated = string.Join(" ",str.Split(' '),startingWord,str.Split(' ').Length - startingWord); - string[] sets = strTruncated.Split('=',' '); - for (int i=0; i