]> Cypherpunks.ru repositories - nncp.git/commitdiff
Merge branch 'develop' v7.0.0
authorSergey Matveev <stargrave@stargrave.org>
Wed, 30 Jun 2021 12:27:04 +0000 (15:27 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Wed, 30 Jun 2021 12:27:04 +0000 (15:27 +0300)
56 files changed:
bin/cmd.list
doc/chunked.texi
doc/cmds.texi
doc/download.texi
doc/eblob.texi
doc/index.texi
doc/mcd.texi
doc/mth.texi [new file with mode: 0644]
doc/news.ru.texi
doc/news.texi
doc/pkt.texi
doc/spool.texi
makedist.sh
ports/nncp/Makefile
ports/nncp/pkg-plist
src/call.go
src/cfg.go
src/check.go
src/chunked.go
src/cmd/nncp-bundle/main.go
src/cmd/nncp-call/main.go
src/cmd/nncp-caller/main.go
src/cmd/nncp-cfgenc/main.go
src/cmd/nncp-cfgmin/main.go
src/cmd/nncp-cfgnew/main.go
src/cmd/nncp-check/main.go
src/cmd/nncp-cronexpr/main.go
src/cmd/nncp-daemon/main.go
src/cmd/nncp-exec/main.go
src/cmd/nncp-file/main.go
src/cmd/nncp-freq/main.go
src/cmd/nncp-hash/main.go [new file with mode: 0644]
src/cmd/nncp-log/main.go
src/cmd/nncp-pkt/main.go
src/cmd/nncp-reass/main.go
src/cmd/nncp-rm/main.go
src/cmd/nncp-stat/main.go
src/cmd/nncp-toss/main.go
src/cmd/nncp-xfer/main.go
src/eblob.go
src/go.mod
src/go.sum
src/jobs.go
src/magic.go [new file with mode: 0644]
src/mcd.go
src/mth.go [new file with mode: 0644]
src/mth_test.go [new file with mode: 0644]
src/nncp.go
src/pkt.go
src/progress.go
src/sp.go
src/tmp.go
src/toss.go
src/toss_test.go
src/tx.go
src/tx_test.go

index 7797d4f7a678cbd9909645ec47dff7dacde90391..b85cb2a2ba575cb4850a1491192f56614dd2c32a 100644 (file)
@@ -10,6 +10,7 @@ nncp-daemon
 nncp-exec
 nncp-file
 nncp-freq
+nncp-hash
 nncp-log
 nncp-pkt
 nncp-reass
index 4ac50b7a139889e422cce0b9fc68220f2fd73125..5f78c2311bda9d716028d4de1b0de13d7422729f 100644 (file)
@@ -1,7 +1,7 @@
 @node Chunked
 @unnumbered Chunked files
 
-There is ability to transfer huge files with splitting them into smaller
+There is ability to transfer huge files with dividing them into smaller
 chunks. Each chunk is treated like a separate file, producing separate
 outbound packet unrelated with other ones.
 
@@ -32,7 +32,7 @@ size and their hash checksums. This is
 @headitem @tab XDR type @tab Value
 @item Magic number @tab
     8-byte, fixed length opaque data @tab
-    @verb{|N N C P M 0x00 0x00 0x01|}
+    @verb{|N N C P M 0x00 0x00 0x02|}
 @item File size @tab
     unsigned hyper integer @tab
     Whole reassembled file's size
@@ -41,7 +41,7 @@ size and their hash checksums. This is
     Size of each chunk (except for the last one, that could be smaller)
 @item Checksums @tab
     variable length array of 32 byte fixed length opaque data @tab
-    BLAKE2b-256 checksum of each chunk
+    @ref{MTH} checksum of each chunk
 @end multitable
 
 @anchor{ChunkedZFS}
index 45825932821df2a207ab4668aabe9a84893cf3f8..665b57629d394f9926f95d1b04f79deabf3c6cb8 100644 (file)
@@ -238,7 +238,7 @@ $ nncp-check [-nock] [options]
 
 Perform @ref{Spool, spool} directory integrity check. Read all files
 that has Base32-encoded filenames and compare it with recalculated
-BLAKE2b hash output of their contents.
+@ref{MTH} hash output of their contents.
 
 The most useful mode of operation is with @option{-nock} option, that
 checks integrity of @file{.nock} files, renaming them to ordinary
@@ -362,7 +362,7 @@ for that temporary file and resulting encrypted packet. You can control
 temporary file location directory with @env{TMPDIR} environment
 variable. Encryption is performed in AEAD mode with
 @url{https://cr.yp.to/chacha.html, ChaCha20}-@url{https://en.wikipedia.org/wiki/Poly1305, Poly1305}
-algorithms. Data is splitted on 128 KiB blocks. Each block is encrypted
+algorithms. Data is divided on 128 KiB blocks. Each block is encrypted
 with increasing nonce counter. File is deletes immediately after
 creation, so even if program crashes -- disk space will be reclaimed, no
 need in cleaning it up later.
@@ -404,6 +404,24 @@ If @ref{CfgNotify, notification} is enabled on the remote side for
 file request, then it will sent simple letter after successful file
 queuing.
 
+@node nncp-hash
+@section nncp-hash
+
+@example
+$ nncp-log [-file ...] [-seek X] [-debug] [-progress]
+@end example
+
+Calculate @ref{MTH} hash of either stdin, or @option{-file} if
+specified.
+
+You can optionally force seeking the file first, reading only part of
+the file, and then prepending unread portion of data, with the
+@option{-seek} option. It is intended only for testing and debugging of
+MTH hasher capabilities.
+
+@option{-debug} option shows all intermediate MTH hashes.
+And @option{-progress} will show progress bar.
+
 @node nncp-log
 @section nncp-log
 
index e0cbd42116906f1a3601ee70b561e70f5c3867a9..4f920c1156a1650fe68f42c1256b6f637ceb4298 100644 (file)
@@ -15,16 +15,23 @@ Tarballs include all necessary required libraries:
 @item @code{github.com/gosuri/uilive} @tab MIT
 @item @code{github.com/hjson/hjson-go} @tab MIT
 @item @code{github.com/klauspost/compress} @tab BSD 3-Clause
+@item @code{github.com/klauspost/cpuid} @tab BSD 3-Clause
 @item @code{go.cypherpunks.ru/balloon} @tab GNU LGPLv3
+@item @code{go.cypherpunks.ru/recfile} @tab GNU GPLv3
 @item @code{golang.org/x/crypto} @tab BSD 3-Clause
 @item @code{golang.org/x/net} @tab BSD 3-Clause
 @item @code{golang.org/x/sys} @tab BSD 3-Clause
 @item @code{golang.org/x/term} @tab BSD 3-Clause
+@item @code{lukechampine.com/blake3} @tab MIT
 @end multitable
 
 @multitable {XXXXX} {XXXX-XX-XX} {XXXX KiB} {link sign} {xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
 @headitem Version @tab Date @tab Size @tab Tarball @tab SHA256 checksum
 
+@item @ref{Release 6.6.0, 6.6.0} @tab 2021-06-26 @tab 1041 KiB
+@tab @url{download/nncp-6.6.0.tar.xz, link} @url{download/nncp-6.6.0.tar.xz.sig, sign}
+@tab @code{73DB666F A5C30282 770516B2 F39F1240 74117B45 A9F4B484 0361861A 183577F1}
+
 @item @ref{Release 6.5.0, 6.5.0} @tab 2021-05-30 @tab 1041 KiB
 @tab @url{download/nncp-6.5.0.tar.xz, link} @url{download/nncp-6.5.0.tar.xz.sig, sign}
 @tab @code{241D2AA7 27275CCF 86F06797 1AA8B3B8 D625C85C 4279DFDE 560216E3 38670B9A}
index 45de886d3defe40b7a82905107f64a79293d02ca..3a5c08d79659fc858b49217f9d66c3a2a1d55944 100644 (file)
@@ -1,7 +1,7 @@
 @node EBlob
 @unnumbered EBlob format
 
-Eblob is an encrypted blob (binary large object, in the terms of
+EBlob is an encrypted blob (binary large object, in the terms of
 databases), holding any kind of symmetrically encrypted data with the
 passphrase used to derive the key. It is used to secure configuration
 files, holding valuable private keys, allowing them to be transferred
@@ -29,7 +29,7 @@ attacks and seems more secure than Argon2
 (@url{https://password-hashing.net/, Password Hashing Competition}
 winner).
 
-Eblob is an @url{https://tools.ietf.org/html/rfc4506, XDR}-encoded structure:
+EBlob is an @url{https://tools.ietf.org/html/rfc4506, XDR}-encoded structure:
 
 @verbatim
 +-------+------------------+------+
@@ -54,15 +54,8 @@ Eblob is an @url{https://tools.ietf.org/html/rfc4506, XDR}-encoded structure:
 @end multitable
 
 @enumerate
-@item generate the main key using @code{balloon(BLAKE2b-256, S, T, P,
-salt, password)}
-@item initialize @url{https://blake2.net/, BLAKE2Xb} XOF with generated
-main key and 32-byte output length
-@item feed @verb{|N N C P B 0x00 0x00 0x03|} magic number to XOF
-@item read 32-bytes of blob AEAD encryption key
+@item generate the key using @code{balloon(BLAKE2b-256, S, T, P, salt, password)}
 @item encrypt and authenticate blob using
     @url{https://cr.yp.to/chacha.html, ChaCha20}-@url{https://en.wikipedia.org/wiki/Poly1305, Poly1305}.
-    Blob is splitted on 128 KiB blocks. Each block is encrypted with
-    increasing nonce counter. Eblob packet itself, with empty blob
-    field, is fed as an additional authenticated data
+    EBlob packet itself, with empty blob field, is fed as an additional authenticated data
 @end enumerate
index cbf6aeda864e7a3f252ff32164c5218babbac2f6..27be3bb0d9ccd25048fdeb221f46ac3f10bb621e 100644 (file)
@@ -52,6 +52,7 @@ There are also articles about its usage outside this website:
 * Spool directory: Spool.
 * Log format: Log.
 * Packet format: Packet.
+* Merkle Tree Hashing: MTH.
 * Sync protocol: Sync.
 * MultiCast Discovery: MCD.
 * EBlob format: EBlob.
@@ -77,6 +78,7 @@ There are also articles about its usage outside this website:
 @include spool.texi
 @include log.texi
 @include pkt.texi
+@include mth.texi
 @include sp.texi
 @include mcd.texi
 @include eblob.texi
index 0e47ce65684149c9f8f5b60beda5ddd9664c617e..958e0feb7380a48e2bb7da3f255115270bcada96 100644 (file)
@@ -7,11 +7,10 @@ in local area network. It is very simple:
 
 @itemize
 @item
-    @command{nncp-daemon} sends multicast messages about its presence
-    from time to time.
-    See @ref{CfgMCDSend, mcd-send} configuration option.
+    @ref{nncp-daemon} sends multicast messages about its presence from
+    time to time. See @ref{CfgMCDSend, mcd-send} configuration option.
 @item
-    When @command{nncp-caller} sees them, it adds them as the most
+    When @ref{nncp-caller} sees them, it adds them as the most
     preferred addresses to already known ones. If MCD address
     announcement was not refreshed after two minutes -- it is removed.
     See @ref{CfgMCDListen, mcd-listen} and
@@ -27,9 +26,9 @@ MCD announcement is an XDR-encoded packet with only two fields:
 @end verbatim
 
 Magic number is @verb{|N N C P D 0x00 0x00 0x01|} and sender is 32-byte
-identifier of the node. It is sent as UDP packet on IPv6 @verb{|ff02::1|}
-multicast address (all hosts in the network) and hard-coded @strong{5400}
-port. Operating system will use IPv6 link-local address as a source one,
-with the port taken from @command{nncp-daemon}'s @option{-bind} option.
-That way, IP packet itself will carry the link-scope reachable address
-of the daemon.
+identifier of the node. It is sent as UDP packet on IPv6
+@strong{@verb{|ff02::4e4e:4350|}} (hexadecimal ASCII @verb{|NNCP|})
+multicast address and @strong{5400} port. Operating system will use IPv6
+link-local address as a source one, with the port taken from
+@command{nncp-daemon}'s @option{-bind} option. That way, IP packet
+itself will carry the link-scope reachable address of the daemon.
diff --git a/doc/mth.texi b/doc/mth.texi
new file mode 100644 (file)
index 0000000..72a38d9
--- /dev/null
@@ -0,0 +1,42 @@
+@node MTH
+@unnumbered Merkle Tree Hashing
+
+NNCP uses @url{https://github.com/BLAKE3-team/BLAKE3, BLAKE3} hash
+function in @url{https://en.wikipedia.org/wiki/Merkle_Tree, Merkle Tree}
+mode of operation for checksumming @ref{Encrypted, encrypted packets}
+and @ref{Chunked, chunked} files.
+
+Previously ordinary BLAKE2b-256 was used, but it prevented partial
+calculations of file parts, so you had to fully read the whole file
+again after its resumed download.
+
+MTH divides data on 128 KiB blocks, hashes each of them independently
+and then calculates the Merkle tree root:
+
+@verbatim
+                node3
+               /   \
+              /     \
+           node2    leaf4
+          /    \       \
+         /      \       \
+        /        \       \
+       /          \       \
+      /            \       \
+    node0         node1    leaf4
+   /    \        /    \      \
+  /      \      /      \      \
+leaf0  leaf1  leaf2  leaf3  leaf4
+  |      |      |      |      |
+block  block  block  block  block
+@end verbatim
+
+Leaf's value is keyed BLAKE3-256 hash of underlying block (128 KiB,
+except for probably the last one). Node's value is keyed BLAKE3-256 hash
+of two underlying leafs. Keys are
+@verb{|BLAKE3-256("NNCP MTH LEAF")|} and
+@verb{|BLAKE3-256("NNCP MTH NODE")|}.
+Keyed operation allows working with an aligned data (128KiB or 64B
+boundaries), unlike popular way of prepending @verb{|0x00|} and
+@verb{|0x01|} to the hashed data, being more efficient with an attention
+to BLAKE3's internal Merkle tree.
index 82710847aa76e08b207b92516897aae8ff687c10..c045d753c72f16c377bc436971f997231c984fa8 100644 (file)
@@ -1,6 +1,50 @@
 @node Новости
 @section Новости
 
+@node Релиз 7.0.0
+@subsection Релиз 7.0.0
+@itemize
+
+@item
+Хэширование с BLAKE3 на базе деревьев Меркле (Merkle Tree Hashing, MTH)
+используется вместо BLAKE2b. Из-за этого, обратно @strong{несовместимое}
+изменение формата шифрованных файлов (всего что находится в spool
+области) и формата @file{.meta} файла при chunked передаче.
+
+Текущая реализация далека от оптимальной: в ней нет распараллеливания
+вычислений и имеет повышенное потребление памяти: около 512 KiB на
+каждый 1 GiB данных файла. Будущая оптимизация производительности и
+потребления памяти не должна привести к изменению формата пакетов. Но
+это всё равно в несколько раз быстрее BLAKE2b.
+
+@item
+Из-за использования MTH, докачиваемые в online режиме файлы потребуют
+чтения с диска только предшествующей части, а не полностью всего файла,
+как было прежде.
+
+@item
+Добавлена @command{nncp-hash} утилита для вычисления MTH хэша файла.
+
+@item
+В шифрованных пакетах BLAKE2 KDF и XOF функции заменены на BLAKE3. Ещё
+уменьшая количество примитивов. А также заголовок шифрованного файла
+теперь является ассоциированными данными при шифровании.
+
+@item
+MultiCast Discovery использует
+@verb{|ff02::4e4e:4350|} адрес вместо @verb{|ff02::1|}.
+
+@item
+@command{nncp-cfgenc} ошибочно трижды спрашивал парольную фразу при шифровании.
+
+@item
+@command{nncp-stat} выводит сводку о частично скачанных пакетах.
+
+@item
+Обновлены зависимые библиотеки.
+
+@end itemize
+
 @node Релиз 6.6.0
 @subsection Релиз 6.6.0
 @itemize
index b4302f48d8453c2e4194b2b89dfae528b2209fd4..cc4eba943acd1d41550398bca9c27c03ab8571bf 100644 (file)
@@ -3,6 +3,49 @@
 
 See also this page @ref{Новости, on russian}.
 
+@node Release 7.0.0
+@section Release 7.0.0
+@itemize
+
+@item
+Merkle Tree-based Hashing with BLAKE3 (MTH) is used instead of BLAKE2b.
+Because of that, there are backward @strong{incompatible} changes of
+encrypted files (everything laying in the spool directory) and
+@file{.meta} files of chunked transfer.
+
+Current implementation is far from being optimal: it lacks
+parallelizable calculations and has higher memory consumption: nearly
+512 KiB for each 1 GiB of file's data. Future performance and memory
+size optimizations should not lead to packet's format change. But it is
+still several times faster than BLAKE2b.
+
+@item
+Resumed online downloads, because of MTH, require reading only of the
+preceding part of file, not the whole one as was before.
+
+@item
+@command{nncp-hash} utility appeared for calculating file's MTH hash.
+
+@item
+BLAKE2 KDF and XOF functions are replaced with BLAKE3 in encrypted
+packets. Lowering number of used primitives. Also, its encrypted
+packet's header is used as an associated data during encryption.
+
+@item
+MultiCast Discovery uses
+@verb{|ff02::4e4e:4350|} address instead of @verb{|ff02::1|}.
+
+@item
+@command{nncp-cfgenc} mistakenly asked passphrase three times during encryption.
+
+@item
+@command{nncp-stat} reports about partly downloaded packets.
+
+@item
+Updated dependencies.
+
+@end itemize
+
 @node Release 6.6.0
 @section Release 6.6.0
 @itemize
index 93841e107234eb6e82e8e1abf71be7c18b50a4df..b47911330cd05978912fa2e317a30a995076de8c 100644 (file)
@@ -95,7 +95,7 @@ Each encrypted packet has the following header:
 @headitem @tab XDR type @tab Value
 @item Magic number @tab
     8-byte, fixed length opaque data @tab
-    @verb{|N N C P E 0x00 0x00 0x04|}
+    @verb{|N N C P E 0x00 0x00 0x05|}
 @item Niceness @tab
     unsigned integer @tab
     1-255, packet @ref{Niceness, niceness} level
@@ -110,26 +110,20 @@ Each encrypted packet has the following header:
     Ephemeral curve25519 public key
 @item Signature @tab
     64-byte, fixed length opaque data @tab
-    ed25519 signature for that packet's header
+    ed25519 signature for that packet's header over all previous fields.
 @end multitable
 
-Signature is calculated over all previous fields.
-
 All following encryption is done in AEAD mode using
 @url{https://cr.yp.to/chacha.html, ChaCha20}-@url{https://en.wikipedia.org/wiki/Poly1305, Poly1305}
-algorithms. Data is splitted on 128 KiB blocks. Each block is encrypted with
-increasing nonce counter.
-
-Authenticated and encrypted size come after the header:
-
-@multitable @columnfractions 0.2 0.3 0.5
-@headitem @tab XDR type @tab Value
-@item Size @tab
-    unsigned hyper integer @tab
-    Payload size.
-@end multitable
+algorithms. Authenticated data is BLAKE3-256 hash of the unsigned
+portion of the header (the same data used in the signature). Size is
+XDR-encoded unsigned hyper integer, carrying the payload size, encrypted
+as a single AEAD-block (with the tag) independently from the following
+blocks. It is encoded with the zero nonce.
 
-Then comes the actual payload.
+Payload with possible padding is divided on 128 KiB blocks blocks. They
+are encrypted with the same authenticated data and increasing big-endian
+64-bit nonce, starting at 1.
 
 Each node has static @strong{exchange} and @strong{signature} keypairs.
 When node A want to send encrypted packet to node B, it:
@@ -142,18 +136,17 @@ When node A want to send encrypted packet to node B, it:
 @item takes remote node's exchange public key and performs
     Diffie-Hellman computation on this remote static public key and
     private ephemeral one
-@item derive the keys:
-    @enumerate
-    @item initialize @url{https://blake2.net/, BLAKE2Xb} XOF with
-    derived ephemeral key and 96-byte output length
-    @item feed @verb{|N N C P E 0x00 0x00 0x04|} magic number to XOF
-    @item read 32-bytes of "size" AEAD encryption key
-    @item read 32-bytes of payload AEAD encryption key
-    @item optionally read 32-bytes pad generation key
-    @end enumerate
+@item derives 32-bytes AEAD encryption key with BLAKE3 derivation
+    function. Source key is the derived ephemeral key. Context is
+    @verb{|N N C P E 0x00 0x00 0x05|} magic number
+@item calculates authenticated data: it is BLAKE3-256 hash of the
+    unsigned header (same used for signing)
 @item encrypts size, appends its authenticated ciphertext to the header
-@item encrypts payload, appends its authenticated ciphertext
+    (with authenticated data, nonce=0)
+@item encrypts each payload block, appending its authenticated ciphertext
+    (with authenticated data, nonce starting at 1, increasing with each block)
 @item possibly appends any kind of "junk" noise data to hide real
-    payload's size from the adversary (generated using XOF with
-    unlimited output length)
+    payload's size from the adversary (generated using BLAKE3 XOF, with
+    the key derived from the ephemeral one and context string of
+    @verb{|N N C P E 0x00 0x00 0x05 <SP> P A D|})
 @end enumerate
index 8dcaa3ead7121328bd8c87cfed38ae77f6cfff37..14f8ee2b6c4663a363ce20f23de1f0e024847414 100644 (file)
@@ -37,7 +37,7 @@ directories at once.
 
 @item LYT64MWSNDK34CVYOO7TA6ZCJ3NWI2OUDBBMX2A4QWF34FIRY4DQ
 is an example @ref{Encrypted, encrypted packet}. Its filename is Base32
-encoded BLAKE2b hash of the whole contents. It can be integrity checked
+encoded @ref{MTH} hash of the whole contents. It can be integrity checked
 anytime.
 
 @item LYT64MWSNDK34CVYOO7TA6ZCJ3NWI2OUDBBMX2A4QWF34FIRY4DQ.part
index eef4931af8725dc9c8626572e3c7d4b8a5a4c97d..96946f12b662eac2770171e567923e1a415f6d4a 100755 (executable)
@@ -17,7 +17,7 @@ rm -r \
     github.com/flynn/noise/vector* \
     github.com/gorhill/cronexpr/APLv2 \
     github.com/hjson/hjson-go/build_release.sh \
-    github.com/klauspost/compress/snappy \
+    github.com/golang/snappy \
     github.com/klauspost/compress/zstd/snappy.go \
     golang.org/x/sys/plan9 \
     golang.org/x/sys/windows
index 4fd2908c87ecd47b174af322d6fbb393b14bd98a..44408504872f69602776023e201e351704ddb16a 100644 (file)
@@ -1,5 +1,5 @@
 PORTNAME=      nncp
-DISTVERSION=   6.5.0
+DISTVERSION=   7.0.0
 CATEGORIES=    net
 MASTER_SITES=  http://www.nncpgo.org/download/
 
index 4a584028612e2fc58654072ac010f4328e2710f5..6c422ad005b034a7037bd15af48fde4670bb1a3c 100644 (file)
@@ -10,6 +10,7 @@ bin/nncp-daemon
 bin/nncp-exec
 bin/nncp-file
 bin/nncp-freq
+bin/nncp-hash
 bin/nncp-log
 bin/nncp-pkt
 bin/nncp-reass
index 6fc6045d4371ce15144a9aea7579646b4586c319..648190f22856e45bbc437a1d2a49e54e8522fb1d 100644 (file)
@@ -56,7 +56,7 @@ func (ctx *Ctx) CallNode(
        onlineDeadline, maxOnlineTime time.Duration,
        listOnly bool,
        noCK bool,
-       onlyPkts map[[32]byte]bool,
+       onlyPkts map[[MTHSize]byte]bool,
 ) (isGood bool) {
        for _, addr := range addrs {
                les := LEs{{"Node", node.Id}, {"Addr", addr}}
@@ -110,7 +110,7 @@ func (ctx *Ctx) CallNode(
                                        node.Name,
                                        int(state.Duration.Hours()),
                                        int(state.Duration.Minutes()),
-                                       int(state.Duration.Seconds()),
+                                       int(state.Duration.Seconds()/60),
                                        humanize.IBytes(uint64(state.RxBytes)),
                                        humanize.IBytes(uint64(state.RxSpeed)),
                                        humanize.IBytes(uint64(state.TxBytes)),
index 6b10007bd8646502ee593da517a7c4f8f5d00713..bb101cac2914c8af566e5dd7cbf0fe6b3c91da73 100644 (file)
@@ -416,7 +416,7 @@ func NewNodeOur(cfg *NodeOurJSON) (*NodeOur, error) {
 
 func CfgParse(data []byte) (*Ctx, error) {
        var err error
-       if bytes.Compare(data[:8], MagicNNCPBv3[:]) == 0 {
+       if bytes.Compare(data[:8], MagicNNCPBv3.B[:]) == 0 {
                os.Stderr.WriteString("Passphrase:") // #nosec G104
                password, err := term.ReadPassword(0)
                if err != nil {
@@ -427,6 +427,10 @@ func CfgParse(data []byte) (*Ctx, error) {
                if err != nil {
                        return nil, err
                }
+       } else if bytes.Compare(data[:8], MagicNNCPBv2.B[:]) == 0 {
+               log.Fatalln(MagicNNCPBv2.TooOld())
+       } else if bytes.Compare(data[:8], MagicNNCPBv1.B[:]) == 0 {
+               log.Fatalln(MagicNNCPBv1.TooOld())
        }
        var cfgGeneral map[string]interface{}
        if err = hjson.Unmarshal(data, &cfgGeneral); err != nil {
index a8a32e2c7899bcb68c4c0f6b23394f72312c2aac..4f0a081479c9e12b3e6a56beae33028ac12c7155 100644 (file)
@@ -23,21 +23,21 @@ import (
        "errors"
        "fmt"
        "io"
-       "log"
        "os"
        "path/filepath"
-
-       "golang.org/x/crypto/blake2b"
 )
 
 const NoCKSuffix = ".nock"
 
-func Check(src io.Reader, checksum []byte, les LEs, showPrgrs bool) (bool, error) {
-       hsh, err := blake2b.New256(nil)
-       if err != nil {
-               log.Fatalln(err)
-       }
-       if _, err = CopyProgressed(hsh, bufio.NewReader(src), "check", les, showPrgrs); err != nil {
+func Check(
+       src io.Reader,
+       size int64,
+       checksum []byte,
+       les LEs,
+       showPrgrs bool,
+) (bool, error) {
+       hsh := MTHNew(size, 0)
+       if _, err := CopyProgressed(hsh, bufio.NewReaderSize(src, MTHSize), "check", les, showPrgrs); err != nil {
                return false, err
        }
        return bytes.Compare(hsh.Sum(nil), checksum) == 0, nil
@@ -61,7 +61,7 @@ func (ctx *Ctx) checkXxIsBad(nodeId *NodeId, xx TRxTx) bool {
                        ctx.LogE("checking", les, err, logMsg)
                        return true
                }
-               gut, err := Check(fd, job.HshValue[:], les, ctx.ShowPrgrs)
+               gut, err := Check(fd, job.Size, job.HshValue[:], les, ctx.ShowPrgrs)
                fd.Close() // #nosec G104
                if err != nil {
                        ctx.LogE("checking", les, err, logMsg)
@@ -79,7 +79,7 @@ func (ctx *Ctx) Check(nodeId *NodeId) bool {
        return !(ctx.checkXxIsBad(nodeId, TRx) || ctx.checkXxIsBad(nodeId, TTx))
 }
 
-func (ctx *Ctx) CheckNoCK(nodeId *NodeId, hshValue *[32]byte) (int64, error) {
+func (ctx *Ctx) CheckNoCK(nodeId *NodeId, hshValue *[MTHSize]byte, mth *MTH) (int64, error) {
        dirToSync := filepath.Join(ctx.Spool, nodeId.String(), string(TRx))
        pktName := Base32Codec.EncodeToString(hshValue[:])
        pktPath := filepath.Join(dirToSync, pktName)
@@ -92,7 +92,6 @@ func (ctx *Ctx) CheckNoCK(nodeId *NodeId, hshValue *[32]byte) (int64, error) {
        if err != nil {
                return 0, err
        }
-       defer fd.Close()
        size := fi.Size()
        les := LEs{
                {"XX", string(TRx)},
@@ -100,7 +99,18 @@ func (ctx *Ctx) CheckNoCK(nodeId *NodeId, hshValue *[32]byte) (int64, error) {
                {"Pkt", pktName},
                {"FullSize", size},
        }
-       gut, err := Check(fd, hshValue[:], les, ctx.ShowPrgrs)
+       var gut bool
+       if mth == nil {
+               gut, err = Check(fd, size, hshValue[:], les, ctx.ShowPrgrs)
+       } else {
+               mth.PktName = pktName
+               if _, err = mth.PrependFrom(bufio.NewReaderSize(fd, MTHSize)); err != nil {
+                       return 0, err
+               }
+               if bytes.Compare(mth.Sum(nil), hshValue[:]) == 0 {
+                       gut = true
+               }
+       }
        if err != nil || !gut {
                return 0, errors.New("checksum mismatch")
        }
index 573bb9a8f0f636894ab7d03cb171cf81df3cfce8..212d16f4c990327cf06510aea8fc3aa3164bc7d2 100644 (file)
@@ -18,8 +18,6 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 package nncp
 
 var (
-       MagicNNCPMv1 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'M', 0, 0, 1}
-
        ChunkedSuffixMeta = ".nncp.meta"
        ChunkedSuffixPart = ".nncp.chunk"
 )
@@ -28,5 +26,5 @@ type ChunkedMeta struct {
        Magic     [8]byte
        FileSize  uint64
        ChunkSize uint64
-       Checksums [][32]byte
+       Checksums [][MTHSize]byte
 }
index c808a66657cded797ff0f296b3ee11f7945c9de4..113f8cf9dbd14222caf9984889b90ff21b3b4828 100644 (file)
@@ -34,8 +34,7 @@ import (
 
        xdr "github.com/davecgh/go-xdr/xdr2"
        "github.com/dustin/go-humanize"
-       "go.cypherpunks.ru/nncp/v6"
-       "golang.org/x/crypto/blake2b"
+       "go.cypherpunks.ru/nncp/v7"
 )
 
 const (
@@ -307,10 +306,23 @@ func main() {
                                )
                                continue
                        }
-                       if pktEnc.Magic != nncp.MagicNNCPEv4 {
+                       switch pktEnc.Magic {
+                       case nncp.MagicNNCPEv1.B:
+                               err = nncp.MagicNNCPEv1.TooOld()
+                       case nncp.MagicNNCPEv2.B:
+                               err = nncp.MagicNNCPEv2.TooOld()
+                       case nncp.MagicNNCPEv3.B:
+                               err = nncp.MagicNNCPEv3.TooOld()
+                       case nncp.MagicNNCPEv4.B:
+                               err = nncp.MagicNNCPEv4.TooOld()
+                       case nncp.MagicNNCPEv5.B:
+                       default:
+                               err = errors.New("Bad packet magic number")
+                       }
+                       if err != nil {
                                ctx.LogD(
                                        "bundle-rx",
-                                       append(les, nncp.LE{K: "Err", V: "Bad packet magic number"}),
+                                       append(les, nncp.LE{K: "Err", V: err.Error()}),
                                        logMsg,
                                )
                                continue
@@ -346,10 +358,7 @@ func main() {
                                        })
                                        continue
                                }
-                               hsh, err := blake2b.New256(nil)
-                               if err != nil {
-                                       log.Fatalln("Error during hasher creation:", err)
-                               }
+                               hsh := nncp.MTHNew(entry.Size, 0)
                                if _, err = hsh.Write(pktEncBuf); err != nil {
                                        log.Fatalln("Error during writing:", err)
                                }
@@ -415,10 +424,7 @@ func main() {
                        }
                        if *doCheck {
                                if *dryRun {
-                                       hsh, err := blake2b.New256(nil)
-                                       if err != nil {
-                                               log.Fatalln("Error during hasher creation:", err)
-                                       }
+                                       hsh := nncp.MTHNew(entry.Size, 0)
                                        if _, err = hsh.Write(pktEncBuf); err != nil {
                                                log.Fatalln("Error during writing:", err)
                                        }
index 3b84e70df4acf00ffc08ac0a4955a5686e087765..5d632d4ed231b5261aae95042839156e9a7c4b96 100644 (file)
@@ -26,7 +26,7 @@ import (
        "strings"
        "time"
 
-       "go.cypherpunks.ru/nncp/v6"
+       "go.cypherpunks.ru/nncp/v7"
 )
 
 func usage() {
@@ -195,6 +195,7 @@ func main() {
                close(autoTossFinish)
                badCode = (<-autoTossBadCode) || badCode
        }
+       nncp.SPCheckerWg.Wait()
        if badCode {
                os.Exit(1)
        }
index e338ac95b5febdc887ef40c00dbcffb8cee09199..4608484570a9061c0df4ede8db1ceb25728aeec0 100644 (file)
@@ -27,7 +27,7 @@ import (
        "sync"
        "time"
 
-       "go.cypherpunks.ru/nncp/v6"
+       "go.cypherpunks.ru/nncp/v7"
 )
 
 func usage() {
@@ -244,4 +244,5 @@ func main() {
                }
        }
        wg.Wait()
+       nncp.SPCheckerWg.Wait()
 }
index 95272ad4715f669dbf9994a8075354ad22497999..7c41ee097943bf05a679c3fde8c71b5eb957d5d9 100644 (file)
@@ -28,7 +28,7 @@ import (
        "os"
 
        xdr "github.com/davecgh/go-xdr/xdr2"
-       "go.cypherpunks.ru/nncp/v6"
+       "go.cypherpunks.ru/nncp/v7"
        "golang.org/x/crypto/blake2b"
        "golang.org/x/term"
 )
@@ -79,7 +79,13 @@ func main() {
                if _, err := xdr.Unmarshal(bytes.NewReader(data), &eblob); err != nil {
                        log.Fatalln(err)
                }
-               if eblob.Magic != nncp.MagicNNCPBv3 {
+               switch eblob.Magic {
+               case nncp.MagicNNCPBv1.B:
+                       log.Fatalln(nncp.MagicNNCPBv1.TooOld())
+               case nncp.MagicNNCPBv2.B:
+                       log.Fatalln(nncp.MagicNNCPBv2.TooOld())
+               case nncp.MagicNNCPBv3.B:
+               default:
                        log.Fatalln(errors.New("Unknown eblob type"))
                }
                fmt.Println("Strengthening function: Balloon with BLAKE2b-256")
@@ -91,27 +97,19 @@ func main() {
        }
 
        os.Stderr.WriteString("Passphrase:") // #nosec G104
-       password, err := term.ReadPassword(0)
+       password1, err := term.ReadPassword(0)
        if err != nil {
                log.Fatalln(err)
        }
-       os.Stderr.WriteString("\n") // #nosec G104
-
        if *decrypt {
-               cfgRaw, err := nncp.DeEBlob(data, password)
+               cfgRaw, err := nncp.DeEBlob(data, password1)
                if err != nil {
                        log.Fatalln(err)
                }
                os.Stdout.Write(cfgRaw) // #nosec G104
                return
        }
-
-       password1, err := term.ReadPassword(0)
-       if err != nil {
-               log.Fatalln(err)
-       }
-       os.Stderr.WriteString("\n")                 // #nosec G104
-       os.Stderr.WriteString("Repeat passphrase:") // #nosec G104
+       os.Stderr.WriteString("\nRepeat passphrase:") // #nosec G104
        password2, err := term.ReadPassword(0)
        if err != nil {
                log.Fatalln(err)
index 765175539934b4808787c1f60a978f5bdde6d681..4185302349bd3707e31da162794a727843724972 100644 (file)
@@ -25,7 +25,7 @@ import (
        "os"
 
        "github.com/hjson/hjson-go"
-       "go.cypherpunks.ru/nncp/v6"
+       "go.cypherpunks.ru/nncp/v7"
 )
 
 func usage() {
index d26c901a14efdd8ecf99ef114772d9e74e62bedb..f9926010d3d7e5a68d4cda93286314832c7dd715 100644 (file)
@@ -24,7 +24,7 @@ import (
        "log"
        "os"
 
-       "go.cypherpunks.ru/nncp/v6"
+       "go.cypherpunks.ru/nncp/v7"
 )
 
 func usage() {
index ddb2949c6a5e9d9b9a55e5e57ea566dfc957bf1b..6575ded385ef07b06ef5d78aa634b5b9482c6f02 100644 (file)
@@ -25,7 +25,7 @@ import (
        "os"
        "path/filepath"
 
-       "go.cypherpunks.ru/nncp/v6"
+       "go.cypherpunks.ru/nncp/v7"
 )
 
 func usage() {
@@ -90,7 +90,7 @@ func main() {
                }
                if *nock {
                        for job := range ctx.JobsNoCK(node.Id) {
-                               if _, err = ctx.CheckNoCK(node.Id, job.HshValue); err != nil {
+                               if _, err = ctx.CheckNoCK(node.Id, job.HshValue, nil); err != nil {
                                        pktName := nncp.Base32Codec.EncodeToString(job.HshValue[:])
                                        log.Println(filepath.Join(
                                                ctx.Spool,
index 569b9002bf509749938d57e9e72563538e20af24..a74fb0e296fd10c1b0a38869d9f5ddcd11411eba 100644 (file)
@@ -27,7 +27,7 @@ import (
        "time"
 
        "github.com/gorhill/cronexpr"
-       "go.cypherpunks.ru/nncp/v6"
+       "go.cypherpunks.ru/nncp/v7"
 )
 
 func usage() {
index 75fd40b8bb9a6136c42a903657efe6e3368c9b01..39f2bbada1477935d610281c9036f50a7123e4af 100644 (file)
@@ -29,7 +29,7 @@ import (
        "time"
 
        "github.com/dustin/go-humanize"
-       "go.cypherpunks.ru/nncp/v6"
+       "go.cypherpunks.ru/nncp/v7"
        "golang.org/x/net/netutil"
 )
 
@@ -72,6 +72,7 @@ func (c InetdConn) Close() error {
 func performSP(
        ctx *nncp.Ctx,
        conn nncp.ConnDeadlined,
+       addr string,
        nice uint8,
        noCK bool,
        nodeIdC chan *nncp.NodeId,
@@ -85,7 +86,9 @@ func performSP(
                ctx.LogI(
                        "call-started",
                        nncp.LEs{{K: "Node", V: state.Node.Id}},
-                       func(les nncp.LEs) string { return "Connection with " + state.Node.Name },
+                       func(les nncp.LEs) string {
+                               return fmt.Sprintf("Connection with %s (%s)", state.Node.Name, addr)
+                       },
                )
                nodeIdC <- state.Node.Id
                state.Wait()
@@ -102,7 +105,7 @@ func performSP(
                                state.Node.Name,
                                int(state.Duration.Hours()),
                                int(state.Duration.Minutes()),
-                               int(state.Duration.Seconds()),
+                               int(state.Duration.Seconds()/60),
                                humanize.IBytes(uint64(state.RxBytes)),
                                humanize.IBytes(uint64(state.RxSpeed)),
                                humanize.IBytes(uint64(state.TxBytes)),
@@ -192,7 +195,7 @@ func main() {
                os.Stderr.Close() // #nosec G104
                conn := &InetdConn{os.Stdin, os.Stdout}
                nodeIdC := make(chan *nncp.NodeId)
-               go performSP(ctx, conn, nice, *noCK, nodeIdC)
+               go performSP(ctx, conn, "PIPE", nice, *noCK, nodeIdC)
                nodeId := <-nodeIdC
                var autoTossFinish chan struct{}
                var autoTossBadCode chan bool
@@ -257,7 +260,7 @@ func main() {
                )
                go func(conn net.Conn) {
                        nodeIdC := make(chan *nncp.NodeId)
-                       go performSP(ctx, conn, nice, *noCK, nodeIdC)
+                       go performSP(ctx, conn, conn.RemoteAddr().String(), nice, *noCK, nodeIdC)
                        nodeId := <-nodeIdC
                        var autoTossFinish chan struct{}
                        var autoTossBadCode chan bool
index 0aa4444254de3add2b45f22760a6afef72ae4ab2..c379ef0c302f8c9d1a7ccc8de1d029b6ffa195e4 100644 (file)
@@ -25,7 +25,7 @@ import (
        "log"
        "os"
 
-       "go.cypherpunks.ru/nncp/v6"
+       "go.cypherpunks.ru/nncp/v7"
 )
 
 func usage() {
index 793a6ef22c3741fa950e39ff2224a3cee4962d8a..6fbbdc9ab8166b0008c465e7e0eea849fa0389ea 100644 (file)
@@ -25,7 +25,7 @@ import (
        "os"
        "strings"
 
-       "go.cypherpunks.ru/nncp/v6"
+       "go.cypherpunks.ru/nncp/v7"
 )
 
 func usage() {
index 8fe7e5fc33ba4c4e551d4e1554771eb7ee0dab90..9596e80787e36a7864f294fd1e609affca44662c 100644 (file)
@@ -26,7 +26,7 @@ import (
        "path/filepath"
        "strings"
 
-       "go.cypherpunks.ru/nncp/v6"
+       "go.cypherpunks.ru/nncp/v7"
 )
 
 func usage() {
diff --git a/src/cmd/nncp-hash/main.go b/src/cmd/nncp-hash/main.go
new file mode 100644 (file)
index 0000000..ba07232
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+NNCP -- Node to Node copy, utilities for store-and-forward data exchange
+Copyright (C) 2016-2021 Sergey Matveev <stargrave@stargrave.org>
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+// Calculate MTH hash of the file
+package main
+
+import (
+       "bufio"
+       "encoding/hex"
+       "flag"
+       "fmt"
+       "io"
+       "log"
+       "os"
+       "sync"
+
+       "go.cypherpunks.ru/nncp/v7"
+)
+
+func usage() {
+       fmt.Fprintf(os.Stderr, nncp.UsageHeader())
+       fmt.Fprintf(os.Stderr, "nncp-hash -- calculate MTH hash of the file\n\n")
+       fmt.Fprintf(os.Stderr, "Usage: %s [-file ...] [-seek X] [-debug] [-progress] [options]\nOptions:\n", os.Args[0])
+       flag.PrintDefaults()
+}
+
+func main() {
+       var (
+               fn        = flag.String("file", "", "Read the file instead of stdin")
+               seek      = flag.Uint64("seek", 0, "Seek the file, hash, rewind, hash remaining")
+               showPrgrs = flag.Bool("progress", false, "Progress showing")
+               debug     = flag.Bool("debug", false, "Print MTH steps calculations")
+               version   = flag.Bool("version", false, "Print version information")
+               warranty  = flag.Bool("warranty", false, "Print warranty information")
+       )
+       log.SetFlags(log.Lshortfile)
+       flag.Usage = usage
+       flag.Parse()
+       if *warranty {
+               fmt.Println(nncp.Warranty)
+               return
+       }
+       if *version {
+               fmt.Println(nncp.VersionGet())
+               return
+       }
+
+       fd := os.Stdin
+       var err error
+       var size int64
+       if *fn == "" {
+               *showPrgrs = false
+       } else {
+               fd, err = os.Open(*fn)
+               if err != nil {
+                       log.Fatalln(err)
+               }
+               fi, err := fd.Stat()
+               if err != nil {
+                       log.Fatalln(err)
+               }
+               size = fi.Size()
+       }
+       mth := nncp.MTHNew(size, int64(*seek))
+       var debugger sync.WaitGroup
+       if *debug {
+               fmt.Println("Leaf BLAKE3 key:", hex.EncodeToString(nncp.MTHLeafKey[:]))
+               fmt.Println("Node BLAKE3 key:", hex.EncodeToString(nncp.MTHNodeKey[:]))
+               mth.Events = make(chan nncp.MTHEvent)
+               debugger.Add(1)
+               go func() {
+                       for e := range mth.Events {
+                               var t string
+                               switch e.Type {
+                               case nncp.MTHEventAppend:
+                                       t = "Add"
+                               case nncp.MTHEventPrepend:
+                                       t = "Pre"
+                               case nncp.MTHEventFold:
+                                       t = "Fold"
+                               }
+                               fmt.Printf(
+                                       "%s\t%03d\t%06d\t%s\n",
+                                       t, e.Level, e.Ctr, hex.EncodeToString(e.Hsh),
+                               )
+                       }
+                       debugger.Done()
+               }()
+       }
+       if *seek != 0 {
+               if *fn == "" {
+                       log.Fatalln("-file is required with -seek")
+               }
+               if _, err = fd.Seek(int64(*seek), io.SeekStart); err != nil {
+                       log.Fatalln(err)
+               }
+       }
+       if _, err = nncp.CopyProgressed(
+               mth, bufio.NewReaderSize(fd, nncp.MTHBlockSize),
+               "hash", nncp.LEs{{K: "Pkt", V: *fn}, {K: "FullSize", V: size - int64(*seek)}},
+               *showPrgrs,
+       ); err != nil {
+               log.Fatalln(err)
+       }
+       if *seek != 0 {
+               if _, err = fd.Seek(0, io.SeekStart); err != nil {
+                       log.Fatalln(err)
+               }
+               if *showPrgrs {
+                       mth.PktName = *fn
+               }
+               if _, err = mth.PrependFrom(bufio.NewReaderSize(fd, nncp.MTHBlockSize)); err != nil {
+                       log.Fatalln(err)
+               }
+       }
+       sum := mth.Sum(nil)
+       debugger.Wait()
+       fmt.Println(hex.EncodeToString(sum))
+}
index 6fc0ae78759416db4beee691a01409e2a5e6a965..82becc67ae41840edb7058267b8f5045d53865b4 100644 (file)
@@ -25,7 +25,7 @@ import (
        "log"
        "os"
 
-       "go.cypherpunks.ru/nncp/v6"
+       "go.cypherpunks.ru/nncp/v7"
        "go.cypherpunks.ru/recfile"
 )
 
index 9d8ba2ce137fd312eeba09e710df5d7b2afbdea9..49c23a246df01248921dc321b824e14e9ecfa77c 100644 (file)
@@ -29,7 +29,7 @@ import (
 
        xdr "github.com/davecgh/go-xdr/xdr2"
        "github.com/klauspost/compress/zstd"
-       "go.cypherpunks.ru/nncp/v6"
+       "go.cypherpunks.ru/nncp/v7"
 )
 
 func usage() {
@@ -40,6 +40,92 @@ func usage() {
        fmt.Fprintln(os.Stderr, "Packet is read from stdin.")
 }
 
+func doPlain(pkt nncp.Pkt, dump, decompress bool) {
+       if dump {
+               bufW := bufio.NewWriter(os.Stdout)
+               var r io.Reader
+               r = bufio.NewReader(os.Stdin)
+               if decompress {
+                       decompressor, err := zstd.NewReader(r)
+                       if err != nil {
+                               log.Fatalln(err)
+                       }
+                       r = decompressor
+               }
+               if _, err := io.Copy(bufW, r); err != nil {
+                       log.Fatalln(err)
+               }
+               if err := bufW.Flush(); err != nil {
+                       log.Fatalln(err)
+               }
+               return
+       }
+       payloadType := "unknown"
+       switch pkt.Type {
+       case nncp.PktTypeFile:
+               payloadType = "file"
+       case nncp.PktTypeFreq:
+               payloadType = "file request"
+       case nncp.PktTypeExec:
+               payloadType = "exec compressed"
+       case nncp.PktTypeExecFat:
+               payloadType = "exec uncompressed"
+       case nncp.PktTypeTrns:
+               payloadType = "transitional"
+       }
+       var path string
+       switch pkt.Type {
+       case nncp.PktTypeExec, nncp.PktTypeExecFat:
+               path = string(bytes.Replace(
+                       pkt.Path[:pkt.PathLen],
+                       []byte{0},
+                       []byte(" "),
+                       -1,
+               ))
+       case nncp.PktTypeTrns:
+               path = nncp.Base32Codec.EncodeToString(pkt.Path[:pkt.PathLen])
+       default:
+               path = string(pkt.Path[:pkt.PathLen])
+       }
+       fmt.Printf(
+               "Packet type: plain\nPayload type: %s\nNiceness: %s (%d)\nPath: %s\n",
+               payloadType, nncp.NicenessFmt(pkt.Nice), pkt.Nice, path,
+       )
+       return
+}
+
+func doEncrypted(pktEnc nncp.PktEnc, dump bool, cfgPath string, beginning []byte) {
+       if !dump {
+               fmt.Printf(
+                       "Packet type: encrypted\nNiceness: %s (%d)\nSender: %s\nRecipient: %s\n",
+                       nncp.NicenessFmt(pktEnc.Nice), pktEnc.Nice, pktEnc.Sender, pktEnc.Recipient,
+               )
+               return
+       }
+       ctx, err := nncp.CtxFromCmdline(cfgPath, "", "", false, false, false, false)
+       if err != nil {
+               log.Fatalln("Error during initialization:", err)
+       }
+       if ctx.Self == nil {
+               log.Fatalln("Config lacks private keys")
+       }
+       bufW := bufio.NewWriter(os.Stdout)
+       if _, _, err = nncp.PktEncRead(
+               ctx.Self,
+               ctx.Neigh,
+               io.MultiReader(
+                       bytes.NewReader(beginning),
+                       bufio.NewReader(os.Stdin),
+               ),
+               bufW,
+       ); err != nil {
+               log.Fatalln(err)
+       }
+       if err = bufW.Flush(); err != nil {
+               log.Fatalln(err)
+       }
+}
+
 func main() {
        var (
                overheads  = flag.Bool("overheads", false, "Print packet overheads")
@@ -71,99 +157,37 @@ func main() {
                return
        }
 
-       var err error
        beginning := make([]byte, nncp.PktOverhead)
-       if _, err = io.ReadFull(os.Stdin, beginning); err != nil {
+       if _, err := io.ReadFull(os.Stdin, beginning); err != nil {
                log.Fatalln("Not enough data to read")
        }
        var pkt nncp.Pkt
-       _, err = xdr.Unmarshal(bytes.NewReader(beginning), &pkt)
-       if err == nil && pkt.Magic == nncp.MagicNNCPPv3 {
-               if *dump {
-                       bufW := bufio.NewWriter(os.Stdout)
-                       var r io.Reader
-                       r = bufio.NewReader(os.Stdin)
-                       if *decompress {
-                               decompressor, err := zstd.NewReader(r)
-                               if err != nil {
-                                       log.Fatalln(err)
-                               }
-                               r = decompressor
-                       }
-                       if _, err = io.Copy(bufW, r); err != nil {
-                               log.Fatalln(err)
-                       }
-                       if err = bufW.Flush(); err != nil {
-                               log.Fatalln(err)
-                       }
+       if _, err := xdr.Unmarshal(bytes.NewReader(beginning), &pkt); err == nil {
+               switch pkt.Magic {
+               case nncp.MagicNNCPPv1.B:
+                       log.Fatalln(nncp.MagicNNCPPv1.TooOld())
+               case nncp.MagicNNCPPv2.B:
+                       log.Fatalln(nncp.MagicNNCPPv2.TooOld())
+               case nncp.MagicNNCPPv3.B:
+                       doPlain(pkt, *dump, *decompress)
                        return
                }
-               payloadType := "unknown"
-               switch pkt.Type {
-               case nncp.PktTypeFile:
-                       payloadType = "file"
-               case nncp.PktTypeFreq:
-                       payloadType = "file request"
-               case nncp.PktTypeExec:
-                       payloadType = "exec"
-               case nncp.PktTypeExecFat:
-                       payloadType = "exec uncompressed"
-               case nncp.PktTypeTrns:
-                       payloadType = "transitional"
-               }
-               var path string
-               switch pkt.Type {
-               case nncp.PktTypeExec, nncp.PktTypeExecFat:
-                       path = string(bytes.Replace(
-                               pkt.Path[:pkt.PathLen],
-                               []byte{0},
-                               []byte(" "),
-                               -1,
-                       ))
-               case nncp.PktTypeTrns:
-                       path = nncp.Base32Codec.EncodeToString(pkt.Path[:pkt.PathLen])
-               default:
-                       path = string(pkt.Path[:pkt.PathLen])
-               }
-               fmt.Printf(
-                       "Packet type: plain\nPayload type: %s\nNiceness: %s (%d)\nPath: %s\n",
-                       payloadType, nncp.NicenessFmt(pkt.Nice), pkt.Nice, path,
-               )
-               return
        }
        var pktEnc nncp.PktEnc
-       _, err = xdr.Unmarshal(bytes.NewReader(beginning), &pktEnc)
-       if err == nil && pktEnc.Magic == nncp.MagicNNCPEv4 {
-               if *dump {
-                       ctx, err := nncp.CtxFromCmdline(*cfgPath, "", "", false, false, false, false)
-                       if err != nil {
-                               log.Fatalln("Error during initialization:", err)
-                       }
-                       if ctx.Self == nil {
-                               log.Fatalln("Config lacks private keys")
-                       }
-                       bufW := bufio.NewWriter(os.Stdout)
-                       if _, _, err = nncp.PktEncRead(
-                               ctx.Self,
-                               ctx.Neigh,
-                               io.MultiReader(
-                                       bytes.NewReader(beginning),
-                                       bufio.NewReader(os.Stdin),
-                               ),
-                               bufW,
-                       ); err != nil {
-                               log.Fatalln(err)
-                       }
-                       if err = bufW.Flush(); err != nil {
-                               log.Fatalln(err)
-                       }
+       if _, err := xdr.Unmarshal(bytes.NewReader(beginning), &pktEnc); err == nil {
+               switch pkt.Magic {
+               case nncp.MagicNNCPEv1.B:
+                       log.Fatalln(nncp.MagicNNCPEv1.TooOld())
+               case nncp.MagicNNCPEv2.B:
+                       log.Fatalln(nncp.MagicNNCPEv2.TooOld())
+               case nncp.MagicNNCPEv3.B:
+                       log.Fatalln(nncp.MagicNNCPEv3.TooOld())
+               case nncp.MagicNNCPEv4.B:
+                       log.Fatalln(nncp.MagicNNCPEv4.TooOld())
+               case nncp.MagicNNCPEv5.B:
+                       doEncrypted(pktEnc, *dump, *cfgPath, beginning)
                        return
                }
-               fmt.Printf(
-                       "Packet type: encrypted\nNiceness: %s (%d)\nSender: %s\nRecipient: %s\n",
-                       nncp.NicenessFmt(pktEnc.Nice), pktEnc.Nice, pktEnc.Sender, pktEnc.Recipient,
-               )
-               return
        }
        log.Fatalln("Unable to determine packet type")
 }
index ec9bdd20fa499ab0e6fe738c135fc496dd98c1a7..d7774c95bd2fb718136d62b8a43dff5e1b8f1561 100644 (file)
@@ -35,8 +35,7 @@ import (
 
        xdr "github.com/davecgh/go-xdr/xdr2"
        "github.com/dustin/go-humanize"
-       "go.cypherpunks.ru/nncp/v6"
-       "golang.org/x/crypto/blake2b"
+       "go.cypherpunks.ru/nncp/v7"
 )
 
 func usage() {
@@ -68,7 +67,11 @@ func process(ctx *nncp.Ctx, path string, keep, dryRun, stdout, dumpMeta bool) bo
                return false
        }
        fd.Close() // #nosec G104
-       if metaPkt.Magic != nncp.MagicNNCPMv1 {
+       if metaPkt.Magic == nncp.MagicNNCPMv1.B {
+               ctx.LogE("reass", les, nncp.MagicNNCPMv1.TooOld(), logMsg)
+               return false
+       }
+       if metaPkt.Magic != nncp.MagicNNCPMv2.B {
                ctx.LogE("reass", les, nncp.BadMagic, logMsg)
                return false
        }
@@ -152,10 +155,7 @@ func process(ctx *nncp.Ctx, path string, keep, dryRun, stdout, dumpMeta bool) bo
                if err != nil {
                        log.Fatalln("Can not stat file:", err)
                }
-               hsh, err = blake2b.New256(nil)
-               if err != nil {
-                       log.Fatalln(err)
-               }
+               hsh = nncp.MTHNew(fi.Size(), 0)
                if _, err = nncp.CopyProgressed(
                        hsh, bufio.NewReader(fd), "check",
                        nncp.LEs{{K: "Pkt", V: chunkPath}, {K: "FullSize", V: fi.Size()}},
index 1fc1fc2cd5d6b3d3b2f416d4ed6ac794e854c3d8..f8f4289639c0f685cd3a777471eec40cada25b7b 100644 (file)
@@ -29,7 +29,7 @@ import (
        "strings"
        "time"
 
-       "go.cypherpunks.ru/nncp/v6"
+       "go.cypherpunks.ru/nncp/v7"
 )
 
 func usage() {
index edeed8b4688dd620e171c61c87591d8cbe0a8d2f..ea5f407dfddd8738d26f41ffa1311cfbf0b4264e 100644 (file)
@@ -26,7 +26,7 @@ import (
        "sort"
 
        "github.com/dustin/go-humanize"
-       "go.cypherpunks.ru/nncp/v6"
+       "go.cypherpunks.ru/nncp/v7"
 )
 
 func usage() {
@@ -36,11 +36,11 @@ func usage() {
        flag.PrintDefaults()
 }
 
-func jobPrint(xx nncp.TRxTx, job nncp.Job) {
+func jobPrint(xx nncp.TRxTx, job nncp.Job, suffix string) {
        fmt.Printf(
-               "\t%s %s %s (nice: %s)\n",
+               "\t%s %s%s %s (nice: %s)\n",
                string(xx),
-               nncp.Base32Codec.EncodeToString(job.HshValue[:]),
+               nncp.Base32Codec.EncodeToString(job.HshValue[:]), suffix,
                humanize.IBytes(uint64(job.Size)),
                nncp.NicenessFmt(job.PktEnc.Nice),
        )
@@ -101,30 +101,50 @@ func main() {
                rxBytes := make(map[uint8]int64)
                noCKNums := make(map[uint8]int)
                noCKBytes := make(map[uint8]int64)
+               partNums := 0
+               partBytes := int64(0)
                for job := range ctx.Jobs(node.Id, nncp.TRx) {
                        if *showPkt {
-                               jobPrint(nncp.TRx, job)
+                               jobPrint(nncp.TRx, job, "")
                        }
                        rxNums[job.PktEnc.Nice] = rxNums[job.PktEnc.Nice] + 1
                        rxBytes[job.PktEnc.Nice] = rxBytes[job.PktEnc.Nice] + job.Size
                }
                for job := range ctx.JobsNoCK(node.Id) {
                        if *showPkt {
-                               jobPrint(nncp.TRx, job)
+                               jobPrint(nncp.TRx, job, ".nock")
                        }
                        noCKNums[job.PktEnc.Nice] = noCKNums[job.PktEnc.Nice] + 1
                        noCKBytes[job.PktEnc.Nice] = noCKBytes[job.PktEnc.Nice] + job.Size
                }
+               for job := range ctx.JobsPart(node.Id) {
+                       if *showPkt {
+                               fmt.Printf(
+                                       "\t%s %s.part %s\n",
+                                       string(nncp.TRx),
+                                       nncp.Base32Codec.EncodeToString(job.HshValue[:]),
+                                       humanize.IBytes(uint64(job.Size)),
+                               )
+                       }
+                       partNums++
+                       partBytes += job.Size
+               }
                txNums := make(map[uint8]int)
                txBytes := make(map[uint8]int64)
                for job := range ctx.Jobs(node.Id, nncp.TTx) {
                        if *showPkt {
-                               jobPrint(nncp.TTx, job)
+                               jobPrint(nncp.TTx, job, "")
                        }
                        txNums[job.PktEnc.Nice] = txNums[job.PktEnc.Nice] + 1
                        txBytes[job.PktEnc.Nice] = txBytes[job.PktEnc.Nice] + job.Size
                }
                var nice uint8
+               if partNums > 0 {
+                       fmt.Printf(
+                               "\tpart: % 10s, % 3d pkts\n",
+                               humanize.IBytes(uint64(partBytes)), partNums,
+                       )
+               }
                for nice = 1; nice > 0; nice++ {
                        rxNum, rxExists := rxNums[nice]
                        txNum, txExists := txNums[nice]
index b8a0d3ca81bf44d07f79be5f74a5ec6e7bcfe8cc..d6b82fb5ad13e87282f83b6d8391a0eaebccbe3d 100644 (file)
@@ -25,7 +25,7 @@ import (
        "os"
        "time"
 
-       "go.cypherpunks.ru/nncp/v6"
+       "go.cypherpunks.ru/nncp/v7"
 )
 
 func usage() {
index ae47ec6a842687e9cfb20d733d77a27427acdb56..0acba4b8edcb50804e8be030e66e9b5c8185383a 100644 (file)
@@ -29,7 +29,7 @@ import (
        "path/filepath"
 
        "github.com/dustin/go-humanize"
-       "go.cypherpunks.ru/nncp/v6"
+       "go.cypherpunks.ru/nncp/v7"
 )
 
 func usage() {
@@ -218,10 +218,29 @@ func main() {
                                continue
                        }
                        pktEnc, pktEncRaw, err := ctx.HdrRead(fd)
-                       if err != nil || pktEnc.Magic != nncp.MagicNNCPEv4 {
-                               ctx.LogD("xfer-rx-not-packet", les, func(les nncp.LEs) string {
-                                       return logMsg(les) + ": is not a packet"
-                               })
+                       if err != nil {
+                               switch pktEnc.Magic {
+                               case nncp.MagicNNCPEv1.B:
+                                       err = nncp.MagicNNCPEv1.TooOld()
+                               case nncp.MagicNNCPEv2.B:
+                                       err = nncp.MagicNNCPEv2.TooOld()
+                               case nncp.MagicNNCPEv3.B:
+                                       err = nncp.MagicNNCPEv3.TooOld()
+                               case nncp.MagicNNCPEv4.B:
+                                       err = nncp.MagicNNCPEv4.TooOld()
+                               case nncp.MagicNNCPEv5.B:
+                               default:
+                                       err = errors.New("is not an encrypted packet")
+                               }
+                       }
+                       if err != nil {
+                               ctx.LogD(
+                                       "xfer-rx-not-packet",
+                                       append(les, nncp.LE{K: "Err", V: err}),
+                                       func(les nncp.LEs) string {
+                                               return logMsg(les) + ": not valid packet: " + err.Error()
+                                       },
+                               )
                                fd.Close() // #nosec G104
                                continue
                        }
index c743d1552802ac47218e0a75140fe990cc77f2a4..8bf2e978fd4895df548625530906b4e0c6c080cc 100644 (file)
@@ -34,10 +34,6 @@ const (
        DefaultP = 2
 )
 
-var (
-       MagicNNCPBv3 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'B', 0, 0, 3}
-)
-
 type EBlob struct {
        Magic [8]byte
        SCost uint32
@@ -65,7 +61,7 @@ func NewEBlob(sCost, tCost, pCost int, password, data []byte) ([]byte, error) {
                return nil, err
        }
        eblob := EBlob{
-               Magic: MagicNNCPBv3,
+               Magic: MagicNNCPBv3.B,
                SCost: uint32(sCost),
                TCost: uint32(tCost),
                PCost: uint32(pCost),
@@ -97,8 +93,17 @@ func DeEBlob(eblobRaw, password []byte) ([]byte, error) {
        if _, err = xdr.Unmarshal(bytes.NewReader(eblobRaw), &eblob); err != nil {
                return nil, err
        }
-       if eblob.Magic != MagicNNCPBv3 {
-               return nil, BadMagic
+       switch eblob.Magic {
+       case MagicNNCPBv1.B:
+               err = MagicNNCPBv1.TooOld()
+       case MagicNNCPBv2.B:
+               err = MagicNNCPBv1.TooOld()
+       case MagicNNCPBv3.B:
+       default:
+               err = BadMagic
+       }
+       if err != nil {
+               return nil, err
        }
        key := balloon.H(
                blake256,
index 3c3d627c71699165689fb80474082baa85fffa0d..3afd29255a62095f6acc40d45a57980cdc7e125e 100644 (file)
@@ -1,20 +1,19 @@
-module go.cypherpunks.ru/nncp/v6
+module go.cypherpunks.ru/nncp/v7
 
 require (
        github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892
        github.com/dustin/go-humanize v1.0.0
-       github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6
+       github.com/flynn/noise v1.0.0
        github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75
        github.com/hjson/hjson-go v3.1.0+incompatible
-       github.com/klauspost/compress v1.11.7
-       github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
+       github.com/klauspost/compress v1.13.1
        go.cypherpunks.ru/balloon v1.1.1
        go.cypherpunks.ru/recfile v0.4.3
-       golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
-       golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d
-       golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43
-       golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d
-       gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
+       golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
+       golang.org/x/net v0.0.0-20210614182718-04defd469f4e
+       golang.org/x/sys v0.0.0-20210616094352-59db8d763f22
+       golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
+       lukechampine.com/blake3 v1.1.5
 )
 
 go 1.12
index 83c3eac7f123449bc29b58b8ae59718f1ac6c802..cc42083d02bd406bac67980daf299c6e552bb02a 100644 (file)
@@ -2,41 +2,47 @@ github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892 h1:qg9VbHo1TlL0KDM0
 github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892/go.mod h1:CTDl0pzVzE5DEzZhPfvhY/9sPFMQIxaJ9VAMs9AagrE=
 github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6 h1:u/UEqS66A5ckRmS4yNpjmVH56sVtS/RfclBAYocb4as=
-github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:1i71OnUq3iUe1ma7Lr6yG6/rjvM3emb6yoL7xLFzcVQ=
+github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ=
+github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
+github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75 h1:f0n1xnMSmBLzVfsMMvriDyA75NB/oBgILX2GcHXIQzY=
 github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA=
 github.com/hjson/hjson-go v3.1.0+incompatible h1:DY/9yE8ey8Zv22bY+mHV1uk2yRy0h8tKhZ77hEdi0Aw=
 github.com/hjson/hjson-go v3.1.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
-github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg=
-github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.13.1 h1:wXr2uRxZTJXHLly6qhJabee5JqIhTRoLBhDOA74hDEQ=
+github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
+github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
+github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 go.cypherpunks.ru/balloon v1.1.1 h1:ypHM1DRf/XuCrp9pDkTHg00CqZX/Np/APb//iHvDJTA=
 go.cypherpunks.ru/balloon v1.1.1/go.mod h1:k4s4ozrIrhpBjj78Z7LX8ZHxMQ+XE7DZUWl8gP2ojCo=
 go.cypherpunks.ru/recfile v0.4.3 h1:ephokihmV//p0ob6gx2FWXvm28/NBDbWTOJPUNahxO8=
 go.cypherpunks.ru/recfile v0.4.3/go.mod h1:sR+KajB+vzofL3SFVFwKt3Fke0FaCcN1g3YPNAhU3qI=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
-golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d h1:1aflnvSoWWLI2k/dMUAl5lvU1YO4Mb4hz0gh+1rjcxU=
-golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
+golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
+golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 h1:SgQ6LNaYJU0JIuEHv9+s6EbhSCwYeAf5Yvj6lpYlqAE=
-golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
-golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
+golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
-gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+lukechampine.com/blake3 v1.1.5 h1:hsACfxWvLdGmjYbWGrumQIphOvO+ZruZehWtgd2fxoM=
+lukechampine.com/blake3 v1.1.5/go.mod h1:hE8RpzdO8ttZ7446CXEwDP1eu2V4z7stv0Urj1El20g=
index 1b5cef8fbf24c5e1824cbff0c473cd478cf69bb2..0819738f4033b4ffdc8022b9d9adf600e308289c 100644 (file)
@@ -41,7 +41,7 @@ type Job struct {
        PktEnc   *PktEnc
        Path     string
        Size     int64
-       HshValue *[32]byte
+       HshValue *[MTHSize]byte
 }
 
 func (ctx *Ctx) HdrRead(fd *os.File) (*PktEnc, []byte, error) {
@@ -88,7 +88,7 @@ func (ctx *Ctx) HdrWrite(pktEncRaw []byte, tgt string) error {
        return err
 }
 
-func (ctx *Ctx) jobsFind(nodeId *NodeId, xx TRxTx, nock bool) chan Job {
+func (ctx *Ctx) jobsFind(nodeId *NodeId, xx TRxTx, nock, part bool) chan Job {
        rxPath := filepath.Join(ctx.Spool, nodeId.String(), string(xx))
        jobs := make(chan Job, 16)
        go func() {
@@ -113,6 +113,14 @@ func (ctx *Ctx) jobsFind(nodeId *NodeId, xx TRxTx, nock bool) chan Job {
                                hshValue, err = Base32Codec.DecodeString(
                                        strings.TrimSuffix(name, NoCKSuffix),
                                )
+                       } else if part {
+                               if !strings.HasSuffix(name, PartSuffix) ||
+                                       len(name) != Base32Encoded32Len+len(PartSuffix) {
+                                       continue
+                               }
+                               hshValue, err = Base32Codec.DecodeString(
+                                       strings.TrimSuffix(name, PartSuffix),
+                               )
                        } else {
                                if len(name) != Base32Encoded32Len {
                                        continue
@@ -125,7 +133,7 @@ func (ctx *Ctx) jobsFind(nodeId *NodeId, xx TRxTx, nock bool) chan Job {
                        pth := filepath.Join(rxPath, name)
                        hdrExists := true
                        var fd *os.File
-                       if nock {
+                       if nock || part {
                                fd, err = os.Open(pth)
                        } else {
                                fd, err = os.Open(pth + HdrSuffix)
@@ -137,9 +145,46 @@ func (ctx *Ctx) jobsFind(nodeId *NodeId, xx TRxTx, nock bool) chan Job {
                        if err != nil {
                                continue
                        }
+                       if part {
+                               job := Job{
+                                       Path:     pth,
+                                       Size:     fi.Size(),
+                                       HshValue: new([MTHSize]byte),
+                               }
+                               copy(job.HshValue[:], hshValue)
+                               jobs <- job
+                               continue
+                       }
                        pktEnc, pktEncRaw, err := ctx.HdrRead(fd)
                        fd.Close()
-                       if err != nil || pktEnc.Magic != MagicNNCPEv4 {
+                       if err != nil {
+                               continue
+                       }
+                       switch pktEnc.Magic {
+                       case MagicNNCPEv1.B:
+                               err = MagicNNCPEv1.TooOld()
+                       case MagicNNCPEv2.B:
+                               err = MagicNNCPEv2.TooOld()
+                       case MagicNNCPEv3.B:
+                               err = MagicNNCPEv3.TooOld()
+                       case MagicNNCPEv4.B:
+                               err = MagicNNCPEv4.TooOld()
+                       case MagicNNCPEv5.B:
+                       default:
+                               err = BadMagic
+                       }
+                       if err != nil {
+                               ctx.LogE("job", LEs{
+                                       {"XX", string(xx)},
+                                       {"Name", name},
+                                       {"Size", fi.Size()},
+                               }, err, func(les LEs) string {
+                                       return fmt.Sprintf(
+                                               "Job %s/%s size: %s",
+                                               string(xx), name,
+                                               humanize.IBytes(uint64(fi.Size())),
+                                       )
+                               })
                                continue
                        }
                        ctx.LogD("job", LEs{
@@ -163,7 +208,7 @@ func (ctx *Ctx) jobsFind(nodeId *NodeId, xx TRxTx, nock bool) chan Job {
                                PktEnc:   pktEnc,
                                Path:     pth,
                                Size:     fi.Size(),
-                               HshValue: new([32]byte),
+                               HshValue: new([MTHSize]byte),
                        }
                        copy(job.HshValue[:], hshValue)
                        jobs <- job
@@ -173,9 +218,13 @@ func (ctx *Ctx) jobsFind(nodeId *NodeId, xx TRxTx, nock bool) chan Job {
 }
 
 func (ctx *Ctx) Jobs(nodeId *NodeId, xx TRxTx) chan Job {
-       return ctx.jobsFind(nodeId, xx, false)
+       return ctx.jobsFind(nodeId, xx, false, false)
 }
 
 func (ctx *Ctx) JobsNoCK(nodeId *NodeId) chan Job {
-       return ctx.jobsFind(nodeId, TRx, true)
+       return ctx.jobsFind(nodeId, TRx, true, false)
+}
+
+func (ctx *Ctx) JobsPart(nodeId *NodeId) chan Job {
+       return ctx.jobsFind(nodeId, TRx, false, true)
 }
diff --git a/src/magic.go b/src/magic.go
new file mode 100644 (file)
index 0000000..2f0835c
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+NNCP -- Node to Node copy, utilities for store-and-forward data exchange
+Copyright (C) 2016-2021 Sergey Matveev <stargrave@stargrave.org>
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+package nncp
+
+import "fmt"
+
+type Magic struct {
+       B    [8]byte
+       Name string
+       Till string
+}
+
+var (
+       MagicNNCPBv1 = Magic{
+               B:    [8]byte{'N', 'N', 'C', 'P', 'B', 0, 0, 1},
+               Name: "NNCPBv1 (EBlob v1)", Till: "1.0",
+       }
+       MagicNNCPBv2 = Magic{
+               B:    [8]byte{'N', 'N', 'C', 'P', 'B', 0, 0, 2},
+               Name: "NNCPBv2 (EBlob v2)", Till: "3.4",
+       }
+       MagicNNCPBv3 = Magic{
+               B:    [8]byte{'N', 'N', 'C', 'P', 'B', 0, 0, 3},
+               Name: "NNCPBv3 (EBlob v3)", Till: "now",
+       }
+       MagicNNCPDv1 = Magic{
+               B:    [8]byte{'N', 'N', 'C', 'P', 'D', 0, 0, 1},
+               Name: "NNCPDv1 (multicast discovery v1)", Till: "now",
+       }
+       MagicNNCPEv1 = Magic{
+               B:    [8]byte{'N', 'N', 'C', 'P', 'E', 0, 0, 1},
+               Name: "NNCPEv1 (encrypted packet v1)", Till: "0.12",
+       }
+       MagicNNCPEv2 = Magic{
+               B:    [8]byte{'N', 'N', 'C', 'P', 'E', 0, 0, 2},
+               Name: "NNCPEv2 (encrypted packet v2)", Till: "1.0",
+       }
+       MagicNNCPEv3 = Magic{
+               B:    [8]byte{'N', 'N', 'C', 'P', 'E', 0, 0, 3},
+               Name: "NNCPEv3 (encrypted packet v3)", Till: "3.4",
+       }
+       MagicNNCPEv4 = Magic{
+               B:    [8]byte{'N', 'N', 'C', 'P', 'E', 0, 0, 4},
+               Name: "NNCPEv4 (encrypted packet v4)", Till: "6.6.0",
+       }
+       MagicNNCPEv5 = Magic{
+               B:    [8]byte{'N', 'N', 'C', 'P', 'E', 0, 0, 5},
+               Name: "NNCPEv5 (encrypted packet v5)", Till: "now",
+       }
+       MagicNNCPSv1 = Magic{
+               B:    [8]byte{'N', 'N', 'C', 'P', 'S', 0, 0, 1},
+               Name: "NNCPSv1 (sync protocol v1)", Till: "now",
+       }
+       MagicNNCPMv1 = Magic{
+               B:    [8]byte{'N', 'N', 'C', 'P', 'M', 0, 0, 1},
+               Name: "NNCPMv1 (chunked .meta v1)", Till: "6.6.0",
+       }
+       MagicNNCPMv2 = Magic{
+               B:    [8]byte{'N', 'N', 'C', 'P', 'M', 0, 0, 2},
+               Name: "NNCPMv2 (chunked .meta v2)", Till: "now",
+       }
+       MagicNNCPPv1 = Magic{
+               B:    [8]byte{'N', 'N', 'C', 'P', 'P', 0, 0, 1},
+               Name: "NNCPPv1 (plain packet v1)", Till: "2.0",
+       }
+       MagicNNCPPv2 = Magic{
+               B:    [8]byte{'N', 'N', 'C', 'P', 'P', 0, 0, 2},
+               Name: "NNCPPv2 (plain packet v2)", Till: "4.1",
+       }
+       MagicNNCPPv3 = Magic{
+               B:    [8]byte{'N', 'N', 'C', 'P', 'P', 0, 0, 3},
+               Name: "NNCPPv3 (plain packet v3)", Till: "now",
+       }
+)
+
+func (m *Magic) TooOld() error {
+       return fmt.Errorf("%s format is unsupported (used till %s)", m.Name, m.Till)
+}
index dc733ca9bbfaa1d13624d4de44ad078ddf283982..be40f42de1edd50f03e0a1bc5d045ce88bf76bb5 100644 (file)
@@ -43,9 +43,7 @@ type MCDAddr struct {
 }
 
 var (
-       MagicNNCPDv1 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'D', 0, 0, 1}
-
-       mcdIP           = net.ParseIP("ff02::1")
+       mcdIP           = net.ParseIP("ff02::4e4e:4350")
        mcdAddrLifetime = 2 * time.Minute
 
        mcdPktSize int
@@ -125,7 +123,7 @@ func (ctx *Ctx) MCDRx(ifiName string) error {
                                })
                                continue
                        }
-                       if mcd.Magic != MagicNNCPDv1 {
+                       if mcd.Magic != MagicNNCPDv1.B {
                                ctx.LogD("mcd", les, func(les LEs) string {
                                        return fmt.Sprintf(
                                                "MCD Rx %s/%d: unexpected magic: %s",
@@ -186,7 +184,7 @@ func (ctx *Ctx) MCDTx(ifiName string, port int, interval time.Duration) error {
                return err
        }
        var buf bytes.Buffer
-       mcd := MCD{Magic: MagicNNCPDv1, Sender: ctx.Self.Id}
+       mcd := MCD{Magic: MagicNNCPDv1.B, Sender: ctx.Self.Id}
        if _, err := xdr.Marshal(&buf, mcd); err != nil {
                panic(err)
        }
diff --git a/src/mth.go b/src/mth.go
new file mode 100644 (file)
index 0000000..6cb1f36
--- /dev/null
@@ -0,0 +1,266 @@
+/*
+NNCP -- Node to Node copy, utilities for store-and-forward data exchange
+Copyright (C) 2016-2021 Sergey Matveev <stargrave@stargrave.org>
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+package nncp
+
+import (
+       "bytes"
+       "errors"
+       "io"
+
+       "lukechampine.com/blake3"
+)
+
+const (
+       MTHBlockSize = 128 * 1024
+       MTHSize      = 32
+)
+
+var (
+       MTHLeafKey = blake3.Sum256([]byte("NNCP MTH LEAF"))
+       MTHNodeKey = blake3.Sum256([]byte("NNCP MTH NODE"))
+)
+
+type MTHEventType uint8
+
+const (
+       MTHEventAppend  MTHEventType = iota
+       MTHEventPrepend MTHEventType = iota
+       MTHEventFold    MTHEventType = iota
+)
+
+type MTHEvent struct {
+       Type  MTHEventType
+       Level int
+       Ctr   int
+       Hsh   []byte
+}
+
+type MTH struct {
+       size        int64
+       PrependSize int64
+       skip        int64
+       skipped     bool
+       hasher      *blake3.Hasher
+       hashes      [][MTHSize]byte
+       buf         *bytes.Buffer
+       finished    bool
+       Events      chan MTHEvent
+       PktName     string
+}
+
+func MTHNew(size, offset int64) *MTH {
+       mth := MTH{
+               hasher: blake3.New(MTHSize, MTHLeafKey[:]),
+               buf:    bytes.NewBuffer(make([]byte, 0, 2*MTHBlockSize)),
+       }
+       if size == 0 {
+               return &mth
+       }
+       prepends := int(offset / MTHBlockSize)
+       skip := MTHBlockSize - (offset - int64(prepends)*MTHBlockSize)
+       if skip == MTHBlockSize {
+               skip = 0
+       } else if skip > 0 {
+               prepends++
+       }
+       prependSize := int64(prepends * MTHBlockSize)
+       if prependSize > size {
+               prependSize = size
+       }
+       if offset+skip > size {
+               skip = size - offset
+       }
+       mth.size = size
+       mth.PrependSize = prependSize
+       mth.skip = skip
+       mth.hashes = make([][MTHSize]byte, prepends, 1+size/MTHBlockSize)
+       return &mth
+}
+
+func (mth *MTH) Reset() { panic("not implemented") }
+
+func (mth *MTH) Size() int { return MTHSize }
+
+func (mth *MTH) BlockSize() int { return MTHBlockSize }
+
+func (mth *MTH) Write(data []byte) (int, error) {
+       if mth.finished {
+               return 0, errors.New("already Sum()ed")
+       }
+       n, err := mth.buf.Write(data)
+       if err != nil {
+               return n, err
+       }
+       if mth.skip > 0 && int64(mth.buf.Len()) >= mth.skip {
+               mth.buf.Next(int(mth.skip))
+               mth.skip = 0
+       }
+       for mth.buf.Len() >= MTHBlockSize {
+               if _, err = mth.hasher.Write(mth.buf.Next(MTHBlockSize)); err != nil {
+                       return n, err
+               }
+               h := new([MTHSize]byte)
+               mth.hasher.Sum(h[:0])
+               mth.hasher.Reset()
+               mth.hashes = append(mth.hashes, *h)
+               if mth.Events != nil {
+                       mth.Events <- MTHEvent{
+                               MTHEventAppend,
+                               0, len(mth.hashes) - 1,
+                               mth.hashes[len(mth.hashes)-1][:],
+                       }
+               }
+       }
+       return n, err
+}
+
+func (mth *MTH) PrependFrom(r io.Reader) (int, error) {
+       if mth.finished {
+               return 0, errors.New("already Sum()ed")
+       }
+       var err error
+       buf := make([]byte, MTHBlockSize)
+       var i, n, read int
+       fullsize := mth.PrependSize
+       les := LEs{{"Pkt", mth.PktName}, {"FullSize", fullsize}, {"Size", 0}}
+       for mth.PrependSize >= MTHBlockSize {
+               n, err = io.ReadFull(r, buf)
+               read += n
+               mth.PrependSize -= MTHBlockSize
+               if err != nil {
+                       return read, err
+               }
+               if _, err = mth.hasher.Write(buf); err != nil {
+                       panic(err)
+               }
+               mth.hasher.Sum(mth.hashes[i][:0])
+               mth.hasher.Reset()
+               if mth.Events != nil {
+                       mth.Events <- MTHEvent{MTHEventPrepend, 0, i, mth.hashes[i][:]}
+               }
+               if mth.PktName != "" {
+                       les[len(les)-1].V = int64(read)
+                       Progress("check", les)
+               }
+               i++
+       }
+       if mth.PrependSize > 0 {
+               n, err = io.ReadFull(r, buf[:mth.PrependSize])
+               read += n
+               if err != nil {
+                       return read, err
+               }
+               if _, err = mth.hasher.Write(buf[:mth.PrependSize]); err != nil {
+                       panic(err)
+               }
+               mth.hasher.Sum(mth.hashes[i][:0])
+               mth.hasher.Reset()
+               if mth.Events != nil {
+                       mth.Events <- MTHEvent{MTHEventPrepend, 0, i, mth.hashes[i][:]}
+               }
+               if mth.PktName != "" {
+                       les[len(les)-1].V = fullsize
+                       Progress("check", les)
+               }
+       }
+       return read, nil
+}
+
+func (mth *MTH) Sum(b []byte) []byte {
+       if mth.finished {
+               return append(b, mth.hashes[0][:]...)
+       }
+       if mth.buf.Len() > 0 {
+               b := mth.buf.Next(MTHBlockSize)
+               if _, err := mth.hasher.Write(b); err != nil {
+                       panic(err)
+               }
+               h := new([MTHSize]byte)
+               mth.hasher.Sum(h[:0])
+               mth.hasher.Reset()
+               mth.hashes = append(mth.hashes, *h)
+               if mth.Events != nil {
+                       mth.Events <- MTHEvent{
+                               MTHEventAppend,
+                               0, len(mth.hashes) - 1,
+                               mth.hashes[len(mth.hashes)-1][:],
+                       }
+               }
+       }
+       switch len(mth.hashes) {
+       case 0:
+               h := new([MTHSize]byte)
+               if _, err := mth.hasher.Write(nil); err != nil {
+                       panic(err)
+               }
+               mth.hasher.Sum(h[:0])
+               mth.hasher.Reset()
+               mth.hashes = append(mth.hashes, *h)
+               if mth.Events != nil {
+                       mth.Events <- MTHEvent{MTHEventAppend, 0, 0, mth.hashes[0][:]}
+               }
+               fallthrough
+       case 1:
+               mth.hashes = append(mth.hashes, mth.hashes[0])
+               if mth.Events != nil {
+                       mth.Events <- MTHEvent{MTHEventAppend, 0, 1, mth.hashes[1][:]}
+               }
+       }
+       mth.hasher = blake3.New(MTHSize, MTHNodeKey[:])
+       level := 1
+       for len(mth.hashes) != 1 {
+               hashesUp := make([][MTHSize]byte, 0, 1+len(mth.hashes)/2)
+               pairs := (len(mth.hashes) / 2) * 2
+               for i := 0; i < pairs; i += 2 {
+                       if _, err := mth.hasher.Write(mth.hashes[i][:]); err != nil {
+                               panic(err)
+                       }
+                       if _, err := mth.hasher.Write(mth.hashes[i+1][:]); err != nil {
+                               panic(err)
+                       }
+                       h := new([MTHSize]byte)
+                       mth.hasher.Sum(h[:0])
+                       mth.hasher.Reset()
+                       hashesUp = append(hashesUp, *h)
+                       if mth.Events != nil {
+                               mth.Events <- MTHEvent{
+                                       MTHEventFold,
+                                       level, len(hashesUp) - 1,
+                                       hashesUp[len(hashesUp)-1][:],
+                               }
+                       }
+               }
+               if len(mth.hashes)%2 == 1 {
+                       hashesUp = append(hashesUp, mth.hashes[len(mth.hashes)-1])
+                       if mth.Events != nil {
+                               mth.Events <- MTHEvent{
+                                       MTHEventAppend,
+                                       level, len(hashesUp) - 1,
+                                       hashesUp[len(hashesUp)-1][:],
+                               }
+                       }
+               }
+               mth.hashes = hashesUp
+               level++
+       }
+       mth.finished = true
+       if mth.Events != nil {
+               close(mth.Events)
+       }
+       return append(b, mth.hashes[0][:]...)
+}
diff --git a/src/mth_test.go b/src/mth_test.go
new file mode 100644 (file)
index 0000000..2d36f07
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+NNCP -- Node to Node copy, utilities for store-and-forward data exchange
+Copyright (C) 2016-2021 Sergey Matveev <stargrave@stargrave.org>
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+package nncp
+
+import (
+       "bytes"
+       "io"
+       "testing"
+       "testing/quick"
+
+       "lukechampine.com/blake3"
+)
+
+func TestMTHSymmetric(t *testing.T) {
+       xof := blake3.New(32, nil).XOF()
+       f := func(size uint32, offset uint32) bool {
+               size %= 2 * 1024 * 1024
+               data := make([]byte, int(size), int(size)+1)
+               if _, err := io.ReadFull(xof, data); err != nil {
+                       panic(err)
+               }
+               offset = offset % size
+
+               mth := MTHNew(int64(size), 0)
+               if _, err := io.Copy(mth, bytes.NewReader(data)); err != nil {
+                       panic(err)
+               }
+               hsh0 := mth.Sum(nil)
+
+               mth = MTHNew(int64(size), int64(offset))
+               if _, err := io.Copy(mth, bytes.NewReader(data[int(offset):])); err != nil {
+                       panic(err)
+               }
+               if _, err := mth.PrependFrom(bytes.NewReader(data)); err != nil {
+                       panic(err)
+               }
+               if bytes.Compare(hsh0, mth.Sum(nil)) != 0 {
+                       return false
+               }
+
+               mth = MTHNew(0, 0)
+               mth.Write(data)
+               if bytes.Compare(hsh0, mth.Sum(nil)) != 0 {
+                       return false
+               }
+
+               data = append(data, 0)
+               mth = MTHNew(int64(size)+1, 0)
+               if _, err := io.Copy(mth, bytes.NewReader(data)); err != nil {
+                       panic(err)
+               }
+               hsh00 := mth.Sum(nil)
+               if bytes.Compare(hsh0, hsh00) == 0 {
+                       return false
+               }
+
+               mth = MTHNew(int64(size)+1, int64(offset))
+               if _, err := io.Copy(mth, bytes.NewReader(data[int(offset):])); err != nil {
+                       panic(err)
+               }
+               if _, err := mth.PrependFrom(bytes.NewReader(data)); err != nil {
+                       panic(err)
+               }
+               if bytes.Compare(hsh00, mth.Sum(nil)) != 0 {
+                       return false
+               }
+
+               mth = MTHNew(0, 0)
+               mth.Write(data)
+               if bytes.Compare(hsh00, mth.Sum(nil)) != 0 {
+                       return false
+               }
+
+               return true
+       }
+       if err := quick.Check(f, nil); err != nil {
+               t.Error(err)
+       }
+}
+
+func TestMTHNull(t *testing.T) {
+       mth := MTHNew(0, 0)
+       if _, err := mth.Write(nil); err != nil {
+               t.Error(err)
+       }
+       mth.Sum(nil)
+}
index 1dfdb7497142d5c698b77f2f7c377cd3b9cc71fb..1edc777fe0f1574878dbd68410446824d4d53446 100644 (file)
@@ -40,7 +40,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.`
 const Base32Encoded32Len = 52
 
 var (
-       Version string = "6.6.0"
+       Version string = "7.0.0"
 
        Base32Codec *base32.Encoding = base32.StdEncoding.WithPadding(base32.NoPadding)
 )
index c2715e4928d585f7fb9541af01802f87d530f337..135e850950e688c087abfe4ce0fb1ae6987fe63f 100644 (file)
@@ -26,19 +26,18 @@ import (
        "io"
 
        xdr "github.com/davecgh/go-xdr/xdr2"
-       "golang.org/x/crypto/blake2b"
        "golang.org/x/crypto/chacha20poly1305"
        "golang.org/x/crypto/curve25519"
        "golang.org/x/crypto/ed25519"
        "golang.org/x/crypto/nacl/box"
        "golang.org/x/crypto/poly1305"
+       "lukechampine.com/blake3"
 )
 
 type PktType uint8
 
 const (
        EncBlkSize = 128 * (1 << 10)
-       KDFXOFSize = chacha20poly1305.KeySize * 2
 
        PktTypeFile    PktType = iota
        PktTypeFreq    PktType = iota
@@ -54,10 +53,8 @@ const (
 )
 
 var (
-       MagicNNCPPv3 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'P', 0, 0, 3}
-       MagicNNCPEv4 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'E', 0, 0, 4}
-       BadMagic     error   = errors.New("Unknown magic number")
-       BadPktType   error   = errors.New("Unknown packet type")
+       BadMagic   error = errors.New("Unknown magic number")
+       BadPktType error = errors.New("Unknown packet type")
 
        PktOverhead    int64
        PktEncOverhead int64
@@ -103,7 +100,7 @@ func init() {
                panic(err)
        }
        pktEnc := PktEnc{
-               Magic:     MagicNNCPEv4,
+               Magic:     MagicNNCPEv5.B,
                Sender:    dummyId,
                Recipient: dummyId,
        }
@@ -119,7 +116,7 @@ func NewPkt(typ PktType, nice uint8, path []byte) (*Pkt, error) {
                return nil, errors.New("Too long path")
        }
        pkt := Pkt{
-               Magic:   MagicNNCPPv3,
+               Magic:   MagicNNCPPv3.B,
                Type:    typ,
                Nice:    nice,
                PathLen: uint8(len(path)),
@@ -128,14 +125,23 @@ func NewPkt(typ PktType, nice uint8, path []byte) (*Pkt, error) {
        return &pkt, nil
 }
 
+func ctrIncr(b []byte) {
+       for i := len(b) - 1; i >= 0; i-- {
+               b[i]++
+               if b[i] != 0 {
+                       return
+               }
+       }
+       panic("counter overflow")
+}
+
 func aeadProcess(
        aead cipher.AEAD,
-       nonce []byte,
+       nonce, ad []byte,
        doEncrypt bool,
        r io.Reader,
        w io.Writer,
 ) (int, error) {
-       var blkCtr uint64
        ciphCtr := nonce[len(nonce)-8:]
        buf := make([]byte, EncBlkSize+aead.Overhead())
        var toRead []byte
@@ -159,12 +165,11 @@ func aeadProcess(
                        }
                }
                readBytes += n
-               blkCtr++
-               binary.BigEndian.PutUint64(ciphCtr, blkCtr)
+               ctrIncr(ciphCtr)
                if doEncrypt {
-                       toWrite = aead.Seal(buf[:0], nonce, buf[:n], nil)
+                       toWrite = aead.Seal(buf[:0], nonce, buf[:n], ad)
                } else {
-                       toWrite, err = aead.Open(buf[:0], nonce, buf[:n], nil)
+                       toWrite, err = aead.Open(buf[:0], nonce, buf[:n], ad)
                        if err != nil {
                                return readBytes, err
                        }
@@ -202,7 +207,7 @@ func PktEncWrite(
                return nil, err
        }
        tbs := PktTbs{
-               Magic:     MagicNNCPEv4,
+               Magic:     MagicNNCPEv5.B,
                Nice:      nice,
                Sender:    our.Id,
                Recipient: their.Id,
@@ -215,13 +220,14 @@ func PktEncWrite(
        signature := new([ed25519.SignatureSize]byte)
        copy(signature[:], ed25519.Sign(our.SignPrv, tbsBuf.Bytes()))
        pktEnc := PktEnc{
-               Magic:     MagicNNCPEv4,
+               Magic:     MagicNNCPEv5.B,
                Nice:      nice,
                Sender:    our.Id,
                Recipient: their.Id,
                ExchPub:   *pubEph,
                Sign:      *signature,
        }
+       ad := blake3.Sum256(tbsBuf.Bytes())
        tbsBuf.Reset()
        if _, err = xdr.Marshal(&tbsBuf, &pktEnc); err != nil {
                return nil, err
@@ -232,18 +238,9 @@ func PktEncWrite(
        }
        sharedKey := new([32]byte)
        curve25519.ScalarMult(sharedKey, prvEph, their.ExchPub)
-       kdf, err := blake2b.NewXOF(KDFXOFSize, sharedKey[:])
-       if err != nil {
-               return nil, err
-       }
-       if _, err = kdf.Write(MagicNNCPEv4[:]); err != nil {
-               return nil, err
-       }
 
        key := make([]byte, chacha20poly1305.KeySize)
-       if _, err = io.ReadFull(kdf, key); err != nil {
-               return nil, err
-       }
+       blake3.DeriveKey(key, string(MagicNNCPEv5.B[:]), sharedKey[:])
        aead, err := chacha20poly1305.New(key)
        if err != nil {
                return nil, err
@@ -253,13 +250,13 @@ func PktEncWrite(
        fullSize := pktBuf.Len() + int(size)
        sizeBuf := make([]byte, 8+aead.Overhead())
        binary.BigEndian.PutUint64(sizeBuf, uint64(sizeWithTags(int64(fullSize))))
-       if _, err = out.Write(aead.Seal(sizeBuf[:0], nonce, sizeBuf[:8], nil)); err != nil {
+       if _, err = out.Write(aead.Seal(sizeBuf[:0], nonce, sizeBuf[:8], ad[:])); err != nil {
                return nil, err
        }
 
        lr := io.LimitedReader{R: data, N: size}
        mr := io.MultiReader(&pktBuf, &lr)
-       written, err := aeadProcess(aead, nonce, true, mr, out)
+       written, err := aeadProcess(aead, nonce, ad[:], true, mr, out)
        if err != nil {
                return nil, err
        }
@@ -267,23 +264,18 @@ func PktEncWrite(
                return nil, io.ErrUnexpectedEOF
        }
        if padSize > 0 {
-               if _, err = io.ReadFull(kdf, key); err != nil {
-                       return nil, err
-               }
-               kdf, err = blake2b.NewXOF(blake2b.OutputLengthUnknown, key)
-               if err != nil {
-                       return nil, err
-               }
-               if _, err = io.CopyN(out, kdf, padSize); err != nil {
+               blake3.DeriveKey(key, string(MagicNNCPEv5.B[:])+" PAD", sharedKey[:])
+               xof := blake3.New(32, key).XOF()
+               if _, err = io.CopyN(out, xof, padSize); err != nil {
                        return nil, err
                }
        }
        return pktEncRaw, nil
 }
 
-func TbsVerify(our *NodeOur, their *Node, pktEnc *PktEnc) (bool, error) {
+func TbsVerify(our *NodeOur, their *Node, pktEnc *PktEnc) ([]byte, bool, error) {
        tbs := PktTbs{
-               Magic:     MagicNNCPEv4,
+               Magic:     MagicNNCPEv5.B,
                Nice:      pktEnc.Nice,
                Sender:    their.Id,
                Recipient: our.Id,
@@ -291,9 +283,9 @@ func TbsVerify(our *NodeOur, their *Node, pktEnc *PktEnc) (bool, error) {
        }
        var tbsBuf bytes.Buffer
        if _, err := xdr.Marshal(&tbsBuf, &tbs); err != nil {
-               return false, err
+               return nil, false, err
        }
-       return ed25519.Verify(their.SignPub, tbsBuf.Bytes(), pktEnc.Sign[:]), nil
+       return tbsBuf.Bytes(), ed25519.Verify(their.SignPub, tbsBuf.Bytes(), pktEnc.Sign[:]), nil
 }
 
 func PktEncRead(
@@ -307,8 +299,21 @@ func PktEncRead(
        if err != nil {
                return nil, 0, err
        }
-       if pktEnc.Magic != MagicNNCPEv4 {
-               return nil, 0, BadMagic
+       switch pktEnc.Magic {
+       case MagicNNCPEv1.B:
+               err = MagicNNCPEv1.TooOld()
+       case MagicNNCPEv2.B:
+               err = MagicNNCPEv2.TooOld()
+       case MagicNNCPEv3.B:
+               err = MagicNNCPEv3.TooOld()
+       case MagicNNCPEv4.B:
+               err = MagicNNCPEv4.TooOld()
+       case MagicNNCPEv5.B:
+       default:
+               err = BadMagic
+       }
+       if err != nil {
+               return nil, 0, err
        }
        their, known := nodes[*pktEnc.Sender]
        if !known {
@@ -317,27 +322,19 @@ func PktEncRead(
        if *pktEnc.Recipient != *our.Id {
                return nil, 0, errors.New("Invalid recipient")
        }
-       verified, err := TbsVerify(our, their, &pktEnc)
+       tbsRaw, verified, err := TbsVerify(our, their, &pktEnc)
        if err != nil {
                return nil, 0, err
        }
        if !verified {
                return their, 0, errors.New("Invalid signature")
        }
+       ad := blake3.Sum256(tbsRaw)
        sharedKey := new([32]byte)
        curve25519.ScalarMult(sharedKey, our.ExchPrv, &pktEnc.ExchPub)
-       kdf, err := blake2b.NewXOF(KDFXOFSize, sharedKey[:])
-       if err != nil {
-               return their, 0, err
-       }
-       if _, err = kdf.Write(MagicNNCPEv4[:]); err != nil {
-               return their, 0, err
-       }
 
        key := make([]byte, chacha20poly1305.KeySize)
-       if _, err = io.ReadFull(kdf, key); err != nil {
-               return their, 0, err
-       }
+       blake3.DeriveKey(key, string(MagicNNCPEv5.B[:]), sharedKey[:])
        aead, err := chacha20poly1305.New(key)
        if err != nil {
                return their, 0, err
@@ -348,14 +345,14 @@ func PktEncRead(
        if _, err = io.ReadFull(data, sizeBuf); err != nil {
                return their, 0, err
        }
-       sizeBuf, err = aead.Open(sizeBuf[:0], nonce, sizeBuf, nil)
+       sizeBuf, err = aead.Open(sizeBuf[:0], nonce, sizeBuf, ad[:])
        if err != nil {
                return their, 0, err
        }
        size := int64(binary.BigEndian.Uint64(sizeBuf))
 
        lr := io.LimitedReader{R: data, N: size}
-       written, err := aeadProcess(aead, nonce, false, &lr, out)
+       written, err := aeadProcess(aead, nonce, ad[:], false, &lr, out)
        if err != nil {
                return their, int64(written), err
        }
index 945c57526419a2166cb20d3ea27057eff844da8b..0c2f0ee2c99f7cd874edd56fbded6e815bbdf8d2 100644 (file)
@@ -25,7 +25,7 @@ import (
        "time"
 
        "github.com/dustin/go-humanize"
-       "go.cypherpunks.ru/nncp/v6/uilive"
+       "go.cypherpunks.ru/nncp/v7/uilive"
 )
 
 func init() {
index 00eaac6b50d58ee0dc8665fa04d2a0a54b6c913b..7e74823e93fdb8bb4094f227ff82134fa094d55d 100644 (file)
--- a/src/sp.go
+++ b/src/sp.go
@@ -22,7 +22,6 @@ import (
        "crypto/subtle"
        "errors"
        "fmt"
-       "hash"
        "io"
        "os"
        "path/filepath"
@@ -33,7 +32,6 @@ import (
        xdr "github.com/davecgh/go-xdr/xdr2"
        "github.com/dustin/go-humanize"
        "github.com/flynn/noise"
-       "golang.org/x/crypto/blake2b"
 )
 
 const (
@@ -42,14 +40,19 @@ const (
        SPHeadOverhead = 4
 )
 
-type SPCheckerQueues struct {
-       appeared chan *[32]byte
-       checked  chan *[32]byte
+type MTHAndOffset struct {
+       mth    *MTH
+       offset uint64
 }
 
-var (
-       MagicNNCPLv1 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'S', 0, 0, 1}
+type SPCheckerTask struct {
+       nodeId *NodeId
+       hsh    *[MTHSize]byte
+       mth    *MTH
+       done   chan []byte
+}
 
+var (
        SPInfoOverhead    int
        SPFreqOverhead    int
        SPFileOverhead    int
@@ -65,8 +68,9 @@ var (
        DefaultDeadline = 10 * time.Second
        PingTimeout     = time.Minute
 
-       spCheckers   = make(map[NodeId]*SPCheckerQueues)
-       SPCheckersWg sync.WaitGroup
+       spCheckerTasks chan SPCheckerTask
+       SPCheckerWg    sync.WaitGroup
+       spCheckerOnce  sync.Once
 )
 
 type FdAndFullSize struct {
@@ -74,11 +78,6 @@ type FdAndFullSize struct {
        fullSize int64
 }
 
-type HasherAndOffset struct {
-       h      hash.Hash
-       offset uint64
-}
-
 type SPType uint8
 
 const (
@@ -97,22 +96,22 @@ type SPHead struct {
 type SPInfo struct {
        Nice uint8
        Size uint64
-       Hash *[32]byte
+       Hash *[MTHSize]byte
 }
 
 type SPFreq struct {
-       Hash   *[32]byte
+       Hash   *[MTHSize]byte
        Offset uint64
 }
 
 type SPFile struct {
-       Hash    *[32]byte
+       Hash    *[MTHSize]byte
        Offset  uint64
        Payload []byte
 }
 
 type SPDone struct {
-       Hash *[32]byte
+       Hash *[MTHSize]byte
 }
 
 type SPRaw struct {
@@ -149,25 +148,26 @@ func init() {
        copy(SPPingMarshalized, buf.Bytes())
        buf.Reset()
 
-       spInfo := SPInfo{Nice: 123, Size: 123, Hash: new([32]byte)}
+       spInfo := SPInfo{Nice: 123, Size: 123, Hash: new([MTHSize]byte)}
        if _, err := xdr.Marshal(&buf, spInfo); err != nil {
                panic(err)
        }
        SPInfoOverhead = buf.Len()
        buf.Reset()
 
-       spFreq := SPFreq{Hash: new([32]byte), Offset: 123}
+       spFreq := SPFreq{Hash: new([MTHSize]byte), Offset: 123}
        if _, err := xdr.Marshal(&buf, spFreq); err != nil {
                panic(err)
        }
        SPFreqOverhead = buf.Len()
        buf.Reset()
 
-       spFile := SPFile{Hash: new([32]byte), Offset: 123}
+       spFile := SPFile{Hash: new([MTHSize]byte), Offset: 123}
        if _, err := xdr.Marshal(&buf, spFile); err != nil {
                panic(err)
        }
        SPFileOverhead = buf.Len()
+       spCheckerTasks = make(chan SPCheckerTask)
 }
 
 func MarshalSP(typ SPType, sp interface{}) []byte {
@@ -209,8 +209,8 @@ type SPState struct {
        csTheir        *noise.CipherState
        payloads       chan []byte
        pings          chan struct{}
-       infosTheir     map[[32]byte]*SPInfo
-       infosOurSeen   map[[32]byte]uint8
+       infosTheir     map[[MTHSize]byte]*SPInfo
+       infosOurSeen   map[[MTHSize]byte]uint8
        queueTheir     []*FreqWithNice
        wg             sync.WaitGroup
        RxBytes        int64
@@ -231,12 +231,11 @@ type SPState struct {
        txRate         int
        isDead         chan struct{}
        listOnly       bool
-       onlyPkts       map[[32]byte]bool
+       onlyPkts       map[[MTHSize]byte]bool
        writeSPBuf     bytes.Buffer
        fds            map[string]FdAndFullSize
        fdsLock        sync.RWMutex
-       fileHashers    map[string]*HasherAndOffset
-       checkerQueues  SPCheckerQueues
+       fileHashers    map[string]*MTHAndOffset
        progressBars   map[string]struct{}
        sync.RWMutex
 }
@@ -275,44 +274,10 @@ func (state *SPState) dirUnlock() {
        state.Ctx.UnlockDir(state.txLock)
 }
 
-func SPChecker(ctx *Ctx, nodeId *NodeId, appeared, checked chan *[32]byte) {
-       for hshValue := range appeared {
-               pktName := Base32Codec.EncodeToString(hshValue[:])
-               les := LEs{
-                       {"XX", string(TRx)},
-                       {"Node", nodeId},
-                       {"Pkt", pktName},
-               }
-               SPCheckersWg.Add(1)
-               ctx.LogD("sp-checker", les, func(les LEs) string {
-                       return fmt.Sprintf("Checksumming %s/rx/%s", ctx.NodeName(nodeId), pktName)
-               })
-               size, err := ctx.CheckNoCK(nodeId, hshValue)
-               les = append(les, LE{"Size", size})
-               if err != nil {
-                       ctx.LogE("sp-checker", les, err, func(les LEs) string {
-                               return fmt.Sprintf(
-                                       "Checksumming %s/rx/%s (%s)", ctx.NodeName(nodeId), pktName,
-                                       humanize.IBytes(uint64(size)),
-                               )
-                       })
-                       continue
-               }
-               ctx.LogI("sp-checker-done", les, func(les LEs) string {
-                       return fmt.Sprintf(
-                               "Packet %s is retreived (%s)",
-                               pktName, humanize.IBytes(uint64(size)),
-                       )
-               })
-               SPCheckersWg.Done()
-               go func(hsh *[32]byte) { checked <- hsh }(hshValue)
-       }
-}
-
 func (state *SPState) WriteSP(dst io.Writer, payload []byte, ping bool) error {
        state.writeSPBuf.Reset()
        n, err := xdr.Marshal(&state.writeSPBuf, SPRaw{
-               Magic:   MagicNNCPLv1,
+               Magic:   MagicNNCPSv1.B,
                Payload: payload,
        })
        if err != nil {
@@ -340,13 +305,13 @@ func (state *SPState) ReadSP(src io.Reader) ([]byte, error) {
        }
        state.RxLastSeen = time.Now()
        state.RxBytes += int64(n)
-       if sp.Magic != MagicNNCPLv1 {
+       if sp.Magic != MagicNNCPSv1.B {
                return nil, BadMagic
        }
        return sp.Payload, nil
 }
 
-func (ctx *Ctx) infosOur(nodeId *NodeId, nice uint8, seen *map[[32]byte]uint8) [][]byte {
+func (ctx *Ctx) infosOur(nodeId *NodeId, nice uint8, seen *map[[MTHSize]byte]uint8) [][]byte {
        var infos []*SPInfo
        var totalSize int64
        for job := range ctx.Jobs(nodeId, TTx) {
@@ -438,8 +403,8 @@ func (state *SPState) StartI(conn ConnDeadlined) error {
        state.hs = hs
        state.payloads = make(chan []byte)
        state.pings = make(chan struct{})
-       state.infosTheir = make(map[[32]byte]*SPInfo)
-       state.infosOurSeen = make(map[[32]byte]uint8)
+       state.infosTheir = make(map[[MTHSize]byte]*SPInfo)
+       state.infosOurSeen = make(map[[MTHSize]byte]uint8)
        state.progressBars = make(map[string]struct{})
        state.started = started
        state.rxLock = rxLock
@@ -556,8 +521,8 @@ func (state *SPState) StartR(conn ConnDeadlined) error {
        state.hs = hs
        state.payloads = make(chan []byte)
        state.pings = make(chan struct{})
-       state.infosOurSeen = make(map[[32]byte]uint8)
-       state.infosTheir = make(map[[32]byte]*SPInfo)
+       state.infosOurSeen = make(map[[MTHSize]byte]uint8)
+       state.infosTheir = make(map[[MTHSize]byte]*SPInfo)
        state.progressBars = make(map[string]struct{})
        state.started = started
        state.xxOnly = xxOnly
@@ -686,43 +651,13 @@ func (state *SPState) StartWorkers(
 ) error {
        les := LEs{{"Node", state.Node.Id}, {"Nice", int(state.Nice)}}
        state.fds = make(map[string]FdAndFullSize)
-       state.fileHashers = make(map[string]*HasherAndOffset)
+       state.fileHashers = make(map[string]*MTHAndOffset)
        state.isDead = make(chan struct{})
        if state.maxOnlineTime > 0 {
                state.mustFinishAt = state.started.Add(state.maxOnlineTime)
        }
-
-       // Checker
        if !state.NoCK {
-               queues := spCheckers[*state.Node.Id]
-               if queues == nil {
-                       queues = &SPCheckerQueues{
-                               appeared: make(chan *[32]byte),
-                               checked:  make(chan *[32]byte),
-                       }
-                       spCheckers[*state.Node.Id] = queues
-                       go SPChecker(state.Ctx, state.Node.Id, queues.appeared, queues.checked)
-               }
-               state.checkerQueues = *queues
-               go func() {
-                       for job := range state.Ctx.JobsNoCK(state.Node.Id) {
-                               if job.PktEnc.Nice <= state.Nice {
-                                       state.checkerQueues.appeared <- job.HshValue
-                               }
-                       }
-               }()
-               state.wg.Add(1)
-               go func() {
-                       defer state.wg.Done()
-                       for {
-                               select {
-                               case <-state.isDead:
-                                       return
-                               case hsh := <-state.checkerQueues.checked:
-                                       state.payloads <- MarshalSP(SPTypeDone, SPDone{hsh})
-                               }
-                       }
-               }()
+               spCheckerOnce.Do(func() { go SPChecker(state.Ctx) })
        }
 
        // Remaining handshake payload sending
@@ -1033,7 +968,12 @@ func (state *SPState) StartWorkers(
                        }
                        state.Ctx.LogD("sp-sending", append(les, LE{"Size", int64(len(payload))}), logMsg)
                        conn.SetWriteDeadline(time.Now().Add(DefaultDeadline)) // #nosec G104
-                       if err := state.WriteSP(conn, state.csOur.Encrypt(nil, nil, payload), ping); err != nil {
+                       ct, err := state.csOur.Encrypt(nil, nil, payload)
+                       if err != nil {
+                               state.Ctx.LogE("sp-encrypting", les, err, logMsg)
+                               return
+                       }
+                       if err := state.WriteSP(conn, ct, ping); err != nil {
                                state.Ctx.LogE("sp-sending", les, err, logMsg)
                                return
                        }
@@ -1139,7 +1079,6 @@ func (state *SPState) Wait() {
        close(state.payloads)
        close(state.pings)
        state.Duration = time.Now().Sub(state.started)
-       SPCheckersWg.Wait()
        state.dirUnlock()
        state.RxSpeed = state.RxBytes
        state.TxSpeed = state.TxBytes
@@ -1367,6 +1306,18 @@ func (state *SPState) ProcessSP(payload []byte) ([][]byte, error) {
                                        pktName, humanize.IBytes(uint64(len(file.Payload))),
                                )
                        }
+                       fullsize := int64(0)
+                       state.RLock()
+                       infoTheir, ok := state.infosTheir[*file.Hash]
+                       state.RUnlock()
+                       if !ok {
+                               state.Ctx.LogE("sp-file-open", lesp, err, func(les LEs) string {
+                                       return logMsg(les) + ": unknown file"
+                               })
+                               continue
+                       }
+                       fullsize = int64(infoTheir.Size)
+                       lesp = append(lesp, LE{"FullSize", fullsize})
                        dirToSync := filepath.Join(
                                state.Ctx.Spool,
                                state.Node.Id.String(),
@@ -1380,6 +1331,7 @@ func (state *SPState) ProcessSP(payload []byte) ([][]byte, error) {
                        state.fdsLock.RLock()
                        fdAndFullSize, exists := state.fds[filePathPart]
                        state.fdsLock.RUnlock()
+                       hasherAndOffset := state.fileHashers[filePath]
                        var fd *os.File
                        if exists {
                                fd = fdAndFullSize.fd
@@ -1398,12 +1350,12 @@ func (state *SPState) ProcessSP(payload []byte) ([][]byte, error) {
                                state.fdsLock.Lock()
                                state.fds[filePathPart] = FdAndFullSize{fd: fd}
                                state.fdsLock.Unlock()
-                               if file.Offset == 0 {
-                                       h, err := blake2b.New256(nil)
-                                       if err != nil {
-                                               panic(err)
+                               if !state.NoCK {
+                                       hasherAndOffset = &MTHAndOffset{
+                                               mth:    MTHNew(fullsize, int64(file.Offset)),
+                                               offset: file.Offset,
                                        }
-                                       state.fileHashers[filePath] = &HasherAndOffset{h: h}
+                                       state.fileHashers[filePath] = hasherAndOffset
                                }
                        }
                        state.Ctx.LogD(
@@ -1429,34 +1381,25 @@ func (state *SPState) ProcessSP(payload []byte) ([][]byte, error) {
                                state.closeFd(filePathPart)
                                return nil, err
                        }
-                       hasherAndOffset, hasherExists := state.fileHashers[filePath]
-                       if hasherExists {
+                       if hasherAndOffset != nil {
                                if hasherAndOffset.offset == file.Offset {
-                                       if _, err = hasherAndOffset.h.Write(file.Payload); err != nil {
+                                       if _, err = hasherAndOffset.mth.Write(file.Payload); err != nil {
                                                panic(err)
                                        }
                                        hasherAndOffset.offset += uint64(len(file.Payload))
                                } else {
-                                       state.Ctx.LogD(
-                                               "sp-file-offset-differs", lesp,
+                                       state.Ctx.LogE(
+                                               "sp-file-offset-differs", lesp, errors.New("offset differs"),
                                                func(les LEs) string {
-                                                       return logMsg(les) + ": offset differs, deleting hasher"
+                                                       return logMsg(les) + ": deleting hasher"
                                                },
                                        )
                                        delete(state.fileHashers, filePath)
-                                       hasherExists = false
+                                       hasherAndOffset = nil
                                }
                        }
                        ourSize := int64(file.Offset + uint64(len(file.Payload)))
-                       lesp[len(lesp)-1].V = ourSize
-                       fullsize := int64(0)
-                       state.RLock()
-                       infoTheir, ok := state.infosTheir[*file.Hash]
-                       state.RUnlock()
-                       if ok {
-                               fullsize = int64(infoTheir.Size)
-                       }
-                       lesp = append(lesp, LE{"FullSize", fullsize})
+                       lesp[len(lesp)-2].V = ourSize
                        if state.Ctx.ShowPrgrs {
                                state.progressBars[pktName] = struct{}{}
                                Progress("Rx", lesp)
@@ -1483,59 +1426,64 @@ func (state *SPState) ProcessSP(payload []byte) ([][]byte, error) {
                                state.closeFd(filePathPart)
                                continue
                        }
-                       if hasherExists {
-                               if bytes.Compare(hasherAndOffset.h.Sum(nil), file.Hash[:]) != 0 {
-                                       state.Ctx.LogE(
-                                               "sp-file-bad-checksum", lesp,
-                                               errors.New("checksum mismatch"),
-                                               logMsg,
-                                       )
-                                       continue
-                               }
-                               if err = os.Rename(filePathPart, filePath); err != nil {
-                                       state.Ctx.LogE("sp-file-rename", lesp, err, func(les LEs) string {
-                                               return logMsg(les) + ": renaming"
-                                       })
-                                       continue
-                               }
-                               if err = DirSync(dirToSync); err != nil {
-                                       state.Ctx.LogE("sp-file-dirsync", lesp, err, func(les LEs) string {
-                                               return logMsg(les) + ": dirsyncing"
-                                       })
-                                       continue
-                               }
-                               state.Ctx.LogI("sp-file-done", lesp, func(les LEs) string {
-                                       return logMsg(les) + ": done"
-                               })
-                               state.wg.Add(1)
-                               go func() {
-                                       state.payloads <- MarshalSP(SPTypeDone, SPDone{file.Hash})
-                                       state.wg.Done()
-                               }()
-                               state.Lock()
-                               delete(state.infosTheir, *file.Hash)
-                               state.Unlock()
-                               if !state.Ctx.HdrUsage {
-                                       state.closeFd(filePathPart)
-                                       continue
-                               }
-                               if _, err = fd.Seek(0, io.SeekStart); err != nil {
-                                       state.Ctx.LogE("sp-file-seek", lesp, err, func(les LEs) string {
-                                               return logMsg(les) + ": seeking"
+                       if hasherAndOffset != nil {
+                               delete(state.fileHashers, filePath)
+                               if hasherAndOffset.mth.PrependSize == 0 {
+                                       if bytes.Compare(hasherAndOffset.mth.Sum(nil), file.Hash[:]) != 0 {
+                                               state.Ctx.LogE(
+                                                       "sp-file-bad-checksum", lesp,
+                                                       errors.New("checksum mismatch"),
+                                                       logMsg,
+                                               )
+                                               state.closeFd(filePathPart)
+                                               continue
+                                       }
+                                       if err = os.Rename(filePathPart, filePath); err != nil {
+                                               state.Ctx.LogE("sp-file-rename", lesp, err, func(les LEs) string {
+                                                       return logMsg(les) + ": renaming"
+                                               })
+                                               state.closeFd(filePathPart)
+                                               continue
+                                       }
+                                       if err = DirSync(dirToSync); err != nil {
+                                               state.Ctx.LogE("sp-file-dirsync", lesp, err, func(les LEs) string {
+                                                       return logMsg(les) + ": dirsyncing"
+                                               })
+                                               state.closeFd(filePathPart)
+                                               continue
+                                       }
+                                       state.Ctx.LogI("sp-file-done", lesp, func(les LEs) string {
+                                               return logMsg(les) + ": done"
                                        })
+                                       state.wg.Add(1)
+                                       go func() {
+                                               state.payloads <- MarshalSP(SPTypeDone, SPDone{file.Hash})
+                                               state.wg.Done()
+                                       }()
+                                       state.Lock()
+                                       delete(state.infosTheir, *file.Hash)
+                                       state.Unlock()
+                                       if !state.Ctx.HdrUsage {
+                                               continue
+                                       }
+                                       if _, err = fd.Seek(0, io.SeekStart); err != nil {
+                                               state.Ctx.LogE("sp-file-seek", lesp, err, func(les LEs) string {
+                                                       return logMsg(les) + ": seeking"
+                                               })
+                                               state.closeFd(filePathPart)
+                                               continue
+                                       }
+                                       _, pktEncRaw, err := state.Ctx.HdrRead(fd)
                                        state.closeFd(filePathPart)
+                                       if err != nil {
+                                               state.Ctx.LogE("sp-file-hdr-read", lesp, err, func(les LEs) string {
+                                                       return logMsg(les) + ": HdrReading"
+                                               })
+                                               continue
+                                       }
+                                       state.Ctx.HdrWrite(pktEncRaw, filePath)
                                        continue
                                }
-                               _, pktEncRaw, err := state.Ctx.HdrRead(fd)
-                               state.closeFd(filePathPart)
-                               if err != nil {
-                                       state.Ctx.LogE("sp-file-hdr-read", lesp, err, func(les LEs) string {
-                                               return logMsg(les) + ": HdrReading"
-                                       })
-                                       continue
-                               }
-                               state.Ctx.HdrWrite(pktEncRaw, filePath)
-                               continue
                        }
                        state.closeFd(filePathPart)
                        if err = os.Rename(filePathPart, filePath+NoCKSuffix); err != nil {
@@ -1556,8 +1504,15 @@ func (state *SPState) ProcessSP(payload []byte) ([][]byte, error) {
                        state.Lock()
                        delete(state.infosTheir, *file.Hash)
                        state.Unlock()
-                       if !state.NoCK {
-                               state.checkerQueues.appeared <- file.Hash
+                       if hasherAndOffset != nil {
+                               go func() {
+                                       spCheckerTasks <- SPCheckerTask{
+                                               nodeId: state.Node.Id,
+                                               hsh:    file.Hash,
+                                               mth:    hasherAndOffset.mth,
+                                               done:   state.payloads,
+                                       }
+                               }()
                        }
 
                case SPTypeDone:
@@ -1701,3 +1656,41 @@ func (state *SPState) ProcessSP(payload []byte) ([][]byte, error) {
        }
        return payloadsSplit(replies), nil
 }
+
+func SPChecker(ctx *Ctx) {
+       for t := range spCheckerTasks {
+               pktName := Base32Codec.EncodeToString(t.hsh[:])
+               les := LEs{
+                       {"XX", string(TRx)},
+                       {"Node", t.nodeId},
+                       {"Pkt", pktName},
+               }
+               SPCheckerWg.Add(1)
+               ctx.LogD("sp-checker", les, func(les LEs) string {
+                       return fmt.Sprintf("Checksumming %s/rx/%s", ctx.NodeName(t.nodeId), pktName)
+               })
+               size, err := ctx.CheckNoCK(t.nodeId, t.hsh, t.mth)
+               les = append(les, LE{"Size", size})
+               if err != nil {
+                       ctx.LogE("sp-checker", les, err, func(les LEs) string {
+                               return fmt.Sprintf(
+                                       "Checksumming %s/rx/%s (%s)", ctx.NodeName(t.nodeId), pktName,
+                                       humanize.IBytes(uint64(size)),
+                               )
+                       })
+                       SPCheckerWg.Done()
+                       continue
+               }
+               ctx.LogI("sp-checker-done", les, func(les LEs) string {
+                       return fmt.Sprintf(
+                               "Packet %s is retreived (%s)",
+                               pktName, humanize.IBytes(uint64(size)),
+                       )
+               })
+               SPCheckerWg.Done()
+               go func(t SPCheckerTask) {
+                       defer func() { recover() }()
+                       t.done <- MarshalSP(SPTypeDone, SPDone{t.hsh})
+               }(t)
+       }
+}
index da96a25bfd0785f9bdc7b8aa48e523b01c47360f..88a7ec3847801a9d9fdbddcdf7ae3d67bc2098f4 100644 (file)
@@ -26,8 +26,6 @@ import (
        "path/filepath"
        "strconv"
        "time"
-
-       "golang.org/x/crypto/blake2b"
 )
 
 func TempFile(dir, prefix string) (*os.File, error) {
@@ -64,10 +62,7 @@ func (ctx *Ctx) NewTmpFileWHash() (*TmpFileWHash, error) {
        if err != nil {
                return nil, err
        }
-       hsh, err := blake2b.New256(nil)
-       if err != nil {
-               return nil, err
-       }
+       hsh := MTHNew(0, 0)
        return &TmpFileWHash{
                W:   bufio.NewWriter(io.MultiWriter(hsh, tmp)),
                Fd:  tmp,
index 1752fd2a4f1c34af444176573f35547df8c022b2..7537d695d08f5edcb874b72a747cf624fea22ad7 100644 (file)
@@ -38,7 +38,6 @@ import (
        xdr "github.com/davecgh/go-xdr/xdr2"
        "github.com/dustin/go-humanize"
        "github.com/klauspost/compress/zstd"
-       "golang.org/x/crypto/blake2b"
        "golang.org/x/crypto/poly1305"
 )
 
@@ -593,7 +592,7 @@ func (ctx *Ctx) Toss(
                        if noTrns {
                                goto Closing
                        }
-                       dst := new([blake2b.Size256]byte)
+                       dst := new([MTHSize]byte)
                        copy(dst[:], pkt.Path[:int(pkt.PathLen)])
                        nodeId := NodeId(*dst)
                        node, known := ctx.Neigh[nodeId]
index 807c49a9ea55c8222a0fa9c7c3ada15faf03bbd1..a66f43ba52ae2ea1a761ff663dad3e5aed3001e6 100644 (file)
@@ -31,7 +31,6 @@ import (
        "testing/quick"
 
        xdr "github.com/davecgh/go-xdr/xdr2"
-       "golang.org/x/crypto/blake2b"
 )
 
 var (
@@ -190,8 +189,9 @@ func TestTossFile(t *testing.T) {
                ctx.Neigh[*nodeOur.Id] = nodeOur.Their()
                incomingPath := filepath.Join(spool, "incoming")
                for _, fileData := range files {
-                       checksum := blake2b.Sum256(fileData)
-                       fileName := Base32Codec.EncodeToString(checksum[:])
+                       hasher := MTHNew(0, 0)
+                       hasher.Write(fileData)
+                       fileName := Base32Codec.EncodeToString(hasher.Sum(nil))
                        src := filepath.Join(spool, fileName)
                        if err := ioutil.WriteFile(src, fileData, os.FileMode(0600)); err != nil {
                                panic(err)
@@ -221,8 +221,9 @@ func TestTossFile(t *testing.T) {
                        return false
                }
                for _, fileData := range files {
-                       checksum := blake2b.Sum256(fileData)
-                       fileName := Base32Codec.EncodeToString(checksum[:])
+                       hasher := MTHNew(0, 0)
+                       hasher.Write(fileData)
+                       fileName := Base32Codec.EncodeToString(hasher.Sum(nil))
                        data, err := ioutil.ReadFile(filepath.Join(incomingPath, fileName))
                        if err != nil {
                                panic(err)
@@ -453,9 +454,9 @@ func TestTossTrns(t *testing.T) {
                os.MkdirAll(txPath, os.FileMode(0700))
                for _, data := range datum {
                        pktTrans := Pkt{
-                               Magic:   MagicNNCPPv3,
+                               Magic:   MagicNNCPPv3.B,
                                Type:    PktTypeTrns,
-                               PathLen: blake2b.Size256,
+                               PathLen: MTHSize,
                        }
                        copy(pktTrans.Path[:], nodeOur.Id[:])
                        var dst bytes.Buffer
@@ -472,9 +473,10 @@ func TestTossTrns(t *testing.T) {
                                t.Error(err)
                                return false
                        }
-                       checksum := blake2b.Sum256(dst.Bytes())
+                       hasher := MTHNew(0, 0)
+                       hasher.Write(dst.Bytes())
                        if err := ioutil.WriteFile(
-                               filepath.Join(rxPath, Base32Codec.EncodeToString(checksum[:])),
+                               filepath.Join(rxPath, Base32Codec.EncodeToString(hasher.Sum(nil))),
                                dst.Bytes(),
                                os.FileMode(0600),
                        ); err != nil {
index 73fbe4ec12bfa118d7d7ff1ab15c6f1ed2667fa1..14f73d23c627313102100a8d340d0aae0e92ed0d 100644 (file)
--- a/src/tx.go
+++ b/src/tx.go
@@ -36,7 +36,6 @@ import (
        xdr "github.com/davecgh/go-xdr/xdr2"
        "github.com/dustin/go-humanize"
        "github.com/klauspost/compress/zstd"
-       "golang.org/x/crypto/blake2b"
        "golang.org/x/crypto/chacha20poly1305"
 )
 
@@ -186,7 +185,7 @@ func throughTmpFile(r io.Reader) (
                return
        }
        nonce := make([]byte, aead.NonceSize())
-       written, err := aeadProcess(aead, nonce, true, r, tmpW)
+       written, err := aeadProcess(aead, nonce, nil, true, r, tmpW)
        if err != nil {
                rerr = err
                return
@@ -202,7 +201,7 @@ func throughTmpFile(r io.Reader) (
        }
        r, w := io.Pipe()
        go func() {
-               if _, err := aeadProcess(aead, nonce, false, bufio.NewReader(src), w); err != nil {
+               if _, err := aeadProcess(aead, nonce, nil, false, bufio.NewReader(src), w); err != nil {
                        w.CloseWithError(err) // #nosec G104
                }
        }()
@@ -406,13 +405,13 @@ func (ctx *Ctx) TxFile(
 
        leftSize := fileSize
        metaPkt := ChunkedMeta{
-               Magic:     MagicNNCPMv1,
+               Magic:     MagicNNCPMv2.B,
                FileSize:  uint64(fileSize),
                ChunkSize: uint64(chunkSize),
-               Checksums: make([][32]byte, 0, (fileSize/chunkSize)+1),
+               Checksums: make([][MTHSize]byte, 0, (fileSize/chunkSize)+1),
        }
        for i := int64(0); i < (fileSize/chunkSize)+1; i++ {
-               hsh := new([32]byte)
+               hsh := new([MTHSize]byte)
                metaPkt.Checksums = append(metaPkt.Checksums, *hsh)
        }
        var sizeToSend int64
@@ -431,10 +430,7 @@ func (ctx *Ctx) TxFile(
                if err != nil {
                        return err
                }
-               hsh, err = blake2b.New256(nil)
-               if err != nil {
-                       return err
-               }
+               hsh = MTHNew(0, 0)
                _, err = ctx.Tx(
                        node,
                        pkt,
index 7e07a0524a1d0a5837e7ce9033c2ea229d18e4e4..72c6f614e60405311cb1e37805f5e5050d4e04fe 100644 (file)
@@ -28,7 +28,6 @@ import (
        "testing/quick"
 
        xdr "github.com/davecgh/go-xdr/xdr2"
-       "golang.org/x/crypto/blake2b"
 )
 
 func TestTx(t *testing.T) {
@@ -141,7 +140,7 @@ func TestTx(t *testing.T) {
                                if pkt.Type != PktTypeTrns {
                                        return false
                                }
-                               if bytes.Compare(pkt.Path[:blake2b.Size256], vias[i+1][:]) != 0 {
+                               if bytes.Compare(pkt.Path[:MTHSize], vias[i+1][:]) != 0 {
                                        return false
                                }
                        }