1 /* $Id: test_ncbi_namedpipe.cpp 627740 2021-03-17 19:21:53Z ivanov $
2  * ===========================================================================
3  *
4  *                            PUBLIC DOMAIN NOTICE
5  *               National Center for Biotechnology Information
6  *
7  *  This software/database is a "United States Government Work" under the
8  *  terms of the United States Copyright Act.  It was written as part of
9  *  the author's official duties as a United States Government employee and
10  *  thus cannot be copyrighted.  This software/database is freely available
11  *  to the public for use. The National Library of Medicine and the U.S.
12  *  Government have not placed any restriction on its use or reproduction.
13  *
14  *  Although all reasonable efforts have been taken to ensure the accuracy
15  *  and reliability of the software and data, the NLM and the U.S.
16  *  Government do not and cannot warrant the performance or results that
17  *  may be obtained by using this software or data. The NLM and the U.S.
18  *  Government disclaim all warranties, express or implied, including
19  *  warranties of performance, merchantability or fitness for any particular
20  *  purpose.
21  *
22  *  Please cite the author in any work or product based on this material.
23  *
24  * ===========================================================================
25  *
26  * Author:  Vladimir Ivanov, Anton Lavrentiev
27  *
28  * File Description:  Test program for CNamedPipe[Client|Server] classes
29  *
30  */
31 
32 #include <ncbi_pch.hpp>
33 #include "../ncbi_priv.h"
34 #include <corelib/ncbiapp.hpp>
35 #include <corelib/ncbifile.hpp>
36 #include <corelib/ncbi_system.hpp>
37 #include <connect/ncbi_connutil.h>
38 #include <connect/ncbi_namedpipe.hpp>
39 
40 #include "test_assert.h"  // This header must go last
41 
42 #define _STR(x)  #x
43 #define  STR(s)  _STR(s)
44 
45 #define DEFAULT_TIMEOUT  3
46 
47 
48 USING_NCBI_SCOPE;
49 
50 
51 // Test pipe name
52 #if defined(NCBI_OS_MSWIN)
53 const string kPipeName = "\\\\.\\pipe\\ncbi\\test_pipename";
54 #elif defined(NCBI_OS_UNIX)
55 const string kPipeName = "./.ncbi_test_pipename";
56 #endif
57 
58 const size_t kNumSubBlobs = 10;
59 const size_t kSubBlobSize = 10*1024;
60 const size_t kBlobSize    = kNumSubBlobs * kSubBlobSize;
61 
62 
63 ////////////////////////////////
64 // Auxiliary functions
65 //
66 
67 // Reading from pipe
s_ReadPipe(CNamedPipe & pipe,void * buf,size_t buf_size,size_t msg_size,size_t * n_read)68 static EIO_Status s_ReadPipe(CNamedPipe& pipe, void* buf, size_t buf_size,
69                              size_t msg_size, size_t* n_read)
70 {
71     size_t     n_read_total = 0;
72     size_t     x_read       = 0;
73     EIO_Status status;
74 
75     do {
76         status = pipe.Read((char*) buf + n_read_total,
77                            buf_size - n_read_total, &x_read);
78         ERR_POST(Info <<
79                  "Read from pipe: "
80                  + NStr::UIntToString((unsigned int) x_read) + " byte(s): "
81                  + string(IO_StatusStr(status)));
82         n_read_total += x_read;
83     } while (status == eIO_Success  &&  n_read_total < msg_size);
84 
85     *n_read = n_read_total;
86     return status;
87 }
88 
89 
90 // Writing to pipe
s_WritePipe(CNamedPipe & pipe,const void * buf,size_t size,size_t * n_written)91 static EIO_Status s_WritePipe(CNamedPipe& pipe, const void* buf, size_t size,
92                               size_t* n_written)
93 {
94     size_t     n_written_total = 0;
95     size_t     x_written       = 0;
96     EIO_Status status;
97 
98     do {
99         status = pipe.Write((char*) buf + n_written_total,
100                            size - n_written_total, &x_written);
101         ERR_POST(Info <<
102                  "Write to pipe: "
103                  + NStr::UIntToString((unsigned int) x_written) + " byte(s): "
104                  + string(IO_StatusStr(status)));
105         n_written_total += x_written;
106     } while (status == eIO_Success  &&  n_written_total < size);
107 
108     *n_written = n_written_total;
109     return status;
110 }
111 
112 
113 
114 ////////////////////////////////
115 // Test application
116 //
117 
118 class CTest : public CNcbiApplication
119 {
120 public:
121     CTest(void);
122 
123     virtual void Init(void);
124     virtual int  Run (void);
125 
126 protected:
127     int Client(int num);
128     int Server(void);
129 
130     string   m_PipeName;
131     STimeout m_Timeout;
132 };
133 
134 
CTest(void)135 CTest::CTest(void)
136 {
137     m_Timeout.sec  = DEFAULT_TIMEOUT;
138     m_Timeout.usec = 0;
139 }
140 
141 
Init(void)142 void CTest::Init(void)
143 {
144     // Set error posting and tracing on maximum
145     //SetDiagTrace(eDT_Enable);
146     SetDiagPostLevel(eDiag_Info);
147     SetDiagPostAllFlags(SetDiagPostAllFlags(eDPF_Default)
148                         | eDPF_All | eDPF_OmitInfoSev);
149     UnsetDiagPostFlag(eDPF_Line);
150     UnsetDiagPostFlag(eDPF_File);
151     UnsetDiagPostFlag(eDPF_Location);
152     UnsetDiagPostFlag(eDPF_LongFilename);
153     SetDiagTraceAllFlags(SetDiagPostAllFlags(eDPF_Default));
154 
155     // Create command-line argument descriptions class
156     auto_ptr<CArgDescriptions> arg_desc(new CArgDescriptions);
157 
158     // Specify USAGE context
159     arg_desc->SetUsageContext(GetArguments().GetProgramBasename(),
160                               "Test named pipe API");
161 
162     // Describe the expected command-line arguments
163     arg_desc->AddDefaultKey
164         ("basename", "basename",
165          "Base name for the pipe",
166          CArgDescriptions::eString, kPipeName);
167     arg_desc->AddDefaultKey
168         ("suffix", "suffix",
169          "Unique string that will be added to the base pipe name",
170          CArgDescriptions::eString, "");
171     arg_desc->AddPositional
172         ("mode", "Test mode",
173          CArgDescriptions::eString);
174     arg_desc->SetConstraint
175         ("mode", &(*new CArgAllow_Strings, "client", "server"));
176     arg_desc->AddOptionalPositional
177         ("timeout", "Input/output timeout"
178          " (default = " + string(STR(DEFAULT_TIMEOUT)) + ')',
179          CArgDescriptions::eDouble);
180     arg_desc->SetConstraint
181         ("timeout", new CArgAllow_Doubles(0.0, 200.0));
182 
183     // Setup arg.descriptions for this application
184     SetupArgDescriptions(arg_desc.release());
185 }
186 
187 
Run(void)188 int CTest::Run(void)
189 {
190     const CArgs& args = GetArgs();
191 
192     g_NCBI_ConnectRandomSeed
193         = (unsigned int) time(0) ^ NCBI_CONNECT_SRAND_ADDEND;
194     ::srand(g_NCBI_ConnectRandomSeed);
195 
196     m_PipeName = args["basename"].AsString();
197     if ( m_PipeName.empty() ) {
198         m_PipeName = kPipeName;
199     }
200     if ( !args["suffix"].AsString().empty() ) {
201         m_PipeName += '_' + args["suffix"].AsString();
202     }
203     ERR_POST(Info << "Using pipe name: " + m_PipeName);
204     if (args["timeout"].HasValue()) {
205         double tv = args["timeout"].AsDouble();
206         m_Timeout.sec  = (unsigned int)  tv;
207         m_Timeout.usec = (unsigned int)((tv - m_Timeout.sec) * kMicroSecondsPerSecond);
208     }
209     if     (args["mode"].AsString() == "client") {
210         SetDiagPostPrefix("Client");
211         for (int i = 1;  i <= 3;  ++i) {
212             int exitcode = Client(i);
213             if (exitcode)
214                 return exitcode;
215         }
216     }
217     else if (args["mode"].AsString() == "server") {
218         SetDiagPostPrefix("Server");
219         return Server();
220     }
221     else {
222         _TROUBLE;
223     }
224     return 0;
225 }
226 
227 
228 /////////////////////////////////
229 // Named pipe client
230 //
231 
Client(int num)232 int CTest::Client(int num)
233 {
234     int exitcode = 0;
235 
236     if (::rand() & 1) {
237         SleepMilliSec(100);
238     }
239     ERR_POST(Info << "Starting client " + NStr::IntToString(num) + "...");
240 
241     CNamedPipeClient pipe;
242     assert(pipe.IsClientSide());
243     assert(pipe.SetTimeout(eIO_Open,  &m_Timeout) == eIO_Success);
244     assert(pipe.SetTimeout(eIO_Read,  &m_Timeout) == eIO_Success);
245     assert(pipe.SetTimeout(eIO_Write, &m_Timeout) == eIO_Success);
246 
247     EIO_Status status;
248     CDeadline timeout(g_STimeoutToCTimeout(&m_Timeout));
249     for (;;) {
250         // Wait for server to come up online
251         status = pipe.Open(m_PipeName, kDefaultTimeout, kSubBlobSize,
252                            CNamedPipeClient::fNoLogIfClosed);
253         if (status == eIO_Success) {
254             break;
255         }
256         if (timeout.IsExpired()) {
257             status  = eIO_Timeout;
258         }
259         if (status != eIO_Closed) {
260             ERR_POST(Error << "Open() failed: " << IO_StatusStr(status));
261             _TROUBLE;
262         }
263         ERR_POST(Info << "Waiting for server...");
264         SleepMilliSec(500);
265     }
266 
267     char buf[kSubBlobSize];
268     size_t n_read    = 0;
269     size_t n_written = 0;
270 
271     if (num > 2  &&  (rand() & 1)) {
272         // Super quick write-and-flee behavior
273         ERR_POST(Info << "Quitting the server!");
274         assert(s_WritePipe(pipe, "Quit!", 5, &n_written) == eIO_Success);
275         assert(n_written == 5);
276         if (rand() & 1) {
277             status = pipe.Close();
278             if (status != eIO_Success) {
279                 ERR_POST(Error << "Close() failed: " << IO_StatusStr(status));
280                 _TROUBLE;
281             }
282         }
283         exitcode = 2;
284         goto out;
285     }
286 
287     // "Hello" test
288     {{
289         assert(s_WritePipe(pipe, "Hello", 5, &n_written) == eIO_Success);
290         assert(n_written == 5);
291         assert(s_ReadPipe(pipe, buf, sizeof(buf), 2, &n_read) == eIO_Success);
292         assert(n_read == 2);
293         assert(::memcmp(buf, "OK", 2) == 0);
294     }}
295 
296     // Big binary blob test
297     {{
298         // Send a very big binary blob
299         size_t i;
300         unsigned char* blob = (unsigned char*) ::malloc(kBlobSize + 1);
301         for (i = 0;  i < kBlobSize;  ++i) {
302             blob[i] = (unsigned char) i;
303         }
304         for (i = 0;  i < kNumSubBlobs;  ++i) {
305             assert(s_WritePipe(pipe, blob + i*kSubBlobSize, kSubBlobSize,
306                                &n_written) == eIO_Success);
307             assert(n_written == kSubBlobSize);
308         }
309         // Receive the blob back
310         ::memset(blob, 0, kBlobSize);
311         assert(s_ReadPipe(pipe, blob, kBlobSize + 1, kBlobSize, &n_read)
312                == eIO_Success);
313         // Check its contents
314         for (i = 0;  i < kBlobSize;  ++i) {
315             assert(blob[i] == (unsigned char) i);
316         }
317         ::free(blob);
318         ERR_POST(Info << "Blob test is OK!");
319     }}
320     if (::rand() & 1) {
321         SleepMilliSec(100);
322     }
323     if (::rand() & 1) {
324         status = s_ReadPipe(pipe, buf, sizeof(buf), sizeof(buf), &n_read);
325         if (status == eIO_Success) {
326             ERR_POST(Error << "Extra read succeeded");
327             _TROUBLE;
328         }
329         ERR_POST(Info << "Error expected, " << IO_StatusStr(status));
330     }
331     if (::rand() & 1) {
332         status = pipe.Close();
333         if (status != eIO_Success) {
334             ERR_POST(Error << "Close() failed: " << IO_StatusStr(status));
335             _TROUBLE;
336         }
337         status = s_ReadPipe(pipe, buf, sizeof(buf), sizeof(buf), &n_read);
338         _ASSERT(status == eIO_Unknown);
339     }
340  out:
341     ERR_POST(Info << "TEST completed successfully");
342     return exitcode;
343 }
344 
345 
346 /////////////////////////////////
347 // Named pipe server
348 //
349 
Server(void)350 int CTest::Server(void)
351 {
352     int exitcode = 1;  //  Normally getting killed at the test completion
353 
354     ERR_POST(Info << "Starting server...");
355 
356     char buf[kSubBlobSize];
357     size_t n_read    = 0;
358     size_t n_written = 0;
359 
360     CNamedPipeServer pipe(m_PipeName, &m_Timeout, kSubBlobSize + 512);
361 
362     assert(pipe.IsServerSide());
363     assert(pipe.SetTimeout(eIO_Read,  &m_Timeout) == eIO_Success);
364     assert(pipe.SetTimeout(eIO_Write, &m_Timeout) == eIO_Success);
365 
366     EIO_Status status;
367 
368     for (int n = 1;  n <= 10;  ++n) {
369         if (::rand() & 1) {
370             SleepMilliSec(100);
371         }
372         ERR_POST(Info << "Listening on \"" + m_PipeName + "\", round "
373                  + NStr::IntToString(n) + "...");
374 
375         status = pipe.Listen();
376         switch (status) {
377         case eIO_Success:
378             ERR_POST(Info << "Client connected!");
379 
380             if (::rand() & 1) {
381                 SleepMilliSec(100);
382             }
383             // "Hello" test
384             {{
385                 assert(s_ReadPipe(pipe, buf, sizeof(buf), 5, &n_read) == eIO_Success);
386                 assert(n_read == 5);
387                 if (memcmp(buf, "Quit!", 5) == 0) {
388                     ERR_POST(Info << "Quit received!");
389                     exitcode = 0;
390                     goto done;
391                 }
392                 assert(memcmp(buf, "Hello", 5) == 0);
393                 assert(s_WritePipe(pipe, "OK", 2, &n_written) == eIO_Success);
394                 assert(n_written == 2);
395             }}
396 
397             if (::rand() & 1) {
398                 SleepMilliSec(100);
399             }
400             // Big binary blob test
401             {{
402                 // Receive a very big binary blob
403                 size_t i;
404                 unsigned char* blob = (unsigned char*) ::malloc(kBlobSize + 1);
405 
406                 assert(s_ReadPipe(pipe, blob, kBlobSize +1, kBlobSize, &n_read)
407                        == eIO_Success);
408                 assert(n_read == kBlobSize);
409 
410                 // Check its content
411                 for (i = 0;  i < kBlobSize;  ++i) {
412                     assert(blob[i] == (unsigned char) i);
413                 }
414                 // Write the blob back
415                 for (i = 0;  i < kNumSubBlobs;  ++i) {
416                     assert(s_WritePipe(pipe, blob + i*kSubBlobSize,
417                                        kSubBlobSize, &n_written)
418                            == eIO_Success);
419                     assert(n_written == kSubBlobSize);
420                 }
421                 ::memset(blob, 0, kBlobSize);
422                 ::free(blob);
423                 ERR_POST(Info << "Blob test is OK!");
424             }}
425             if (::rand() & 1) {
426                 SleepMilliSec(100);
427             }
428             if (::rand() & 1) {
429                 status = s_ReadPipe(pipe, buf, sizeof(buf), sizeof(buf), &n_read);
430                 if (status == eIO_Success) {
431                     ERR_POST(Error << "Extra read succeeded");
432                     _TROUBLE;
433                 }
434                 ERR_POST(Info << "Error expected, " << IO_StatusStr(status));
435             }
436             ERR_POST(Info << "Disconnecting client...");
437             status = pipe.Disconnect();
438             if (status != eIO_Success) {
439                 ERR_POST(Error << "Disconnect() failed: " << IO_StatusStr(status));
440                 _TROUBLE;
441             }
442             if (::rand() & 1) {
443                 status = s_ReadPipe(pipe, buf, sizeof(buf), sizeof(buf), &n_read);
444                 _ASSERT(status == eIO_Unknown);
445             }
446             ERR_POST(Info << "Round completed successfully!");
447             break;
448 
449         case eIO_Timeout:
450             ERR_POST(Info << "Timeout...");
451             break;
452 
453         default:
454             ERR_POST(Error << IO_StatusStr(status));
455             _TROUBLE;
456         }
457     }
458 
459  done:
460     // Close named pipe
461     status = pipe.Close();
462     assert(status == eIO_Success  ||  status == eIO_Closed);
463     return exitcode;
464 }
465 
466 
467 ///////////////////////////////////
468 // APPLICATION OBJECT  and  MAIN
469 //
470 
main(int argc,const char * argv[])471 int main(int argc, const char* argv[])
472 {
473     // Execute main application function
474     return CTest().AppMain(argc, argv);
475 }
476