Set StratumTest as startup project
[StratumLibrary.git] / StratumLibrary / Stratum.cs
1 \feffusing System;\r
2 using System.Net;\r
3 using System.Threading;\r
4 using System.Net.Sockets;\r
5 using Newtonsoft.Json;\r
6 using Newtonsoft.Json.Linq;\r
7 \r
8 using System.Text;\r
9 using System.Collections.Generic;\r
10 \r
11 namespace Stratum\r
12 {\r
13     public class Stratum\r
14     {\r
15         private Socket client;\r
16 \r
17         object responsesLock = new object();\r
18         private Dictionary<string, string> responses = new Dictionary<string, string>();\r
19         ManualResetEvent gotResponse = new ManualResetEvent(false);\r
20 \r
21         /// <summary>\r
22         /// Constructor of Stratum interface class\r
23         /// </summary>\r
24         /// <param name="ipAddress">IPv4 address</param>\r
25         /// <param name="port">Port number</param>\r
26         public Stratum(string ipAddress, int port)\r
27         {\r
28             // End point for the remote device\r
29             IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse(ipAddress), port);\r
30 \r
31             // Create TCP socket\r
32             client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);\r
33 \r
34             // Connect done event\r
35             ManualResetEvent connectDone = new ManualResetEvent(false);\r
36 \r
37             Action<IAsyncResult> ConnectCallback = null;\r
38             ConnectCallback = (IAsyncResult ar) =>\r
39             {\r
40                 // Retrieve socket from the state object\r
41                 Socket arClient = (Socket)ar.AsyncState;\r
42 \r
43                 // Complete the connection\r
44                 arClient.EndConnect(ar);\r
45 \r
46                 // Signal that connection has been established\r
47                 connectDone.Set();\r
48             };\r
49 \r
50             // Connect to the remote device\r
51             client.BeginConnect(remoteEP, new AsyncCallback(ConnectCallback), client);\r
52 \r
53             // Wait for signal\r
54             connectDone.WaitOne();\r
55 \r
56             // Start receive handler\r
57             Receiver(client);\r
58         }\r
59 \r
60         ~Stratum()\r
61         {\r
62             // Release the socket\r
63             client.Shutdown(SocketShutdown.Both);\r
64             client.Close();\r
65         }\r
66 \r
67         private void Send(Socket client, String data)\r
68         {\r
69             // Send done event\r
70             ManualResetEvent sendDone = new ManualResetEvent(false);\r
71 \r
72             Action<IAsyncResult> SendCallback = null;\r
73             SendCallback = (IAsyncResult ar) =>\r
74             {\r
75                 // Retrieve the socket from the state object\r
76                 Socket arClient = (Socket)ar.AsyncState;\r
77 \r
78                 // Complete sending the data to the remote device\r
79                 int bytesSent = arClient.EndSend(ar);\r
80 \r
81                 // Signal that all bytes have been sent\r
82                 sendDone.Set();\r
83             };\r
84 \r
85             // Convert the string data to byte data using ASCII encoding.\r
86             byte[] byteData = Encoding.ASCII.GetBytes(data);\r
87 \r
88             // Begin sending the data to the remote device.\r
89             client.BeginSend(byteData, 0, byteData.Length, SocketFlags.None, new AsyncCallback(SendCallback), client);\r
90 \r
91             // Wait for signal\r
92             sendDone.WaitOne();\r
93         }\r
94 \r
95         /// <summary>\r
96         /// Invoke remote method\r
97         /// </summary>\r
98         /// <typeparam name="T">Return type</typeparam>\r
99         /// <param name="method">Method name</param>\r
100         /// <returns>StratumResponse object</returns>\r
101         public StratumResponse<T> Invoke<T>(string method)\r
102         {\r
103             var req = new StratumRequest()\r
104             {\r
105                 Method = method,\r
106                 Params = new object[] { }\r
107             };\r
108             return Invoke<T>(req);\r
109         }\r
110 \r
111         /// <summary>\r
112         /// Invoke remote method\r
113         /// </summary>\r
114         /// <typeparam name="T">Return type</typeparam>\r
115         /// <param name="method">Method name</param>\r
116         /// <param name="arg">Argument</param>\r
117         /// <returns>StratumResponse object</returns>\r
118         public StratumResponse<T> Invoke<T>(string method, object arg)\r
119         {\r
120             var req = new StratumRequest()\r
121             {\r
122                 Method = method,\r
123                 Params = new object[] { arg }\r
124             };\r
125             return Invoke<T>(req);\r
126         }\r
127 \r
128         /// <summary>\r
129         /// Invoke remote method\r
130         /// </summary>\r
131         /// <typeparam name="T">Return type</typeparam>\r
132         /// <param name="method">Method name</param>\r
133         /// <param name="args">Arguments array</param>\r
134         /// <returns>StratumResponse object</returns>\r
135         public StratumResponse<T> Invoke<T>(string method, object[] args)\r
136         {\r
137             var req = new StratumRequest()\r
138             {\r
139                 Method = method,\r
140                 Params = args\r
141             };\r
142             return Invoke<T>(req);\r
143         }\r
144 \r
145         private StratumResponse<T> Invoke<T>(StratumRequest stratumReq)\r
146         {\r
147             // Serialize stratumReq into JSON string\r
148             var reqJSON = JsonConvert.SerializeObject(stratumReq) + '\n';\r
149 \r
150             // Send JSON data to the remote device.\r
151             Send(client, reqJSON);\r
152 \r
153             // Wait for response\r
154             gotResponse.WaitOne();\r
155 \r
156             var strResponse = string.Empty;\r
157             lock (responsesLock)\r
158             {\r
159                 // Deserialize the response\r
160                 strResponse = responses[stratumReq.Id];\r
161                 responses.Remove(stratumReq.Id);\r
162             }\r
163 \r
164             // Deserialize response into new instance of StratumResponse<T> \r
165             StratumResponse<T> responseObj = JsonConvert.DeserializeObject<StratumResponse<T>>(strResponse);\r
166 \r
167             // Reset the state\r
168             gotResponse.Reset();\r
169 \r
170             if (responseObj == null)\r
171             {\r
172                 try\r
173                 {\r
174                     JObject jResponseObj = JsonConvert.DeserializeObject(strResponse) as JObject;\r
175                     throw new Exception(jResponseObj["Error"].ToString());\r
176                 }\r
177                 catch (JsonSerializationException)\r
178                 {\r
179                     throw new Exception("Inconsistent or empty response");\r
180                 }\r
181             }\r
182 \r
183             return responseObj;\r
184         }\r
185 \r
186         private void Receiver(Socket client)\r
187         {\r
188             // Create the reading state object.\r
189             StratumReadState state = new StratumReadState(client);\r
190 \r
191             Action<IAsyncResult> ReceiveCallback = null;\r
192             ReceiveCallback = (IAsyncResult ar) =>\r
193             {\r
194                 // Retrieve the state object and the client socket \r
195                 // from the asynchronous state object.\r
196                 StratumReadState arStatus = (StratumReadState)ar.AsyncState;\r
197                 Socket arClient = arStatus.workSocket;\r
198 \r
199                 // Read data from the remote device.\r
200                 int bytesRead = arClient.EndReceive(ar);\r
201 \r
202                 if (bytesRead <= 0)\r
203                     return;\r
204 \r
205                 lock (arStatus.sb)\r
206                 {\r
207                     // There might be more data, so store the data received so far.\r
208                     arStatus.sb.Append(Encoding.ASCII.GetString(arStatus.buffer, 0, bytesRead));\r
209 \r
210                     if (arStatus.buffer[bytesRead - 1] == '\n')\r
211                     {\r
212                         var strMessage = arStatus.sb.ToString();\r
213                         arStatus.sb.Clear();\r
214 \r
215                         try\r
216                         {\r
217                             JObject jResponse = JsonConvert.DeserializeObject(strMessage) as JObject;\r
218                             var reqId = (string)jResponse["id"];\r
219 \r
220                             if (!String.IsNullOrEmpty(reqId))\r
221                             {\r
222                                 lock (responsesLock)\r
223                                 {\r
224                                     responses.Add(reqId, strMessage);\r
225                                 }\r
226 \r
227                                 gotResponse.Set();\r
228                             }\r
229                             else\r
230                             {\r
231                                 StratumNotification jNotification = JsonConvert.DeserializeObject<StratumNotification>(strMessage);\r
232 \r
233                                 var NotifyProcessThread = new Thread(() => NotificationHandler(jNotification.Method, jNotification.Params));\r
234                                 NotifyProcessThread.Start();\r
235                             }\r
236                         }\r
237                         catch (JsonSerializationException e)\r
238                         {\r
239                             // TODO: handle parse error\r
240                         }\r
241                     }\r
242                 }\r
243 \r
244                 arClient.BeginReceive(arStatus.buffer, 0, StratumReadState.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), arStatus);\r
245             };\r
246 \r
247             client.BeginReceive(state.buffer, 0, StratumReadState.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), state);\r
248         }\r
249 \r
250         /// <summary>\r
251         /// Notifications stub which is run in a separate thread. If you wish to implement real notification processing then just override this method in the derived class.\r
252         /// </summary>\r
253         /// <param name="NotificationMethod">Method name</param>\r
254         /// <param name="NotificationData">Array of values</param>\r
255         private static void NotificationHandler(string NotificationMethod, JArray NotificationData)\r
256         {\r
257             Console.WriteLine("\nNotification: Method={0}, data={1}", NotificationMethod, NotificationData.ToString());\r
258         }\r
259     }\r
260 \r
261 }