source: box/trunk/test/basicserver/testbasicserver.cpp @ 2983

Revision 2983, 16.9 KB checked in by chris, 9 months ago (diff)

Combine client and server protocols to make way for an offline/local protocol.

Rename ProtocolObject? to Message.

  • Property svn:eol-style set to native
Line 
1// --------------------------------------------------------------------------
2//
3// File
4//              Name:    testbasicserver.cpp
5//              Purpose: Test basic server classes
6//              Created: 2003/07/29
7//
8// --------------------------------------------------------------------------
9
10
11#include "Box.h"
12
13#include <stdio.h>
14#include <unistd.h>
15#include <time.h>
16
17#include <typeinfo>
18
19#include "Test.h"
20#include "Daemon.h"
21#include "Configuration.h"
22#include "ServerStream.h"
23#include "SocketStream.h"
24#include "IOStreamGetLine.h"
25#include "ServerTLS.h"
26#include "CollectInBufferStream.h"
27
28#include "TestContext.h"
29#include "autogen_TestProtocol.h"
30#include "ServerControl.h"
31
32#include "MemLeakFindOn.h"
33
34#define SERVER_LISTEN_PORT      2003
35
36// in ms
37#define COMMS_READ_TIMEOUT                                      4
38#define COMMS_SERVER_WAIT_BEFORE_REPLYING       40
39
40class basicdaemon : public Daemon
41{
42public:
43basicdaemon() {};
44~basicdaemon() {}
45virtual void Run();
46};
47
48void basicdaemon::Run()
49{
50        // Write a file to check it's done...
51        const Configuration &c(GetConfiguration());
52       
53        FILE *f = fopen(c.GetKeyValue("TestFile").c_str(), "w");
54        fclose(f);
55
56        while(!StopRun())
57        {
58                ::sleep(10);
59        }
60}
61
62void testservers_pause_before_reply()
63{
64#ifdef WIN32
65        Sleep(COMMS_SERVER_WAIT_BEFORE_REPLYING);
66#else
67        struct timespec t;
68        t.tv_sec = 0;
69        t.tv_nsec = COMMS_SERVER_WAIT_BEFORE_REPLYING * 1000 * 1000;    // convert to ns
70        ::nanosleep(&t, NULL);
71#endif
72}
73
74#define LARGE_DATA_BLOCK_SIZE 19870
75#define LARGE_DATA_SIZE (LARGE_DATA_BLOCK_SIZE*1000)
76
77void testservers_connection(SocketStream &rStream)
78{
79        IOStreamGetLine getline(rStream);
80
81        if(typeid(rStream) == typeid(SocketStreamTLS))
82        {
83                // need to wait for some data before sending stuff, otherwise timeout test doesn't work
84                std::string line;
85                while(!getline.GetLine(line))
86                        ;
87                SocketStreamTLS &rtls = (SocketStreamTLS&)rStream;
88                std::string line1("CONNECTED:");
89                line1 += rtls.GetPeerCommonName();
90                line1 += '\n';
91                testservers_pause_before_reply();
92                rStream.Write(line1.c_str(), line1.size());
93        }
94
95        while(!getline.IsEOF())
96        {
97                std::string line;
98                while(!getline.GetLine(line))
99                        ;
100                if(line == "QUIT")
101                {
102                        break;
103                }
104                if(line == "LARGEDATA")
105                {
106                        {
107                                // Send lots of data
108                                char data[LARGE_DATA_BLOCK_SIZE];
109                                for(unsigned int y = 0; y < sizeof(data); y++)
110                                {
111                                        data[y] = y & 0xff;
112                                }
113                                for(int s = 0; s < (LARGE_DATA_SIZE / LARGE_DATA_BLOCK_SIZE); ++s)
114                                {
115                                        rStream.Write(data, sizeof(data));
116                                }
117                        }
118                        {
119                                // Receive lots of data
120                                char buf[1024];
121                                int total = 0;
122                                int r = 0;
123                                while(total < LARGE_DATA_SIZE && (r = rStream.Read(buf, sizeof(buf))) != 0)
124                                {
125                                        total += r;
126                                }
127                                TEST_THAT(total == LARGE_DATA_SIZE);
128                                if (total != LARGE_DATA_SIZE)
129                                {
130                                        BOX_ERROR("Expected " << 
131                                                LARGE_DATA_SIZE << " bytes " <<
132                                                "but was " << total);
133                                        return;
134                                }
135                        }
136                        {
137                                // Send lots of data again
138                                char data[LARGE_DATA_BLOCK_SIZE];
139                                for(unsigned int y = 0; y < sizeof(data); y++)
140                                {
141                                        data[y] = y & 0xff;
142                                }
143                                for(int s = 0; s < (LARGE_DATA_SIZE / LARGE_DATA_BLOCK_SIZE); ++s)
144                                {
145                                        rStream.Write(data, sizeof(data));
146                                }
147                        }
148                       
149                        // next!
150                        continue;
151                }
152                std::string backwards;
153                for(std::string::const_reverse_iterator i(line.end()); i != std::string::const_reverse_iterator(line.begin()); ++i)
154                {
155                        backwards += (*i);
156                }
157                backwards += '\n';
158                testservers_pause_before_reply();
159                rStream.Write(backwards.c_str(), backwards.size());
160        }
161        rStream.Shutdown();
162        rStream.Close();
163}
164
165
166
167class testserver : public ServerStream<SocketStream, SERVER_LISTEN_PORT>
168{
169public:
170        testserver() {}
171        ~testserver() {}
172       
173        void Connection(SocketStream &rStream);
174       
175        virtual const char *DaemonName() const
176        {
177                return "test-srv2";
178        }
179        const ConfigurationVerify *GetConfigVerify() const;
180
181};
182
183const ConfigurationVerify *testserver::GetConfigVerify() const
184{
185        static ConfigurationVerifyKey verifyserverkeys[] = 
186        {
187                SERVERSTREAM_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) // no default listen addresses
188        };
189
190        static ConfigurationVerify verifyserver[] = 
191        {
192                {
193                        "Server",
194                        0,
195                        verifyserverkeys,
196                        ConfigTest_Exists | ConfigTest_LastEntry,
197                        0
198                }
199        };
200
201        static ConfigurationVerify verify =
202        {
203                "root",
204                verifyserver,
205                0,
206                ConfigTest_Exists | ConfigTest_LastEntry,
207                0
208        };
209
210        return &verify;
211}
212
213void testserver::Connection(SocketStream &rStream)
214{
215        testservers_connection(rStream);
216}
217
218class testProtocolServer : public testserver
219{
220public:
221        testProtocolServer() {}
222        ~testProtocolServer() {}
223
224        void Connection(SocketStream &rStream);
225       
226        virtual const char *DaemonName() const
227        {
228                return "test-srv4";
229        }
230};
231
232void testProtocolServer::Connection(SocketStream &rStream)
233{
234        TestProtocolServer server(rStream);
235        TestContext context;
236        server.DoServer(context);
237}
238
239
240class testTLSserver : public ServerTLS<SERVER_LISTEN_PORT>
241{
242public:
243        testTLSserver() {}
244        ~testTLSserver() {}
245       
246        void Connection(SocketStreamTLS &rStream);
247       
248        virtual const char *DaemonName() const
249        {
250                return "test-srv3";
251        }
252        const ConfigurationVerify *GetConfigVerify() const;
253
254};
255
256const ConfigurationVerify *testTLSserver::GetConfigVerify() const
257{
258        static ConfigurationVerifyKey verifyserverkeys[] = 
259        {
260                SERVERTLS_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) // no default listen addresses
261        };
262
263        static ConfigurationVerify verifyserver[] = 
264        {
265                {
266                        "Server",
267                        0,
268                        verifyserverkeys,
269                        ConfigTest_Exists | ConfigTest_LastEntry,
270                        0
271                }
272        };
273
274        static ConfigurationVerify verify =
275        {
276                "root",
277                verifyserver,
278                0,
279                ConfigTest_Exists | ConfigTest_LastEntry,
280                0
281        };
282
283        return &verify;
284}
285
286void testTLSserver::Connection(SocketStreamTLS &rStream)
287{
288        testservers_connection(rStream);
289}
290
291
292void Srv2TestConversations(const std::vector<IOStream *> &conns)
293{
294        const static char *tosend[] = {
295                "test 1\n", "carrots\n", "pineapples\n", "booo!\n", 0
296        };
297        const static char *recieve[] = {
298                "1 tset", "storrac", "selppaenip", "!ooob", 0
299        };
300       
301        IOStreamGetLine **getline = new IOStreamGetLine*[conns.size()];
302        for(unsigned int c = 0; c < conns.size(); ++c)
303        {
304                getline[c] = new IOStreamGetLine(*conns[c]);
305               
306                bool hadTimeout = false;
307                if(typeid(*conns[c]) == typeid(SocketStreamTLS))
308                {
309                        SocketStreamTLS *ptls = (SocketStreamTLS *)conns[c];
310                        printf("Connected to '%s'\n", ptls->GetPeerCommonName().c_str());
311               
312                        // Send some data, any data, to get the first response.
313                        conns[c]->Write("Hello\n", 6);
314               
315                        std::string line1;
316                        while(!getline[c]->GetLine(line1, false, COMMS_READ_TIMEOUT))
317                                hadTimeout = true;
318                        TEST_THAT(line1 == "CONNECTED:CLIENT");
319                        TEST_THAT(hadTimeout)
320                }
321        }
322       
323        for(int q = 0; tosend[q] != 0; ++q)
324        {
325                for(unsigned int c = 0; c < conns.size(); ++c)
326                {
327                        //printf("%d: %s", c, tosend[q]);
328                        conns[c]->Write(tosend[q], strlen(tosend[q]));
329                        std::string rep;
330                        bool hadTimeout = false;
331                        while(!getline[c]->GetLine(rep, false, COMMS_READ_TIMEOUT))
332                                hadTimeout = true;
333                        TEST_EQUAL_LINE(rep, recieve[q], "Line " << q);
334                        TEST_LINE(hadTimeout, "Line " << q)
335                }
336        }
337        for(unsigned int c = 0; c < conns.size(); ++c)
338        {
339                conns[c]->Write("LARGEDATA\n", 10);
340        }
341        for(unsigned int c = 0; c < conns.size(); ++c)
342        {
343                // Receive lots of data
344                char buf[1024];
345                int total = 0;
346                int r = 0;
347                while(total < LARGE_DATA_SIZE && (r = conns[c]->Read(buf, sizeof(buf))) != 0)
348                {
349                        total += r;
350                }
351                TEST_THAT(total == LARGE_DATA_SIZE);
352        }
353        for(unsigned int c = 0; c < conns.size(); ++c)
354        {
355                // Send lots of data
356                char data[LARGE_DATA_BLOCK_SIZE];
357                for(unsigned int y = 0; y < sizeof(data); y++)
358                {
359                        data[y] = y & 0xff;
360                }
361                for(int s = 0; s < (LARGE_DATA_SIZE / LARGE_DATA_BLOCK_SIZE); ++s)
362                {
363                        conns[c]->Write(data, sizeof(data));
364                }
365        }
366        for(unsigned int c = 0; c < conns.size(); ++c)
367        {
368                // Receive lots of data again
369                char buf[1024];
370                int total = 0;
371                int r = 0;
372                while(total < LARGE_DATA_SIZE && (r = conns[c]->Read(buf, sizeof(buf))) != 0)
373                {
374                        total += r;
375                }
376                TEST_THAT(total == LARGE_DATA_SIZE);
377        }
378
379        for(unsigned int c = 0; c < conns.size(); ++c)
380        {
381                conns[c]->Write("QUIT\n", 5);
382        }
383       
384        for(unsigned int c = 0; c < conns.size(); ++c)
385        {
386                if ( getline[c] ) delete getline[c];
387                getline[c] = 0;
388        }
389        if ( getline ) delete [] getline;
390        getline = 0;
391}
392
393void TestStreamReceive(TestProtocolClient &protocol, int value, bool uncertainstream)
394{
395        std::auto_ptr<TestProtocolGetStream> reply(protocol.QueryGetStream(value, uncertainstream));
396        TEST_THAT(reply->GetStartingValue() == value);
397       
398        // Get a stream
399        std::auto_ptr<IOStream> stream(protocol.ReceiveStream());
400       
401        // check uncertainty
402        TEST_THAT(uncertainstream == (stream->BytesLeftToRead() == IOStream::SizeOfStreamUnknown));
403       
404        printf("stream is %s\n", uncertainstream?"uncertain size":"fixed size");
405       
406        // Then check the contents
407        int values[998];
408        int v = value;
409        int count = 0;
410        int bytesleft = 0;
411        int bytessofar = 0;
412        while(stream->StreamDataLeft())
413        {
414                // Read some data
415                int bytes = stream->Read(((char*)values) + bytesleft, sizeof(values) - bytesleft);
416                bytessofar += bytes;
417                bytes += bytesleft;
418                int n = bytes / 4;
419                //printf("read %d, n = %d, so far = %d\n", bytes, n, bytessofar);
420                for(int t = 0; t < n; ++t)
421                {
422                        if(values[t] != v) printf("%d, %d, %d\n", t, values[t], v);
423                        TEST_THAT(values[t] == v++);
424                }
425                count += n;
426                bytesleft = bytes - (n*4);
427                if(bytesleft) ::memmove(values, ((char*)values) + bytes - bytesleft, bytesleft);
428        }
429       
430        TEST_THAT(bytesleft == 0);
431        TEST_THAT(count == (24273*3));  // over 64 k of data, definately
432}
433
434
435int test(int argc, const char *argv[])
436{
437        // Server launching stuff
438        if(argc >= 2)
439        {
440                // this is a quick hack to allow passing some options
441                // to the daemon
442
443                const char* mode = argv[1];
444
445                if (test_args.length() > 0)
446                {
447                        argv[1] = test_args.c_str();
448                }
449                else
450                {
451                        argc--;
452                        argv++;
453                }
454
455                if(strcmp(mode, "srv1") == 0)
456                {
457                        // Run very basic daemon
458                        basicdaemon daemon;
459                        return daemon.Main("doesnotexist", argc, argv);
460                }
461                else if(strcmp(mode, "srv2") == 0)
462                {
463                        // Run daemon which accepts connections
464                        testserver daemon;
465                        return daemon.Main("doesnotexist", argc, argv);
466                }               
467                else if(strcmp(mode, "srv3") == 0)
468                {
469                        testTLSserver daemon;
470                        return daemon.Main("doesnotexist", argc, argv);
471                }
472                else if(strcmp(mode, "srv4") == 0)
473                {
474                        testProtocolServer daemon;
475                        return daemon.Main("doesnotexist", argc, argv);
476                }
477        }
478
479        //printf("SKIPPING TESTS------------------------\n");
480        //goto protocolserver;
481
482        // Launch a basic server
483        {
484                std::string cmd = "./test --test-daemon-args=";
485                cmd += test_args;
486                cmd += " srv1 testfiles/srv1.conf";
487                int pid = LaunchServer(cmd, "testfiles/srv1.pid");
488
489                TEST_THAT(pid != -1 && pid != 0);
490                if(pid > 0)
491                {
492                        // Check that it's written the expected file
493                        TEST_THAT(TestFileExists("testfiles" 
494                                DIRECTORY_SEPARATOR "srv1.test1"));
495                        TEST_THAT(ServerIsAlive(pid));
496
497                        // Move the config file over
498                        #ifdef WIN32
499                                TEST_THAT(::unlink("testfiles"
500                                        DIRECTORY_SEPARATOR "srv1.conf") != -1);
501                        #endif
502
503                        TEST_THAT(::rename(
504                                "testfiles" DIRECTORY_SEPARATOR "srv1b.conf", 
505                                "testfiles" DIRECTORY_SEPARATOR "srv1.conf") 
506                                != -1);
507
508                        #ifndef WIN32
509                                // Get it to reread the config file
510                                TEST_THAT(HUPServer(pid));
511                                ::sleep(1);
512                                TEST_THAT(ServerIsAlive(pid));
513                                // Check that new file exists
514                                TEST_THAT(TestFileExists("testfiles" 
515                                        DIRECTORY_SEPARATOR "srv1.test2"));
516                        #endif // !WIN32
517
518                        // Kill it off
519                        TEST_THAT(KillServer(pid));
520
521                        #ifndef WIN32
522                                TestRemoteProcessMemLeaks(
523                                        "generic-daemon.memleaks");
524                        #endif // !WIN32
525                }
526        }
527       
528        // Launch a test forking server
529        {
530                std::string cmd = "./test --test-daemon-args=";
531                cmd += test_args;
532                cmd += " srv2 testfiles/srv2.conf";
533                int pid = LaunchServer(cmd, "testfiles/srv2.pid");
534
535                TEST_THAT(pid != -1 && pid != 0);
536
537                if(pid > 0)
538                {
539                        // Will it restart?
540                        TEST_THAT(ServerIsAlive(pid));
541
542                        #ifndef WIN32
543                                TEST_THAT(HUPServer(pid));
544                                ::sleep(1);
545                                TEST_THAT(ServerIsAlive(pid));
546                        #endif // !WIN32
547
548                        // Make some connections
549                        {
550                                SocketStream conn1;
551                                conn1.Open(Socket::TypeINET, "localhost", 2003);
552
553                                #ifndef WIN32
554                                        SocketStream conn2;
555                                        conn2.Open(Socket::TypeUNIX, 
556                                                "testfiles/srv2.sock");
557                                        SocketStream conn3;
558                                        conn3.Open(Socket::TypeINET, 
559                                                "localhost", 2003);
560                                #endif // !WIN32
561
562                                // Quick check that reconnections fail
563                                TEST_CHECK_THROWS(conn1.Open(Socket::TypeUNIX,
564                                        "testfiles/srv2.sock");, 
565                                        ServerException, SocketAlreadyOpen);
566
567                                // Stuff some data around
568                                std::vector<IOStream *> conns;
569                                conns.push_back(&conn1);
570
571                                #ifndef WIN32
572                                        conns.push_back(&conn2);
573                                        conns.push_back(&conn3);
574                                #endif // !WIN32
575
576                                Srv2TestConversations(conns);
577                                // Implicit close
578                        }
579
580                        #ifndef WIN32
581                                // HUP again
582                                TEST_THAT(HUPServer(pid));
583                                ::sleep(1);
584                                TEST_THAT(ServerIsAlive(pid));
585                        #endif // !WIN32
586
587                        // Kill it
588                        TEST_THAT(KillServer(pid));
589                        ::sleep(1);
590                        TEST_THAT(!ServerIsAlive(pid));
591
592                        #ifndef WIN32
593                                TestRemoteProcessMemLeaks("test-srv2.memleaks");
594                        #endif // !WIN32
595                }
596        }
597
598        // Launch a test SSL server
599        {
600                std::string cmd = "./test --test-daemon-args=";
601                cmd += test_args;
602                cmd += " srv3 testfiles/srv3.conf";
603                int pid = LaunchServer(cmd, "testfiles/srv3.pid");
604
605                TEST_THAT(pid != -1 && pid != 0);
606
607                if(pid > 0)
608                {
609                        // Will it restart?
610                        TEST_THAT(ServerIsAlive(pid));
611
612                        #ifndef WIN32
613                                TEST_THAT(HUPServer(pid));
614                                ::sleep(1);
615                                TEST_THAT(ServerIsAlive(pid));
616                        #endif
617
618                        // Make some connections
619                        {
620                                // SSL library
621                                SSLLib::Initialise();
622                       
623                                // Context first
624                                TLSContext context;
625                                context.Initialise(false /* client */,
626                                                "testfiles/clientCerts.pem",
627                                                "testfiles/clientPrivKey.pem",
628                                                "testfiles/clientTrustedCAs.pem");
629
630                                SocketStreamTLS conn1;
631                                conn1.Open(context, Socket::TypeINET, "localhost", 2003);
632                                #ifndef WIN32
633                                        SocketStreamTLS conn2;
634                                        conn2.Open(context, Socket::TypeUNIX,
635                                                "testfiles/srv3.sock");
636                                        SocketStreamTLS conn3;
637                                        conn3.Open(context, Socket::TypeINET,
638                                                "localhost", 2003);
639                                #endif
640
641                                // Quick check that reconnections fail
642                                TEST_CHECK_THROWS(conn1.Open(context,
643                                        Socket::TypeUNIX,
644                                        "testfiles/srv3.sock");,
645                                        ServerException, SocketAlreadyOpen);
646
647                                // Stuff some data around
648                                std::vector<IOStream *> conns;
649                                conns.push_back(&conn1);
650
651                                #ifndef WIN32
652                                        conns.push_back(&conn2);
653                                        conns.push_back(&conn3);
654                                #endif
655
656                                Srv2TestConversations(conns);
657                                // Implicit close
658                        }
659
660                        #ifndef WIN32
661                                // HUP again
662                                TEST_THAT(HUPServer(pid));
663                                ::sleep(1);
664                                TEST_THAT(ServerIsAlive(pid));
665                        #endif
666
667                        // Kill it
668                        TEST_THAT(KillServer(pid));
669                        ::sleep(1);
670                        TEST_THAT(!ServerIsAlive(pid));
671
672                        #ifndef WIN32
673                                TestRemoteProcessMemLeaks("test-srv3.memleaks");
674                        #endif
675                }
676        }
677       
678//protocolserver:
679        // Launch a test protocol handling server
680        {
681                std::string cmd = "./test --test-daemon-args=";
682                cmd += test_args;
683                cmd += " srv4 testfiles/srv4.conf";
684                int pid = LaunchServer(cmd, "testfiles/srv4.pid");
685
686                TEST_THAT(pid != -1 && pid != 0);
687
688                if(pid > 0)
689                {
690                        ::sleep(1);
691                        TEST_THAT(ServerIsAlive(pid));
692
693                        // Open a connection to it             
694                        SocketStream conn;
695                        #ifdef WIN32
696                                conn.Open(Socket::TypeINET, "localhost", 2003);
697                        #else
698                                conn.Open(Socket::TypeUNIX, "testfiles/srv4.sock");
699                        #endif
700                       
701                        // Create a protocol
702                        TestProtocolClient protocol(conn);
703
704                        // Simple query
705                        {
706                                std::auto_ptr<TestProtocolSimpleReply> reply(protocol.QuerySimple(41));
707                                TEST_THAT(reply->GetValuePlusOne() == 42);
708                        }
709                        {
710                                std::auto_ptr<TestProtocolSimpleReply> reply(protocol.QuerySimple(809));
711                                TEST_THAT(reply->GetValuePlusOne() == 810);
712                        }
713                       
714                        // Streams, twice, both uncertain and certain sizes
715                        TestStreamReceive(protocol, 374, false);
716                        TestStreamReceive(protocol, 23983, true);
717                        TestStreamReceive(protocol, 12098, false);
718                        TestStreamReceive(protocol, 4342, true);
719                       
720                        // Try to send a stream
721                        {
722                                CollectInBufferStream s;
723                                char buf[1663];
724                                s.Write(buf, sizeof(buf));
725                                s.SetForReading();
726                                std::auto_ptr<TestProtocolGetStream> reply(protocol.QuerySendStream(0x73654353298ffLL, s));
727                                TEST_THAT(reply->GetStartingValue() == sizeof(buf));
728                        }
729
730                        // Lots of simple queries
731                        for(int q = 0; q < 514; q++)
732                        {
733                                std::auto_ptr<TestProtocolSimpleReply> reply(protocol.QuerySimple(q));
734                                TEST_THAT(reply->GetValuePlusOne() == (q+1));
735                        }
736                        // Send a list of strings to it
737                        {
738                                std::vector<std::string> strings;
739                                strings.push_back(std::string("test1"));
740                                strings.push_back(std::string("test2"));
741                                strings.push_back(std::string("test3"));
742                                std::auto_ptr<TestProtocolListsReply> reply(protocol.QueryLists(strings));
743                                TEST_THAT(reply->GetNumberOfStrings() == 3);
744                        }
745                       
746                        // And another
747                        {
748                                std::auto_ptr<TestProtocolHello> reply(protocol.QueryHello(41,87,11,std::string("pingu")));
749                                TEST_THAT(reply->GetNumber32() == 12);
750                                TEST_THAT(reply->GetNumber16() == 89);
751                                TEST_THAT(reply->GetNumber8() == 22);
752                                TEST_THAT(reply->GetText() == "Hello world!");
753                        }
754               
755                        // Quit query to finish
756                        protocol.QueryQuit();
757               
758                        // Kill it
759                        TEST_THAT(KillServer(pid));
760                        ::sleep(1);
761                        TEST_THAT(!ServerIsAlive(pid));
762
763                        #ifndef WIN32
764                                TestRemoteProcessMemLeaks("test-srv4.memleaks");
765                        #endif
766                }
767        }
768
769        return 0;
770}
771
Note: See TracBrowser for help on using the repository browser.