/* NNCP -- Node to Node copy, utilities for store-and-forward data exchange Copyright (C) 2016-2023 Sergey Matveev This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package nncp import ( "bytes" "crypto/rand" "fmt" "io" "os" "path/filepath" "strconv" "strings" "testing" "testing/quick" xdr "github.com/davecgh/go-xdr/xdr2" ) var ( TDebug bool = false ) func dirFiles(path string) []string { dir, err := os.Open(path) if err != nil { panic(err) } defer dir.Close() names, err := dir.Readdirnames(0) if err != nil { panic(err) } return names } func TestTossExec(t *testing.T) { f := func(replyNice uint8, handleRaw uint32, recipients [16]uint8) bool { handle := strconv.Itoa(int(handleRaw)) for i, recipient := range recipients { recipients[i] = recipient % 8 } spool, err := os.MkdirTemp("", "testtoss") if err != nil { panic(err) } defer os.RemoveAll(spool) nodeOur, err := NewNodeGenerate() if err != nil { t.Error(err) return false } ctx := Ctx{ Spool: spool, Self: nodeOur, SelfId: nodeOur.Id, Neigh: make(map[NodeId]*Node), Alias: make(map[string]*NodeId), LogPath: filepath.Join(spool, "log.log"), Debug: TDebug, } ctx.Neigh[*nodeOur.Id] = nodeOur.Their() privates := make(map[uint8]*NodeOur) for _, recipient := range recipients { if _, exists := privates[recipient]; exists { continue } our, err := NewNodeGenerate() if err != nil { t.Error(err) return false } privates[recipient] = our ctx.Neigh[*our.Id] = our.Their() } for _, recipient := range recipients { if err := ctx.TxExec( ctx.Neigh[*privates[recipient].Id], DefaultNiceExec, replyNice, handle, []string{"arg0", "arg1"}, strings.NewReader("BODY\n"), 1<<15, MaxFileSize, false, nil, ); err != nil { t.Error(err) return false } } for _, recipient := range recipients { ctx.Self = privates[recipient] rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx)) os.Rename(filepath.Join(spool, ctx.Self.Id.String(), string(TTx)), rxPath) if len(dirFiles(rxPath)) == 0 { continue } ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceExec - 1}) if len(dirFiles(rxPath)) == 0 { return false } ctx.Neigh[*nodeOur.Id].Exec = make(map[string][]string) ctx.Neigh[*nodeOur.Id].Exec[handle] = []string{"/bin/sh", "-c", "false"} ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceExec}) if len(dirFiles(rxPath)) == 0 { return false } ctx.Neigh[*nodeOur.Id].Exec[handle] = []string{ "/bin/sh", "-c", fmt.Sprintf( "echo $NNCP_NICE $0 $1 >> %s ; cat >> %s", filepath.Join(spool, "mbox"), filepath.Join(spool, "mbox"), ), } ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceExec}) if len(dirFiles(rxPath)) != 0 { return false } } mbox, err := os.ReadFile(filepath.Join(spool, "mbox")) if err != nil { return false } expected := make([]byte, 0, 16) for i := 0; i < 16; i++ { expected = append( expected, []byte(fmt.Sprintf("%d arg0 arg1\n", replyNice))..., ) expected = append(expected, []byte("BODY\n")...) } return bytes.Equal(mbox, expected) } if err := quick.Check(f, nil); err != nil { t.Error(err) } } func TestTossFile(t *testing.T) { f := func(fileSizes []uint8) bool { if len(fileSizes) == 0 { return true } files := make(map[string][]byte) for i, fileSize := range fileSizes { if fileSize == 0 { // to prevent chunked send fileSize++ } data := make([]byte, fileSize) if _, err := io.ReadFull(rand.Reader, data); err != nil { panic(err) } files[strconv.Itoa(i)] = data } spool, err := os.MkdirTemp("", "testtoss") if err != nil { panic(err) } defer os.RemoveAll(spool) nodeOur, err := NewNodeGenerate() if err != nil { t.Error(err) return false } ctx := Ctx{ Spool: spool, Self: nodeOur, SelfId: nodeOur.Id, Neigh: make(map[NodeId]*Node), Alias: make(map[string]*NodeId), LogPath: filepath.Join(spool, "log.log"), Debug: TDebug, } ctx.Neigh[*nodeOur.Id] = nodeOur.Their() incomingPath := filepath.Join(spool, "incoming") for _, fileData := range files { hasher := MTHNew(0, 0) hasher.Write(fileData) fileName := Base32Codec.EncodeToString(hasher.Sum(nil)) src := filepath.Join(spool, fileName) if err := os.WriteFile(src, fileData, os.FileMode(0600)); err != nil { panic(err) } if err := ctx.TxFile( ctx.Neigh[*nodeOur.Id], DefaultNiceFile, src, fileName, MaxFileSize, 1<<15, MaxFileSize, nil, ); err != nil { t.Error(err) return false } } rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx)) os.Rename(filepath.Join(spool, ctx.Self.Id.String(), string(TTx)), rxPath) ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceFile}) if len(dirFiles(rxPath)) == 0 { return false } ctx.Neigh[*nodeOur.Id].Incoming = &incomingPath if ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceFile}) { return false } if len(dirFiles(rxPath)) != 0 { return false } for _, fileData := range files { hasher := MTHNew(0, 0) hasher.Write(fileData) fileName := Base32Codec.EncodeToString(hasher.Sum(nil)) data, err := os.ReadFile(filepath.Join(incomingPath, fileName)) if err != nil { panic(err) } if !bytes.Equal(data, fileData) { return false } } return true } if err := quick.Check(f, nil); err != nil { t.Error(err) } } func TestTossFileSameName(t *testing.T) { f := func(filesRaw uint8) bool { files := int(filesRaw)%8 + 1 spool, err := os.MkdirTemp("", "testtoss") if err != nil { panic(err) } defer os.RemoveAll(spool) nodeOur, err := NewNodeGenerate() if err != nil { t.Error(err) return false } ctx := Ctx{ Spool: spool, Self: nodeOur, SelfId: nodeOur.Id, Neigh: make(map[NodeId]*Node), Alias: make(map[string]*NodeId), LogPath: filepath.Join(spool, "log.log"), Debug: TDebug, } ctx.Neigh[*nodeOur.Id] = nodeOur.Their() srcPath := filepath.Join(spool, "junk") if err = os.WriteFile( srcPath, []byte("doesnotmatter"), os.FileMode(0600), ); err != nil { t.Error(err) return false } incomingPath := filepath.Join(spool, "incoming") for i := 0; i < files; i++ { if err := ctx.TxFile( ctx.Neigh[*nodeOur.Id], DefaultNiceFile, srcPath, "samefile", MaxFileSize, 1<<15, MaxFileSize, nil, ); err != nil { t.Error(err) return false } } rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx)) os.Rename(filepath.Join(spool, ctx.Self.Id.String(), string(TTx)), rxPath) ctx.Neigh[*nodeOur.Id].Incoming = &incomingPath ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceFile}) expected := make(map[string]struct{}) expected["samefile"] = struct{}{} for i := 0; i < files-1; i++ { expected["samefile."+strconv.Itoa(i)] = struct{}{} } for _, filename := range dirFiles(incomingPath) { if _, exists := expected[filename]; !exists { return false } delete(expected, filename) } return len(expected) == 0 } if err := quick.Check(f, nil); err != nil { t.Error(err) } } func TestTossFreq(t *testing.T) { f := func(fileSizes []uint8, replyNice uint8) bool { if len(fileSizes) == 0 { return true } spool, err := os.MkdirTemp("", "testtoss") if err != nil { panic(err) } defer os.RemoveAll(spool) nodeOur, err := NewNodeGenerate() if err != nil { t.Error(err) return false } ctx := Ctx{ Spool: spool, Self: nodeOur, SelfId: nodeOur.Id, Neigh: make(map[NodeId]*Node), Alias: make(map[string]*NodeId), LogPath: filepath.Join(spool, "log.log"), Debug: TDebug, } ctx.Neigh[*nodeOur.Id] = nodeOur.Their() files := make(map[string][]byte) for i, fileSize := range fileSizes { if fileSize == 0 { // to prevent chunked send fileSize++ } fileData := make([]byte, fileSize) if _, err := io.ReadFull(rand.Reader, fileData); err != nil { panic(err) } fileName := strconv.Itoa(i) files[fileName] = fileData if err := ctx.TxFreq( ctx.Neigh[*nodeOur.Id], DefaultNiceFreq, replyNice, fileName, fileName, 1<<15, ); err != nil { t.Error(err) return false } } rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx)) txPath := filepath.Join(spool, ctx.Self.Id.String(), string(TTx)) os.Rename(txPath, rxPath) os.MkdirAll(txPath, os.FileMode(0700)) ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceFreq}) if len(dirFiles(txPath)) != 0 || len(dirFiles(rxPath)) == 0 { return false } ctx.Neigh[*nodeOur.Id].FreqPath = &spool ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceFreq}) if len(dirFiles(txPath)) != 0 || len(dirFiles(rxPath)) == 0 { return false } for fileName, fileData := range files { if err := os.WriteFile( filepath.Join(spool, fileName), fileData, os.FileMode(0600), ); err != nil { panic(err) } } ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceFreq}) if len(dirFiles(txPath)) == 0 || len(dirFiles(rxPath)) != 0 { return false } for job := range ctx.Jobs(ctx.Self.Id, TTx) { var buf bytes.Buffer fd, err := os.Open(job.Path) if err != nil { t.Error(err) return false } _, _, _, err = PktEncRead(ctx.Self, ctx.Neigh, fd, &buf, true, nil) if err != nil { t.Error(err) return false } var pkt Pkt if _, err = xdr.Unmarshal(&buf, &pkt); err != nil { t.Error(err) return false } if pkt.Nice != replyNice { return false } if !bytes.Equal(buf.Bytes(), files[string(pkt.Path[:int(pkt.PathLen)])]) { return false } } return true } if err := quick.Check(f, nil); err != nil { t.Error(err) } } func TestTossTrns(t *testing.T) { f := func(datumLens []uint8) bool { if len(datumLens) == 0 { return true } datum := make(map[int][]byte) for i, datumLen := range datumLens { datumLen += 64 data := make([]byte, datumLen) if _, err := io.ReadFull(rand.Reader, data); err != nil { panic(err) } datum[i] = data } spool, err := os.MkdirTemp("", "testtoss") if err != nil { panic(err) } defer os.RemoveAll(spool) nodeOur, err := NewNodeGenerate() if err != nil { t.Error(err) return false } ctx := Ctx{ Spool: spool, Self: nodeOur, SelfId: nodeOur.Id, Neigh: make(map[NodeId]*Node), Alias: make(map[string]*NodeId), LogPath: filepath.Join(spool, "log.log"), Debug: TDebug, } ctx.Neigh[*nodeOur.Id] = nodeOur.Their() rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx)) os.MkdirAll(rxPath, os.FileMode(0700)) txPath := filepath.Join(spool, ctx.Self.Id.String(), string(TTx)) os.MkdirAll(txPath, os.FileMode(0700)) for _, data := range datum { pktTrans := Pkt{ Magic: MagicNNCPPv3.B, Type: PktTypeTrns, PathLen: MTHSize, } copy(pktTrans.Path[:], nodeOur.Id[:]) var dst bytes.Buffer if _, _, err := PktEncWrite( ctx.Self, ctx.Neigh[*nodeOur.Id], &pktTrans, 123, 0, MaxFileSize, 1, bytes.NewReader(data), &dst, ); err != nil { t.Error(err) return false } hasher := MTHNew(0, 0) hasher.Write(dst.Bytes()) if err := os.WriteFile( filepath.Join(rxPath, Base32Codec.EncodeToString(hasher.Sum(nil))), dst.Bytes(), os.FileMode(0600), ); err != nil { panic(err) } } ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: 123}) if len(dirFiles(rxPath)) != 0 { return false } for _, filename := range dirFiles(txPath) { dataRead, err := os.ReadFile(filepath.Join(txPath, filename)) if err != nil { panic(err) } for k, data := range datum { if bytes.Equal(dataRead, data) { delete(datum, k) } } } return len(datum) == 0 } if err := quick.Check(f, nil); err != nil { t.Error(err) } }