1 // NNCP -- Node to Node copy, utilities for store-and-forward data exchange
2 // Copyright (C) 2016-2024 Sergey Matveev <stargrave@stargrave.org>
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.
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.
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/>.
30 xdr "github.com/davecgh/go-xdr/xdr2"
37 func dirFiles(path string) []string {
38 dir, err := os.Open(path)
43 names, err := dir.Readdirnames(0)
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
56 spool, err := os.MkdirTemp("", "testtoss")
60 defer os.RemoveAll(spool)
61 nodeOur, err := NewNodeGenerate()
70 Neigh: make(map[NodeId]*Node),
71 Alias: make(map[string]*NodeId),
72 LogPath: filepath.Join(spool, "log.log"),
75 ctx.Neigh[*nodeOur.Id] = nodeOur.Their()
76 privates := make(map[uint8]*NodeOur)
77 for _, recipient := range recipients {
78 if _, exists := privates[recipient]; exists {
81 our, err := NewNodeGenerate()
86 privates[recipient] = our
87 ctx.Neigh[*our.Id] = our.Their()
89 for _, recipient := range recipients {
91 ctx.Neigh[*privates[recipient].Id],
95 []string{"arg0", "arg1"},
96 strings.NewReader("BODY\n"),
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 {
112 ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceExec - 1})
113 if len(dirFiles(rxPath)) == 0 {
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 {
122 ctx.Neigh[*nodeOur.Id].Exec[handle] = []string{
125 "echo $NNCP_NICE $0 $1 >> %s ; cat >> %s",
126 filepath.Join(spool, "mbox"),
127 filepath.Join(spool, "mbox"),
130 ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceExec})
131 if len(dirFiles(rxPath)) != 0 {
135 mbox, err := os.ReadFile(filepath.Join(spool, "mbox"))
139 expected := make([]byte, 0, 16)
140 for i := 0; i < 16; i++ {
143 []byte(fmt.Sprintf("%d arg0 arg1\n", replyNice))...,
145 expected = append(expected, []byte("BODY\n")...)
147 return bytes.Equal(mbox, expected)
149 if err := quick.Check(f, nil); err != nil {
154 func TestTossFile(t *testing.T) {
155 f := func(fileSizes []uint8) bool {
156 if len(fileSizes) == 0 {
159 files := make(map[string][]byte)
160 for i, fileSize := range fileSizes {
162 // to prevent chunked send
165 data := make([]byte, fileSize)
166 if _, err := io.ReadFull(rand.Reader, data); err != nil {
169 files[strconv.Itoa(i)] = data
171 spool, err := os.MkdirTemp("", "testtoss")
175 defer os.RemoveAll(spool)
176 nodeOur, err := NewNodeGenerate()
185 Neigh: make(map[NodeId]*Node),
186 Alias: make(map[string]*NodeId),
187 LogPath: filepath.Join(spool, "log.log"),
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 {
200 if err := ctx.TxFile(
201 ctx.Neigh[*nodeOur.Id],
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 {
220 ctx.Neigh[*nodeOur.Id].Incoming = &incomingPath
221 if ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceFile}) {
224 if len(dirFiles(rxPath)) != 0 {
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))
235 if !bytes.Equal(data, fileData) {
241 if err := quick.Check(f, nil); err != nil {
246 func TestTossFileSameName(t *testing.T) {
247 f := func(filesRaw uint8) bool {
248 files := int(filesRaw)%8 + 1
249 spool, err := os.MkdirTemp("", "testtoss")
253 defer os.RemoveAll(spool)
254 nodeOur, err := NewNodeGenerate()
263 Neigh: make(map[NodeId]*Node),
264 Alias: make(map[string]*NodeId),
265 LogPath: filepath.Join(spool, "log.log"),
268 ctx.Neigh[*nodeOur.Id] = nodeOur.Their()
269 srcPath := filepath.Join(spool, "junk")
270 if err = os.WriteFile(
272 []byte("doesnotmatter"),
278 incomingPath := filepath.Join(spool, "incoming")
279 for i := 0; i < files; i++ {
280 if err := ctx.TxFile(
281 ctx.Neigh[*nodeOur.Id],
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{}{}
303 for _, filename := range dirFiles(incomingPath) {
304 if _, exists := expected[filename]; !exists {
307 delete(expected, filename)
309 return len(expected) == 0
311 if err := quick.Check(f, nil); err != nil {
316 func TestTossFreq(t *testing.T) {
317 f := func(fileSizes []uint8, replyNice uint8) bool {
318 if len(fileSizes) == 0 {
321 spool, err := os.MkdirTemp("", "testtoss")
325 defer os.RemoveAll(spool)
326 nodeOur, err := NewNodeGenerate()
335 Neigh: make(map[NodeId]*Node),
336 Alias: make(map[string]*NodeId),
337 LogPath: filepath.Join(spool, "log.log"),
340 ctx.Neigh[*nodeOur.Id] = nodeOur.Their()
341 files := make(map[string][]byte)
342 for i, fileSize := range fileSizes {
344 // to prevent chunked send
347 fileData := make([]byte, fileSize)
348 if _, err := io.ReadFull(rand.Reader, fileData); err != nil {
351 fileName := strconv.Itoa(i)
352 files[fileName] = fileData
353 if err := ctx.TxFreq(
354 ctx.Neigh[*nodeOur.Id],
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 {
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 {
378 for fileName, fileData := range files {
379 if err := os.WriteFile(
380 filepath.Join(spool, fileName),
387 ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: DefaultNiceFreq})
388 if len(dirFiles(txPath)) == 0 || len(dirFiles(rxPath)) != 0 {
391 for job := range ctx.Jobs(ctx.Self.Id, TTx) {
393 fd, err := os.Open(job.Path)
398 _, _, _, err = PktEncRead(ctx.Self, ctx.Neigh, fd, &buf, true, nil)
404 if _, err = xdr.Unmarshal(&buf, &pkt); err != nil {
408 if pkt.Nice != replyNice {
411 if !bytes.Equal(buf.Bytes(), files[string(pkt.Path[:int(pkt.PathLen)])]) {
417 if err := quick.Check(f, nil); err != nil {
422 func TestTossTrns(t *testing.T) {
423 f := func(datumLens []uint8) bool {
424 if len(datumLens) == 0 {
427 datum := make(map[int][]byte)
428 for i, datumLen := range datumLens {
430 data := make([]byte, datumLen)
431 if _, err := io.ReadFull(rand.Reader, data); err != nil {
436 spool, err := os.MkdirTemp("", "testtoss")
440 defer os.RemoveAll(spool)
441 nodeOur, err := NewNodeGenerate()
450 Neigh: make(map[NodeId]*Node),
451 Alias: make(map[string]*NodeId),
452 LogPath: filepath.Join(spool, "log.log"),
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 {
462 Magic: MagicNNCPPv3.B,
466 copy(pktTrans.Path[:], nodeOur.Id[:])
468 if _, _, err := PktEncWrite(
470 ctx.Neigh[*nodeOur.Id],
474 bytes.NewReader(data),
480 hasher := MTHNew(0, 0)
481 hasher.Write(dst.Bytes())
482 if err := os.WriteFile(
483 filepath.Join(rxPath, Base32Codec.EncodeToString(hasher.Sum(nil))),
490 ctx.Toss(ctx.Self.Id, TRx, &TossOpts{Nice: 123})
491 if len(dirFiles(rxPath)) != 0 {
494 for _, filename := range dirFiles(txPath) {
495 dataRead, err := os.ReadFile(filepath.Join(txPath, filename))
499 for k, data := range datum {
500 if bytes.Equal(dataRead, data) {
505 return len(datum) == 0
507 if err := quick.Check(f, nil); err != nil {