]> Cypherpunks.ru repositories - nncp.git/blob - src/toss_test.go
Unify copyright comment format
[nncp.git] / src / toss_test.go
1 // NNCP -- Node to Node copy, utilities for store-and-forward data exchange
2 // Copyright (C) 2016-2024 Sergey Matveev <stargrave@stargrave.org>
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, version 3 of the License.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
16 package nncp
17
18 import (
19         "bytes"
20         "crypto/rand"
21         "fmt"
22         "io"
23         "os"
24         "path/filepath"
25         "strconv"
26         "strings"
27         "testing"
28         "testing/quick"
29
30         xdr "github.com/davecgh/go-xdr/xdr2"
31 )
32
33 var (
34         TDebug bool = false
35 )
36
37 func dirFiles(path string) []string {
38         dir, err := os.Open(path)
39         if err != nil {
40                 panic(err)
41         }
42         defer dir.Close()
43         names, err := dir.Readdirnames(0)
44         if err != nil {
45                 panic(err)
46         }
47         return names
48 }
49
50 func TestTossExec(t *testing.T) {
51         f := func(replyNice uint8, handleRaw uint32, recipients [16]uint8) bool {
52                 handle := strconv.Itoa(int(handleRaw))
53                 for i, recipient := range recipients {
54                         recipients[i] = recipient % 8
55                 }
56                 spool, err := os.MkdirTemp("", "testtoss")
57                 if err != nil {
58                         panic(err)
59                 }
60                 defer os.RemoveAll(spool)
61                 nodeOur, err := NewNodeGenerate()
62                 if err != nil {
63                         t.Error(err)
64                         return false
65                 }
66                 ctx := Ctx{
67                         Spool:   spool,
68                         Self:    nodeOur,
69                         SelfId:  nodeOur.Id,
70                         Neigh:   make(map[NodeId]*Node),
71                         Alias:   make(map[string]*NodeId),
72                         LogPath: filepath.Join(spool, "log.log"),
73                         Debug:   TDebug,
74                 }
75                 ctx.Neigh[*nodeOur.Id] = nodeOur.Their()
76                 privates := make(map[uint8]*NodeOur)
77                 for _, recipient := range recipients {
78                         if _, exists := privates[recipient]; exists {
79                                 continue
80                         }
81                         our, err := NewNodeGenerate()
82                         if err != nil {
83                                 t.Error(err)
84                                 return false
85                         }
86                         privates[recipient] = our
87                         ctx.Neigh[*our.Id] = our.Their()
88                 }
89                 for _, recipient := range recipients {
90                         if err := ctx.TxExec(
91                                 ctx.Neigh[*privates[recipient].Id],
92                                 DefaultNiceExec,
93                                 replyNice,
94                                 handle,
95                                 []string{"arg0", "arg1"},
96                                 strings.NewReader("BODY\n"),
97                                 1<<15, MaxFileSize,
98                                 false,
99                                 nil,
100                         ); err != nil {
101                                 t.Error(err)
102                                 return false
103                         }
104                 }
105                 for _, recipient := range recipients {
106                         ctx.Self = privates[recipient]
107                         rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx))
108                         os.Rename(filepath.Join(spool, ctx.Self.Id.String(), string(TTx)), rxPath)
109                         if len(dirFiles(rxPath)) == 0 {
110                                 continue
111                         }
112                         ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceExec - 1})
113                         if len(dirFiles(rxPath)) == 0 {
114                                 return false
115                         }
116                         ctx.Neigh[*nodeOur.Id].Exec = make(map[string][]string)
117                         ctx.Neigh[*nodeOur.Id].Exec[handle] = []string{"/bin/sh", "-c", "false"}
118                         ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceExec})
119                         if len(dirFiles(rxPath)) == 0 {
120                                 return false
121                         }
122                         ctx.Neigh[*nodeOur.Id].Exec[handle] = []string{
123                                 "/bin/sh", "-c",
124                                 fmt.Sprintf(
125                                         "echo $NNCP_NICE $0 $1 >> %s ; cat >> %s",
126                                         filepath.Join(spool, "mbox"),
127                                         filepath.Join(spool, "mbox"),
128                                 ),
129                         }
130                         ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceExec})
131                         if len(dirFiles(rxPath)) != 0 {
132                                 return false
133                         }
134                 }
135                 mbox, err := os.ReadFile(filepath.Join(spool, "mbox"))
136                 if err != nil {
137                         return false
138                 }
139                 expected := make([]byte, 0, 16)
140                 for i := 0; i < 16; i++ {
141                         expected = append(
142                                 expected,
143                                 []byte(fmt.Sprintf("%d arg0 arg1\n", replyNice))...,
144                         )
145                         expected = append(expected, []byte("BODY\n")...)
146                 }
147                 return bytes.Equal(mbox, expected)
148         }
149         if err := quick.Check(f, nil); err != nil {
150                 t.Error(err)
151         }
152 }
153
154 func TestTossFile(t *testing.T) {
155         f := func(fileSizes []uint8) bool {
156                 if len(fileSizes) == 0 {
157                         return true
158                 }
159                 files := make(map[string][]byte)
160                 for i, fileSize := range fileSizes {
161                         if fileSize == 0 {
162                                 // to prevent chunked send
163                                 fileSize++
164                         }
165                         data := make([]byte, fileSize)
166                         if _, err := io.ReadFull(rand.Reader, data); err != nil {
167                                 panic(err)
168                         }
169                         files[strconv.Itoa(i)] = data
170                 }
171                 spool, err := os.MkdirTemp("", "testtoss")
172                 if err != nil {
173                         panic(err)
174                 }
175                 defer os.RemoveAll(spool)
176                 nodeOur, err := NewNodeGenerate()
177                 if err != nil {
178                         t.Error(err)
179                         return false
180                 }
181                 ctx := Ctx{
182                         Spool:   spool,
183                         Self:    nodeOur,
184                         SelfId:  nodeOur.Id,
185                         Neigh:   make(map[NodeId]*Node),
186                         Alias:   make(map[string]*NodeId),
187                         LogPath: filepath.Join(spool, "log.log"),
188                         Debug:   TDebug,
189                 }
190                 ctx.Neigh[*nodeOur.Id] = nodeOur.Their()
191                 incomingPath := filepath.Join(spool, "incoming")
192                 for _, fileData := range files {
193                         hasher := MTHNew(0, 0)
194                         hasher.Write(fileData)
195                         fileName := Base32Codec.EncodeToString(hasher.Sum(nil))
196                         src := filepath.Join(spool, fileName)
197                         if err := os.WriteFile(src, fileData, os.FileMode(0600)); err != nil {
198                                 panic(err)
199                         }
200                         if err := ctx.TxFile(
201                                 ctx.Neigh[*nodeOur.Id],
202                                 DefaultNiceFile,
203                                 src,
204                                 fileName,
205                                 MaxFileSize,
206                                 1<<15,
207                                 MaxFileSize,
208                                 nil,
209                         ); err != nil {
210                                 t.Error(err)
211                                 return false
212                         }
213                 }
214                 rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx))
215                 os.Rename(filepath.Join(spool, ctx.Self.Id.String(), string(TTx)), rxPath)
216                 ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceFile})
217                 if len(dirFiles(rxPath)) == 0 {
218                         return false
219                 }
220                 ctx.Neigh[*nodeOur.Id].Incoming = &incomingPath
221                 if ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceFile}) {
222                         return false
223                 }
224                 if len(dirFiles(rxPath)) != 0 {
225                         return false
226                 }
227                 for _, fileData := range files {
228                         hasher := MTHNew(0, 0)
229                         hasher.Write(fileData)
230                         fileName := Base32Codec.EncodeToString(hasher.Sum(nil))
231                         data, err := os.ReadFile(filepath.Join(incomingPath, fileName))
232                         if err != nil {
233                                 panic(err)
234                         }
235                         if !bytes.Equal(data, fileData) {
236                                 return false
237                         }
238                 }
239                 return true
240         }
241         if err := quick.Check(f, nil); err != nil {
242                 t.Error(err)
243         }
244 }
245
246 func TestTossFileSameName(t *testing.T) {
247         f := func(filesRaw uint8) bool {
248                 files := int(filesRaw)%8 + 1
249                 spool, err := os.MkdirTemp("", "testtoss")
250                 if err != nil {
251                         panic(err)
252                 }
253                 defer os.RemoveAll(spool)
254                 nodeOur, err := NewNodeGenerate()
255                 if err != nil {
256                         t.Error(err)
257                         return false
258                 }
259                 ctx := Ctx{
260                         Spool:   spool,
261                         Self:    nodeOur,
262                         SelfId:  nodeOur.Id,
263                         Neigh:   make(map[NodeId]*Node),
264                         Alias:   make(map[string]*NodeId),
265                         LogPath: filepath.Join(spool, "log.log"),
266                         Debug:   TDebug,
267                 }
268                 ctx.Neigh[*nodeOur.Id] = nodeOur.Their()
269                 srcPath := filepath.Join(spool, "junk")
270                 if err = os.WriteFile(
271                         srcPath,
272                         []byte("doesnotmatter"),
273                         os.FileMode(0600),
274                 ); err != nil {
275                         t.Error(err)
276                         return false
277                 }
278                 incomingPath := filepath.Join(spool, "incoming")
279                 for i := 0; i < files; i++ {
280                         if err := ctx.TxFile(
281                                 ctx.Neigh[*nodeOur.Id],
282                                 DefaultNiceFile,
283                                 srcPath,
284                                 "samefile",
285                                 MaxFileSize,
286                                 1<<15,
287                                 MaxFileSize,
288                                 nil,
289                         ); err != nil {
290                                 t.Error(err)
291                                 return false
292                         }
293                 }
294                 rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx))
295                 os.Rename(filepath.Join(spool, ctx.Self.Id.String(), string(TTx)), rxPath)
296                 ctx.Neigh[*nodeOur.Id].Incoming = &incomingPath
297                 ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceFile})
298                 expected := make(map[string]struct{})
299                 expected["samefile"] = struct{}{}
300                 for i := 0; i < files-1; i++ {
301                         expected["samefile."+strconv.Itoa(i)] = struct{}{}
302                 }
303                 for _, filename := range dirFiles(incomingPath) {
304                         if _, exists := expected[filename]; !exists {
305                                 return false
306                         }
307                         delete(expected, filename)
308                 }
309                 return len(expected) == 0
310         }
311         if err := quick.Check(f, nil); err != nil {
312                 t.Error(err)
313         }
314 }
315
316 func TestTossFreq(t *testing.T) {
317         f := func(fileSizes []uint8, replyNice uint8) bool {
318                 if len(fileSizes) == 0 {
319                         return true
320                 }
321                 spool, err := os.MkdirTemp("", "testtoss")
322                 if err != nil {
323                         panic(err)
324                 }
325                 defer os.RemoveAll(spool)
326                 nodeOur, err := NewNodeGenerate()
327                 if err != nil {
328                         t.Error(err)
329                         return false
330                 }
331                 ctx := Ctx{
332                         Spool:   spool,
333                         Self:    nodeOur,
334                         SelfId:  nodeOur.Id,
335                         Neigh:   make(map[NodeId]*Node),
336                         Alias:   make(map[string]*NodeId),
337                         LogPath: filepath.Join(spool, "log.log"),
338                         Debug:   TDebug,
339                 }
340                 ctx.Neigh[*nodeOur.Id] = nodeOur.Their()
341                 files := make(map[string][]byte)
342                 for i, fileSize := range fileSizes {
343                         if fileSize == 0 {
344                                 // to prevent chunked send
345                                 fileSize++
346                         }
347                         fileData := make([]byte, fileSize)
348                         if _, err := io.ReadFull(rand.Reader, fileData); err != nil {
349                                 panic(err)
350                         }
351                         fileName := strconv.Itoa(i)
352                         files[fileName] = fileData
353                         if err := ctx.TxFreq(
354                                 ctx.Neigh[*nodeOur.Id],
355                                 DefaultNiceFreq,
356                                 replyNice,
357                                 fileName,
358                                 fileName,
359                                 1<<15,
360                         ); err != nil {
361                                 t.Error(err)
362                                 return false
363                         }
364                 }
365                 rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx))
366                 txPath := filepath.Join(spool, ctx.Self.Id.String(), string(TTx))
367                 os.Rename(txPath, rxPath)
368                 os.MkdirAll(txPath, os.FileMode(0700))
369                 ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceFreq})
370                 if len(dirFiles(txPath)) != 0 || len(dirFiles(rxPath)) == 0 {
371                         return false
372                 }
373                 ctx.Neigh[*nodeOur.Id].FreqPath = &spool
374                 ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceFreq})
375                 if len(dirFiles(txPath)) != 0 || len(dirFiles(rxPath)) == 0 {
376                         return false
377                 }
378                 for fileName, fileData := range files {
379                         if err := os.WriteFile(
380                                 filepath.Join(spool, fileName),
381                                 fileData,
382                                 os.FileMode(0600),
383                         ); err != nil {
384                                 panic(err)
385                         }
386                 }
387                 ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceFreq})
388                 if len(dirFiles(txPath)) == 0 || len(dirFiles(rxPath)) != 0 {
389                         return false
390                 }
391                 for job := range ctx.Jobs(ctx.Self.Id, TTx) {
392                         var buf bytes.Buffer
393                         fd, err := os.Open(job.Path)
394                         if err != nil {
395                                 t.Error(err)
396                                 return false
397                         }
398                         _, _, _, err = PktEncRead(ctx.Self, ctx.Neigh, fd, &buf, true, nil)
399                         if err != nil {
400                                 t.Error(err)
401                                 return false
402                         }
403                         var pkt Pkt
404                         if _, err = xdr.Unmarshal(&buf, &pkt); err != nil {
405                                 t.Error(err)
406                                 return false
407                         }
408                         if pkt.Nice != replyNice {
409                                 return false
410                         }
411                         if !bytes.Equal(buf.Bytes(), files[string(pkt.Path[:int(pkt.PathLen)])]) {
412                                 return false
413                         }
414                 }
415                 return true
416         }
417         if err := quick.Check(f, nil); err != nil {
418                 t.Error(err)
419         }
420 }
421
422 func TestTossTrns(t *testing.T) {
423         f := func(datumLens []uint8) bool {
424                 if len(datumLens) == 0 {
425                         return true
426                 }
427                 datum := make(map[int][]byte)
428                 for i, datumLen := range datumLens {
429                         datumLen += 64
430                         data := make([]byte, datumLen)
431                         if _, err := io.ReadFull(rand.Reader, data); err != nil {
432                                 panic(err)
433                         }
434                         datum[i] = data
435                 }
436                 spool, err := os.MkdirTemp("", "testtoss")
437                 if err != nil {
438                         panic(err)
439                 }
440                 defer os.RemoveAll(spool)
441                 nodeOur, err := NewNodeGenerate()
442                 if err != nil {
443                         t.Error(err)
444                         return false
445                 }
446                 ctx := Ctx{
447                         Spool:   spool,
448                         Self:    nodeOur,
449                         SelfId:  nodeOur.Id,
450                         Neigh:   make(map[NodeId]*Node),
451                         Alias:   make(map[string]*NodeId),
452                         LogPath: filepath.Join(spool, "log.log"),
453                         Debug:   TDebug,
454                 }
455                 ctx.Neigh[*nodeOur.Id] = nodeOur.Their()
456                 rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx))
457                 os.MkdirAll(rxPath, os.FileMode(0700))
458                 txPath := filepath.Join(spool, ctx.Self.Id.String(), string(TTx))
459                 os.MkdirAll(txPath, os.FileMode(0700))
460                 for _, data := range datum {
461                         pktTrans := Pkt{
462                                 Magic:   MagicNNCPPv3.B,
463                                 Type:    PktTypeTrns,
464                                 PathLen: MTHSize,
465                         }
466                         copy(pktTrans.Path[:], nodeOur.Id[:])
467                         var dst bytes.Buffer
468                         if _, _, err := PktEncWrite(
469                                 ctx.Self,
470                                 ctx.Neigh[*nodeOur.Id],
471                                 &pktTrans,
472                                 123,
473                                 0, MaxFileSize, 1,
474                                 bytes.NewReader(data),
475                                 &dst,
476                         ); err != nil {
477                                 t.Error(err)
478                                 return false
479                         }
480                         hasher := MTHNew(0, 0)
481                         hasher.Write(dst.Bytes())
482                         if err := os.WriteFile(
483                                 filepath.Join(rxPath, Base32Codec.EncodeToString(hasher.Sum(nil))),
484                                 dst.Bytes(),
485                                 os.FileMode(0600),
486                         ); err != nil {
487                                 panic(err)
488                         }
489                 }
490                 ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: 123})
491                 if len(dirFiles(rxPath)) != 0 {
492                         return false
493                 }
494                 for _, filename := range dirFiles(txPath) {
495                         dataRead, err := os.ReadFile(filepath.Join(txPath, filename))
496                         if err != nil {
497                                 panic(err)
498                         }
499                         for k, data := range datum {
500                                 if bytes.Equal(dataRead, data) {
501                                         delete(datum, k)
502                                 }
503                         }
504                 }
505                 return len(datum) == 0
506         }
507         if err := quick.Check(f, nil); err != nil {
508                 t.Error(err)
509         }
510 }