2 NNCP -- Node to Node copy, utilities for store-and-forward data exchange
3 Copyright (C) 2016-2023 Sergey Matveev <stargrave@stargrave.org>
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, version 3 of the License.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
32 xdr "github.com/davecgh/go-xdr/xdr2"
39 func dirFiles(path string) []string {
40 dir, err := os.Open(path)
45 names, err := dir.Readdirnames(0)
52 func TestTossExec(t *testing.T) {
53 f := func(replyNice uint8, handleRaw uint32, recipients [16]uint8) bool {
54 handle := strconv.Itoa(int(handleRaw))
55 for i, recipient := range recipients {
56 recipients[i] = recipient % 8
58 spool, err := os.MkdirTemp("", "testtoss")
62 defer os.RemoveAll(spool)
63 nodeOur, err := NewNodeGenerate()
72 Neigh: make(map[NodeId]*Node),
73 Alias: make(map[string]*NodeId),
74 LogPath: filepath.Join(spool, "log.log"),
77 ctx.Neigh[*nodeOur.Id] = nodeOur.Their()
78 privates := make(map[uint8]*NodeOur)
79 for _, recipient := range recipients {
80 if _, exists := privates[recipient]; exists {
83 our, err := NewNodeGenerate()
88 privates[recipient] = our
89 ctx.Neigh[*our.Id] = our.Their()
91 for _, recipient := range recipients {
93 ctx.Neigh[*privates[recipient].Id],
97 []string{"arg0", "arg1"},
98 strings.NewReader("BODY\n"),
107 for _, recipient := range recipients {
108 ctx.Self = privates[recipient]
109 rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx))
110 os.Rename(filepath.Join(spool, ctx.Self.Id.String(), string(TTx)), rxPath)
111 if len(dirFiles(rxPath)) == 0 {
114 ctx.Toss(ctx.Self.Id, TRx, DefaultNiceExec-1,
115 false, false, false, false, false, false, false, false)
116 if len(dirFiles(rxPath)) == 0 {
119 ctx.Neigh[*nodeOur.Id].Exec = make(map[string][]string)
120 ctx.Neigh[*nodeOur.Id].Exec[handle] = []string{"/bin/sh", "-c", "false"}
121 ctx.Toss(ctx.Self.Id, TRx, DefaultNiceExec,
122 false, false, false, false, false, false, false, false)
123 if len(dirFiles(rxPath)) == 0 {
126 ctx.Neigh[*nodeOur.Id].Exec[handle] = []string{
129 "echo $NNCP_NICE $0 $1 >> %s ; cat >> %s",
130 filepath.Join(spool, "mbox"),
131 filepath.Join(spool, "mbox"),
134 ctx.Toss(ctx.Self.Id, TRx, DefaultNiceExec,
135 false, false, false, false, false, false, false, false)
136 if len(dirFiles(rxPath)) != 0 {
140 mbox, err := os.ReadFile(filepath.Join(spool, "mbox"))
144 expected := make([]byte, 0, 16)
145 for i := 0; i < 16; i++ {
148 []byte(fmt.Sprintf("%d arg0 arg1\n", replyNice))...,
150 expected = append(expected, []byte("BODY\n")...)
152 return bytes.Equal(mbox, expected)
154 if err := quick.Check(f, nil); err != nil {
159 func TestTossFile(t *testing.T) {
160 f := func(fileSizes []uint8) bool {
161 if len(fileSizes) == 0 {
164 files := make(map[string][]byte)
165 for i, fileSize := range fileSizes {
167 // to prevent chunked send
170 data := make([]byte, fileSize)
171 if _, err := io.ReadFull(rand.Reader, data); err != nil {
174 files[strconv.Itoa(i)] = data
176 spool, err := os.MkdirTemp("", "testtoss")
180 defer os.RemoveAll(spool)
181 nodeOur, err := NewNodeGenerate()
190 Neigh: make(map[NodeId]*Node),
191 Alias: make(map[string]*NodeId),
192 LogPath: filepath.Join(spool, "log.log"),
195 ctx.Neigh[*nodeOur.Id] = nodeOur.Their()
196 incomingPath := filepath.Join(spool, "incoming")
197 for _, fileData := range files {
198 hasher := MTHNew(0, 0)
199 hasher.Write(fileData)
200 fileName := Base32Codec.EncodeToString(hasher.Sum(nil))
201 src := filepath.Join(spool, fileName)
202 if err := os.WriteFile(src, fileData, os.FileMode(0600)); err != nil {
205 if err := ctx.TxFile(
206 ctx.Neigh[*nodeOur.Id],
219 rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx))
220 os.Rename(filepath.Join(spool, ctx.Self.Id.String(), string(TTx)), rxPath)
221 ctx.Toss(ctx.Self.Id, TRx, DefaultNiceFile,
222 false, false, false, false, false, false, false, false)
223 if len(dirFiles(rxPath)) == 0 {
226 ctx.Neigh[*nodeOur.Id].Incoming = &incomingPath
227 if ctx.Toss(ctx.Self.Id, TRx, DefaultNiceFile,
228 false, false, false, false, false, false, false, false) {
231 if len(dirFiles(rxPath)) != 0 {
234 for _, fileData := range files {
235 hasher := MTHNew(0, 0)
236 hasher.Write(fileData)
237 fileName := Base32Codec.EncodeToString(hasher.Sum(nil))
238 data, err := os.ReadFile(filepath.Join(incomingPath, fileName))
242 if !bytes.Equal(data, fileData) {
248 if err := quick.Check(f, nil); err != nil {
253 func TestTossFileSameName(t *testing.T) {
254 f := func(filesRaw uint8) bool {
255 files := int(filesRaw)%8 + 1
256 spool, err := os.MkdirTemp("", "testtoss")
260 defer os.RemoveAll(spool)
261 nodeOur, err := NewNodeGenerate()
270 Neigh: make(map[NodeId]*Node),
271 Alias: make(map[string]*NodeId),
272 LogPath: filepath.Join(spool, "log.log"),
275 ctx.Neigh[*nodeOur.Id] = nodeOur.Their()
276 srcPath := filepath.Join(spool, "junk")
277 if err = os.WriteFile(
279 []byte("doesnotmatter"),
285 incomingPath := filepath.Join(spool, "incoming")
286 for i := 0; i < files; i++ {
287 if err := ctx.TxFile(
288 ctx.Neigh[*nodeOur.Id],
301 rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx))
302 os.Rename(filepath.Join(spool, ctx.Self.Id.String(), string(TTx)), rxPath)
303 ctx.Neigh[*nodeOur.Id].Incoming = &incomingPath
304 ctx.Toss(ctx.Self.Id, TRx, DefaultNiceFile,
305 false, false, false, false, false, false, false, false)
306 expected := make(map[string]struct{})
307 expected["samefile"] = struct{}{}
308 for i := 0; i < files-1; i++ {
309 expected["samefile."+strconv.Itoa(i)] = struct{}{}
311 for _, filename := range dirFiles(incomingPath) {
312 if _, exists := expected[filename]; !exists {
315 delete(expected, filename)
317 return len(expected) == 0
319 if err := quick.Check(f, nil); err != nil {
324 func TestTossFreq(t *testing.T) {
325 f := func(fileSizes []uint8, replyNice uint8) bool {
326 if len(fileSizes) == 0 {
329 spool, err := os.MkdirTemp("", "testtoss")
333 defer os.RemoveAll(spool)
334 nodeOur, err := NewNodeGenerate()
343 Neigh: make(map[NodeId]*Node),
344 Alias: make(map[string]*NodeId),
345 LogPath: filepath.Join(spool, "log.log"),
348 ctx.Neigh[*nodeOur.Id] = nodeOur.Their()
349 files := make(map[string][]byte)
350 for i, fileSize := range fileSizes {
352 // to prevent chunked send
355 fileData := make([]byte, fileSize)
356 if _, err := io.ReadFull(rand.Reader, fileData); err != nil {
359 fileName := strconv.Itoa(i)
360 files[fileName] = fileData
361 if err := ctx.TxFreq(
362 ctx.Neigh[*nodeOur.Id],
373 rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx))
374 txPath := filepath.Join(spool, ctx.Self.Id.String(), string(TTx))
375 os.Rename(txPath, rxPath)
376 os.MkdirAll(txPath, os.FileMode(0700))
377 ctx.Toss(ctx.Self.Id, TRx, DefaultNiceFreq,
378 false, false, false, false, false, false, false, false)
379 if len(dirFiles(txPath)) != 0 || len(dirFiles(rxPath)) == 0 {
382 ctx.Neigh[*nodeOur.Id].FreqPath = &spool
383 ctx.Toss(ctx.Self.Id, TRx, DefaultNiceFreq,
384 false, false, false, false, false, false, false, false)
385 if len(dirFiles(txPath)) != 0 || len(dirFiles(rxPath)) == 0 {
388 for fileName, fileData := range files {
389 if err := os.WriteFile(
390 filepath.Join(spool, fileName),
397 ctx.Toss(ctx.Self.Id, TRx, DefaultNiceFreq,
398 false, false, false, false, false, false, false, false)
399 if len(dirFiles(txPath)) == 0 || len(dirFiles(rxPath)) != 0 {
402 for job := range ctx.Jobs(ctx.Self.Id, TTx) {
404 fd, err := os.Open(job.Path)
409 _, _, _, err = PktEncRead(ctx.Self, ctx.Neigh, fd, &buf, true, nil)
415 if _, err = xdr.Unmarshal(&buf, &pkt); err != nil {
419 if pkt.Nice != replyNice {
422 if !bytes.Equal(buf.Bytes(), files[string(pkt.Path[:int(pkt.PathLen)])]) {
428 if err := quick.Check(f, nil); err != nil {
433 func TestTossTrns(t *testing.T) {
434 f := func(datumLens []uint8) bool {
435 if len(datumLens) == 0 {
438 datum := make(map[int][]byte)
439 for i, datumLen := range datumLens {
441 data := make([]byte, datumLen)
442 if _, err := io.ReadFull(rand.Reader, data); err != nil {
447 spool, err := os.MkdirTemp("", "testtoss")
451 defer os.RemoveAll(spool)
452 nodeOur, err := NewNodeGenerate()
461 Neigh: make(map[NodeId]*Node),
462 Alias: make(map[string]*NodeId),
463 LogPath: filepath.Join(spool, "log.log"),
466 ctx.Neigh[*nodeOur.Id] = nodeOur.Their()
467 rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx))
468 os.MkdirAll(rxPath, os.FileMode(0700))
469 txPath := filepath.Join(spool, ctx.Self.Id.String(), string(TTx))
470 os.MkdirAll(txPath, os.FileMode(0700))
471 for _, data := range datum {
473 Magic: MagicNNCPPv3.B,
477 copy(pktTrans.Path[:], nodeOur.Id[:])
479 if _, _, err := PktEncWrite(
481 ctx.Neigh[*nodeOur.Id],
485 bytes.NewReader(data),
491 hasher := MTHNew(0, 0)
492 hasher.Write(dst.Bytes())
493 if err := os.WriteFile(
494 filepath.Join(rxPath, Base32Codec.EncodeToString(hasher.Sum(nil))),
501 ctx.Toss(ctx.Self.Id, TRx, 123,
502 false, false, false, false, false, false, false, false)
503 if len(dirFiles(rxPath)) != 0 {
506 for _, filename := range dirFiles(txPath) {
507 dataRead, err := os.ReadFile(filepath.Join(txPath, filename))
511 for k, data := range datum {
512 if bytes.Equal(dataRead, data) {
517 return len(datum) == 0
519 if err := quick.Check(f, nil); err != nil {