mirror of
https://github.com/PeernetOfficial/Abstraction.git
synced 2026-07-18 11:27:51 +01:00
added example package
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,3 +5,6 @@ log.txt
|
||||
test.*
|
||||
# Ignore the IDE gitignore files
|
||||
.idea/
|
||||
example
|
||||
Config.yaml
|
||||
data/
|
||||
|
||||
4
files.go
4
files.go
@@ -148,8 +148,10 @@ func Search(api *webapi.WebapiInstance, term string) (*webapi.SearchResult, erro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Running search
|
||||
|
||||
// 6 seconds
|
||||
time.Sleep(1000 * 6)
|
||||
time.Sleep(time.Millisecond * 6000)
|
||||
|
||||
result, err := SearchResult(api, jobID)
|
||||
if err != nil {
|
||||
|
||||
1
vendor/github.com/IncSW/geoip2/.gitignore
generated
vendored
Normal file
1
vendor/github.com/IncSW/geoip2/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
testdata
|
||||
21
vendor/github.com/IncSW/geoip2/LICENSE
generated
vendored
Normal file
21
vendor/github.com/IncSW/geoip2/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Aleksey Lin <aleksey@incsw.in> (https://incsw.in)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
94
vendor/github.com/IncSW/geoip2/README.md
generated
vendored
Normal file
94
vendor/github.com/IncSW/geoip2/README.md
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
[](LICENSE)
|
||||
[](https://goreportcard.com/report/github.com/IncSW/geoip2)
|
||||
[](https://pkg.go.dev/github.com/IncSW/geoip2?tab=doc)
|
||||
|
||||
# GeoIP2 Reader for Go
|
||||
|
||||
This library reads MaxMind GeoIP2 databases.
|
||||
|
||||
Inspired by [oschwald/geoip2-golang](https://github.com/oschwald/geoip2-golang).
|
||||
|
||||
## Installation
|
||||
|
||||
`go get github.com/IncSW/geoip2`
|
||||
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
import "github.com/IncSW/geoip2"
|
||||
|
||||
reader, err := geoip2.NewCityReaderFromFile("path/to/GeoIP2-City.mmdb")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
record, err := reader.Lookup(net.ParseIP("81.2.69.142"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
println(record.Continent.Names["zh-CN"]) // 欧洲
|
||||
println(record.City.Names["pt-BR"]) // Wimbledon
|
||||
if len(record.Subdivisions) != 0 {
|
||||
println(record.Subdivisions[0].Names["en"]) // England
|
||||
}
|
||||
println(record.Country.Names["ru"]) // Великобритания
|
||||
println(record.Country.ISOCode) // GB
|
||||
println(record.Location.TimeZone) // Europe/London
|
||||
println(record.Country.GeoNameID) // 2635167, https://www.geonames.org/2635167
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### [IncSW/geoip2](https://github.com/IncSW/geoip2)
|
||||
```
|
||||
city-24 342847 2981 ns/op 2032 B/op 12 allocs/op
|
||||
city_parallel-24 4477626 269 ns/op 2032 B/op 12 allocs/op
|
||||
isp-24 3539738 336 ns/op 64 B/op 1 allocs/op
|
||||
isp_parallel-24 46938070 25.7 ns/op 64 B/op 1 allocs/op
|
||||
connection_type-24 8759110 133 ns/op 0 B/op 0 allocs/op
|
||||
connection_type_parallel-24 142261742 8.34 ns/op 0 B/op 0 allocs/op
|
||||
```
|
||||
|
||||
### [oschwald/geoip2-golang](https://github.com/oschwald/geoip2-golang)
|
||||
```
|
||||
city-24 109092 10717 ns/op 2848 B/op 103 allocs/op
|
||||
city_parallel-24 662510 1718 ns/op 2848 B/op 103 allocs/op
|
||||
isp-24 1688287 705 ns/op 112 B/op 4 allocs/op
|
||||
isp_parallel-24 14285560 84.4 ns/op 112 B/op 4 allocs/op
|
||||
connection_type-24 3883234 305 ns/op 32 B/op 2 allocs/op
|
||||
connection_type_parallel-24 34284831 32.1 ns/op 32 B/op 2 allocs/op
|
||||
```
|
||||
|
||||
## Supported databases types
|
||||
|
||||
### Country
|
||||
- GeoIP2-Country
|
||||
- GeoLite2-Country
|
||||
- DBIP-Country
|
||||
- DBIP-Country-Lite
|
||||
|
||||
### City
|
||||
- GeoIP2-City
|
||||
- GeoLite2-City
|
||||
- GeoIP2-Enterprise
|
||||
- DBIP-City-Lite
|
||||
|
||||
### ISP
|
||||
- GeoIP2-ISP
|
||||
|
||||
### ASN
|
||||
- GeoLite2-ASN
|
||||
- DBIP-ASN-Lite
|
||||
- DBIP-ASN-Lite (compat=GeoLite2-ASN)
|
||||
|
||||
### Connection Type
|
||||
- GeoIP2-Connection-Type
|
||||
|
||||
### Anonymous IP
|
||||
- GeoIP2-Anonymous-IP
|
||||
|
||||
### Domain
|
||||
- GeoIP2-Domain
|
||||
|
||||
## License
|
||||
|
||||
[MIT License](LICENSE).
|
||||
49
vendor/github.com/IncSW/geoip2/anonymous_ip.go
generated
vendored
Normal file
49
vendor/github.com/IncSW/geoip2/anonymous_ip.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package geoip2
|
||||
|
||||
import "errors"
|
||||
|
||||
func readAnonymousIPMap(result *AnonymousIP, buffer []byte, mapSize uint, offset uint) (uint, error) {
|
||||
var key []byte
|
||||
var err error
|
||||
for i := uint(0); i < mapSize; i++ {
|
||||
key, offset, err = readMapKey(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch b2s(key) {
|
||||
case "is_anonymous":
|
||||
result.IsAnonymous, offset, err = readBool(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "is_anonymous_vpn":
|
||||
result.IsAnonymousVPN, offset, err = readBool(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "is_hosting_provider":
|
||||
result.IsHostingProvider, offset, err = readBool(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "is_public_proxy":
|
||||
result.IsPublicProxy, offset, err = readBool(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "is_tor_exit_node":
|
||||
result.IsTorExitNode, offset, err = readBool(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "is_residential_proxy":
|
||||
result.IsResidentialProxy, offset, err = readBool(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
return 0, errors.New("unknown anonymous ip key: " + string(key))
|
||||
}
|
||||
}
|
||||
return offset, nil
|
||||
}
|
||||
29
vendor/github.com/IncSW/geoip2/asn.go
generated
vendored
Normal file
29
vendor/github.com/IncSW/geoip2/asn.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package geoip2
|
||||
|
||||
import "errors"
|
||||
|
||||
func readASNMap(result *ASN, buffer []byte, mapSize uint, offset uint) (uint, error) {
|
||||
var key []byte
|
||||
var err error
|
||||
for i := uint(0); i < mapSize; i++ {
|
||||
key, offset, err = readMapKey(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch b2s(key) {
|
||||
case "autonomous_system_number":
|
||||
result.AutonomousSystemNumber, offset, err = readUInt32(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "autonomous_system_organization":
|
||||
result.AutonomousSystemOrganization, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
return 0, errors.New("unknown asn key: " + string(key))
|
||||
}
|
||||
}
|
||||
return offset, nil
|
||||
}
|
||||
67
vendor/github.com/IncSW/geoip2/city.go
generated
vendored
Normal file
67
vendor/github.com/IncSW/geoip2/city.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
package geoip2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func readCity(city *City, buffer []byte, offset uint) (uint, error) {
|
||||
dataType, size, offset, err := readControl(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch dataType {
|
||||
case dataTypeMap:
|
||||
return readCityMap(city, buffer, size, offset)
|
||||
case dataTypePointer:
|
||||
pointer, newOffset, err := readPointer(buffer, size, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(buffer, pointer)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if dataType != dataTypeMap {
|
||||
return 0, errors.New("invalid city pointer type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
_, err = readCityMap(city, buffer, size, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return newOffset, nil
|
||||
default:
|
||||
return 0, errors.New("invalid city type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
}
|
||||
|
||||
func readCityMap(city *City, buffer []byte, mapSize uint, offset uint) (uint, error) {
|
||||
var key []byte
|
||||
var err error
|
||||
for i := uint(0); i < mapSize; i++ {
|
||||
key, offset, err = readMapKey(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch b2s(key) {
|
||||
case "geoname_id":
|
||||
city.GeoNameID, offset, err = readUInt32(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "names":
|
||||
city.Names, offset, err = readStringMap(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "confidence":
|
||||
city.Confidence, offset, err = readUInt16(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
return 0, errors.New("unknown city key: " + string(key))
|
||||
}
|
||||
}
|
||||
return offset, nil
|
||||
}
|
||||
370
vendor/github.com/IncSW/geoip2/common.go
generated
vendored
Normal file
370
vendor/github.com/IncSW/geoip2/common.go
generated
vendored
Normal file
@@ -0,0 +1,370 @@
|
||||
package geoip2
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math"
|
||||
"strconv"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func readControl(buffer []byte, offset uint) (byte, uint, uint, error) {
|
||||
controlByte := buffer[offset]
|
||||
offset++
|
||||
dataType := controlByte >> 5
|
||||
if dataType == dataTypeExtended {
|
||||
dataType = buffer[offset] + 7
|
||||
offset++
|
||||
}
|
||||
size := uint(controlByte & 0x1f)
|
||||
if dataType == dataTypeExtended || size < 29 {
|
||||
return dataType, size, offset, nil
|
||||
}
|
||||
bytesToRead := size - 28
|
||||
newOffset := offset + bytesToRead
|
||||
if newOffset > uint(len(buffer)) {
|
||||
return 0, 0, 0, errors.New("invalid offset")
|
||||
}
|
||||
size = uint(bytesToUInt64(buffer[offset:newOffset]))
|
||||
switch bytesToRead {
|
||||
case 1:
|
||||
size += 29
|
||||
case 2:
|
||||
size += 285
|
||||
default:
|
||||
size += 65821
|
||||
}
|
||||
return dataType, size, newOffset, nil
|
||||
}
|
||||
|
||||
func readPointer(buffer []byte, size uint, offset uint) (uint, uint, error) {
|
||||
pointerSize := ((size >> 3) & 0x3) + 1
|
||||
newOffset := offset + pointerSize
|
||||
if newOffset > uint(len(buffer)) {
|
||||
return 0, 0, errors.New("invalid offset")
|
||||
}
|
||||
prefix := uint64(0)
|
||||
if pointerSize != 4 {
|
||||
prefix = uint64(size) & 0x7
|
||||
}
|
||||
unpacked := uint(bytesToUInt64WithPrefix(prefix, buffer[offset:newOffset]))
|
||||
pointerValueOffset := uint(0)
|
||||
switch pointerSize {
|
||||
case 2:
|
||||
pointerValueOffset = 2048
|
||||
case 3:
|
||||
pointerValueOffset = 526336
|
||||
case 4:
|
||||
pointerValueOffset = 0
|
||||
}
|
||||
return unpacked + pointerValueOffset, newOffset, nil
|
||||
}
|
||||
|
||||
func readFloat64(buffer []byte, offset uint) (float64, uint, error) {
|
||||
dataType, size, offset, err := readControl(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
switch dataType {
|
||||
case dataTypeFloat64:
|
||||
newOffset := offset + size
|
||||
return bytesToFloat64(buffer[offset:newOffset]), newOffset, nil
|
||||
case dataTypePointer:
|
||||
pointer, newOffset, err := readPointer(buffer, size, offset)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(buffer, pointer)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
if dataType != dataTypeFloat64 {
|
||||
return 0, 0, errors.New("invalid float64 pointer type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
return bytesToFloat64(buffer[offset : offset+size]), newOffset, nil
|
||||
default:
|
||||
return 0, 0, errors.New("invalid float64 type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
}
|
||||
|
||||
func readUInt16(buffer []byte, offset uint) (uint16, uint, error) {
|
||||
dataType, size, offset, err := readControl(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
switch dataType {
|
||||
case dataTypeUint16:
|
||||
newOffset := offset + size
|
||||
return uint16(bytesToUInt64(buffer[offset:newOffset])), newOffset, nil
|
||||
case dataTypePointer:
|
||||
pointer, newOffset, err := readPointer(buffer, size, offset)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(buffer, pointer)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
if dataType != dataTypeUint16 {
|
||||
return 0, 0, errors.New("invalid uint16 pointer type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
return uint16(bytesToUInt64(buffer[offset : offset+size])), newOffset, nil
|
||||
default:
|
||||
return 0, 0, errors.New("invalid uint16 type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
}
|
||||
|
||||
func readUInt32(buffer []byte, offset uint) (uint32, uint, error) {
|
||||
dataType, size, offset, err := readControl(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
switch dataType {
|
||||
case dataTypeUint32:
|
||||
newOffset := offset + size
|
||||
return uint32(bytesToUInt64(buffer[offset:newOffset])), newOffset, nil
|
||||
case dataTypePointer:
|
||||
pointer, newOffset, err := readPointer(buffer, size, offset)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(buffer, pointer)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
if dataType != dataTypeUint32 {
|
||||
return 0, 0, errors.New("invalid uint32 pointer type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
return uint32(bytesToUInt64(buffer[offset : offset+size])), newOffset, nil
|
||||
default:
|
||||
return 0, 0, errors.New("invalid uint32 type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
}
|
||||
|
||||
func readBool(buffer []byte, offset uint) (bool, uint, error) {
|
||||
dataType, size, offset, err := readControl(buffer, offset)
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
switch dataType {
|
||||
case dataTypeBool:
|
||||
return size != 0, offset, nil
|
||||
case dataTypePointer:
|
||||
pointer, newOffset, err := readPointer(buffer, size, offset)
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
dataType, size, _, err := readControl(buffer, pointer)
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
if dataType != dataTypeBool {
|
||||
return false, 0, errors.New("invalid bool pointer type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
return size != 0, newOffset, nil
|
||||
default:
|
||||
return false, 0, errors.New("invalid bool type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
}
|
||||
|
||||
func readString(buffer []byte, offset uint) (string, uint, error) {
|
||||
dataType, size, offset, err := readControl(buffer, offset)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
switch dataType {
|
||||
case dataTypeString:
|
||||
newOffset := offset + size
|
||||
return b2s(buffer[offset:newOffset]), newOffset, nil
|
||||
case dataTypePointer:
|
||||
pointer, newOffset, err := readPointer(buffer, size, offset)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(buffer, pointer)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
if dataType != dataTypeString {
|
||||
return "", 0, errors.New("invalid string pointer type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
return b2s(buffer[offset : offset+size]), newOffset, nil
|
||||
default:
|
||||
return "", 0, errors.New("invalid string type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
}
|
||||
|
||||
func readStringMap(buffer []byte, offset uint) (map[string]string, uint, error) {
|
||||
dataType, size, offset, err := readControl(buffer, offset)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
switch dataType {
|
||||
case dataTypeMap:
|
||||
return readStringMapMap(buffer, size, offset)
|
||||
case dataTypePointer:
|
||||
pointer, newOffset, err := readPointer(buffer, size, offset)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(buffer, pointer)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if dataType != dataTypeMap {
|
||||
return nil, 0, errors.New("invalid stringMap pointer type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
value, _, err := readStringMapMap(buffer, size, offset)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return value, newOffset, nil
|
||||
default:
|
||||
return nil, 0, errors.New("invalid stringMap type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
}
|
||||
|
||||
func readStringMapMap(buffer []byte, mapSize uint, offset uint) (map[string]string, uint, error) {
|
||||
var key []byte
|
||||
var err error
|
||||
var dataType byte
|
||||
var size uint
|
||||
result := map[string]string{}
|
||||
for i := uint(0); i < mapSize; i++ {
|
||||
key, offset, err = readMapKey(buffer, offset)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
dataType, size, offset, err = readControl(buffer, offset)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
switch dataType {
|
||||
case dataTypePointer:
|
||||
pointer, newOffset, err := readPointer(buffer, size, offset)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
dataType, size, valueOffset, err := readControl(buffer, pointer)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if dataType != dataTypeString {
|
||||
return nil, 0, errors.New("map key must be a string, got: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
offset = newOffset
|
||||
result[b2s(key)] = b2s(buffer[valueOffset : valueOffset+size])
|
||||
case dataTypeString:
|
||||
newOffset := offset + size
|
||||
value := b2s(buffer[offset:newOffset])
|
||||
offset = newOffset
|
||||
result[b2s(key)] = value
|
||||
default:
|
||||
return nil, 0, errors.New("invalid data type of key " + string(key) + ": " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
}
|
||||
return result, offset, nil
|
||||
}
|
||||
|
||||
func readMapKey(buffer []byte, offset uint) ([]byte, uint, error) {
|
||||
dataType, size, offset, err := readControl(buffer, offset)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if dataType == dataTypePointer {
|
||||
pointer, newOffset, err := readPointer(buffer, size, offset)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(buffer, pointer)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if dataType != dataTypeString {
|
||||
return nil, 0, errors.New("map key must be a string, got: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
return buffer[offset : offset+size], newOffset, nil
|
||||
}
|
||||
if dataType != dataTypeString {
|
||||
return nil, 0, errors.New("map key must be a string, got: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
newOffset := offset + size
|
||||
if newOffset > uint(len(buffer)) {
|
||||
return nil, 0, errors.New("invalid offset")
|
||||
}
|
||||
return buffer[offset:newOffset], newOffset, nil
|
||||
}
|
||||
|
||||
func readStringSlice(buffer []byte, sliceSize uint, offset uint) ([]string, uint, error) {
|
||||
var err error
|
||||
var value string
|
||||
result := make([]string, sliceSize)
|
||||
for i := uint(0); i < sliceSize; i++ {
|
||||
value, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
result[i] = value
|
||||
}
|
||||
return result, offset, nil
|
||||
}
|
||||
|
||||
func bytesToUInt64(buffer []byte) uint64 {
|
||||
switch len(buffer) {
|
||||
case 1:
|
||||
return uint64(buffer[0])
|
||||
case 2:
|
||||
return uint64(buffer[0])<<8 | uint64(buffer[1])
|
||||
case 3:
|
||||
return (uint64(buffer[0])<<8|uint64(buffer[1]))<<8 | uint64(buffer[2])
|
||||
case 4:
|
||||
return ((uint64(buffer[0])<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8 | uint64(buffer[3])
|
||||
case 5:
|
||||
return (((uint64(buffer[0])<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8|uint64(buffer[3]))<<8 | uint64(buffer[4])
|
||||
case 6:
|
||||
return ((((uint64(buffer[0])<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8|uint64(buffer[3]))<<8|uint64(buffer[4]))<<8 | uint64(buffer[5])
|
||||
case 7:
|
||||
return (((((uint64(buffer[0])<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8|uint64(buffer[3]))<<8|uint64(buffer[4]))<<8|uint64(buffer[5]))<<8 | uint64(buffer[6])
|
||||
case 8:
|
||||
return ((((((uint64(buffer[0])<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8|uint64(buffer[3]))<<8|uint64(buffer[4]))<<8|uint64(buffer[5]))<<8|uint64(buffer[6]))<<8 | uint64(buffer[7])
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func bytesToUInt64WithPrefix(prefix uint64, buffer []byte) uint64 {
|
||||
switch len(buffer) {
|
||||
case 0:
|
||||
return prefix
|
||||
case 1:
|
||||
return prefix<<8 | uint64(buffer[0])
|
||||
case 2:
|
||||
return (prefix<<8|uint64(buffer[0]))<<8 | uint64(buffer[1])
|
||||
case 3:
|
||||
return ((prefix<<8|uint64(buffer[0]))<<8|uint64(buffer[1]))<<8 | uint64(buffer[2])
|
||||
case 4:
|
||||
return (((prefix<<8|uint64(buffer[0]))<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8 | uint64(buffer[3])
|
||||
case 5:
|
||||
return ((((prefix<<8|uint64(buffer[0]))<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8|uint64(buffer[3]))<<8 | uint64(buffer[4])
|
||||
case 6:
|
||||
return (((((prefix<<8|uint64(buffer[0]))<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8|uint64(buffer[3]))<<8|uint64(buffer[4]))<<8 | uint64(buffer[5])
|
||||
case 7:
|
||||
return ((((((prefix<<8|uint64(buffer[0]))<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8|uint64(buffer[3]))<<8|uint64(buffer[4]))<<8|uint64(buffer[5]))<<8 | uint64(buffer[6])
|
||||
case 8:
|
||||
return (((((((prefix<<8|uint64(buffer[0]))<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8|uint64(buffer[3]))<<8|uint64(buffer[4]))<<8|uint64(buffer[5]))<<8|uint64(buffer[6]))<<8 | uint64(buffer[7])
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func bytesToFloat32(buffer []byte) float32 {
|
||||
bits := binary.BigEndian.Uint32(buffer)
|
||||
return math.Float32frombits(bits)
|
||||
}
|
||||
|
||||
func bytesToFloat64(buffer []byte) float64 {
|
||||
bits := binary.BigEndian.Uint64(buffer)
|
||||
return math.Float64frombits(bits)
|
||||
}
|
||||
|
||||
func b2s(value []byte) string {
|
||||
return *(*string)(unsafe.Pointer(&value))
|
||||
}
|
||||
24
vendor/github.com/IncSW/geoip2/connection_type.go
generated
vendored
Normal file
24
vendor/github.com/IncSW/geoip2/connection_type.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package geoip2
|
||||
|
||||
import "errors"
|
||||
|
||||
func readConnectionTypeMap(result *ConnectionType, buffer []byte, mapSize uint, offset uint) (uint, error) {
|
||||
var key []byte
|
||||
var err error
|
||||
for i := uint(0); i < mapSize; i++ {
|
||||
key, offset, err = readMapKey(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch b2s(key) {
|
||||
case "connection_type":
|
||||
result.ConnectionType, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
return 0, errors.New("unknown connectionType key: " + string(key))
|
||||
}
|
||||
}
|
||||
return offset, nil
|
||||
}
|
||||
67
vendor/github.com/IncSW/geoip2/continent.go
generated
vendored
Normal file
67
vendor/github.com/IncSW/geoip2/continent.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
package geoip2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func readContinent(continent *Continent, buffer []byte, offset uint) (uint, error) {
|
||||
dataType, size, offset, err := readControl(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch dataType {
|
||||
case dataTypeMap:
|
||||
return readContinentMap(continent, buffer, size, offset)
|
||||
case dataTypePointer:
|
||||
pointer, newOffset, err := readPointer(buffer, size, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(buffer, pointer)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if dataType != dataTypeMap {
|
||||
return 0, errors.New("invalid continent pointer type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
_, err = readContinentMap(continent, buffer, size, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return newOffset, nil
|
||||
default:
|
||||
return 0, errors.New("invalid continent type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
}
|
||||
|
||||
func readContinentMap(continent *Continent, buffer []byte, mapSize uint, offset uint) (uint, error) {
|
||||
var key []byte
|
||||
var err error
|
||||
for i := uint(0); i < mapSize; i++ {
|
||||
key, offset, err = readMapKey(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch b2s(key) {
|
||||
case "geoname_id":
|
||||
continent.GeoNameID, offset, err = readUInt32(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "code":
|
||||
continent.Code, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "names":
|
||||
continent.Names, offset, err = readStringMap(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
return 0, errors.New("unknown continent key: " + string(key))
|
||||
}
|
||||
}
|
||||
return offset, nil
|
||||
}
|
||||
82
vendor/github.com/IncSW/geoip2/country.go
generated
vendored
Normal file
82
vendor/github.com/IncSW/geoip2/country.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
package geoip2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func readCountry(country *Country, buffer []byte, offset uint) (uint, error) {
|
||||
dataType, size, offset, err := readControl(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch dataType {
|
||||
case dataTypeMap:
|
||||
return readCountryMap(country, buffer, size, offset)
|
||||
case dataTypePointer:
|
||||
pointer, newOffset, err := readPointer(buffer, size, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(buffer, pointer)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if dataType != dataTypeMap {
|
||||
return 0, errors.New("invalid country pointer type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
_, err = readCountryMap(country, buffer, size, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return newOffset, nil
|
||||
default:
|
||||
return 0, errors.New("invalid country type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
}
|
||||
|
||||
func readCountryMap(country *Country, buffer []byte, mapSize uint, offset uint) (uint, error) {
|
||||
var key []byte
|
||||
var err error
|
||||
for i := uint(0); i < mapSize; i++ {
|
||||
key, offset, err = readMapKey(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch b2s(key) {
|
||||
case "geoname_id":
|
||||
country.GeoNameID, offset, err = readUInt32(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "iso_code":
|
||||
country.ISOCode, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "names":
|
||||
country.Names, offset, err = readStringMap(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "is_in_european_union":
|
||||
country.IsInEuropeanUnion, offset, err = readBool(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "type":
|
||||
country.Type, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "confidence":
|
||||
country.Confidence, offset, err = readUInt16(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
return 0, errors.New("unknown country key: " + string(key))
|
||||
}
|
||||
}
|
||||
return offset, nil
|
||||
}
|
||||
24
vendor/github.com/IncSW/geoip2/domain.go
generated
vendored
Normal file
24
vendor/github.com/IncSW/geoip2/domain.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package geoip2
|
||||
|
||||
import "errors"
|
||||
|
||||
func readDomainMap(result *Domain, buffer []byte, mapSize uint, offset uint) (uint, error) {
|
||||
var key []byte
|
||||
var err error
|
||||
for i := uint(0); i < mapSize; i++ {
|
||||
key, offset, err = readMapKey(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch b2s(key) {
|
||||
case "domain":
|
||||
result.Domain, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
return 0, errors.New("unknown domain key: " + string(key))
|
||||
}
|
||||
}
|
||||
return offset, nil
|
||||
}
|
||||
49
vendor/github.com/IncSW/geoip2/isp.go
generated
vendored
Normal file
49
vendor/github.com/IncSW/geoip2/isp.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package geoip2
|
||||
|
||||
import "errors"
|
||||
|
||||
func readISPMap(result *ISP, buffer []byte, mapSize uint, offset uint) (uint, error) {
|
||||
var key []byte
|
||||
var err error
|
||||
for i := uint(0); i < mapSize; i++ {
|
||||
key, offset, err = readMapKey(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch b2s(key) {
|
||||
case "autonomous_system_number":
|
||||
result.AutonomousSystemNumber, offset, err = readUInt32(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "autonomous_system_organization":
|
||||
result.AutonomousSystemOrganization, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "isp":
|
||||
result.ISP, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "organization":
|
||||
result.Organization, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "mobile_country_code":
|
||||
result.MobileCountryCode, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "mobile_network_code":
|
||||
result.MobileNetworkCode, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
return 0, errors.New("unknown isp key: " + string(key))
|
||||
}
|
||||
}
|
||||
return offset, nil
|
||||
}
|
||||
77
vendor/github.com/IncSW/geoip2/location.go
generated
vendored
Normal file
77
vendor/github.com/IncSW/geoip2/location.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
package geoip2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func readLocation(location *Location, buffer []byte, offset uint) (uint, error) {
|
||||
dataType, size, offset, err := readControl(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch dataType {
|
||||
case dataTypeMap:
|
||||
return readLocationMap(location, buffer, size, offset)
|
||||
case dataTypePointer:
|
||||
pointer, newOffset, err := readPointer(buffer, size, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(buffer, pointer)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if dataType != dataTypeMap {
|
||||
return 0, errors.New("invalid location pointer type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
_, err = readLocationMap(location, buffer, size, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return newOffset, nil
|
||||
default:
|
||||
return 0, errors.New("invalid location type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
}
|
||||
|
||||
func readLocationMap(location *Location, buffer []byte, mapSize uint, offset uint) (uint, error) {
|
||||
var key []byte
|
||||
var err error
|
||||
for i := uint(0); i < mapSize; i++ {
|
||||
key, offset, err = readMapKey(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch b2s(key) {
|
||||
case "latitude":
|
||||
location.Latitude, offset, err = readFloat64(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "longitude":
|
||||
location.Longitude, offset, err = readFloat64(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "accuracy_radius":
|
||||
location.AccuracyRadius, offset, err = readUInt16(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "time_zone":
|
||||
location.TimeZone, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "metro_code":
|
||||
location.MetroCode, offset, err = readUInt16(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
return 0, errors.New("unknown location key: " + string(key))
|
||||
}
|
||||
}
|
||||
return offset, nil
|
||||
}
|
||||
108
vendor/github.com/IncSW/geoip2/metadata.go
generated
vendored
Normal file
108
vendor/github.com/IncSW/geoip2/metadata.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
package geoip2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Metadata struct {
|
||||
NodeCount uint32 // node_count This is an unsigned 32-bit integer indicating the number of nodes in the search tree.
|
||||
RecordSize uint16 // record_size This is an unsigned 16-bit integer. It indicates the number of bits in a record in the search tree. Note that each node consists of two records.
|
||||
IPVersion uint16 // ip_version This is an unsigned 16-bit integer which is always 4 or 6. It indicates whether the database contains IPv4 or IPv6 address data.
|
||||
DatabaseType string // database_type This is a string that indicates the structure of each data record associated with an IP address. The actual definition of these structures is left up to the database creator. Names starting with “GeoIP” are reserved for use by MaxMind (and “GeoIP” is a trademark anyway).
|
||||
Languages []string // languages An array of strings, each of which is a locale code. A given record may contain data items that have been localized to some or all of these locales. Records should not contain localized data for locales not included in this array. This is an optional key, as this may not be relevant for all types of data.
|
||||
BinaryFormatMajorVersion uint16 // binary_format_major_version This is an unsigned 16-bit integer indicating the major version number for the database’s binary format.
|
||||
BinaryFormatMinorVersion uint16 // binary_format_minor_version This is an unsigned 16-bit integer indicating the minor version number for the database’s binary format.
|
||||
BuildEpoch uint64 // build_epoch This is an unsigned 64-bit integer that contains the database build timestamp as a Unix epoch value.
|
||||
Description map[string]string // description This key will always point to a map. The keys of that map will be language codes, and the values will be a description in that language as a UTF-8 string. The codes may include additional information such as script or country identifiers, like “zh-TW” or “mn-Cyrl-MN”. The additional identifiers will be separated by a dash character (“-“).
|
||||
}
|
||||
|
||||
var metadataStartMarker = []byte("\xAB\xCD\xEFMaxMind.com")
|
||||
|
||||
func readMetadata(buffer []byte) (*Metadata, error) {
|
||||
dataType, metadataSize, offset, err := readControl(buffer, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dataType != dataTypeMap {
|
||||
return nil, errors.New("invalid metadata type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
var key []byte
|
||||
metadata := &Metadata{}
|
||||
for i := uint(0); i < metadataSize; i++ {
|
||||
key, offset, err = readMapKey(buffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
size := uint(0)
|
||||
dataType, size, offset, err = readControl(buffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newOffset := uint(0)
|
||||
switch b2s(key) {
|
||||
case "binary_format_major_version":
|
||||
if dataType != dataTypeUint16 {
|
||||
return nil, errors.New("invalid binary_format_major_version type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
newOffset = offset + size
|
||||
metadata.BinaryFormatMajorVersion = uint16(bytesToUInt64(buffer[offset:newOffset]))
|
||||
case "binary_format_minor_version":
|
||||
if dataType != dataTypeUint16 {
|
||||
return nil, errors.New("invalid binary_format_minor_version type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
newOffset = offset + size
|
||||
metadata.BinaryFormatMinorVersion = uint16(bytesToUInt64(buffer[offset:newOffset]))
|
||||
case "build_epoch":
|
||||
if dataType != dataTypeUint64 {
|
||||
return nil, errors.New("invalid build_epoch type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
newOffset = offset + size
|
||||
metadata.BuildEpoch = bytesToUInt64(buffer[offset:newOffset])
|
||||
case "database_type":
|
||||
if dataType != dataTypeString {
|
||||
return nil, errors.New("invalid database_type type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
newOffset = offset + size
|
||||
metadata.DatabaseType = b2s(buffer[offset:newOffset])
|
||||
case "description":
|
||||
if dataType != dataTypeMap {
|
||||
return nil, errors.New("invalid description type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
metadata.Description, newOffset, err = readStringMapMap(buffer, size, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "ip_version":
|
||||
if dataType != dataTypeUint16 {
|
||||
return nil, errors.New("invalid ip_version type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
newOffset = offset + size
|
||||
metadata.IPVersion = uint16(bytesToUInt64(buffer[offset:newOffset]))
|
||||
case "languages":
|
||||
if dataType != dataTypeSlice {
|
||||
return nil, errors.New("invalid languages type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
metadata.Languages, newOffset, err = readStringSlice(buffer, size, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "node_count":
|
||||
if dataType != dataTypeUint32 {
|
||||
return nil, errors.New("invalid node_count type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
newOffset = offset + size
|
||||
metadata.NodeCount = uint32(bytesToUInt64(buffer[offset:newOffset]))
|
||||
case "record_size":
|
||||
if dataType != dataTypeUint16 {
|
||||
return nil, errors.New("invalid record_size type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
newOffset = offset + size
|
||||
metadata.RecordSize = uint16(bytesToUInt64(buffer[offset:newOffset]))
|
||||
default:
|
||||
return nil, errors.New("unknown key: " + string(key) + ", type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
offset = newOffset
|
||||
}
|
||||
return metadata, nil
|
||||
}
|
||||
62
vendor/github.com/IncSW/geoip2/postal.go
generated
vendored
Normal file
62
vendor/github.com/IncSW/geoip2/postal.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
package geoip2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func readPostal(postal *Postal, buffer []byte, offset uint) (uint, error) {
|
||||
dataType, size, offset, err := readControl(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch dataType {
|
||||
case dataTypeMap:
|
||||
return readPostalMap(postal, buffer, size, offset)
|
||||
case dataTypePointer:
|
||||
pointer, newOffset, err := readPointer(buffer, size, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(buffer, pointer)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if dataType != dataTypeMap {
|
||||
return 0, errors.New("invalid postal pointer type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
_, err = readPostalMap(postal, buffer, size, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return newOffset, nil
|
||||
default:
|
||||
return 0, errors.New("invalid postal type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
}
|
||||
|
||||
func readPostalMap(postal *Postal, buffer []byte, mapSize uint, offset uint) (uint, error) {
|
||||
var key []byte
|
||||
var err error
|
||||
for i := uint(0); i < mapSize; i++ {
|
||||
key, offset, err = readMapKey(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch b2s(key) {
|
||||
case "code":
|
||||
postal.Code, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "confidence":
|
||||
postal.Confidence, offset, err = readUInt16(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
return 0, errors.New("unknown postal key: " + string(key))
|
||||
}
|
||||
}
|
||||
return offset, nil
|
||||
}
|
||||
124
vendor/github.com/IncSW/geoip2/reader.go
generated
vendored
Normal file
124
vendor/github.com/IncSW/geoip2/reader.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
package geoip2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var ErrNotFound = errors.New("not found")
|
||||
|
||||
type reader struct {
|
||||
metadata *Metadata
|
||||
buffer []byte
|
||||
decoderBuffer []byte
|
||||
nodeBuffer []byte
|
||||
ipV4Start uint
|
||||
ipV4StartBitDepth uint
|
||||
nodeOffsetMult uint
|
||||
}
|
||||
|
||||
func (r *reader) getOffset(ip net.IP) (uint, error) {
|
||||
pointer, err := r.lookupPointer(ip)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
offset := pointer - uint(r.metadata.NodeCount) - uint(dataSectionSeparatorSize)
|
||||
if offset >= uint(len(r.buffer)) {
|
||||
return 0, errors.New("the MaxMind DB search tree is corrupt: " + strconv.Itoa(int(pointer)))
|
||||
}
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
func (r *reader) lookupPointer(ip net.IP) (uint, error) {
|
||||
if ip == nil {
|
||||
return 0, errors.New("IP cannot be nil")
|
||||
}
|
||||
ipV4 := ip.To4()
|
||||
if ipV4 != nil {
|
||||
ip = ipV4
|
||||
}
|
||||
if len(ip) == 16 && r.metadata.IPVersion == 4 {
|
||||
return 0, errors.New("cannot look up an IPv6 address in an IPv4-only database")
|
||||
}
|
||||
bitCount := uint(len(ip)) * 8
|
||||
node := uint(0)
|
||||
if bitCount == 32 {
|
||||
node = r.ipV4Start
|
||||
}
|
||||
nodeCount := uint(r.metadata.NodeCount)
|
||||
i := uint(0)
|
||||
for ; i < bitCount && node < nodeCount; i++ {
|
||||
bit := 1 & (ip[i>>3] >> (7 - (i % 8)))
|
||||
offset := node * r.nodeOffsetMult
|
||||
if bit == 0 {
|
||||
node = r.readLeft(offset)
|
||||
} else {
|
||||
node = r.readRight(offset)
|
||||
}
|
||||
}
|
||||
if node == nodeCount {
|
||||
return 0, ErrNotFound
|
||||
} else if node > nodeCount {
|
||||
return node, nil
|
||||
}
|
||||
return 0, errors.New("invalid node in search tree")
|
||||
}
|
||||
|
||||
func (r *reader) readLeft(nodeNumber uint) uint {
|
||||
switch r.metadata.RecordSize {
|
||||
case 28:
|
||||
return ((uint(r.nodeBuffer[nodeNumber+3]) & 0xF0) << 20) | (uint(r.nodeBuffer[nodeNumber]) << 16) | (uint(r.nodeBuffer[nodeNumber+1]) << 8) | uint(r.nodeBuffer[nodeNumber+2])
|
||||
case 24:
|
||||
return (uint(r.nodeBuffer[nodeNumber]) << 16) | (uint(r.nodeBuffer[nodeNumber+1]) << 8) | uint(r.nodeBuffer[nodeNumber+2])
|
||||
default: // case 32:
|
||||
return (uint(r.nodeBuffer[nodeNumber]) << 24) | (uint(r.nodeBuffer[nodeNumber+1]) << 16) | (uint(r.nodeBuffer[nodeNumber+2]) << 8) | uint(r.nodeBuffer[nodeNumber+3])
|
||||
}
|
||||
}
|
||||
|
||||
func (r *reader) readRight(nodeNumber uint) uint {
|
||||
switch r.metadata.RecordSize {
|
||||
case 28:
|
||||
return ((uint(r.nodeBuffer[nodeNumber+3]) & 0x0F) << 24) | (uint(r.nodeBuffer[nodeNumber+4]) << 16) | (uint(r.nodeBuffer[nodeNumber+5]) << 8) | uint(r.nodeBuffer[nodeNumber+6])
|
||||
case 24:
|
||||
return (uint(r.nodeBuffer[nodeNumber+3]) << 16) | (uint(r.nodeBuffer[nodeNumber+4]) << 8) | uint(r.nodeBuffer[nodeNumber+5])
|
||||
default: // case 32:
|
||||
return (uint(r.nodeBuffer[nodeNumber+4]) << 24) | (uint(r.nodeBuffer[nodeNumber+5]) << 16) | (uint(r.nodeBuffer[nodeNumber+6]) << 8) | uint(r.nodeBuffer[nodeNumber+7])
|
||||
}
|
||||
}
|
||||
|
||||
func newReader(buffer []byte) (*reader, error) {
|
||||
if len(buffer) == 0 {
|
||||
return nil, errors.New("buffer is empty")
|
||||
}
|
||||
|
||||
metadataStart := bytes.LastIndex(buffer, metadataStartMarker)
|
||||
metadata, err := readMetadata(buffer[metadataStart+len(metadataStartMarker):])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodeOffsetMult := uint(metadata.RecordSize) / 4
|
||||
searchTreeSize := uint(metadata.NodeCount) * nodeOffsetMult
|
||||
dataSectionStart := searchTreeSize + dataSectionSeparatorSize
|
||||
if dataSectionStart > uint(metadataStart) {
|
||||
return nil, errors.New("the MaxMind DB contains invalid metadata")
|
||||
}
|
||||
reader := &reader{
|
||||
metadata: metadata,
|
||||
buffer: buffer,
|
||||
decoderBuffer: buffer[searchTreeSize+dataSectionSeparatorSize : metadataStart],
|
||||
nodeBuffer: buffer[:searchTreeSize],
|
||||
nodeOffsetMult: nodeOffsetMult,
|
||||
}
|
||||
if metadata.IPVersion == 6 {
|
||||
node := uint(0)
|
||||
i := uint(0)
|
||||
for ; i < 96 && node < uint(metadata.NodeCount); i++ {
|
||||
node = reader.readLeft(node * nodeOffsetMult)
|
||||
}
|
||||
reader.ipV4Start = node
|
||||
reader.ipV4StartBitDepth = i
|
||||
}
|
||||
return reader, nil
|
||||
}
|
||||
71
vendor/github.com/IncSW/geoip2/reader_anonymous_ip.go
generated
vendored
Normal file
71
vendor/github.com/IncSW/geoip2/reader_anonymous_ip.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
package geoip2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type AnonymousIPReader struct {
|
||||
*reader
|
||||
}
|
||||
|
||||
func (r *AnonymousIPReader) Lookup(ip net.IP) (*AnonymousIP, error) {
|
||||
offset, err := r.getOffset(ip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &AnonymousIP{}
|
||||
switch dataType {
|
||||
case dataTypeMap:
|
||||
_, err = readAnonymousIPMap(result, r.decoderBuffer, size, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case dataTypePointer:
|
||||
pointer, _, err := readPointer(r.decoderBuffer, size, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(r.decoderBuffer, pointer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dataType != dataTypeMap {
|
||||
return nil, errors.New("invalid Anonymous-IP pointer type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
_, err = readAnonymousIPMap(result, r.decoderBuffer, size, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("invalid Anonymous-IP type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func NewAnonymousIPReader(buffer []byte) (*AnonymousIPReader, error) {
|
||||
reader, err := newReader(buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if reader.metadata.DatabaseType != "GeoIP2-Anonymous-IP" {
|
||||
return nil, errors.New("wrong MaxMind DB Anonymous-IP type: " + reader.metadata.DatabaseType)
|
||||
}
|
||||
return &AnonymousIPReader{
|
||||
reader: reader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewAnonymousIPReaderFromFile(filename string) (*AnonymousIPReader, error) {
|
||||
buffer, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewAnonymousIPReader(buffer)
|
||||
}
|
||||
73
vendor/github.com/IncSW/geoip2/reader_asn.go
generated
vendored
Normal file
73
vendor/github.com/IncSW/geoip2/reader_asn.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
package geoip2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ASNReader struct {
|
||||
*reader
|
||||
}
|
||||
|
||||
func (r *ASNReader) Lookup(ip net.IP) (*ASN, error) {
|
||||
offset, err := r.getOffset(ip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &ASN{}
|
||||
switch dataType {
|
||||
case dataTypeMap:
|
||||
_, err = readASNMap(result, r.decoderBuffer, size, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case dataTypePointer:
|
||||
pointer, _, err := readPointer(r.decoderBuffer, size, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(r.decoderBuffer, pointer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dataType != dataTypeMap {
|
||||
return nil, errors.New("invalid ASN pointer type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
_, err = readASNMap(result, r.decoderBuffer, size, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("invalid ASN type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func NewASNReader(buffer []byte) (*ASNReader, error) {
|
||||
reader, err := newReader(buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if reader.metadata.DatabaseType != "GeoLite2-ASN" &&
|
||||
reader.metadata.DatabaseType != "DBIP-ASN-Lite" &&
|
||||
reader.metadata.DatabaseType != "DBIP-ASN-Lite (compat=GeoLite2-ASN)" {
|
||||
return nil, errors.New("wrong MaxMind DB ASN type: " + reader.metadata.DatabaseType)
|
||||
}
|
||||
return &ASNReader{
|
||||
reader: reader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewASNReaderFromFile(filename string) (*ASNReader, error) {
|
||||
buffer, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewASNReader(buffer)
|
||||
}
|
||||
116
vendor/github.com/IncSW/geoip2/reader_city.go
generated
vendored
Normal file
116
vendor/github.com/IncSW/geoip2/reader_city.go
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
package geoip2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type CityReader struct {
|
||||
*reader
|
||||
}
|
||||
|
||||
func (r *CityReader) Lookup(ip net.IP) (*CityResult, error) {
|
||||
offset, err := r.getOffset(ip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dataType != dataTypeMap {
|
||||
return nil, errors.New("invalid City type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
var key []byte
|
||||
result := &CityResult{}
|
||||
for i := uint(0); i < size; i++ {
|
||||
key, offset, err = readMapKey(r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch b2s(key) {
|
||||
case "city":
|
||||
offset, err = readCity(&result.City, r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "continent":
|
||||
offset, err = readContinent(&result.Continent, r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "country":
|
||||
offset, err = readCountry(&result.Country, r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "location":
|
||||
offset, err = readLocation(&result.Location, r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "postal":
|
||||
offset, err = readPostal(&result.Postal, r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "registered_country":
|
||||
offset, err = readCountry(&result.RegisteredCountry, r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "represented_country":
|
||||
offset, err = readCountry(&result.RepresentedCountry, r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "subdivisions":
|
||||
result.Subdivisions, offset, err = readSubdivisions(r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "traits":
|
||||
offset, err = readTraits(&result.Traits, r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("unknown City response key: " + string(key) + ", type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func NewCityReader(buffer []byte) (*CityReader, error) {
|
||||
reader, err := newReader(buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if reader.metadata.DatabaseType != "GeoIP2-City" &&
|
||||
reader.metadata.DatabaseType != "GeoLite2-City" &&
|
||||
reader.metadata.DatabaseType != "GeoIP2-Enterprise" &&
|
||||
reader.metadata.DatabaseType != "DBIP-City-Lite" {
|
||||
return nil, errors.New("wrong MaxMind DB City type: " + reader.metadata.DatabaseType)
|
||||
}
|
||||
return &CityReader{
|
||||
reader: reader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewCityReaderFromFile(filename string) (*CityReader, error) {
|
||||
buffer, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewCityReader(buffer)
|
||||
}
|
||||
|
||||
func NewEnterpriseReader(buffer []byte) (*CityReader, error) {
|
||||
return NewCityReader(buffer)
|
||||
}
|
||||
|
||||
func NewEnterpriseReaderFromFile(filename string) (*CityReader, error) {
|
||||
return NewCityReaderFromFile(filename)
|
||||
}
|
||||
71
vendor/github.com/IncSW/geoip2/reader_connection_type.go
generated
vendored
Normal file
71
vendor/github.com/IncSW/geoip2/reader_connection_type.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
package geoip2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ConnectionTypeReader struct {
|
||||
*reader
|
||||
}
|
||||
|
||||
func (r *ConnectionTypeReader) Lookup(ip net.IP) (string, error) {
|
||||
offset, err := r.getOffset(ip)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dataType, size, offset, err := readControl(r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result := &ConnectionType{}
|
||||
switch dataType {
|
||||
case dataTypeMap:
|
||||
_, err = readConnectionTypeMap(result, r.decoderBuffer, size, offset)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
case dataTypePointer:
|
||||
pointer, _, err := readPointer(r.decoderBuffer, size, offset)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dataType, size, offset, err := readControl(r.decoderBuffer, pointer)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if dataType != dataTypeMap {
|
||||
return "", errors.New("invalid Connection-Type pointer type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
_, err = readConnectionTypeMap(result, r.decoderBuffer, size, offset)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
default:
|
||||
return "", errors.New("invalid Connection-Type type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
return result.ConnectionType, nil
|
||||
}
|
||||
|
||||
func NewConnectionTypeReader(buffer []byte) (*ConnectionTypeReader, error) {
|
||||
reader, err := newReader(buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if reader.metadata.DatabaseType != "GeoIP2-Connection-Type" {
|
||||
return nil, errors.New("wrong MaxMind DB Connection-Type type: " + reader.metadata.DatabaseType)
|
||||
}
|
||||
return &ConnectionTypeReader{
|
||||
reader: reader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewConnectionTypeReaderFromFile(filename string) (*ConnectionTypeReader, error) {
|
||||
buffer, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewConnectionTypeReader(buffer)
|
||||
}
|
||||
88
vendor/github.com/IncSW/geoip2/reader_country.go
generated
vendored
Normal file
88
vendor/github.com/IncSW/geoip2/reader_country.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
package geoip2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type CountryReader struct {
|
||||
*reader
|
||||
}
|
||||
|
||||
func (r *CountryReader) Lookup(ip net.IP) (*CountryResult, error) {
|
||||
offset, err := r.getOffset(ip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dataType != dataTypeMap {
|
||||
return nil, errors.New("invalid Country type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
var key []byte
|
||||
result := &CountryResult{}
|
||||
for i := uint(0); i < size; i++ {
|
||||
key, offset, err = readMapKey(r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch b2s(key) {
|
||||
case "continent":
|
||||
offset, err = readContinent(&result.Continent, r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "country":
|
||||
offset, err = readCountry(&result.Country, r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "registered_country":
|
||||
offset, err = readCountry(&result.RegisteredCountry, r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "represented_country":
|
||||
offset, err = readCountry(&result.RepresentedCountry, r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "traits":
|
||||
offset, err = readTraits(&result.Traits, r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("unknown Country response key: " + string(key) + ", type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func NewCountryReader(buffer []byte) (*CountryReader, error) {
|
||||
reader, err := newReader(buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if reader.metadata.DatabaseType != "GeoIP2-Country" &&
|
||||
reader.metadata.DatabaseType != "GeoLite2-Country" &&
|
||||
reader.metadata.DatabaseType != "DBIP-Country" &&
|
||||
reader.metadata.DatabaseType != "DBIP-Country-Lite" {
|
||||
return nil, errors.New("wrong MaxMind DB Country type: " + reader.metadata.DatabaseType)
|
||||
}
|
||||
return &CountryReader{
|
||||
reader: reader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewCountryReaderFromFile(filename string) (*CountryReader, error) {
|
||||
buffer, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewCountryReader(buffer)
|
||||
}
|
||||
71
vendor/github.com/IncSW/geoip2/reader_domain.go
generated
vendored
Normal file
71
vendor/github.com/IncSW/geoip2/reader_domain.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
package geoip2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type DomainReader struct {
|
||||
*reader
|
||||
}
|
||||
|
||||
func (r *DomainReader) Lookup(ip net.IP) (string, error) {
|
||||
offset, err := r.getOffset(ip)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dataType, size, offset, err := readControl(r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result := &Domain{}
|
||||
switch dataType {
|
||||
case dataTypeMap:
|
||||
_, err = readDomainMap(result, r.decoderBuffer, size, offset)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
case dataTypePointer:
|
||||
pointer, _, err := readPointer(r.decoderBuffer, size, offset)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dataType, size, offset, err := readControl(r.decoderBuffer, pointer)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if dataType != dataTypeMap {
|
||||
return "", errors.New("invalid Domain pointer type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
_, err = readDomainMap(result, r.decoderBuffer, size, offset)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
default:
|
||||
return "", errors.New("invalid Domain type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
return result.Domain, nil
|
||||
}
|
||||
|
||||
func NewDomainReader(buffer []byte) (*DomainReader, error) {
|
||||
reader, err := newReader(buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if reader.metadata.DatabaseType != "GeoIP2-Domain" {
|
||||
return nil, errors.New("wrong MaxMind DB Domain type: " + reader.metadata.DatabaseType)
|
||||
}
|
||||
return &DomainReader{
|
||||
reader: reader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewDomainReaderFromFile(filename string) (*DomainReader, error) {
|
||||
buffer, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewDomainReader(buffer)
|
||||
}
|
||||
71
vendor/github.com/IncSW/geoip2/reader_isp.go
generated
vendored
Normal file
71
vendor/github.com/IncSW/geoip2/reader_isp.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
package geoip2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ISPReader struct {
|
||||
*reader
|
||||
}
|
||||
|
||||
func (r *ISPReader) Lookup(ip net.IP) (*ISP, error) {
|
||||
offset, err := r.getOffset(ip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(r.decoderBuffer, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &ISP{}
|
||||
switch dataType {
|
||||
case dataTypeMap:
|
||||
_, err = readISPMap(result, r.decoderBuffer, size, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case dataTypePointer:
|
||||
pointer, _, err := readPointer(r.decoderBuffer, size, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(r.decoderBuffer, pointer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dataType != dataTypeMap {
|
||||
return nil, errors.New("invalid ISP pointer type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
_, err = readISPMap(result, r.decoderBuffer, size, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("invalid ISP type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func NewISPReader(buffer []byte) (*ISPReader, error) {
|
||||
reader, err := newReader(buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if reader.metadata.DatabaseType != "GeoIP2-ISP" {
|
||||
return nil, errors.New("wrong MaxMind DB ISP type: " + reader.metadata.DatabaseType)
|
||||
}
|
||||
return &ISPReader{
|
||||
reader: reader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewISPReaderFromFile(filename string) (*ISPReader, error) {
|
||||
buffer, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewISPReader(buffer)
|
||||
}
|
||||
114
vendor/github.com/IncSW/geoip2/subdivision.go
generated
vendored
Normal file
114
vendor/github.com/IncSW/geoip2/subdivision.go
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
package geoip2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func readSubdivisions(buffer []byte, offset uint) ([]Subdivision, uint, error) {
|
||||
dataType, size, offset, err := readControl(buffer, offset)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
switch dataType {
|
||||
case dataTypeSlice:
|
||||
return readSubdivisionsSlice(buffer, size, offset)
|
||||
case dataTypePointer:
|
||||
pointer, newOffset, err := readPointer(buffer, size, offset)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(buffer, pointer)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if dataType != dataTypeSlice {
|
||||
return nil, 0, errors.New("invalid subdivisions pointer type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
subdivisions, _, err := readSubdivisionsSlice(buffer, size, offset)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return subdivisions, newOffset, nil
|
||||
default:
|
||||
return nil, 0, errors.New("invalid subdivisions type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
}
|
||||
|
||||
func readSubdivisionsSlice(buffer []byte, subdivisionsSize uint, offset uint) ([]Subdivision, uint, error) {
|
||||
var err error
|
||||
subdivisions := make([]Subdivision, subdivisionsSize)
|
||||
for i := uint(0); i < subdivisionsSize; i++ {
|
||||
offset, err = readSubdivision(&subdivisions[i], buffer, offset)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
return subdivisions, offset, nil
|
||||
}
|
||||
|
||||
func readSubdivision(subdivision *Subdivision, buffer []byte, offset uint) (uint, error) {
|
||||
dataType, size, offset, err := readControl(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch dataType {
|
||||
case dataTypeMap:
|
||||
return readSubdivisionMap(subdivision, buffer, size, offset)
|
||||
case dataTypePointer:
|
||||
pointer, newOffset, err := readPointer(buffer, size, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(buffer, pointer)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if dataType != dataTypeMap {
|
||||
return 0, errors.New("invalid subdivision pointer type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
_, err = readSubdivisionMap(subdivision, buffer, size, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return newOffset, nil
|
||||
default:
|
||||
return 0, errors.New("invalid subdivision type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
}
|
||||
|
||||
func readSubdivisionMap(subdivision *Subdivision, buffer []byte, mapSize uint, offset uint) (uint, error) {
|
||||
var key []byte
|
||||
var err error
|
||||
for i := uint(0); i < mapSize; i++ {
|
||||
key, offset, err = readMapKey(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch b2s(key) {
|
||||
case "geoname_id":
|
||||
subdivision.GeoNameID, offset, err = readUInt32(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "iso_code":
|
||||
subdivision.ISOCode, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "names":
|
||||
subdivision.Names, offset, err = readStringMap(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "confidence":
|
||||
subdivision.Confidence, offset, err = readUInt16(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
return 0, errors.New("unknown subdivision key: " + string(key))
|
||||
}
|
||||
}
|
||||
return offset, nil
|
||||
}
|
||||
117
vendor/github.com/IncSW/geoip2/traits.go
generated
vendored
Normal file
117
vendor/github.com/IncSW/geoip2/traits.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
package geoip2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func readTraits(traits *Traits, buffer []byte, offset uint) (uint, error) {
|
||||
dataType, size, offset, err := readControl(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch dataType {
|
||||
case dataTypeMap:
|
||||
return readTraitsMap(traits, buffer, size, offset)
|
||||
case dataTypePointer:
|
||||
pointer, newOffset, err := readPointer(buffer, size, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dataType, size, offset, err := readControl(buffer, pointer)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if dataType != dataTypeMap {
|
||||
return 0, errors.New("invalid traits pointer type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
_, err = readTraitsMap(traits, buffer, size, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return newOffset, nil
|
||||
default:
|
||||
return 0, errors.New("invalid traits type: " + strconv.Itoa(int(dataType)))
|
||||
}
|
||||
}
|
||||
|
||||
func readTraitsMap(traits *Traits, buffer []byte, mapSize uint, offset uint) (uint, error) {
|
||||
var key []byte
|
||||
var err error
|
||||
for i := uint(0); i < mapSize; i++ {
|
||||
key, offset, err = readMapKey(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch b2s(key) {
|
||||
case "is_anonymous_proxy":
|
||||
traits.IsAnonymousProxy, offset, err = readBool(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "is_satellite_provider":
|
||||
traits.IsSatelliteProvider, offset, err = readBool(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "is_legitimate_proxy":
|
||||
traits.IsLegitimateProxy, offset, err = readBool(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "static_ip_score":
|
||||
traits.StaticIPScore, offset, err = readFloat64(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "autonomous_system_number":
|
||||
traits.AutonomousSystemNumber, offset, err = readUInt32(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "autonomous_system_organization":
|
||||
traits.AutonomousSystemOrganization, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "isp":
|
||||
traits.ISP, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "organization":
|
||||
traits.Organization, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "connection_type":
|
||||
traits.ConnectionType, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "domain":
|
||||
traits.Domain, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "user_type":
|
||||
traits.UserType, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "mobile_country_code":
|
||||
traits.MobileCountryCode, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case "mobile_network_code":
|
||||
traits.MobileNetworkCode, offset, err = readString(buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
return 0, errors.New("unknown traits key: " + string(key))
|
||||
}
|
||||
}
|
||||
return offset, nil
|
||||
}
|
||||
130
vendor/github.com/IncSW/geoip2/types.go
generated
vendored
Normal file
130
vendor/github.com/IncSW/geoip2/types.go
generated
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
package geoip2
|
||||
|
||||
const (
|
||||
dataTypeExtended = 0
|
||||
dataTypePointer = 1
|
||||
dataTypeString = 2
|
||||
dataTypeFloat64 = 3
|
||||
dataTypeBytes = 4
|
||||
dataTypeUint16 = 5
|
||||
dataTypeUint32 = 6
|
||||
dataTypeMap = 7
|
||||
dataTypeInt32 = 8
|
||||
dataTypeUint64 = 9
|
||||
dataTypeUint128 = 10
|
||||
dataTypeSlice = 11
|
||||
dataTypeDataCacheContainer = 12
|
||||
dataTypeEndMarker = 13
|
||||
dataTypeBool = 14
|
||||
dataTypeFloat32 = 15
|
||||
|
||||
dataSectionSeparatorSize = 16
|
||||
)
|
||||
|
||||
type Continent struct {
|
||||
GeoNameID uint32
|
||||
Code string
|
||||
Names map[string]string
|
||||
}
|
||||
|
||||
type Country struct {
|
||||
ISOCode string
|
||||
Names map[string]string
|
||||
Type string // [RepresentedCountry]
|
||||
GeoNameID uint32
|
||||
Confidence uint16 // Enterprise [Country, RegisteredCountry]
|
||||
IsInEuropeanUnion bool
|
||||
}
|
||||
|
||||
type Subdivision struct {
|
||||
ISOCode string
|
||||
Names map[string]string
|
||||
GeoNameID uint32
|
||||
Confidence uint16 // Enterprise
|
||||
}
|
||||
|
||||
type City struct {
|
||||
Names map[string]string
|
||||
GeoNameID uint32
|
||||
Confidence uint16 // Enterprise
|
||||
}
|
||||
|
||||
type Location struct {
|
||||
Latitude float64
|
||||
Longitude float64
|
||||
TimeZone string
|
||||
AccuracyRadius uint16
|
||||
MetroCode uint16
|
||||
}
|
||||
|
||||
type Postal struct {
|
||||
Code string
|
||||
Confidence uint16 // Enterprise
|
||||
}
|
||||
|
||||
type Traits struct {
|
||||
StaticIPScore float64 // Enterprise
|
||||
ISP string // Enterprise
|
||||
Organization string // Enterprise
|
||||
ConnectionType string // Enterprise
|
||||
Domain string // Enterprise
|
||||
UserType string // Enterprise
|
||||
AutonomousSystemOrganization string // Enterprise
|
||||
AutonomousSystemNumber uint32 // Enterprise
|
||||
IsLegitimateProxy bool // Enterprise
|
||||
MobileCountryCode string // Enterprise
|
||||
MobileNetworkCode string // Enterprise
|
||||
IsAnonymousProxy bool
|
||||
IsSatelliteProvider bool
|
||||
}
|
||||
|
||||
type CountryResult struct {
|
||||
Continent Continent
|
||||
Country Country
|
||||
RegisteredCountry Country
|
||||
RepresentedCountry Country
|
||||
Traits Traits
|
||||
}
|
||||
|
||||
type CityResult struct {
|
||||
Continent Continent
|
||||
Country Country
|
||||
Subdivisions []Subdivision
|
||||
City City
|
||||
Location Location
|
||||
Postal Postal
|
||||
RegisteredCountry Country
|
||||
RepresentedCountry Country
|
||||
Traits Traits
|
||||
}
|
||||
|
||||
type ISP struct {
|
||||
AutonomousSystemNumber uint32
|
||||
AutonomousSystemOrganization string
|
||||
ISP string
|
||||
Organization string
|
||||
MobileCountryCode string
|
||||
MobileNetworkCode string
|
||||
}
|
||||
|
||||
type ConnectionType struct {
|
||||
ConnectionType string
|
||||
}
|
||||
|
||||
type AnonymousIP struct {
|
||||
IsAnonymous bool
|
||||
IsAnonymousVPN bool
|
||||
IsHostingProvider bool
|
||||
IsPublicProxy bool
|
||||
IsTorExitNode bool
|
||||
IsResidentialProxy bool
|
||||
}
|
||||
|
||||
type ASN struct {
|
||||
AutonomousSystemNumber uint32
|
||||
AutonomousSystemOrganization string
|
||||
}
|
||||
|
||||
type Domain struct {
|
||||
Domain string
|
||||
}
|
||||
6
vendor/github.com/PeernetOfficial/core/.gitignore
generated
vendored
Normal file
6
vendor/github.com/PeernetOfficial/core/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
*.exe
|
||||
log.txt
|
||||
*debug_bin
|
||||
test.*
|
||||
# Ignore the IDE gitignore files
|
||||
.idea/
|
||||
144
vendor/github.com/PeernetOfficial/core/Blockchain Cache Global.go
generated
vendored
Normal file
144
vendor/github.com/PeernetOfficial/core/Blockchain Cache Global.go
generated
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
File Name: Blockchain Cache Global.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/PeernetOfficial/core/blockchain"
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
"github.com/enfipy/locker"
|
||||
)
|
||||
|
||||
// The blockchain cache stores blockchains.
|
||||
type BlockchainCache struct {
|
||||
BlockchainDirectory string // The directory for storing blockchains in a key-value store.
|
||||
MaxBlockSize uint64 // Max block size to accept.
|
||||
MaxBlockCount uint64 // Max block count to cache per peer.
|
||||
LimitTotalRecords uint64 // Max count of blocks and header in total to keep across all blockchains. 0 = unlimited. Max Records * Max Block Size = Size Limit.
|
||||
ReadOnly bool // Whether the cache is read only.
|
||||
|
||||
Store *blockchain.MultiStore
|
||||
peerLock *locker.Locker
|
||||
|
||||
backend *Backend
|
||||
}
|
||||
|
||||
func (backend *Backend) initBlockchainCache() {
|
||||
if backend.Config.BlockchainGlobal == "" {
|
||||
return
|
||||
}
|
||||
|
||||
backend.GlobalBlockchainCache = &BlockchainCache{
|
||||
backend: backend,
|
||||
BlockchainDirectory: backend.Config.BlockchainGlobal,
|
||||
MaxBlockSize: backend.Config.CacheMaxBlockSize,
|
||||
MaxBlockCount: backend.Config.CacheMaxBlockCount,
|
||||
LimitTotalRecords: backend.Config.LimitTotalRecords,
|
||||
}
|
||||
|
||||
var err error
|
||||
backend.GlobalBlockchainCache.Store, err = blockchain.InitMultiStore(backend.Config.BlockchainGlobal)
|
||||
if err != nil {
|
||||
backend.LogError("initBlockchainCache", "initializing database '%s': %s", backend.Config.BlockchainGlobal, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
backend.GlobalBlockchainCache.peerLock = locker.Initialize()
|
||||
|
||||
// Set the blockchain cache to read-only if the record limit is reached.
|
||||
if backend.Config.LimitTotalRecords > 0 && backend.GlobalBlockchainCache.Store.Database.Count() >= backend.Config.LimitTotalRecords {
|
||||
backend.GlobalBlockchainCache.ReadOnly = true
|
||||
}
|
||||
|
||||
backend.GlobalBlockchainCache.Store.FilterStatisticUpdate = backend.Filters.GlobalBlockchainCacheStatistic
|
||||
backend.GlobalBlockchainCache.Store.FilterBlockchainDelete = backend.Filters.GlobalBlockchainCacheDelete
|
||||
}
|
||||
|
||||
// SeenBlockchainVersion shall be called with information about another peer's blockchain.
|
||||
// If the reported version number is newer, all existing blocks are immediately deleted.
|
||||
func (cache *BlockchainCache) SeenBlockchainVersion(peer *PeerInfo) {
|
||||
cache.peerLock.Lock(string(peer.PublicKey.SerializeCompressed()))
|
||||
defer cache.peerLock.Unlock(string(peer.PublicKey.SerializeCompressed()))
|
||||
|
||||
// intermediate function to download and process blocks
|
||||
downloadAndProcessBlocks := func(peer *PeerInfo, header *blockchain.MultiBlockchainHeader, offset, limit uint64) {
|
||||
if limit > cache.MaxBlockCount {
|
||||
limit = cache.MaxBlockCount
|
||||
}
|
||||
|
||||
peer.BlockDownload(peer.PublicKey, cache.MaxBlockCount, cache.MaxBlockSize, []protocol.BlockRange{{Offset: offset, Limit: limit}}, func(data []byte, targetBlock protocol.BlockRange, blockSize uint64, availability uint8) {
|
||||
if availability != protocol.GetBlockStatusAvailable {
|
||||
return
|
||||
}
|
||||
|
||||
if decoded, _ := cache.Store.IngestBlock(header, targetBlock.Offset, data, true); decoded != nil {
|
||||
// index it for search
|
||||
cache.backend.SearchIndex.IndexNewBlockDecoded(peer.PublicKey, peer.BlockchainVersion, targetBlock.Offset, decoded.RecordsDecoded)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// get the old header
|
||||
header, status, err := cache.Store.AssessBlockchainHeader(peer.PublicKey, peer.BlockchainVersion, peer.BlockchainHeight)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch status {
|
||||
case blockchain.MultiStatusEqual:
|
||||
return
|
||||
|
||||
case blockchain.MultiStatusInvalidRemote:
|
||||
cache.Store.DeleteBlockchain(header)
|
||||
|
||||
cache.backend.SearchIndex.UnindexBlockchain(peer.PublicKey)
|
||||
|
||||
case blockchain.MultiStatusHeaderNA:
|
||||
if header, err = cache.Store.NewBlockchainHeader(peer.PublicKey, peer.BlockchainVersion, peer.BlockchainHeight); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
downloadAndProcessBlocks(peer, header, 0, peer.BlockchainHeight)
|
||||
|
||||
case blockchain.MultiStatusNewVersion:
|
||||
// delete existing data first, then create it new
|
||||
cache.Store.DeleteBlockchain(header)
|
||||
|
||||
cache.backend.SearchIndex.UnindexBlockchain(peer.PublicKey)
|
||||
|
||||
if header, err = cache.Store.NewBlockchainHeader(peer.PublicKey, peer.BlockchainVersion, peer.BlockchainHeight); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
downloadAndProcessBlocks(peer, header, 0, peer.BlockchainHeight)
|
||||
|
||||
case blockchain.MultiStatusNewBlocks:
|
||||
offset := header.Height
|
||||
limit := peer.BlockchainHeight - header.Height
|
||||
|
||||
header.Height = peer.BlockchainHeight
|
||||
|
||||
downloadAndProcessBlocks(peer, header, offset, limit)
|
||||
|
||||
}
|
||||
|
||||
if cache.LimitTotalRecords > 0 {
|
||||
// Bug: This code is currently never reached if ReadOnly is true.
|
||||
cache.ReadOnly = cache.Store.Database.Count() >= cache.LimitTotalRecords
|
||||
}
|
||||
}
|
||||
|
||||
// remoteBlockchainUpdate shall be called to indicate a potential update of the remotes blockchain.
|
||||
// It will use the blockchain version and height to update the data lake as appropriate.
|
||||
// This function is called in the Go routine of the packet worker and therefore must not stall.
|
||||
func (peer *PeerInfo) remoteBlockchainUpdate() {
|
||||
if peer.Backend.GlobalBlockchainCache == nil || peer.Backend.GlobalBlockchainCache.ReadOnly || peer.BlockchainVersion == 0 && peer.BlockchainHeight == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: This entire function should be instead a non-blocking message via a buffer channel.
|
||||
go peer.Backend.GlobalBlockchainCache.SeenBlockchainVersion(peer)
|
||||
}
|
||||
110
vendor/github.com/PeernetOfficial/core/Blockchain User.go
generated
vendored
Normal file
110
vendor/github.com/PeernetOfficial/core/Blockchain User.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
File Name: Blockchain User.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/PeernetOfficial/core/blockchain"
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// initUserBlockchain initializes the users blockchain. It creates the blockchain file if it does not exist already.
|
||||
// If it is corrupted, it will log the error and exit the process.
|
||||
func (backend *Backend) initUserBlockchain() {
|
||||
var err error
|
||||
backend.UserBlockchain, err = blockchain.Init(backend.peerPrivateKey, backend.Config.BlockchainMain)
|
||||
|
||||
if err != nil {
|
||||
backend.LogError("initUserBlockchain", "error: %s\n", err.Error())
|
||||
os.Exit(ExitBlockchainCorrupt)
|
||||
}
|
||||
}
|
||||
|
||||
// Index the user's blockchain each time there is an update.
|
||||
func (backend *Backend) userBlockchainUpdateSearchIndex() {
|
||||
backend.UserBlockchain.BlockchainUpdate = func(blockchainU *blockchain.Blockchain, oldHeight, oldVersion, newHeight, newVersion uint64) {
|
||||
|
||||
if newVersion != oldVersion || newHeight < oldHeight {
|
||||
// invalidate search index data for the user's blockchain
|
||||
backend.SearchIndex.UnindexBlockchain(backend.peerPublicKey)
|
||||
|
||||
// reindex everything
|
||||
for blockN := uint64(0); blockN < newHeight; blockN++ {
|
||||
raw, status, err := blockchainU.GetBlockRaw(blockN)
|
||||
if err != nil || status != blockchain.StatusOK {
|
||||
continue
|
||||
}
|
||||
|
||||
backend.SearchIndex.IndexNewBlock(backend.peerPublicKey, newVersion, blockN, raw)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if newVersion == oldVersion && newHeight > oldHeight {
|
||||
// index the new blocks
|
||||
for blockN := oldHeight; blockN < newHeight; blockN++ {
|
||||
raw, status, err := blockchainU.GetBlockRaw(blockN)
|
||||
if err != nil || status != blockchain.StatusOK {
|
||||
continue
|
||||
}
|
||||
|
||||
backend.SearchIndex.IndexNewBlock(backend.peerPublicKey, newVersion, blockN, raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ReadBlock reads a block and decodes the records. This may be a block of the user's blockchain, or any other that is cached in the global blockchain cache.
|
||||
func (backend *Backend) ReadBlock(PublicKey *btcec.PublicKey, Version, BlockNumber uint64) (decoded *blockchain.BlockDecoded, raw []byte, found bool, err error) {
|
||||
// requesting a block from the user's blockchain?
|
||||
if PublicKey.IsEqual(backend.peerPublicKey) {
|
||||
_, _, version := backend.UserBlockchain.Header()
|
||||
if Version != version {
|
||||
return nil, nil, false, nil
|
||||
}
|
||||
|
||||
var status int
|
||||
raw, status, err = backend.UserBlockchain.GetBlockRaw(BlockNumber)
|
||||
if err != nil || status != blockchain.StatusOK {
|
||||
return nil, raw, false, err
|
||||
}
|
||||
} else if backend.GlobalBlockchainCache != nil {
|
||||
// read from the cache
|
||||
if raw, found = backend.GlobalBlockchainCache.Store.ReadBlock(PublicKey, Version, BlockNumber); !found {
|
||||
return nil, nil, false, nil
|
||||
}
|
||||
} else {
|
||||
return nil, nil, false, nil
|
||||
}
|
||||
|
||||
// decode the entire block
|
||||
blockDecoded, status, err := blockchain.DecodeBlockRaw(raw)
|
||||
if err != nil || status != blockchain.StatusOK {
|
||||
return nil, raw, false, err
|
||||
}
|
||||
|
||||
return blockDecoded, raw, true, nil
|
||||
}
|
||||
|
||||
// ReadFile decodes a file from the given blockchain (the user or any other cached). The block number must be provided.
|
||||
func (backend *Backend) ReadFile(PublicKey *btcec.PublicKey, Version, BlockNumber uint64, FileID uuid.UUID) (file blockchain.BlockRecordFile, raw []byte, found bool, err error) {
|
||||
blockDecoded, raw, found, err := backend.ReadBlock(PublicKey, Version, BlockNumber)
|
||||
if !found {
|
||||
return file, raw, found, err
|
||||
}
|
||||
|
||||
for _, decodedR := range blockDecoded.RecordsDecoded {
|
||||
if file, ok := decodedR.(blockchain.BlockRecordFile); ok && file.ID == FileID {
|
||||
return file, raw, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return file, raw, false, nil
|
||||
}
|
||||
395
vendor/github.com/PeernetOfficial/core/Bootstrap.go
generated
vendored
Normal file
395
vendor/github.com/PeernetOfficial/core/Bootstrap.go
generated
vendored
Normal file
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
File Name: Bootstrap.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Strategy for sending our IPv6 Multicast and IPv4 Broadcast messages:
|
||||
* During bootstrap: Immediately at the beginning, then every 10 seconds until there is at least 1 peer.
|
||||
* Every 10 minutes during regular operation.
|
||||
* Each time a network adapter / IP change is detected.
|
||||
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
)
|
||||
|
||||
// rootPeer is a single root peer info
|
||||
type rootPeer struct {
|
||||
peer *PeerInfo // loaded PeerInfo
|
||||
publicKey *btcec.PublicKey // Public key
|
||||
addresses []*net.UDPAddr // IP:Port addresses
|
||||
backend *Backend
|
||||
}
|
||||
|
||||
var rootPeers map[[btcec.PubKeyBytesLenCompressed]byte]*rootPeer
|
||||
|
||||
// initSeedList loads the seed list from the config
|
||||
// Note: This should be called before any network listening function so that incoming root peers are properly recognized.
|
||||
func (backend *Backend) initSeedList() {
|
||||
rootPeers = make(map[[btcec.PubKeyBytesLenCompressed]byte]*rootPeer)
|
||||
recentContacts = make(map[[btcec.PubKeyBytesLenCompressed]byte]*recentContactInfo)
|
||||
|
||||
loopSeedList:
|
||||
for _, seed := range backend.Config.SeedList {
|
||||
peer := &rootPeer{backend: backend}
|
||||
|
||||
// parse the Public Key
|
||||
publicKeyB, err := hex.DecodeString(seed.PublicKey)
|
||||
if err != nil {
|
||||
backend.LogError("initSeedList", "public key '%s': %v\n", seed.PublicKey, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if peer.publicKey, err = btcec.ParsePubKey(publicKeyB, btcec.S256()); err != nil {
|
||||
backend.LogError("initSeedList", "public key '%s': %v\n", seed.PublicKey, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if peer.publicKey.IsEqual(backend.peerPublicKey) { // skip if self
|
||||
continue
|
||||
}
|
||||
|
||||
// parse all IP addresses
|
||||
for _, addressA := range seed.Address {
|
||||
address, err := parseAddress(addressA)
|
||||
if err != nil {
|
||||
backend.LogError("initSeedList", "public key '%s' address '%s': %v\n", seed.PublicKey, addressA, err.Error())
|
||||
continue loopSeedList
|
||||
}
|
||||
|
||||
peer.addresses = append(peer.addresses, address)
|
||||
}
|
||||
|
||||
rootPeers[publicKey2Compressed(peer.publicKey)] = peer
|
||||
}
|
||||
}
|
||||
|
||||
// parseAddress parses an input peer address in the form "IP:Port".
|
||||
func parseAddress(Address string) (remote *net.UDPAddr, err error) {
|
||||
host, portA, err := net.SplitHostPort(Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
portI, err := strconv.Atoi(portA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if portI <= 0 || portI > 65535 {
|
||||
return nil, errors.New("invalid port number")
|
||||
}
|
||||
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil {
|
||||
return nil, errors.New("invalid input IP")
|
||||
}
|
||||
|
||||
return &net.UDPAddr{IP: ip, Port: portI}, err
|
||||
}
|
||||
|
||||
// contact tries to contact the root peer on all networks
|
||||
func (peer *rootPeer) contact() {
|
||||
// If already in peer list, no need to contact.
|
||||
if peer.backend.PeerlistLookup(peer.publicKey) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, address := range peer.addresses {
|
||||
// Port internal is always set to 0 for root peers. It disables NAT detection and will not send out a Traverse message.
|
||||
peer.backend.contactArbitraryPeer(peer.publicKey, address, 0, false)
|
||||
}
|
||||
}
|
||||
|
||||
// bootstrap connects to the initial set of peers.
|
||||
func (backend *Backend) bootstrap() {
|
||||
go resetRecentContacts()
|
||||
|
||||
if len(rootPeers) == 0 {
|
||||
backend.LogError("bootstrap", "warning: Empty list of root peers. Connectivity relies on local peer discovery and incoming connections.\n")
|
||||
return
|
||||
}
|
||||
|
||||
contactRootPeers := func() {
|
||||
for _, peer := range rootPeers {
|
||||
if peer.peer == nil {
|
||||
peer.contact()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
countConnectedRootPeers := func() (connectedCount, total int) {
|
||||
for _, peer := range rootPeers {
|
||||
if peer.peer != nil {
|
||||
connectedCount++
|
||||
} else if peer.peer = peer.backend.PeerlistLookup(peer.publicKey); peer.peer != nil {
|
||||
connectedCount++
|
||||
}
|
||||
}
|
||||
return connectedCount, len(rootPeers)
|
||||
}
|
||||
|
||||
// initial contact to all root peer
|
||||
contactRootPeers()
|
||||
|
||||
// Phase 1: First 10 minutes. Try every 7 seconds to connect to all root peers until at least 2 peers connected.
|
||||
for n := 0; n < 10*60/7; n++ {
|
||||
time.Sleep(time.Second * 7)
|
||||
|
||||
if connected, total := countConnectedRootPeers(); connected == total || connected >= 2 {
|
||||
return
|
||||
}
|
||||
|
||||
contactRootPeers()
|
||||
}
|
||||
|
||||
// Phase 2: After that (if not 2 peers), try every 5 minutes to connect to remaining root peers for a maximum of 1 hour.
|
||||
for n := 0; n < 1*60/5; n++ {
|
||||
time.Sleep(time.Minute * 5)
|
||||
|
||||
contactRootPeers()
|
||||
|
||||
if connected, total := countConnectedRootPeers(); connected == total || connected >= 2 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
backend.LogError("bootstrap", "unable to connect to at least 2 root peers, aborting\n")
|
||||
}
|
||||
|
||||
func (nets *Networks) autoMulticastBroadcast() {
|
||||
sendMulticastBroadcast := func() {
|
||||
nets.RLock()
|
||||
defer nets.RUnlock()
|
||||
|
||||
for _, network := range nets.networks6 {
|
||||
if err := network.MulticastIPv6Send(); err != nil {
|
||||
nets.backend.LogError("autoMulticastBroadcast", "multicast from network address '%s': %v\n", network.address.IP.String(), err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
for _, network := range nets.networks4 {
|
||||
if err := network.BroadcastIPv4Send(); err != nil {
|
||||
nets.backend.LogError("autoMulticastBroadcast", "broadcast from network address '%s': %v\n", network.address.IP.String(), err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send out multicast/broadcast immediately.
|
||||
sendMulticastBroadcast()
|
||||
|
||||
// Phase 1: Resend every 10 seconds until at least 1 peer in the peer list.
|
||||
for {
|
||||
time.Sleep(time.Second * 10)
|
||||
|
||||
if nets.backend.PeerlistCount() >= 1 {
|
||||
break
|
||||
}
|
||||
|
||||
sendMulticastBroadcast()
|
||||
}
|
||||
|
||||
// Phase 2: Every 10 minutes.
|
||||
for {
|
||||
time.Sleep(time.Minute * 10)
|
||||
sendMulticastBroadcast()
|
||||
}
|
||||
}
|
||||
|
||||
// contactArbitraryPeer contacts a new arbitrary peer for the first time.
|
||||
func (backend *Backend) contactArbitraryPeer(publicKey *btcec.PublicKey, address *net.UDPAddr, receiverPortInternal uint16, receiverFirewall bool) (contacted bool) {
|
||||
findSelf := ShouldSendFindSelf()
|
||||
_, blockchainHeight, blockchainVersion := backend.UserBlockchain.Header()
|
||||
packets := protocol.EncodeAnnouncement(true, findSelf, nil, nil, nil, backend.FeatureSupport(), blockchainHeight, blockchainVersion, backend.userAgent)
|
||||
if len(packets) == 0 {
|
||||
return false
|
||||
}
|
||||
raw := &protocol.PacketRaw{Command: protocol.CommandAnnouncement, Payload: packets[0]}
|
||||
|
||||
backend.Filters.MessageOutAnnouncement(publicKey, nil, raw, findSelf, nil, nil, nil)
|
||||
|
||||
backend.networks.sendAllNetworks(publicKey, raw, address, receiverPortInternal, receiverFirewall, nil, &bootstrapFindSelf{})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// bootstrapFindSelf is a dummy structure assigned to sequences when sending the Announcement message.
|
||||
// When receiving the Response message, it will know that it was a legitimate bootstrap request.
|
||||
type bootstrapFindSelf struct {
|
||||
}
|
||||
|
||||
// bootstrapAcceptContacts is the maximum count of contacts considered. It limits the impact of fake peers.
|
||||
const bootstrapAcceptContacts = 5
|
||||
|
||||
// cmdResponseBootstrapFindSelf processes FIND_SELF responses
|
||||
func (peer *PeerInfo) cmdResponseBootstrapFindSelf(msg *protocol.MessageResponse, closest []protocol.PeerRecord) {
|
||||
if len(closest) > bootstrapAcceptContacts {
|
||||
closest = closest[:bootstrapAcceptContacts]
|
||||
}
|
||||
|
||||
for _, closePeer := range closest {
|
||||
if peer.Backend.isReturnedPeerBadQuality(&closePeer) {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the peer is already in the peer list, no need to contact it again.
|
||||
if peer.Backend.PeerlistLookup(closePeer.PublicKey) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if the reported peer was recently contacted (in connection with the origin peer) for bootstrapping. This makes sure inactive peers are not contacted over and over again.
|
||||
recent, blacklisted := isReturnedPeerRecent(&closePeer, peer.NodeID)
|
||||
if blacklisted {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, address := range peerRecordToAddresses(&closePeer) {
|
||||
// Check if the specific IP:Port was already contacted in the last 5-10 minutes.
|
||||
if recent.IsAddressContacted(address) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Initiate contact. Once a response comes back, the peer will be actually added to the peer list.
|
||||
peer.Backend.contactArbitraryPeer(closePeer.PublicKey, &net.UDPAddr{IP: address.IP, Port: int(address.Port)}, address.PortInternal, closePeer.Features&(1<<protocol.FeatureFirewall) > 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ShouldSendFindSelf checks if FIND_SELF should be send
|
||||
func ShouldSendFindSelf() bool {
|
||||
// TODO
|
||||
return true
|
||||
}
|
||||
|
||||
// isReturnedPeerBadQuality checks if the returned peer record is bad quality and should be discarded
|
||||
func (backend *Backend) isReturnedPeerBadQuality(record *protocol.PeerRecord) bool {
|
||||
isIPv4 := record.IPv4 != nil && !record.IPv4.IsUnspecified()
|
||||
isIPv6 := record.IPv6 != nil && !record.IPv6.IsUnspecified()
|
||||
|
||||
// At least one IP must be provided.
|
||||
if !isIPv4 && !isIPv6 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Internal port must be provided. Otherwise the external port is likely not provided either, and checking the NAT and port forwarded status is not possible.
|
||||
if isIPv4 && record.IPv4PortReportedInternal == 0 || isIPv6 && record.IPv6PortReportedInternal == 0 {
|
||||
//fmt.Printf("IsReturnedPeerBadQuality port internal not available for target %s port %d, peer %s\n", record.IP.String(), record.Port, hex.EncodeToString(record.PublicKey.SerializeCompressed()))
|
||||
return true
|
||||
}
|
||||
|
||||
// Must not be self. There is no point that a remote peer would return self
|
||||
if record.PublicKey.IsEqual(backend.peerPublicKey) {
|
||||
//fmt.Printf("IsReturnedPeerBadQuality received self peer\n")
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// peerRecordToAddresses returns the addresses in a usable way
|
||||
func peerRecordToAddresses(record *protocol.PeerRecord) (addresses []*peerAddress) {
|
||||
// IPv4
|
||||
ipv4Port := record.IPv4Port
|
||||
if record.IPv4PortReportedExternal > 0 { // Use the external port if available
|
||||
ipv4Port = record.IPv4PortReportedExternal
|
||||
}
|
||||
if record.IPv4 != nil && !record.IPv4.IsUnspecified() {
|
||||
addresses = append(addresses, &peerAddress{IP: record.IPv4, Port: ipv4Port, PortInternal: record.IPv4PortReportedInternal})
|
||||
}
|
||||
|
||||
// IPv6
|
||||
ipv6Port := record.IPv6Port
|
||||
if record.IPv6PortReportedExternal > 0 { // Use the external port if available
|
||||
ipv6Port = record.IPv6PortReportedExternal
|
||||
}
|
||||
if record.IPv6 != nil && !record.IPv6.IsUnspecified() {
|
||||
addresses = append(addresses, &peerAddress{IP: record.IPv6, Port: ipv6Port, PortInternal: record.IPv6PortReportedInternal})
|
||||
}
|
||||
|
||||
return addresses
|
||||
}
|
||||
|
||||
// ---- bootstrap cache of contacted peers to prevent flooding ----
|
||||
|
||||
// bootstrapRecentContact is the time in seconds when a peer will not be contacted again for bootstrapping.
|
||||
// This prevents unnecessary flooding and prevents some attacks. Especially in small networks it will be the case that the same peer is returned multiple times.
|
||||
const bootstrapRecentContact = 5 * 60 // 5-10 minutes
|
||||
|
||||
type recentContactInfo struct {
|
||||
added time.Time // When the peer was added to the list
|
||||
addresses []*peerAddress // List of contacted addresses in IP:Port format
|
||||
origin map[string]struct{} // List of node IDs who reported this contact
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
var (
|
||||
recentContacts map[[btcec.PubKeyBytesLenCompressed]byte]*recentContactInfo
|
||||
recentContactsMutex sync.RWMutex
|
||||
)
|
||||
|
||||
func resetRecentContacts() {
|
||||
for {
|
||||
time.Sleep(bootstrapRecentContact * time.Second)
|
||||
threshold := time.Now().Add(-bootstrapRecentContact * time.Second)
|
||||
|
||||
recentContactsMutex.Lock()
|
||||
|
||||
for key, recent := range recentContacts {
|
||||
if recent.added.Before(threshold) {
|
||||
delete(recentContacts, key)
|
||||
}
|
||||
}
|
||||
|
||||
recentContactsMutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// isReturnedPeerRecent checks if the peer is blacklisted related to the origin peer due to recent contact. It will create a "recent contact" if none exists.
|
||||
func isReturnedPeerRecent(record *protocol.PeerRecord, originNodeID []byte) (recent *recentContactInfo, blacklisted bool) {
|
||||
key := publicKey2Compressed(record.PublicKey)
|
||||
|
||||
recentContactsMutex.Lock()
|
||||
defer recentContactsMutex.Unlock()
|
||||
|
||||
if recent = recentContacts[key]; recent == nil {
|
||||
recent = &recentContactInfo{added: time.Now(), origin: make(map[string]struct{})}
|
||||
recent.origin[string(originNodeID)] = struct{}{}
|
||||
|
||||
recentContacts[key] = recent
|
||||
} else {
|
||||
if _, blacklisted = recent.origin[string(originNodeID)]; !blacklisted {
|
||||
recent.origin[string(originNodeID)] = struct{}{}
|
||||
|
||||
// Here we could add an additional check: If number of recent.addresses (i.e. unique IP:Port tried) exceeds a threshold.
|
||||
// However, this is currently not done due to risk of peer isolation. This could happen if enough peers would gang up to report false addresses for a given peer (such peer could still establish an inbound connection to this peer, however).
|
||||
// Rather, those peers who report inactive peers should be blacklisted after a given threshold of garbage responses.
|
||||
}
|
||||
}
|
||||
|
||||
return recent, blacklisted
|
||||
}
|
||||
|
||||
// IsAddressContacted checks if the address was contacted recently
|
||||
func (recent *recentContactInfo) IsAddressContacted(address *peerAddress) bool {
|
||||
recent.Lock()
|
||||
defer recent.Unlock()
|
||||
|
||||
for _, addressE := range recent.addresses {
|
||||
if addressE.IP.Equal(address.IP) && addressE.Port == address.Port {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
recent.addresses = append(recent.addresses, address)
|
||||
|
||||
return false
|
||||
}
|
||||
134
vendor/github.com/PeernetOfficial/core/Command Traverse.go
generated
vendored
Normal file
134
vendor/github.com/PeernetOfficial/core/Command Traverse.go
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
File Name: Command Traverse.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
)
|
||||
|
||||
// cmdTraverseForward handles an incoming traverse message that should be forwarded to another peer
|
||||
func (peer *PeerInfo) cmdTraverseForward(msg *protocol.MessageTraverse) {
|
||||
// Verify the signature. This makes sure that a fowarded message cannot be replayed by others.
|
||||
if !msg.SignerPublicKey.IsEqual(peer.PublicKey) || !msg.SignerPublicKey.IsEqual(msg.SenderPublicKey) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check expiration
|
||||
if msg.Expires.Before(time.Now()) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the target peer is known in the peer list. If not, nothing will be done.
|
||||
// The original sender should only send the Traverse message as answer to a Response that contains a reported peer that is behind a NAT.
|
||||
// In that case the target peer should be still in this peers' peer list.
|
||||
peerTarget := peer.Backend.PeerlistLookup(msg.TargetPeer)
|
||||
if peerTarget == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Get the right IP:Port of the original sender to share to the target peer.
|
||||
allowIPv4 := peerTarget.Features&(1<<protocol.FeatureIPv4Listen) > 0
|
||||
allowIPv6 := peerTarget.Features&(1<<protocol.FeatureIPv6Listen) > 0
|
||||
connectionIPv4 := peer.GetConnection2Share(false, allowIPv4, false)
|
||||
connectionIPv6 := peer.GetConnection2Share(false, false, allowIPv6)
|
||||
|
||||
if connectionIPv4 == nil && connectionIPv6 == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// get the individual fields
|
||||
var IPv4, IPv6 net.IP
|
||||
var PortIPv4, PortIPv4ReportedExternal, PortIPv6, PortIPv6ReportedExternal uint16
|
||||
if connectionIPv4 != nil {
|
||||
IPv4 = connectionIPv4.Address.IP
|
||||
PortIPv4 = uint16(connectionIPv4.Address.Port)
|
||||
PortIPv4ReportedExternal = connectionIPv4.PortExternal
|
||||
}
|
||||
if connectionIPv6 != nil {
|
||||
IPv6 = connectionIPv6.Address.IP
|
||||
PortIPv6 = uint16(connectionIPv6.Address.Port)
|
||||
PortIPv6ReportedExternal = connectionIPv6.PortExternal
|
||||
}
|
||||
|
||||
if err := protocol.EncodeTraverseSetAddress(msg.Payload, IPv4, PortIPv4, PortIPv4ReportedExternal, IPv6, PortIPv6, PortIPv6ReportedExternal); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
peerTarget.send(&protocol.PacketRaw{Command: protocol.CommandTraverse, Payload: msg.Payload})
|
||||
}
|
||||
|
||||
func (peer *PeerInfo) cmdTraverseReceive(msg *protocol.MessageTraverse) {
|
||||
if msg.Expires.Before(time.Now()) {
|
||||
return
|
||||
}
|
||||
|
||||
// Already an active connection established? The relayed message should not be needed in this case.
|
||||
// This could be changed in the future if it turns out that there are 1-way connection issues.
|
||||
if peerTarget := peer.Backend.PeerlistLookup(msg.SignerPublicKey); peerTarget != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// parse IP addresses of the original sender
|
||||
var addresses []*peerAddress
|
||||
|
||||
if !msg.IPv4.IsUnspecified() {
|
||||
port := msg.PortIPv4
|
||||
if msg.PortIPv4ReportedExternal > 0 {
|
||||
port = msg.PortIPv4ReportedExternal
|
||||
}
|
||||
addresses = append(addresses, &peerAddress{IP: msg.IPv4, Port: port, PortInternal: 0})
|
||||
}
|
||||
if !msg.IPv6.IsUnspecified() {
|
||||
port := msg.PortIPv6
|
||||
if msg.PortIPv6ReportedExternal > 0 {
|
||||
port = msg.PortIPv6ReportedExternal
|
||||
}
|
||||
addresses = append(addresses, &peerAddress{IP: msg.IPv6, Port: port, PortInternal: 0})
|
||||
}
|
||||
if len(addresses) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// ---- fork packetWorker to decode and validate embedded packet ---
|
||||
// Due to missing connection and other embedded details in the message (such as ports), the packet is not just simply queued to rawPacketsIncoming.
|
||||
decoded, senderPublicKey, err := protocol.PacketDecrypt(msg.EmbeddedPacketRaw, peer.Backend.peerPublicKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !senderPublicKey.IsEqual(msg.SignerPublicKey) {
|
||||
return
|
||||
} else if senderPublicKey.IsEqual(peer.Backend.peerPublicKey) {
|
||||
return
|
||||
} else if decoded.Protocol != 0 {
|
||||
return
|
||||
} else if decoded.Command != protocol.CommandAnnouncement {
|
||||
return
|
||||
}
|
||||
|
||||
// process the packet and create a virtual peer
|
||||
raw := &protocol.MessageRaw{SenderPublicKey: senderPublicKey, PacketRaw: *decoded}
|
||||
peerV := &PeerInfo{Backend: peer.Backend, PublicKey: senderPublicKey, connectionActive: nil, connectionLatest: nil, NodeID: protocol.PublicKey2NodeID(senderPublicKey), messageSequence: rand.Uint32(), isVirtual: true, targetAddresses: addresses}
|
||||
|
||||
// process it!
|
||||
switch decoded.Command {
|
||||
case protocol.CommandAnnouncement: // Announce
|
||||
if announce, _ := protocol.DecodeAnnouncement(raw); announce != nil {
|
||||
if len(announce.UserAgent) > 0 {
|
||||
peerV.UserAgent = announce.UserAgent
|
||||
}
|
||||
peerV.Features = announce.Features
|
||||
|
||||
peerV.cmdAnouncement(announce, nil)
|
||||
}
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
330
vendor/github.com/PeernetOfficial/core/Commands.go
generated
vendored
Normal file
330
vendor/github.com/PeernetOfficial/core/Commands.go
generated
vendored
Normal file
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
File Name: Commands.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/PeernetOfficial/core/dht"
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
"github.com/PeernetOfficial/core/warehouse"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// respondClosesContactsCount is the number of closest contact to respond.
|
||||
// Each peer record will take 70 bytes. Overhead is 77 + 20 payload header + UA length + 6 + 34 = 137 bytes without UA.
|
||||
// It makes sense to stay below 508 bytes (no fragmentation). Reporting back 5 contacts for FIND_SELF requests should do the magic.
|
||||
const respondClosesContactsCount = 5
|
||||
|
||||
// cmdAnouncement handles an incoming announcement. Connection may be nil for traverse relayed messages.
|
||||
func (peer *PeerInfo) cmdAnouncement(msg *protocol.MessageAnnouncement, connection *Connection) {
|
||||
// Filter function to only share peers that are "connectable" to the remote one. It checks IPv4, IPv6, and local connection.
|
||||
filterFunc := func(allowLocal, allowIPv4, allowIPv6 bool) dht.NodeFilterFunc {
|
||||
return func(node *dht.Node) (accept bool) {
|
||||
return node.Info.(*PeerInfo).IsConnectable(allowLocal, allowIPv4, allowIPv6)
|
||||
}
|
||||
}
|
||||
|
||||
allowIPv4 := msg.Features&(1<<protocol.FeatureIPv4Listen) > 0
|
||||
allowIPv6 := msg.Features&(1<<protocol.FeatureIPv6Listen) > 0
|
||||
|
||||
var hash2Peers []protocol.Hash2Peer
|
||||
var hashesNotFound [][]byte
|
||||
var filesEmbed []protocol.EmbeddedFileData
|
||||
|
||||
// FIND_SELF: Requesting peers close to the sender?
|
||||
if msg.Actions&(1<<protocol.ActionFindSelf) > 0 {
|
||||
peer.Backend.Filters.IncomingRequest(peer, protocol.ActionFindSelf, peer.NodeID, nil)
|
||||
|
||||
selfD := protocol.Hash2Peer{ID: protocol.KeyHash{Hash: peer.NodeID}}
|
||||
|
||||
// do not respond the caller's own peer (add to ignore list)
|
||||
for _, node := range peer.Backend.nodesDHT.GetClosestContacts(respondClosesContactsCount, peer.NodeID, filterFunc(connection.IsLocal(), allowIPv4, allowIPv6), peer.NodeID) {
|
||||
if info := node.Info.(*PeerInfo).peer2Record(connection.IsLocal(), allowIPv4, allowIPv6); info != nil {
|
||||
selfD.Closest = append(selfD.Closest, *info)
|
||||
}
|
||||
}
|
||||
|
||||
if len(selfD.Closest) > 0 {
|
||||
hash2Peers = append(hash2Peers, selfD)
|
||||
} else {
|
||||
hashesNotFound = append(hashesNotFound, peer.NodeID)
|
||||
}
|
||||
}
|
||||
|
||||
// FIND_PEER: Find a different peer?
|
||||
if msg.Actions&(1<<protocol.ActionFindPeer) > 0 && len(msg.FindPeerKeys) > 0 {
|
||||
for _, findPeer := range msg.FindPeerKeys {
|
||||
peer.Backend.Filters.IncomingRequest(peer, protocol.ActionFindPeer, findPeer.Hash, nil)
|
||||
|
||||
details := protocol.Hash2Peer{ID: findPeer}
|
||||
|
||||
// Same as before, put self as ignoredNodes.
|
||||
for _, node := range peer.Backend.nodesDHT.GetClosestContacts(respondClosesContactsCount, findPeer.Hash, filterFunc(connection.IsLocal(), allowIPv4, allowIPv6), peer.NodeID) {
|
||||
if info := node.Info.(*PeerInfo).peer2Record(connection.IsLocal(), allowIPv4, allowIPv6); info != nil {
|
||||
details.Closest = append(details.Closest, *info)
|
||||
}
|
||||
}
|
||||
|
||||
if len(details.Closest) > 0 {
|
||||
hash2Peers = append(hash2Peers, details)
|
||||
} else {
|
||||
hashesNotFound = append(hashesNotFound, findPeer.Hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find a value?
|
||||
if msg.Actions&(1<<protocol.ActionFindValue) > 0 {
|
||||
for _, findHash := range msg.FindDataKeys {
|
||||
peer.Backend.Filters.IncomingRequest(peer, protocol.ActionFindValue, findHash.Hash, nil)
|
||||
|
||||
stored, data := peer.announcementGetData(findHash.Hash)
|
||||
if stored && len(data) > 0 {
|
||||
filesEmbed = append(filesEmbed, protocol.EmbeddedFileData{ID: findHash, Data: data})
|
||||
} else if stored {
|
||||
selfRecord := peer.Backend.selfPeerRecord()
|
||||
hash2Peers = append(hash2Peers, protocol.Hash2Peer{ID: findHash, Storing: []protocol.PeerRecord{selfRecord}})
|
||||
} else {
|
||||
hashesNotFound = append(hashesNotFound, findHash.Hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Information about files stored by the sender?
|
||||
if msg.Actions&(1<<protocol.ActionInfoStore) > 0 && len(msg.InfoStoreFiles) > 0 {
|
||||
for n := range msg.InfoStoreFiles {
|
||||
peer.Backend.Filters.IncomingRequest(peer, protocol.ActionInfoStore, msg.InfoStoreFiles[n].ID.Hash, &msg.InfoStoreFiles[n])
|
||||
}
|
||||
|
||||
peer.announcementStore(msg.InfoStoreFiles)
|
||||
}
|
||||
|
||||
sendUA := msg.UserAgent != "" // Send user agent if one was provided. Per protocol the first announcement message must have the User Agent set.
|
||||
peer.sendResponse(msg.Sequence, sendUA, hash2Peers, filesEmbed, hashesNotFound)
|
||||
}
|
||||
|
||||
func (peer *PeerInfo) peer2Record(allowLocal, allowIPv4, allowIPv6 bool) (result *protocol.PeerRecord) {
|
||||
connectionIPv4 := peer.GetConnection2Share(allowLocal, allowIPv4, false)
|
||||
connectionIPv6 := peer.GetConnection2Share(allowLocal, false, allowIPv6)
|
||||
if connectionIPv4 == nil && connectionIPv6 == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result = &protocol.PeerRecord{
|
||||
PublicKey: peer.PublicKey,
|
||||
NodeID: peer.NodeID,
|
||||
Features: peer.Features,
|
||||
}
|
||||
|
||||
if connectionIPv4 != nil {
|
||||
result.IPv4 = connectionIPv4.Address.IP
|
||||
result.IPv4Port = uint16(connectionIPv4.Address.Port)
|
||||
result.IPv4PortReportedInternal = connectionIPv4.PortInternal
|
||||
result.IPv4PortReportedExternal = connectionIPv4.PortExternal
|
||||
}
|
||||
|
||||
if connectionIPv6 != nil {
|
||||
result.IPv6 = connectionIPv6.Address.IP
|
||||
result.IPv6Port = uint16(connectionIPv6.Address.Port)
|
||||
result.IPv6PortReportedInternal = connectionIPv6.PortInternal
|
||||
result.IPv6PortReportedExternal = connectionIPv6.PortExternal
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// cmdResponse handles the response to the announcement
|
||||
func (peer *PeerInfo) cmdResponse(msg *protocol.MessageResponse, connection *Connection) {
|
||||
// The sequence data is used to correlate this response with the announcement.
|
||||
if msg.SequenceInfo == nil || msg.SequenceInfo.Data == nil {
|
||||
// If there is no sequence data but there were results returned, it means we received unsolicited response data. It will be rejected.
|
||||
if len(msg.HashesNotFound) > 0 || len(msg.Hash2Peers) > 0 || len(msg.FilesEmbed) > 0 {
|
||||
peer.Backend.LogError("cmdResponse", "unsolicited response data received from %s\n", connection.Address.String())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// bootstrap FIND_SELF?
|
||||
if _, ok := msg.SequenceInfo.Data.(*bootstrapFindSelf); ok {
|
||||
for _, hash2Peer := range msg.Hash2Peers {
|
||||
// Make sure no garbage is returned. The key must be self and only Closest is expected.
|
||||
if !bytes.Equal(hash2Peer.ID.Hash, peer.Backend.nodeID) || len(hash2Peer.Closest) == 0 {
|
||||
peer.Backend.LogError("cmdResponse", "incoming response to bootstrap FIND_SELF contains invalid data from %s\n", connection.Address.String())
|
||||
return
|
||||
}
|
||||
|
||||
peer.cmdResponseBootstrapFindSelf(msg, hash2Peer.Closest)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Response to an information request?
|
||||
if _, ok := msg.SequenceInfo.Data.(*dht.InformationRequest); ok {
|
||||
// Future: Once multiple information requests are pooled (multiplexed) into one or multiple Announcement sequences (messages), the responses need to be de-pooled.
|
||||
// A simple multiplex structure linked via the sequence containing a map (hash 2 IR) could simplify this.
|
||||
info := msg.SequenceInfo.Data.(*dht.InformationRequest)
|
||||
|
||||
if len(msg.HashesNotFound) > 0 {
|
||||
info.Done()
|
||||
}
|
||||
|
||||
for _, hash2Peer := range msg.Hash2Peers {
|
||||
info.QueueResult(&dht.NodeMessage{SenderID: peer.NodeID, Closest: peer.records2Nodes(hash2Peer.Closest), Storing: peer.records2Nodes(hash2Peer.Storing)})
|
||||
|
||||
if hash2Peer.IsLast {
|
||||
info.Done()
|
||||
}
|
||||
}
|
||||
|
||||
for _, file := range msg.FilesEmbed {
|
||||
info.QueueResult(&dht.NodeMessage{SenderID: peer.NodeID, Data: file.Data})
|
||||
|
||||
info.Done()
|
||||
info.Terminate() // file was found, terminate the request.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cmdPing handles an incoming ping message
|
||||
func (peer *PeerInfo) cmdPing(msg *protocol.MessageRaw, connection *Connection) {
|
||||
// If PortInternal is 0, it means no incoming announcement or response message was received on that connection.
|
||||
// This means the ping is unexpected. In that case for security reasons the remote peer is not asked for FIND_SELF.
|
||||
if connection.PortInternal == 0 {
|
||||
peer.sendAnnouncement(true, false, nil, nil, nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
raw := &protocol.PacketRaw{Command: protocol.CommandPong, Sequence: msg.Sequence}
|
||||
|
||||
peer.Backend.Filters.MessageOutPong(peer, raw)
|
||||
|
||||
peer.send(raw)
|
||||
}
|
||||
|
||||
// cmdPong handles an incoming pong message
|
||||
func (peer *PeerInfo) cmdPong(msg *protocol.MessageRaw, connection *Connection) {
|
||||
}
|
||||
|
||||
// cmdChat handles a chat message [debug]
|
||||
func (peer *PeerInfo) cmdChat(msg *protocol.MessageRaw, connection *Connection) {
|
||||
fmt.Fprintf(peer.Backend.Stdout, "Chat from %s '%s': %s\n", hex.EncodeToString(peer.PublicKey.SerializeCompressed()), connection.Address.String(), string(msg.PacketRaw.Payload))
|
||||
}
|
||||
|
||||
// cmdLocalDiscovery handles an incoming announcement via local discovery
|
||||
func (peer *PeerInfo) cmdLocalDiscovery(msg *protocol.MessageAnnouncement, connection *Connection) {
|
||||
// 21.04.2021 update: Local peer discovery from public IPv4s is possible in datacenter situations. Keep it enabled for now.
|
||||
// only accept local discovery message from private IPs for IPv4
|
||||
// IPv6 DHCP routers typically assign public IPv6s and they can join multicast in the local network.
|
||||
//if connection.IsIPv4() && !connection.IsLocal() {
|
||||
// LogError("cmdLocalDiscovery", "message received from non-local IP %s peer ID %s\n", connection.Address.String(), hex.EncodeToString(msg.SenderPublicKey.SerializeCompressed()))
|
||||
// return
|
||||
//}
|
||||
|
||||
peer.sendAnnouncement(true, ShouldSendFindSelf(), nil, nil, nil, &bootstrapFindSelf{})
|
||||
}
|
||||
|
||||
// SendChatAll sends a text message to all peers
|
||||
func (backend *Backend) SendChatAll(text string) {
|
||||
for _, peer := range backend.PeerlistGet() {
|
||||
peer.Chat(text)
|
||||
}
|
||||
}
|
||||
|
||||
// cmdTransfer handles an incoming transfer message
|
||||
func (peer *PeerInfo) cmdTransfer(msg *protocol.MessageTransfer, connection *Connection) {
|
||||
// Only UDT protocol is currently supported for file transfer.
|
||||
if msg.TransferProtocol != protocol.TransferProtocolUDT {
|
||||
return
|
||||
}
|
||||
|
||||
switch msg.Control {
|
||||
case protocol.TransferControlRequestStart:
|
||||
// First check if the file available in the warehouse.
|
||||
_, fileSize, status, _ := peer.Backend.UserWarehouse.FileExists(msg.Hash)
|
||||
if status != warehouse.StatusOK {
|
||||
// File not available.
|
||||
peer.sendTransfer(nil, protocol.TransferControlNotAvailable, msg.TransferProtocol, msg.Hash, 0, 0, msg.Sequence, uuid.UUID{}, false)
|
||||
return
|
||||
} else if msg.Limit > 0 && fileSize < msg.Offset+msg.Limit {
|
||||
// If the read limit is out of bounds, this request is considered invalid and silently discarded.
|
||||
return
|
||||
}
|
||||
|
||||
// Create a local UDT client to connect to the remote UDT server and serve the file!
|
||||
go peer.startFileTransferUDT(msg.Hash, fileSize, msg.Offset, msg.Limit, msg.Sequence, msg.TransferID, msg.TransferProtocol)
|
||||
|
||||
case protocol.TransferControlActive:
|
||||
if v, ok := msg.SequenceInfo.Data.(*VirtualPacketConn); ok {
|
||||
go v.receiveData(msg.Data)
|
||||
return
|
||||
}
|
||||
|
||||
case protocol.TransferControlNotAvailable:
|
||||
if v, ok := msg.SequenceInfo.Data.(*VirtualPacketConn); ok {
|
||||
v.Terminate(404)
|
||||
return
|
||||
}
|
||||
|
||||
case protocol.TransferControlTerminate:
|
||||
if v, ok := msg.SequenceInfo.Data.(*VirtualPacketConn); ok {
|
||||
v.Terminate(2)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// cmdGetBlock handles an incoming block message
|
||||
func (peer *PeerInfo) cmdGetBlock(msg *protocol.MessageGetBlock, connection *Connection) {
|
||||
switch msg.Control {
|
||||
case protocol.GetBlockControlRequestStart:
|
||||
// Currently only support the local blockchain.
|
||||
if !msg.BlockchainPublicKey.IsEqual(peer.Backend.peerPublicKey) {
|
||||
peer.sendGetBlock(nil, protocol.GetBlockControlNotAvailable, msg.BlockchainPublicKey, 0, 0, nil, msg.Sequence, uuid.UUID{}, false)
|
||||
return
|
||||
} else if _, height, _ := peer.Backend.UserBlockchain.Header(); height == 0 {
|
||||
peer.sendGetBlock(nil, protocol.GetBlockControlEmpty, msg.BlockchainPublicKey, 0, 0, nil, msg.Sequence, uuid.UUID{}, false)
|
||||
return
|
||||
} else if msg.LimitBlockCount == 0 {
|
||||
peer.sendGetBlock(nil, protocol.GetBlockControlTerminate, msg.BlockchainPublicKey, 0, 0, nil, msg.Sequence, uuid.UUID{}, false)
|
||||
return
|
||||
}
|
||||
|
||||
// Create a local UDT client to connect to the remote UDT server and serve the blocks!
|
||||
go peer.startBlockTransfer(msg.BlockchainPublicKey, msg.LimitBlockCount, msg.MaxBlockSize, msg.TargetBlocks, msg.Sequence, msg.TransferID)
|
||||
|
||||
case protocol.GetBlockControlActive:
|
||||
if v, ok := msg.SequenceInfo.Data.(*VirtualPacketConn); ok {
|
||||
go v.receiveData(msg.Data)
|
||||
return
|
||||
}
|
||||
|
||||
case protocol.GetBlockControlNotAvailable:
|
||||
if v, ok := msg.SequenceInfo.Data.(*VirtualPacketConn); ok {
|
||||
v.Terminate(404)
|
||||
return
|
||||
}
|
||||
|
||||
case protocol.GetBlockControlEmpty:
|
||||
if v, ok := msg.SequenceInfo.Data.(*VirtualPacketConn); ok {
|
||||
v.Terminate(410)
|
||||
return
|
||||
}
|
||||
|
||||
case protocol.GetBlockControlTerminate:
|
||||
if v, ok := msg.SequenceInfo.Data.(*VirtualPacketConn); ok {
|
||||
v.Terminate(2)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
48
vendor/github.com/PeernetOfficial/core/Config Default.yaml
generated
vendored
Normal file
48
vendor/github.com/PeernetOfficial/core/Config Default.yaml
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
# Locations of important files and folders
|
||||
LogFile: "data/log backend.txt" # Log file for the backend. It contains informational and error messages.
|
||||
BlockchainMain: "data/blockchain main/" # Blockchain main stores the end-users blockchain data. It contains meta data of shared files, profile data, and social interactions.
|
||||
BlockchainGlobal: "data/blockchain global/" # Blockchain global caches blockchain data from global users. Empty to disable.
|
||||
WarehouseMain: "data/warehouse main/" # Warehouse main stores the actual data of files shared by the end-user.
|
||||
SearchIndex: "data/search index/" # Local search index of blockchain records. Empty to disable.
|
||||
GeoIPDatabase: "data/GeoLite2-City.mmdb" # GeoLite2 City database to provide GeoIP information.
|
||||
DataFolder: "data/" # Data folder.
|
||||
|
||||
# Listen defines all IP:Port combinations to listen on. If empty, it will listen on all IPs automatically on available ports.
|
||||
# IPv6 must be in the form "[IPv6]:Port". This setting is only recommended to be set on servers.
|
||||
Listen: []
|
||||
|
||||
# Count of workers to process incoming raw packets. Default 2.
|
||||
ListenWorkers: 0
|
||||
|
||||
# Count of workers to process incoming lite packets. Default 2.
|
||||
ListenWorkersLite: 0
|
||||
|
||||
# AutoUpdateSeedList enables auto update of the seed list.
|
||||
AutoUpdateSeedList: true
|
||||
|
||||
# Initial peer seed list. If AutoUpdateSeedList is enabled then any changes will be overwritten on update.
|
||||
SeedListVersion: 1
|
||||
SeedList:
|
||||
- PublicKey: 02c490e4252bc7608fd55ddd9d7ca4a488ad152f3da6a6c2e9061f4c7e59f5b7f8 # 1.peernet.network
|
||||
Address: ["185.254.123.112:112","[2a0c:1880::112]:112"]
|
||||
- PublicKey: 02b270f6fdac85e76df0d2f7374f33a620ede82542ff7cd62d6934b4c069921322 # 2.peernet.network
|
||||
Address: ["167.99.45.141:112","[2a03:b0c0:2:d0::af0:e001]:112"]
|
||||
- PublicKey: 026836d40a77779b7df5f1a08ca1ef8a83e15edcf0e4d8dc6ed5fc351cb60bd07c # 3.peernet.network
|
||||
Address: ["[2a0b:6580::238:112]:112"]
|
||||
- PublicKey: 0382728d11096efb211de8a9b7bb90f8e248be5572d4f5449f58a2af881b22dbe9 # 4.peernet.network
|
||||
Address: ["178.18.241.68:112","[2a02:c206:2056:9660::1]:112"]
|
||||
- PublicKey: 03174f370cb6d6f361d0511565b6b456a82c3d16b53d6b63b227d76a4f0f2abd2c # 5.peernet.network
|
||||
Address: ["194.233.66.99:112","[2407:3640:2057:5241::1]:112"]
|
||||
|
||||
# Connection settings
|
||||
EnableUPnP: true # Enables support for UPnP.
|
||||
LocalFirewall: false # Indicates that a local firewall may drop unsolicited incoming packets.
|
||||
|
||||
# PortForward specifies an external port that was manually forwarded by the user. All listening IPs must have that same port number forwarded!
|
||||
# If this setting is invalid, it will prohibit other peers from connecting. If set, it automatically disables UPnP.
|
||||
PortForward: 0 # Default not set.
|
||||
|
||||
# Global blockchain cache limits
|
||||
CacheMaxBlockSize: 4096 # Max block size to accept in bytes.
|
||||
CacheMaxBlockCount: 256 # Max block count to cache per peer.
|
||||
LimitTotalRecords: 0 # Record count limit. 0 = unlimited. Max Records * Max Block Size = Size Limit.
|
||||
195
vendor/github.com/PeernetOfficial/core/Config.go
generated
vendored
Normal file
195
vendor/github.com/PeernetOfficial/core/Config.go
generated
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
File Name: Settings.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
_ "embed" // Required for embedding default Config file
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Version is the current core library version
|
||||
const Version = "Alpha 9/01.11.2022"
|
||||
|
||||
// Config defines the minimum required config for a Peernet client.
|
||||
type Config struct {
|
||||
// Locations of important files and folders
|
||||
LogFile string `yaml:"LogFile"` // Log file. It contains informational and error messages.
|
||||
BlockchainMain string `yaml:"BlockchainMain"` // Blockchain main stores the end-users blockchain data. It contains meta data of shared files, profile data, and social interactions.
|
||||
BlockchainGlobal string `yaml:"BlockchainGlobal"` // Blockchain global caches blockchain data from global users. Empty to disable.
|
||||
WarehouseMain string `yaml:"WarehouseMain"` // Warehouse main stores the actual data of files shared by the end-user.
|
||||
SearchIndex string `yaml:"SearchIndex"` // Local search index of blockchain records. Empty to disable.
|
||||
GeoIPDatabase string `yaml:"GeoIPDatabase"` // GeoLite2 City database to provide GeoIP information.
|
||||
DataFolder string `yaml:"DataFolder"` // Data folder.
|
||||
|
||||
// Target for the log messages: 0 = Log file, 1 = Stdout, 2 = Log file + Stdout, 3 = None
|
||||
LogTarget int `yaml:"LogTarget"`
|
||||
|
||||
// Listen settings
|
||||
Listen []string `yaml:"Listen"` // IP:Port combinations
|
||||
ListenWorkers int `yaml:"ListenWorkers"` // Count of workers to process incoming raw packets. Default 2.
|
||||
ListenWorkersLite int `yaml:"ListenWorkersLite"` // Count of workers to process incoming lite packets. Default 2.
|
||||
|
||||
// User specific settings
|
||||
PrivateKey string `yaml:"PrivateKey"` // The Private Key, hex encoded so it can be copied manually
|
||||
|
||||
// Initial peer seed list
|
||||
SeedList []peerSeed `yaml:"SeedList"`
|
||||
AutoUpdateSeedList bool `yaml:"AutoUpdateSeedList"`
|
||||
SeedListVersion int `yaml:"SeedListVersion"`
|
||||
|
||||
// Connection settings
|
||||
EnableUPnP bool `yaml:"EnableUPnP"` // Enables support for UPnP.
|
||||
LocalFirewall bool `yaml:"LocalFirewall"` // Indicates that a local firewall may drop unsolicited incoming packets.
|
||||
|
||||
// PortForward specifies an external port that was manually forwarded by the user. All listening IPs must have that same port number forwarded!
|
||||
// If this setting is invalid, it will prohibit other peers from connecting. If set, it automatically disables UPnP.
|
||||
PortForward uint16 `yaml:"PortForward"`
|
||||
|
||||
// Global blockchain cache limits
|
||||
CacheMaxBlockSize uint64 `yaml:"CacheMaxBlockSize"` // Max block size to accept in bytes.
|
||||
CacheMaxBlockCount uint64 `yaml:"CacheMaxBlockCount"` // Max block count to cache per peer.
|
||||
LimitTotalRecords uint64 `yaml:"LimitTotalRecords"` // Record count limit. 0 = unlimited. Max Records * Max Block Size = Size Limit.
|
||||
}
|
||||
|
||||
// peerSeed is a singl peer entry from the config's seed list
|
||||
type peerSeed struct {
|
||||
PublicKey string `yaml:"PublicKey"` // Public key = peer ID. Hex encoded.
|
||||
Address []string `yaml:"Address"` // IP:Port
|
||||
}
|
||||
|
||||
//go:embed "Config Default.yaml"
|
||||
var ConfigDefault []byte
|
||||
|
||||
// LoadConfig reads the YAML configuration file and unmarshals it into the provided structure.
|
||||
// If the config file does not exist or is empty, it will fall back to the default config which is hardcoded.
|
||||
// Status is of type ExitX.
|
||||
func LoadConfig(Filename string, ConfigOut interface{}) (status int, err error) {
|
||||
var configData []byte
|
||||
|
||||
// check if the file is non existent or empty
|
||||
stats, err := os.Stat(Filename)
|
||||
if err != nil && os.IsNotExist(err) || err == nil && stats.Size() == 0 {
|
||||
configData = ConfigDefault
|
||||
} else if err != nil {
|
||||
return ExitErrorConfigAccess, err
|
||||
} else if configData, err = ioutil.ReadFile(Filename); err != nil {
|
||||
return ExitErrorConfigRead, err
|
||||
}
|
||||
|
||||
// parse the config
|
||||
err = yaml.Unmarshal(configData, ConfigOut)
|
||||
if err != nil {
|
||||
return ExitErrorConfigParse, err
|
||||
}
|
||||
|
||||
return ExitSuccess, nil
|
||||
}
|
||||
|
||||
// SaveConfig stores the config.
|
||||
func SaveConfig(Filename string, Config interface{}) (err error) {
|
||||
data, err := yaml.Marshal(Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(Filename, data, 0666)
|
||||
}
|
||||
|
||||
// SaveConfigs stores the multiple configurations into a single file. They are essentially merged.
|
||||
// It is the callers responsibility to make sure no field collision ensue.
|
||||
func SaveConfigs(Filename string, Configs ...interface{}) (err error) {
|
||||
var dataOut []byte
|
||||
|
||||
for n, config := range Configs {
|
||||
if n > 0 {
|
||||
dataOut = append(dataOut, []byte("\n\n")...)
|
||||
}
|
||||
|
||||
data, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dataOut = append(dataOut, data...)
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(Filename, dataOut, 0666)
|
||||
}
|
||||
|
||||
// SaveConfig stores the current runtime config to file. Any foreign settings not present in the Config structure will be deleted.
|
||||
func (backend *Backend) SaveConfig() {
|
||||
if backend.ConfigClient != nil {
|
||||
if err := SaveConfigs(backend.ConfigFilename, *backend.Config, backend.ConfigClient); err != nil {
|
||||
backend.LogError("SaveConfig", "writing config '%s': %v\n", backend.ConfigFilename, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := SaveConfig(backend.ConfigFilename, *backend.Config); err != nil {
|
||||
backend.LogError("SaveConfig", "writing config '%s': %v\n", backend.ConfigFilename, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (backend *Backend) configUpdateSeedList() {
|
||||
// parse the embedded config
|
||||
var configD Config
|
||||
if err := yaml.Unmarshal(ConfigDefault, &configD); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// check if the seed list needs an update
|
||||
if backend.Config.SeedListVersion < configD.SeedListVersion {
|
||||
backend.Config.SeedList = configD.SeedList
|
||||
backend.Config.SeedListVersion = configD.SeedListVersion
|
||||
backend.SaveConfig()
|
||||
}
|
||||
}
|
||||
|
||||
// InitLog redirects subsequent log messages into the default log file specified in the configuration
|
||||
func (backend *Backend) initLog() (err error) {
|
||||
// create the directory to the log file if specified
|
||||
if directory, _ := path.Split(backend.Config.LogFile); directory != "" {
|
||||
os.MkdirAll(directory, os.ModePerm)
|
||||
}
|
||||
|
||||
logFile, err := os.OpenFile(backend.Config.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) // 666 : All uses can read/write
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//defer logFile.Close() // has to remain open until program closes
|
||||
|
||||
log.SetOutput(logFile)
|
||||
log.Printf("---- Peernet Command-Line Client " + Version + " ----\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Logs an error message.
|
||||
func (backend *Backend) LogError(function, format string, v ...interface{}) {
|
||||
switch backend.Config.LogTarget {
|
||||
case 0:
|
||||
log.Printf("["+function+"] "+format, v...)
|
||||
|
||||
case 1:
|
||||
fmt.Fprintf(backend.Stdout, "["+function+"] "+format, v...)
|
||||
|
||||
case 2:
|
||||
log.Printf("["+function+"] "+format, v...)
|
||||
|
||||
fmt.Fprintf(backend.Stdout, "["+function+"] "+format, v...)
|
||||
|
||||
case 3: // None
|
||||
}
|
||||
|
||||
backend.Filters.LogError(function, format, v)
|
||||
}
|
||||
499
vendor/github.com/PeernetOfficial/core/Connection.go
generated
vendored
Normal file
499
vendor/github.com/PeernetOfficial/core/Connection.go
generated
vendored
Normal file
@@ -0,0 +1,499 @@
|
||||
/*
|
||||
File Name: Connection.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
)
|
||||
|
||||
// Connection is an established connection between a remote IP address and a local network adapter.
|
||||
// New connections may only be created in case of successful INCOMING packets.
|
||||
type Connection struct {
|
||||
Network *Network // Network which received the packet.
|
||||
Address *net.UDPAddr // Address of the remote peer.
|
||||
PortInternal uint16 // Internal listening port reported by remote peer. 0 if no Announcement/Response message was yet received.
|
||||
PortExternal uint16 // External listening port reported by remote peer. 0 if not known by the peer.
|
||||
LastPacketIn time.Time // Last time an incoming packet was received.
|
||||
LastPacketOut time.Time // Last time an outgoing packet was attempted to send.
|
||||
LastPingOut time.Time // Last ping out.
|
||||
Expires time.Time // Inactive connections only: Expiry date. If it does not become active by that date, it will be considered expired and removed.
|
||||
Status int // 0 = Active established connection, 1 = Inactive, 2 = Removed, 3 = Redundant
|
||||
RoundTripTime time.Duration // Full round-trip time of last reply.
|
||||
Firewall bool // Whether the remote peer indicates a potential firewall. This means a Traverse message shall be sent to establish a connection.
|
||||
traversePeer *PeerInfo // Temporary peer that may act as proxy for a Traverse message used for the first packet. This is used to establish this Connection to a peer that is behind a NAT or firewall.
|
||||
backend *Backend
|
||||
}
|
||||
|
||||
// Connection status
|
||||
const (
|
||||
ConnectionActive = iota
|
||||
ConnectionInactive
|
||||
ConnectionRemoved
|
||||
ConnectionRedundant // Same as active. Incoming packets are accepted. Outgoing use only for redundancy. Reduces ping overhead.
|
||||
)
|
||||
|
||||
// Equal checks if the connection was established other the same network adapter using the same IP address. Port is intentionally not checked.
|
||||
func (c *Connection) Equal(other *Connection) bool {
|
||||
return c.Address.IP.Equal(other.Address.IP) && c.Network.address.IP.Equal(other.Network.address.IP)
|
||||
}
|
||||
|
||||
// IsLocal checks if the connection is a local network one (LAN)
|
||||
func (c *Connection) IsLocal() bool {
|
||||
return c != nil && IsIPLocal(c.Address.IP)
|
||||
}
|
||||
|
||||
// IsIPv4 checks if the connection is using IPv4
|
||||
func (c *Connection) IsIPv4() bool {
|
||||
return IsIPv4(c.Address.IP)
|
||||
}
|
||||
|
||||
// IsIPv6 checks if the connection is using IPv6
|
||||
func (c *Connection) IsIPv6() bool {
|
||||
return IsIPv6(c.Address.IP)
|
||||
}
|
||||
|
||||
// IsBehindNAT checks if the remote peer on the connection is likely behind a NAT
|
||||
func (c *Connection) IsBehindNAT() bool {
|
||||
return c.PortInternal > 0 && c.Address.Port != int(c.PortInternal)
|
||||
}
|
||||
|
||||
// IsPortForward checks if the remote peer uses port forwarding on the connection
|
||||
func (c *Connection) IsPortForward() bool {
|
||||
return c.PortExternal > 0
|
||||
}
|
||||
|
||||
// IsVirtual returns true if the peer has not been connected yet. This is the case if another peer responds with peer details, and that peer shall be contacted.
|
||||
func (peer *PeerInfo) IsVirtual() bool {
|
||||
return peer.isVirtual
|
||||
}
|
||||
|
||||
// GetConnections returns the list of connections
|
||||
func (peer *PeerInfo) GetConnections(active bool) (connections []*Connection) {
|
||||
peer.RLock()
|
||||
defer peer.RUnlock()
|
||||
|
||||
if active {
|
||||
return peer.connectionActive
|
||||
}
|
||||
return peer.connectionInactive
|
||||
}
|
||||
|
||||
// IsConnectionActive checks if the peer has an active connection that can be used to send and receive messages
|
||||
func (peer *PeerInfo) IsConnectionActive() bool {
|
||||
peer.RLock()
|
||||
defer peer.RUnlock()
|
||||
|
||||
return len(peer.connectionActive) > 0
|
||||
}
|
||||
|
||||
// IsConnectable checks if the peer is connectable to the given IP parameters.
|
||||
func (peer *PeerInfo) IsConnectable(allowLocal, allowIPv4, allowIPv6 bool) bool {
|
||||
peer.RLock()
|
||||
defer peer.RUnlock()
|
||||
|
||||
// Only 1 active connection must be allowed for being connectable.
|
||||
for _, connection := range peer.connectionActive {
|
||||
// If the internal port is not known, which happens if no Announcement or Response was returned, do not share the peer details.
|
||||
// This can happen if only other messages such as Ping/Pong were received, or the protocol implementation is not compatible. The external port is also likely not available.
|
||||
// In this case sharing the peer would be bad, since the receiving peer could not use internal/external port to detemine the NAT status and port forwarding.
|
||||
if connection.PortInternal == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if IsIPv4(connection.Address.IP) && allowIPv4 || IsIPv6(connection.Address.IP) && allowIPv6 {
|
||||
if !(!allowLocal && connection.IsLocal()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetConnection2Share returns a connection to share. Nil if none.
|
||||
// allowLocal specifies whether it is OK to return local IPs.
|
||||
func (peer *PeerInfo) GetConnection2Share(allowLocal, allowIPv4, allowIPv6 bool) (connection *Connection) {
|
||||
if !allowLocal && !allowIPv4 && !allowIPv6 {
|
||||
return nil
|
||||
}
|
||||
|
||||
peer.RLock()
|
||||
defer peer.RUnlock()
|
||||
|
||||
if peer.connectionLatest != nil && !(!allowLocal && peer.connectionLatest.IsLocal()) &&
|
||||
(IsIPv4(peer.connectionLatest.Address.IP) && allowIPv4 || IsIPv6(peer.connectionLatest.Address.IP) && allowIPv6) && peer.connectionLatest.PortInternal > 0 {
|
||||
return peer.connectionLatest
|
||||
}
|
||||
|
||||
for _, connection := range peer.connectionActive {
|
||||
if (IsIPv4(connection.Address.IP) && allowIPv4 || IsIPv6(connection.Address.IP) && allowIPv6) && !(!allowLocal && connection.IsLocal()) && connection.PortInternal > 0 {
|
||||
return connection
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// registerConnection registers an incoming connection for an existing peer. If new, it will add to the list. If previously inactive, it will elevate.
|
||||
func (peer *PeerInfo) registerConnection(incoming *Connection) (result *Connection) {
|
||||
peer.Lock()
|
||||
defer peer.Unlock()
|
||||
|
||||
// first check if already an active connection to the same IP
|
||||
for _, connection := range peer.connectionActive {
|
||||
if connection.Equal(incoming) {
|
||||
// Connection already established. Verify port and update if necessary.
|
||||
// Some NATs may rotate ports. Some mobile phone providers even rotate IPs which is not detected here.
|
||||
if connection.Address.Port != incoming.Address.Port {
|
||||
connection.Address.Port = incoming.Address.Port
|
||||
}
|
||||
|
||||
connection.Status = ConnectionActive
|
||||
peer.setConnectionLatest(connection)
|
||||
return connection
|
||||
}
|
||||
}
|
||||
|
||||
// if an inactive connection, elevate it to activated one
|
||||
for n, connection := range peer.connectionInactive {
|
||||
if connection.Equal(incoming) {
|
||||
if connection.Address.Port != incoming.Address.Port {
|
||||
connection.Address.Port = incoming.Address.Port
|
||||
}
|
||||
|
||||
// elevate by adding to active and mark as latest active
|
||||
connection.Status = ConnectionActive
|
||||
peer.connectionActive = append(peer.connectionActive, connection)
|
||||
peer.setConnectionLatest(connection)
|
||||
|
||||
// remove from inactive
|
||||
inactiveNew := peer.connectionInactive[:n]
|
||||
if n < len(peer.connectionInactive)-1 {
|
||||
inactiveNew = append(inactiveNew, peer.connectionInactive[n+1:]...)
|
||||
}
|
||||
peer.connectionInactive = inactiveNew
|
||||
|
||||
return connection
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise it is a new connection!
|
||||
peer.connectionActive = append(peer.connectionActive, incoming)
|
||||
peer.setConnectionLatest(incoming)
|
||||
|
||||
peer.Backend.Filters.NewPeerConnection(peer, incoming)
|
||||
|
||||
return incoming
|
||||
}
|
||||
|
||||
// setConnectionLatest updates the latest valid connection to use for sending. All other connections will be changed to redundant, which reduces ping overhead.
|
||||
func (peer *PeerInfo) setConnectionLatest(latest *Connection) {
|
||||
if peer.connectionLatest == latest {
|
||||
return
|
||||
}
|
||||
|
||||
peer.connectionLatest = latest
|
||||
|
||||
for _, connection := range peer.connectionActive {
|
||||
if connection == latest {
|
||||
continue
|
||||
}
|
||||
connection.Status = ConnectionRedundant
|
||||
}
|
||||
}
|
||||
|
||||
// invalidateActiveConnection invalidates an active connection
|
||||
func (peer *PeerInfo) invalidateActiveConnection(input *Connection) {
|
||||
peer.Lock()
|
||||
defer peer.Unlock()
|
||||
|
||||
// Change the status to inactive and start the expiration. If the connection does not become valid by that date, it will be removed.
|
||||
input.Status = ConnectionInactive
|
||||
input.Expires = time.Now().Add(connectionRemove * time.Second)
|
||||
|
||||
// remove from connectionLatest if selected so it won't be used by standard send function
|
||||
if peer.connectionLatest == input {
|
||||
peer.connectionLatest = nil
|
||||
}
|
||||
|
||||
for n, connection := range peer.connectionActive {
|
||||
if connection == input {
|
||||
// add to list of inactive connections
|
||||
peer.connectionInactive = append(peer.connectionInactive, connection)
|
||||
|
||||
// remove from active
|
||||
activeNew := peer.connectionActive[:n]
|
||||
if n < len(peer.connectionActive)-1 {
|
||||
activeNew = append(activeNew, peer.connectionActive[n+1:]...)
|
||||
}
|
||||
peer.connectionActive = activeNew
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removeInactiveConnection removes an inactive connection.
|
||||
func (peer *PeerInfo) removeInactiveConnection(input *Connection) {
|
||||
peer.Lock()
|
||||
defer peer.Unlock()
|
||||
|
||||
input.Status = ConnectionRemoved
|
||||
|
||||
for n, connection := range peer.connectionInactive {
|
||||
if connection == input {
|
||||
|
||||
// remove from inactive
|
||||
inactiveNew := peer.connectionInactive[:n]
|
||||
if n < len(peer.connectionInactive)-1 {
|
||||
inactiveNew = append(inactiveNew, peer.connectionInactive[n+1:]...)
|
||||
}
|
||||
peer.connectionInactive = inactiveNew
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetRTT returns the round-trip time for the most recent active connection. 0 if not available.
|
||||
func (peer *PeerInfo) GetRTT() (rtt time.Duration) {
|
||||
peer.Lock()
|
||||
defer peer.Unlock()
|
||||
|
||||
if peer.connectionLatest != nil && peer.connectionLatest.RoundTripTime > 0 {
|
||||
return peer.connectionLatest.RoundTripTime
|
||||
}
|
||||
|
||||
for _, connection := range peer.connectionActive {
|
||||
if connection.RoundTripTime > 0 {
|
||||
return connection.RoundTripTime
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// IsBehindNAT checks if the peer is behind NAT
|
||||
func (peer *PeerInfo) IsBehindNAT() (result bool) {
|
||||
peer.Lock()
|
||||
defer peer.Unlock()
|
||||
|
||||
// Default is no. Only if a public network reports different connected port vs internal one, NAT is assumed.
|
||||
// This also assumes that all 3rd party clients bind their connection to the outgoing port.
|
||||
// PortInternal is 0 if no Announcement or Response message was received.
|
||||
|
||||
for _, connection := range peer.connectionActive {
|
||||
if connection.IsBehindNAT() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, connection := range peer.connectionInactive {
|
||||
if connection.IsBehindNAT() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsPortForward checks if the peer uses port forwarding
|
||||
func (peer *PeerInfo) IsPortForward() (result bool) {
|
||||
peer.Lock()
|
||||
defer peer.Unlock()
|
||||
|
||||
for _, connection := range peer.connectionActive {
|
||||
if connection.IsPortForward() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, connection := range peer.connectionInactive {
|
||||
if connection.IsPortForward() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsFirewallReported checks if the peer reported to be behind a firewall
|
||||
func (peer *PeerInfo) IsFirewallReported() (result bool) {
|
||||
return peer.Features&(1<<protocol.FeatureFirewall) > 0
|
||||
}
|
||||
|
||||
// ---- sending code ----
|
||||
|
||||
// send sends the packet to the peer on the connection
|
||||
// isFirstPacket indicates whether this is the first packet to an uncontacted peer.
|
||||
func (c *Connection) send(packet *protocol.PacketRaw, receiverPublicKey *btcec.PublicKey, isFirstPacket bool) (err error) {
|
||||
if c == nil {
|
||||
return errors.New("invalid connection")
|
||||
}
|
||||
|
||||
packet.Protocol = protocol.ProtocolVersion
|
||||
packet.SetSelfReportedPorts(c.Network.SelfReportedPorts())
|
||||
|
||||
c.backend.Filters.PacketOut(packet, receiverPublicKey, c)
|
||||
|
||||
raw, err := protocol.PacketEncrypt(c.backend.peerPrivateKey, receiverPublicKey, packet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.LastPacketOut = time.Now()
|
||||
|
||||
err = c.Network.send(c.Address.IP, c.Address.Port, raw)
|
||||
|
||||
// Send Traverse message if the peer is behind a NAT or firewall and this is the first message. Only for Announcement.
|
||||
if err == nil && isFirstPacket && (c.IsBehindNAT() || c.Firewall) && c.traversePeer != nil && packet.Command == protocol.CommandAnnouncement {
|
||||
c.traversePeer.sendTraverse(packet, receiverPublicKey)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// send sends a raw packet to the peer. Only uses active connections.
|
||||
func (peer *PeerInfo) send(packet *protocol.PacketRaw) (err error) {
|
||||
if peer.isVirtual { // special case for peers that were not contacted before
|
||||
for _, address := range peer.targetAddresses {
|
||||
peer.Backend.networks.sendAllNetworks(peer.PublicKey, packet, &net.UDPAddr{IP: address.IP, Port: int(address.Port)}, address.PortInternal, peer.Features&(1<<protocol.FeatureFirewall) > 0, peer.traversePeer, nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(peer.connectionActive) == 0 {
|
||||
return errors.New("no valid connection to peer")
|
||||
}
|
||||
|
||||
// For Traverse: check if no packet has been sent, and none received (i.e. initial contact).
|
||||
// If a packet was already received directly (note: not via incoming traversed message), a valid connection is already established.
|
||||
isFirstPacketOut := atomic.LoadUint64(&peer.StatsPacketSent) == 0 && atomic.LoadUint64(&peer.StatsPacketReceived) == 0
|
||||
|
||||
// always count as one sent packet even if sent via broadcast
|
||||
atomic.AddUint64(&peer.StatsPacketSent, 1)
|
||||
|
||||
// Send out the wire. Use connectionLatest if available.
|
||||
// Failover: If sending fails and there are other connections available, try those. Automatically update connectionLatest if one is successful.
|
||||
// Windows: This works great in case the adapter gets disabled, however, does not detect if the network cable is unplugged.
|
||||
cLatest := peer.connectionLatest
|
||||
if cLatest != nil {
|
||||
if err := cLatest.send(packet, peer.PublicKey, isFirstPacketOut); err == nil {
|
||||
return nil
|
||||
} else if IsNetworkErrorFatal(err) {
|
||||
// Invalid connection, immediately invalidate. Fallback to broadcast to all other active ones.
|
||||
// Windows: A common error when the network adapter is disabled is "wsasendto: The requested address is not valid in its context".
|
||||
peer.invalidateActiveConnection(cLatest)
|
||||
}
|
||||
}
|
||||
|
||||
// If no latest connection available, broadcast on all other available connections.
|
||||
// This might be noisy, but if no latest connection is available it means the last established connection is already considered dead.
|
||||
// The receiver is responsible for incoming deduplication of packets.
|
||||
activeConnections := peer.GetConnections(true)
|
||||
for _, c := range activeConnections {
|
||||
if c == cLatest {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := c.send(packet, peer.PublicKey, isFirstPacketOut); err != nil && IsNetworkErrorFatal(err) {
|
||||
peer.invalidateActiveConnection(c)
|
||||
}
|
||||
}
|
||||
|
||||
return nil // on broadcast no error is known and returned
|
||||
}
|
||||
|
||||
// sendConnection sends a packet to the peer using the specific connection
|
||||
func (peer *PeerInfo) sendConnection(packet *protocol.PacketRaw, connection *Connection) (err error) {
|
||||
isFirstPacketOut := atomic.LoadUint64(&peer.StatsPacketSent) == 0 && atomic.LoadUint64(&peer.StatsPacketReceived) == 0
|
||||
atomic.AddUint64(&peer.StatsPacketSent, 1)
|
||||
|
||||
return connection.send(packet, peer.PublicKey, isFirstPacketOut)
|
||||
}
|
||||
|
||||
// sendAllNetworks sends a raw packet via all networks. It assigns a new sequence for each sent packet.
|
||||
// receiverPortInternal is important for NAT detection and sending the traverse message. Firewall indicates whether the remote peer was reported to be behind a firewall.
|
||||
func (nets *Networks) sendAllNetworks(receiverPublicKey *btcec.PublicKey, packet *protocol.PacketRaw, remote *net.UDPAddr, receiverPortInternal uint16, receiverFirewall bool, traversePeer *PeerInfo, sequenceData interface{}) (err error) {
|
||||
nets.RLock()
|
||||
defer nets.RUnlock()
|
||||
|
||||
networksTarget := nets.networks4
|
||||
if IsIPv6(remote.IP.To16()) {
|
||||
networksTarget = nets.networks6
|
||||
}
|
||||
|
||||
successCount := 0
|
||||
isFirstPacket := true
|
||||
|
||||
for _, network := range networksTarget {
|
||||
// Do not mix link-local unicast targets with non link-local networks (only when iface is known, i.e. not catch all local)
|
||||
if network.iface != nil && remote.IP.IsLinkLocalUnicast() != network.address.IP.IsLinkLocalUnicast() {
|
||||
continue
|
||||
}
|
||||
|
||||
if sequenceData != nil {
|
||||
packet.Sequence = nets.Sequences.ArbitrarySequence(receiverPublicKey, sequenceData).SequenceNumber
|
||||
}
|
||||
err = (&Connection{backend: nets.backend, Network: network, Address: remote, PortInternal: receiverPortInternal, traversePeer: traversePeer, Firewall: receiverFirewall}).send(packet, receiverPublicKey, isFirstPacket)
|
||||
isFirstPacket = false
|
||||
|
||||
if err == nil {
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
|
||||
if successCount == 0 {
|
||||
return errors.New("no successful send")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// send sends a raw packet to the peer. Only uses active connections.
|
||||
func (peer *PeerInfo) sendLite(raw []byte) (err error) {
|
||||
if peer.isVirtual { // special case for peers that were not contacted before
|
||||
return errors.New("cannot send lite packet to virtual peer")
|
||||
} else if len(peer.connectionActive) == 0 {
|
||||
return errors.New("no valid connection to peer")
|
||||
} else if atomic.LoadUint64(&peer.StatsPacketSent) == 0 && atomic.LoadUint64(&peer.StatsPacketReceived) == 0 {
|
||||
return errors.New("uncontacted peer") // A valid connection must have been established.
|
||||
}
|
||||
|
||||
// always count as one sent packet even if sent via broadcast
|
||||
atomic.AddUint64(&peer.StatsPacketSent, 1)
|
||||
|
||||
// Send out the wire. Use connectionLatest if available.
|
||||
cLatest := peer.connectionLatest
|
||||
if cLatest != nil {
|
||||
if err := cLatest.Network.send(cLatest.Address.IP, cLatest.Address.Port, raw); err == nil {
|
||||
return nil
|
||||
} else if IsNetworkErrorFatal(err) {
|
||||
// Invalid connection, immediately invalidate. Fallback to broadcast to all other active ones.
|
||||
// Windows: A common error when the network adapter is disabled is "wsasendto: The requested address is not valid in its context".
|
||||
peer.invalidateActiveConnection(cLatest)
|
||||
}
|
||||
}
|
||||
|
||||
// If no latest connection available, broadcast on all other available connections.
|
||||
for _, c := range peer.GetConnections(true) {
|
||||
if c == cLatest {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := c.Network.send(c.Address.IP, c.Address.Port, raw); err != nil && IsNetworkErrorFatal(err) {
|
||||
peer.invalidateActiveConnection(c)
|
||||
}
|
||||
}
|
||||
|
||||
return nil // on broadcast no error is known and returned
|
||||
}
|
||||
48
vendor/github.com/PeernetOfficial/core/DHT Store.go
generated
vendored
Normal file
48
vendor/github.com/PeernetOfficial/core/DHT Store.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
File Name: DHT Store.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
"github.com/PeernetOfficial/core/store"
|
||||
)
|
||||
|
||||
// TODO: Via descriptors, files stored by other peers
|
||||
|
||||
func (backend *Backend) initStore() {
|
||||
backend.dhtStore = store.NewMemoryStore()
|
||||
}
|
||||
|
||||
// announcementGetData returns data for an announcement
|
||||
func (peer *PeerInfo) announcementGetData(hash []byte) (stored bool, data []byte) {
|
||||
// TODO: Create RetrieveIfSize to prevent files larger than EmbeddedFileSizeMax from being loaded
|
||||
data, found := peer.Backend.dhtStore.Get(hash)
|
||||
if !found {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if len(data) <= protocol.EmbeddedFileSizeMax {
|
||||
return true, data
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// announcementStore handles an incoming announcement by another peer about storing data
|
||||
func (peer *PeerInfo) announcementStore(records []protocol.InfoStore) {
|
||||
// TODO: Only store the other peers data if certain conditions are met:
|
||||
// - enough storage available
|
||||
// - not exceeding record count per peer
|
||||
// - not exceeding total record count limit
|
||||
// - not exceeding record count per CIDR
|
||||
//for _, record := range records {
|
||||
//fmt.Printf("Remote node %s stores hash %s (size %d type %d)\n", hex.EncodeToString(peer.NodeID), hex.EncodeToString(record.ID.Hash), record.Size, record.Type)
|
||||
//Warehouse.Store(record.ID.Hash, )
|
||||
// TODO: Request data from remote node.
|
||||
//peer.sendAnnouncement(false, false, nil, []KeyHash{record.ID}, nil, nil)
|
||||
//}
|
||||
}
|
||||
24
vendor/github.com/PeernetOfficial/core/Exit.go
generated
vendored
Normal file
24
vendor/github.com/PeernetOfficial/core/Exit.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
File Name: Exit.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
// Exit codes signal why the application exited. These are universal between clients developed by the Peernet organization.
|
||||
// Clients are encouraged to log additional details in a log file. 3rd party clients may define additional exit codes.
|
||||
const (
|
||||
ExitSuccess = 0 // This is actually never used.
|
||||
ExitErrorConfigAccess = 1 // Error accessing the config file.
|
||||
ExitErrorConfigRead = 2 // Error reading the config file.
|
||||
ExitErrorConfigParse = 3 // Error parsing the config file.
|
||||
ExitErrorLogInit = 4 // Error initializing log file.
|
||||
ExitParamWebapiInvalid = 5 // Parameter for webapi is invalid.
|
||||
ExitPrivateKeyCorrupt = 6 // Private key is corrupt.
|
||||
ExitPrivateKeyCreate = 7 // Cannot create a new private key.
|
||||
ExitBlockchainCorrupt = 8 // Blockchain is corrupt.
|
||||
ExitGraceful = 9 // Graceful shutdown.
|
||||
ExitParamApiKeyInvalid = 10 // API key parameter is invalid.
|
||||
STATUS_CONTROL_C_EXIT = 0xC000013A // The application terminated as a result of a CTRL+C. This is a Windows NTSTATUS value.
|
||||
)
|
||||
49
vendor/github.com/PeernetOfficial/core/File Formats.go
generated
vendored
Normal file
49
vendor/github.com/PeernetOfficial/core/File Formats.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
File Name: File Formats.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Definition of all recognized file formats. This file is likely being updated more frequently than regular code.
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
// General content types of data.
|
||||
const (
|
||||
TypeBinary = iota // Binary/unspecified
|
||||
TypeText // Plain text
|
||||
TypePicture // Picture of any format
|
||||
TypeVideo // Video
|
||||
TypeAudio // Audio
|
||||
TypeDocument // Any document file, including office documents, PDFs, power point, spreadsheets
|
||||
TypeExecutable // Any executable file, OS independent
|
||||
TypeContainer // Container files like ZIP, RAR, TAR, ISO
|
||||
TypeCompressed // Compressed files like GZ, BZ
|
||||
TypeFolder // Virtual folder
|
||||
TypeEbook // Ebook
|
||||
)
|
||||
|
||||
// File formats. New ones may be added to the list as required.
|
||||
const (
|
||||
FormatBinary = iota // Binary/unspecified
|
||||
FormatPDF // PDF document
|
||||
FormatWord // Word document
|
||||
FormatExcel // Excel
|
||||
FormatPowerpoint // Powerpoint
|
||||
FormatPicture // Pictures (including GIF, excluding icons)
|
||||
FormatAudio // Audio files
|
||||
FormatVideo // Video files
|
||||
FormatContainer // Compressed files including ZIP, RAR, TAR and others
|
||||
FormatHTML // HTML file
|
||||
FormatText // Text file
|
||||
FormatEbook // Ebook file
|
||||
FormatCompressed // Compressed file
|
||||
FormatDatabase // Database file
|
||||
FormatEmail // Single email
|
||||
FormatCSV // CSV file
|
||||
FormatFolder // Virtual folder
|
||||
FormatExecutable // Executable file
|
||||
FormatInstaller // Installer
|
||||
FormatAPK // APK
|
||||
FormatISO // ISO
|
||||
)
|
||||
164
vendor/github.com/PeernetOfficial/core/Filter.go
generated
vendored
Normal file
164
vendor/github.com/PeernetOfficial/core/Filter.go
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
File Name: Filter.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Filters allow the caller to intercept events. The filter functions must not modify any data.
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/PeernetOfficial/core/blockchain"
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
"github.com/PeernetOfficial/core/dht"
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Filters contains all functions to install the hook. Use nil for unused.
|
||||
// The functions are called sequentially and block execution; if the filter takes a long time it should start a Go routine.
|
||||
type Filters struct {
|
||||
// NewPeer is called every time a new peer, that is one that is not currently in the peer list.
|
||||
// Note that peers might be removed from peer lists and reappear quickly, i.e. this function may be called multiple times for the same peers.
|
||||
// The filter must maintain its own map of unique peer IDs if actual uniqueness of new peers is desired.
|
||||
NewPeer func(peer *PeerInfo, connection *Connection)
|
||||
|
||||
// NewPeerConnection is called for each new established connection to a peer. Note that connections might be dropped and reconnected at anytime.
|
||||
NewPeerConnection func(peer *PeerInfo, connection *Connection)
|
||||
|
||||
// LogError is called for any error.
|
||||
LogError func(function, format string, v ...interface{})
|
||||
|
||||
// DHTSearchStatus is called with updates of searches in the DHT. It allows to see the live progress of searches.
|
||||
DHTSearchStatus func(client *dht.SearchClient, function, format string, v ...interface{})
|
||||
|
||||
// IncomingRequest receives all incoming information requests. The action field is set accordingly.
|
||||
IncomingRequest func(peer *PeerInfo, Action int, Key []byte, Info interface{})
|
||||
|
||||
// PacketIn is a low-level filter for incoming packets after they are decrypted.
|
||||
// Traverse messages are not covered.
|
||||
PacketIn func(packet *protocol.PacketRaw, senderPublicKey *btcec.PublicKey, connection *Connection)
|
||||
|
||||
// PacketOut is a low-level filter for outgoing packets before they are encrypted.
|
||||
// IPv4 broadcast, IPv6 multicast, and Traverse messages are not covered.
|
||||
PacketOut func(packet *protocol.PacketRaw, receiverPublicKey *btcec.PublicKey, connection *Connection)
|
||||
|
||||
// MessageIn is a high-level filter for decoded incoming messages. message is of type nil, MessageAnnouncement, MessageResponse, or MessageTraverse
|
||||
MessageIn func(peer *PeerInfo, raw *protocol.MessageRaw, message interface{})
|
||||
|
||||
// MessageOutAnnouncement is a high-level filter for outgoing announcements. Peer is nil on first contact.
|
||||
// Broadcast and Multicast messages are not covered.
|
||||
MessageOutAnnouncement func(receiverPublicKey *btcec.PublicKey, peer *PeerInfo, packet *protocol.PacketRaw, findSelf bool, findPeer []protocol.KeyHash, findValue []protocol.KeyHash, files []protocol.InfoStore)
|
||||
|
||||
// MessageOutResponse is a high-level filter for outgoing responses.
|
||||
MessageOutResponse func(peer *PeerInfo, packet *protocol.PacketRaw, hash2Peers []protocol.Hash2Peer, filesEmbed []protocol.EmbeddedFileData, hashesNotFound [][]byte)
|
||||
|
||||
// MessageOutTraverse is a high-level filter for outgoing traverse messages.
|
||||
MessageOutTraverse func(peer *PeerInfo, packet *protocol.PacketRaw, embeddedPacket *protocol.PacketRaw, receiverEnd *btcec.PublicKey)
|
||||
|
||||
// MessageOutPing is a high-level filter for outgoing pings.
|
||||
MessageOutPing func(peer *PeerInfo, packet *protocol.PacketRaw, connection *Connection)
|
||||
|
||||
// MessageOutPong is a high-level filter for outgoing pongs.
|
||||
MessageOutPong func(peer *PeerInfo, packet *protocol.PacketRaw)
|
||||
|
||||
// Called when the statistics change of a single blockchain in the cache. Must be set on init.
|
||||
GlobalBlockchainCacheStatistic func(multi *blockchain.MultiStore, header *blockchain.MultiBlockchainHeader, statsOld blockchain.BlockchainStats)
|
||||
|
||||
// Called after a blockchain is deleted from the blockchain cache. The header reflects the status before deletion. Must be set on init.
|
||||
GlobalBlockchainCacheDelete func(multi *blockchain.MultiStore, header *blockchain.MultiBlockchainHeader)
|
||||
}
|
||||
|
||||
func (backend *Backend) initFilters() {
|
||||
// Set default filters to blank functions so they can be safely called without constant nil checks.
|
||||
// Only if not already set before init.
|
||||
|
||||
if backend.Filters.NewPeer == nil {
|
||||
backend.Filters.NewPeer = func(peer *PeerInfo, connection *Connection) {}
|
||||
}
|
||||
if backend.Filters.NewPeerConnection == nil {
|
||||
backend.Filters.NewPeerConnection = func(peer *PeerInfo, connection *Connection) {}
|
||||
}
|
||||
if backend.Filters.DHTSearchStatus == nil {
|
||||
backend.Filters.DHTSearchStatus = func(client *dht.SearchClient, function, format string, v ...interface{}) {}
|
||||
}
|
||||
if backend.Filters.LogError == nil {
|
||||
backend.Filters.LogError = func(function, format string, v ...interface{}) {}
|
||||
}
|
||||
if backend.Filters.IncomingRequest == nil {
|
||||
backend.Filters.IncomingRequest = func(peer *PeerInfo, Action int, Key []byte, Info interface{}) {}
|
||||
}
|
||||
if backend.Filters.PacketIn == nil {
|
||||
backend.Filters.PacketIn = func(packet *protocol.PacketRaw, senderPublicKey *btcec.PublicKey, c *Connection) {}
|
||||
}
|
||||
if backend.Filters.PacketOut == nil {
|
||||
backend.Filters.PacketOut = func(packet *protocol.PacketRaw, receiverPublicKey *btcec.PublicKey, c *Connection) {}
|
||||
}
|
||||
if backend.Filters.MessageIn == nil {
|
||||
backend.Filters.MessageIn = func(peer *PeerInfo, raw *protocol.MessageRaw, message interface{}) {}
|
||||
}
|
||||
if backend.Filters.MessageOutAnnouncement == nil {
|
||||
backend.Filters.MessageOutAnnouncement = func(receiverPublicKey *btcec.PublicKey, peer *PeerInfo, packet *protocol.PacketRaw, findSelf bool, findPeer []protocol.KeyHash, findValue []protocol.KeyHash, files []protocol.InfoStore) {
|
||||
}
|
||||
}
|
||||
if backend.Filters.MessageOutResponse == nil {
|
||||
backend.Filters.MessageOutResponse = func(peer *PeerInfo, packet *protocol.PacketRaw, hash2Peers []protocol.Hash2Peer, filesEmbed []protocol.EmbeddedFileData, hashesNotFound [][]byte) {
|
||||
}
|
||||
}
|
||||
if backend.Filters.MessageOutTraverse == nil {
|
||||
backend.Filters.MessageOutTraverse = func(peer *PeerInfo, packet *protocol.PacketRaw, embeddedPacket *protocol.PacketRaw, receiverEnd *btcec.PublicKey) {
|
||||
}
|
||||
}
|
||||
if backend.Filters.MessageOutPing == nil {
|
||||
backend.Filters.MessageOutPing = func(peer *PeerInfo, packet *protocol.PacketRaw, connection *Connection) {}
|
||||
}
|
||||
if backend.Filters.MessageOutPong == nil {
|
||||
backend.Filters.MessageOutPong = func(peer *PeerInfo, packet *protocol.PacketRaw) {}
|
||||
}
|
||||
}
|
||||
|
||||
// MultiWriter code that allows to subscribe/unsubscribe.
|
||||
type multiWriter struct {
|
||||
writers map[uuid.UUID]io.Writer
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// Creates a new writer that duplicates its writes to all the subscribed writers.
|
||||
// Each write is written to each subscribed writer, one at a time. If any writer returns an error, the entire write operation continues.
|
||||
func newMultiWriter() *multiWriter {
|
||||
return &multiWriter{writers: make(map[uuid.UUID]io.Writer)}
|
||||
}
|
||||
|
||||
// Subscribe a new writer to the list of writers
|
||||
func (m *multiWriter) Subscribe(writer io.Writer) (id uuid.UUID) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
id = uuid.New()
|
||||
m.writers[id] = writer
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
// Unsubscribe a writer from the list of writers
|
||||
func (m *multiWriter) Unsubscribe(id uuid.UUID) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
delete(m.writers, id)
|
||||
}
|
||||
|
||||
// Write a slice of byte to each of the subscribed writers. It will not return any errors.
|
||||
func (m *multiWriter) Write(p []byte) (n int, err error) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
for _, w := range m.writers {
|
||||
w.Write(p)
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
203
vendor/github.com/PeernetOfficial/core/Kademlia.go
generated
vendored
Normal file
203
vendor/github.com/PeernetOfficial/core/Kademlia.go
generated
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
File Name: Kademlia.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/dht"
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
)
|
||||
|
||||
const alpha = 5 // Count of nodes to be contacted in parallel for finding a key
|
||||
const bucketSize = 20 // Count of nodes per bucket
|
||||
|
||||
func (backend *Backend) initKademlia() {
|
||||
backend.nodesDHT = dht.NewDHT(&dht.Node{ID: backend.nodeID}, 256, bucketSize, alpha)
|
||||
|
||||
// ShouldEvict determines whether node 1 shall be evicted in favor of node 2
|
||||
backend.nodesDHT.ShouldEvict = func(node1, node2 *dht.Node) bool {
|
||||
rttOld := node1.Info.(*PeerInfo).GetRTT()
|
||||
rttNew := node2.Info.(*PeerInfo).GetRTT()
|
||||
|
||||
// evict the old node if the new one has a faster ping time
|
||||
if rttOld == 0 { // old one has no recent RTT (happens if all connections are inactive)?
|
||||
return true
|
||||
} else if rttNew > 0 {
|
||||
// If new RTT is smaller, evict old one.
|
||||
return rttNew < rttOld
|
||||
}
|
||||
|
||||
// If here, none has a RTT. Keep the closer (by distance) one.
|
||||
return backend.nodesDHT.IsNodeCloser(node1.ID, node2.ID)
|
||||
}
|
||||
|
||||
// SendRequestStore sends a store message to the remote node. I.e. asking it to store the given key-value
|
||||
backend.nodesDHT.SendRequestStore = func(node *dht.Node, key []byte, dataSize uint64) {
|
||||
node.Info.(*PeerInfo).sendAnnouncementStore(key, dataSize)
|
||||
}
|
||||
|
||||
// SendRequestFindNode sends an information request to find a particular node. nodes are the nodes to send the request to.
|
||||
backend.nodesDHT.SendRequestFindNode = func(request *dht.InformationRequest) {
|
||||
for _, node := range request.Nodes {
|
||||
node.Info.(*PeerInfo).sendAnnouncementFindNode(request)
|
||||
}
|
||||
}
|
||||
|
||||
// SendRequestFindValue sends an information request to find data. nodes are the nodes to send the request to.
|
||||
backend.nodesDHT.SendRequestFindValue = func(request *dht.InformationRequest) {
|
||||
for _, node := range request.Nodes {
|
||||
node.Info.(*PeerInfo).sendAnnouncementFindValue(request)
|
||||
}
|
||||
}
|
||||
|
||||
backend.nodesDHT.FilterSearchStatus = backend.Filters.DHTSearchStatus
|
||||
}
|
||||
|
||||
// autoBucketRefresh refreshes buckets every 5 minutes to meet the alpha nodes per bucket target. Force full refresh every hour.
|
||||
func (backend *Backend) autoBucketRefresh() {
|
||||
for minute := 5; ; minute += 5 {
|
||||
time.Sleep(time.Minute * 5)
|
||||
|
||||
target := alpha
|
||||
if minute%60 == 0 {
|
||||
target = 0
|
||||
}
|
||||
|
||||
backend.nodesDHT.RefreshBuckets(target)
|
||||
}
|
||||
}
|
||||
|
||||
// bootstrapKademlia bootstraps the Kademlia bucket list
|
||||
func (backend *Backend) bootstrapKademlia() {
|
||||
monitor := make(chan *PeerInfo)
|
||||
backend.registerPeerMonitor(monitor)
|
||||
|
||||
// Wait until there are at least 2 peers connected.
|
||||
for {
|
||||
<-monitor
|
||||
if backend.nodesDHT.NumNodes() >= 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
backend.unregisterPeerMonitor(monitor)
|
||||
|
||||
// Refresh every 10 seconds 3 times
|
||||
for n := 0; n < 3; n++ {
|
||||
backend.nodesDHT.RefreshBuckets(alpha)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// Future sendAnnouncementX: If it detects that announcements are sent out to the same peer within 50ms it should activate a wait-and-group scheme.
|
||||
|
||||
func (peer *PeerInfo) sendAnnouncementFindNode(request *dht.InformationRequest) {
|
||||
// If the key is self, send it as FIND_SELF
|
||||
if bytes.Equal(request.Key, peer.Backend.nodeID) {
|
||||
peer.sendAnnouncement(false, true, nil, nil, nil, request)
|
||||
} else {
|
||||
peer.sendAnnouncement(false, false, []protocol.KeyHash{{Hash: request.Key}}, nil, nil, request)
|
||||
}
|
||||
}
|
||||
|
||||
func (peer *PeerInfo) sendAnnouncementFindValue(request *dht.InformationRequest) {
|
||||
|
||||
findSelf := false
|
||||
var findPeer []protocol.KeyHash
|
||||
var findValue []protocol.KeyHash
|
||||
|
||||
findValue = append(findValue, protocol.KeyHash{Hash: request.Key})
|
||||
|
||||
peer.sendAnnouncement(false, findSelf, findPeer, findValue, nil, request)
|
||||
}
|
||||
|
||||
func (peer *PeerInfo) sendAnnouncementStore(fileHash []byte, fileSize uint64) {
|
||||
peer.sendAnnouncement(false, false, nil, nil, []protocol.InfoStore{{ID: protocol.KeyHash{Hash: fileHash}, Size: fileSize, Type: 0}}, nil)
|
||||
}
|
||||
|
||||
// ---- CORE DATA FUNCTIONS ----
|
||||
|
||||
// Data2Hash returns the hash for the data
|
||||
func Data2Hash(data []byte) (hash []byte) {
|
||||
return protocol.HashData(data)
|
||||
}
|
||||
|
||||
// GetData returns the requested data. It checks first the local store and then tries via DHT.
|
||||
func (backend *Backend) GetData(hash []byte) (data []byte, senderNodeID []byte, found bool) {
|
||||
if data, found = backend.GetDataLocal(hash); found {
|
||||
return data, backend.nodeID, found
|
||||
}
|
||||
|
||||
return backend.GetDataDHT(hash)
|
||||
}
|
||||
|
||||
// GetDataLocal returns data from the local warehouse.
|
||||
func (backend *Backend) GetDataLocal(hash []byte) (data []byte, found bool) {
|
||||
return backend.dhtStore.Get(hash)
|
||||
}
|
||||
|
||||
// GetDataDHT requests data via DHT
|
||||
func (backend *Backend) GetDataDHT(hash []byte) (data []byte, senderNodeID []byte, found bool) {
|
||||
data, senderNodeID, found, _ = backend.nodesDHT.Get(hash)
|
||||
return data, senderNodeID, found
|
||||
}
|
||||
|
||||
// StoreDataLocal stores data into the local warehouse.
|
||||
func (backend *Backend) StoreDataLocal(data []byte) error {
|
||||
key := protocol.HashData(data)
|
||||
return backend.dhtStore.Set(key, data)
|
||||
}
|
||||
|
||||
// StoreDataDHT stores data locally and informs closestCount peers in the DHT about it.
|
||||
// Remote peers may choose to keep a record (in case another peers asks) or mirror the full data.
|
||||
func (backend *Backend) StoreDataDHT(data []byte, closestCount int) error {
|
||||
key := protocol.HashData(data)
|
||||
if err := backend.dhtStore.Set(key, data); err != nil {
|
||||
return err
|
||||
}
|
||||
return backend.nodesDHT.Store(key, uint64(len(data)), closestCount)
|
||||
}
|
||||
|
||||
// ---- NODE FUNCTIONS ----
|
||||
|
||||
// IsNodeContact checks if the node is a contact in the local DHT routing table
|
||||
func (backend *Backend) IsNodeContact(nodeID []byte) (node *dht.Node, peer *PeerInfo) {
|
||||
node = backend.nodesDHT.IsNodeContact(nodeID)
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return node, node.Info.(*PeerInfo)
|
||||
}
|
||||
|
||||
// FindNode finds a node via the DHT
|
||||
func (backend *Backend) FindNode(nodeID []byte, Timeout time.Duration) (node *dht.Node, peer *PeerInfo, err error) {
|
||||
// first check if in mirrored node list
|
||||
if peer = backend.NodelistLookup(nodeID); peer != nil {
|
||||
return nil, peer, nil
|
||||
}
|
||||
|
||||
// Search the node via DHT.
|
||||
node, err = backend.nodesDHT.FindNode(nodeID)
|
||||
if node == nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return node, node.Info.(*PeerInfo), err
|
||||
}
|
||||
|
||||
// ---- Asynchronous Search ----
|
||||
|
||||
// AsyncSearch creates an async search for the given key in the DHT.
|
||||
// Timeout is the total time the search may take, covering all information requests. TimeoutIR is the time an information request may take.
|
||||
// Alpha is the number of concurrent requests that will be performed.
|
||||
func (backend *Backend) AsyncSearch(Action int, Key []byte, Timeout, TimeoutIR time.Duration, Alpha int) (client *dht.SearchClient) {
|
||||
return backend.nodesDHT.NewSearch(Action, Key, Timeout, TimeoutIR, Alpha)
|
||||
}
|
||||
7
vendor/github.com/PeernetOfficial/core/LICENSE
generated
vendored
Normal file
7
vendor/github.com/PeernetOfficial/core/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright (c) 2021 Peernet s.r.o.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
154
vendor/github.com/PeernetOfficial/core/Message Send.go
generated
vendored
Normal file
154
vendor/github.com/PeernetOfficial/core/Message Send.go
generated
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
File Name: Message Send.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// pingConnection sends a ping to the target peer via the specified connection
|
||||
func (peer *PeerInfo) pingConnection(connection *Connection) {
|
||||
raw := &protocol.PacketRaw{Command: protocol.CommandPing, Sequence: peer.Backend.networks.Sequences.NewSequence(peer.PublicKey, &peer.messageSequence, nil).SequenceNumber}
|
||||
peer.Backend.Filters.MessageOutPing(peer, raw, connection)
|
||||
|
||||
err := peer.sendConnection(raw, connection)
|
||||
connection.LastPingOut = time.Now()
|
||||
|
||||
if (connection.Status == ConnectionActive || connection.Status == ConnectionRedundant) && IsNetworkErrorFatal(err) {
|
||||
peer.invalidateActiveConnection(connection)
|
||||
}
|
||||
}
|
||||
|
||||
// pingConnectionAnnouncement sends an empty announcement via a particular connection.
|
||||
// It has the same effect as ping, but returns the blockchain version and height of the other peer in the Response message, which may be useful for keeping the global blockchain cache up to date.
|
||||
func (peer *PeerInfo) pingConnectionAnnouncement(connection *Connection) {
|
||||
_, blockchainHeight, blockchainVersion := peer.Backend.UserBlockchain.Header()
|
||||
packets := protocol.EncodeAnnouncement(false, false, nil, nil, nil, peer.Backend.FeatureSupport(), blockchainHeight, blockchainVersion, peer.Backend.userAgent)
|
||||
if len(packets) != 1 {
|
||||
return
|
||||
}
|
||||
|
||||
raw := &protocol.PacketRaw{Command: protocol.CommandAnnouncement, Payload: packets[0], Sequence: peer.Backend.networks.Sequences.NewSequence(peer.PublicKey, &peer.messageSequence, nil).SequenceNumber}
|
||||
peer.Backend.Filters.MessageOutAnnouncement(peer.PublicKey, peer, raw, false, nil, nil, nil)
|
||||
|
||||
err := peer.sendConnection(raw, connection)
|
||||
connection.LastPingOut = time.Now()
|
||||
|
||||
if (connection.Status == ConnectionActive || connection.Status == ConnectionRedundant) && IsNetworkErrorFatal(err) {
|
||||
peer.invalidateActiveConnection(connection)
|
||||
}
|
||||
}
|
||||
|
||||
// Ping sends a ping. This function exists only for debugging purposes, it should not be used normally.
|
||||
// This ping is not used for uptime detection and the LastPingOut time in connections is not set.
|
||||
func (peer *PeerInfo) Ping() {
|
||||
peer.send(&protocol.PacketRaw{Command: protocol.CommandPing, Sequence: peer.Backend.networks.Sequences.NewSequence(peer.PublicKey, &peer.messageSequence, nil).SequenceNumber})
|
||||
}
|
||||
|
||||
// Chat sends a text message
|
||||
func (peer *PeerInfo) Chat(text string) {
|
||||
peer.send(&protocol.PacketRaw{Command: protocol.CommandChat, Payload: []byte(text)})
|
||||
}
|
||||
|
||||
// sendAnnouncement sends the announcement message. It acquires a new sequence for each message.
|
||||
func (peer *PeerInfo) sendAnnouncement(sendUA, findSelf bool, findPeer []protocol.KeyHash, findValue []protocol.KeyHash, files []protocol.InfoStore, sequenceData interface{}) {
|
||||
_, blockchainHeight, blockchainVersion := peer.Backend.UserBlockchain.Header()
|
||||
packets := protocol.EncodeAnnouncement(sendUA, findSelf, findPeer, findValue, files, peer.Backend.FeatureSupport(), blockchainHeight, blockchainVersion, peer.Backend.userAgent)
|
||||
|
||||
for _, packet := range packets {
|
||||
raw := &protocol.PacketRaw{Command: protocol.CommandAnnouncement, Payload: packet, Sequence: peer.Backend.networks.Sequences.NewSequence(peer.PublicKey, &peer.messageSequence, sequenceData).SequenceNumber}
|
||||
peer.Backend.Filters.MessageOutAnnouncement(peer.PublicKey, peer, raw, findSelf, findPeer, findValue, files)
|
||||
peer.send(raw)
|
||||
}
|
||||
}
|
||||
|
||||
// sendResponse sends the response message
|
||||
func (peer *PeerInfo) sendResponse(sequence uint32, sendUA bool, hash2Peers []protocol.Hash2Peer, filesEmbed []protocol.EmbeddedFileData, hashesNotFound [][]byte) (err error) {
|
||||
_, blockchainHeight, blockchainVersion := peer.Backend.UserBlockchain.Header()
|
||||
packets, err := protocol.EncodeResponse(sendUA, hash2Peers, filesEmbed, hashesNotFound, peer.Backend.FeatureSupport(), blockchainHeight, blockchainVersion, peer.Backend.userAgent)
|
||||
|
||||
for _, packet := range packets {
|
||||
raw := &protocol.PacketRaw{Command: protocol.CommandResponse, Payload: packet, Sequence: sequence}
|
||||
peer.Backend.Filters.MessageOutResponse(peer, raw, hash2Peers, filesEmbed, hashesNotFound)
|
||||
peer.send(raw)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// sendTraverse sends a traverse message
|
||||
func (peer *PeerInfo) sendTraverse(packet *protocol.PacketRaw, receiverEnd *btcec.PublicKey) (err error) {
|
||||
packet.Protocol = protocol.ProtocolVersion
|
||||
// self-reported ports are not set, as this isn't sent via a specific network but a relay
|
||||
//packet.SetSelfReportedPorts(c.Network.SelfReportedPorts())
|
||||
|
||||
embeddedPacketRaw, err := protocol.PacketEncrypt(peer.Backend.peerPrivateKey, receiverEnd, packet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
packetRaw, err := protocol.EncodeTraverse(peer.Backend.peerPrivateKey, embeddedPacketRaw, receiverEnd, peer.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
raw := &protocol.PacketRaw{Command: protocol.CommandTraverse, Payload: packetRaw}
|
||||
|
||||
peer.Backend.Filters.MessageOutTraverse(peer, raw, packet, receiverEnd)
|
||||
|
||||
return peer.send(raw)
|
||||
}
|
||||
|
||||
// sendTransfer sends a transfer message
|
||||
func (peer *PeerInfo) sendTransfer(data []byte, control, transferProtocol uint8, hash []byte, offset, limit uint64, sequenceNumber uint32, transferID uuid.UUID, isLite bool) (err error) {
|
||||
// Send optionally as lite packet. This bypasses the signing overhead of regular Peernet packets which is CPU intensive and a bottleneck.
|
||||
if control == protocol.TransferControlActive && isLite {
|
||||
raw, err := protocol.PacketLiteEncode(transferID, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return peer.sendLite(raw)
|
||||
}
|
||||
|
||||
packetRaw, err := protocol.EncodeTransfer(peer.Backend.peerPrivateKey, data, control, transferProtocol, hash, offset, limit, transferID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
raw := &protocol.PacketRaw{Command: protocol.CommandTransfer, Payload: packetRaw, Sequence: sequenceNumber}
|
||||
|
||||
//Filters.MessageOutTransfer(peer, raw, control, transferProtocol, hash, offset, limit)
|
||||
|
||||
return peer.send(raw)
|
||||
}
|
||||
|
||||
// sendGetBlock sends a get block message
|
||||
func (peer *PeerInfo) sendGetBlock(data []byte, control uint8, blockchainPublicKey *btcec.PublicKey, limitBlockCount, maxBlockSize uint64, targetBlocks []protocol.BlockRange, sequenceNumber uint32, transferID uuid.UUID, isLite bool) (err error) {
|
||||
// Send optionally as lite packet. This bypasses the signing overhead of regular Peernet packets which is CPU intensive and a bottleneck.
|
||||
if control == protocol.GetBlockControlActive && isLite {
|
||||
raw, err := protocol.PacketLiteEncode(transferID, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return peer.sendLite(raw)
|
||||
}
|
||||
|
||||
packetRaw, err := protocol.EncodeGetBlock(peer.Backend.peerPrivateKey, data, control, blockchainPublicKey, limitBlockCount, maxBlockSize, targetBlocks, transferID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
raw := &protocol.PacketRaw{Command: protocol.CommandGetBlock, Payload: packetRaw, Sequence: sequenceNumber}
|
||||
|
||||
//Filters.MessageOutGetBlock(peer, raw, control, )
|
||||
|
||||
return peer.send(raw)
|
||||
}
|
||||
275
vendor/github.com/PeernetOfficial/core/Network Detection.go
generated
vendored
Normal file
275
vendor/github.com/PeernetOfficial/core/Network Detection.go
generated
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
File Name: Network Detection.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FindInterfaceByIP finds an interface based on the IP. The IP must be available at the interface.
|
||||
func FindInterfaceByIP(ip net.IP) (iface *net.Interface, ipnet *net.IPNet) {
|
||||
interfaceList, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// iterate through all interfaces
|
||||
for _, ifaceSingle := range interfaceList {
|
||||
addresses, err := ifaceSingle.Addrs()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// iterate through all IPs of the interfaces
|
||||
for _, address := range addresses {
|
||||
addressIP := address.(*net.IPNet).IP
|
||||
|
||||
if addressIP.Equal(ip) {
|
||||
return &ifaceSingle, address.(*net.IPNet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// NetworkListIPs returns a list of all IPs
|
||||
func NetworkListIPs() (IPs []net.IP, err error) {
|
||||
|
||||
interfaceList, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// iterate through all interfaces
|
||||
for _, ifaceSingle := range interfaceList {
|
||||
addresses, err := ifaceSingle.Addrs()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// iterate through all IPs of the interfaces
|
||||
for _, address := range addresses {
|
||||
addressIP := address.(*net.IPNet).IP
|
||||
IPs = append(IPs, addressIP)
|
||||
}
|
||||
}
|
||||
|
||||
return IPs, nil
|
||||
}
|
||||
|
||||
// IsIPv4 checks if an IP address is IPv4
|
||||
func IsIPv4(IP net.IP) bool {
|
||||
return IP.To4() != nil
|
||||
}
|
||||
|
||||
// IsIPv6 checks if an IP address is IPv6
|
||||
func IsIPv6(IP net.IP) bool {
|
||||
return IP.To4() == nil && IP.To16() != nil
|
||||
}
|
||||
|
||||
// IsNetworkErrorFatal checks if a network error indicates a broken connection.
|
||||
// Not every network error indicates a broken connection. This function prevents from over-dropping connections.
|
||||
func IsNetworkErrorFatal(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Windows: A common error when the network adapter is disabled is "wsasendto: The requested address is not valid in its context".
|
||||
if strings.Contains(err.Error(), "requested address is not valid in its context") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// changeMonitorFrequency is the frequency in seconds to check for a network change
|
||||
const changeMonitorFrequency = 10
|
||||
|
||||
// networkChangeMonitor() monitors for network changes to act accordingly
|
||||
func (nets *Networks) networkChangeMonitor() {
|
||||
// If manual IPs are entered, no need for monitoring for any network changes.
|
||||
if len(nets.backend.Config.Listen) > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
time.Sleep(time.Second * changeMonitorFrequency)
|
||||
|
||||
interfaceList, err := net.Interfaces()
|
||||
if err != nil {
|
||||
nets.backend.LogError("networkChangeMonitor", "enumerating network adapters failed: %s\n", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
ifacesNew := make(map[string][]net.Addr)
|
||||
|
||||
for _, iface := range interfaceList {
|
||||
addressesNew, err := iface.Addrs()
|
||||
if err != nil {
|
||||
nets.backend.LogError("networkChangeMonitor", "enumerating IPs for network adapter '%s': %s\n", iface.Name, err.Error())
|
||||
continue
|
||||
}
|
||||
ifacesNew[iface.Name] = addressesNew
|
||||
|
||||
// was the interface added?
|
||||
addressesExist, ok := nets.ipListen.ifacesExist[iface.Name]
|
||||
if !ok {
|
||||
nets.networkChangeInterfaceNew(iface, addressesNew)
|
||||
} else {
|
||||
// new IPs added for this interface?
|
||||
for _, addr := range addressesNew {
|
||||
exists := false
|
||||
for _, exist := range addressesExist {
|
||||
if exist.String() == addr.String() {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !exists {
|
||||
nets.networkChangeIPNew(iface, addr)
|
||||
}
|
||||
}
|
||||
|
||||
// were IPs removed from this interface
|
||||
for _, exist := range addressesExist {
|
||||
removed := true
|
||||
for _, addr := range addressesNew {
|
||||
if exist.String() == addr.String() {
|
||||
removed = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if removed {
|
||||
nets.networkChangeIPRemove(iface, exist)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// was an existing interface removed?
|
||||
for ifaceExist, addressesExist := range nets.ipListen.ifacesExist {
|
||||
if _, ok := ifacesNew[ifaceExist]; !ok {
|
||||
nets.networkChangeInterfaceRemove(ifaceExist, addressesExist)
|
||||
}
|
||||
}
|
||||
|
||||
nets.ipListen.ifacesExist = ifacesNew
|
||||
}
|
||||
}
|
||||
|
||||
// networkChangeInterfaceNew is called when a new interface is detected
|
||||
func (nets *Networks) networkChangeInterfaceNew(iface net.Interface, addresses []net.Addr) {
|
||||
nets.backend.LogError("networkChangeInterfaceNew", "new interface '%s' (%d IPs)\n", iface.Name, len(addresses))
|
||||
|
||||
networksNew := nets.InterfaceStart(iface, addresses)
|
||||
|
||||
for _, network := range networksNew {
|
||||
go network.upnpAuto()
|
||||
}
|
||||
|
||||
go nets.backend.nodesDHT.RefreshBuckets(0)
|
||||
}
|
||||
|
||||
// networkChangeInterfaceRemove is called when an existing interface is removed
|
||||
func (nets *Networks) networkChangeInterfaceRemove(iface string, addresses []net.Addr) {
|
||||
nets.RLock()
|
||||
defer nets.RUnlock()
|
||||
|
||||
nets.backend.LogError("networkChangeInterfaceRemove", "removing interface '%s' (%d IPs)\n", iface, len(addresses))
|
||||
|
||||
for n, network := range nets.networks6 {
|
||||
if network.iface != nil && network.iface.Name == iface {
|
||||
network.Terminate()
|
||||
|
||||
// remove from list
|
||||
networksNew := nets.networks6[:n]
|
||||
if n < len(nets.networks6)-1 {
|
||||
networksNew = append(networksNew, nets.networks6[n+1:]...)
|
||||
}
|
||||
nets.networks6 = networksNew
|
||||
}
|
||||
}
|
||||
|
||||
for n, network := range nets.networks4 {
|
||||
if network.iface != nil && network.iface.Name == iface {
|
||||
network.Terminate()
|
||||
|
||||
// remove from list
|
||||
networksNew := nets.networks4[:n]
|
||||
if n < len(nets.networks4)-1 {
|
||||
networksNew = append(networksNew, nets.networks4[n+1:]...)
|
||||
}
|
||||
nets.networks4 = networksNew
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// networkChangeIPNew is called when an existing interface lists a new IP
|
||||
func (nets *Networks) networkChangeIPNew(iface net.Interface, address net.Addr) {
|
||||
nets.backend.LogError("networkChangeIPNew", "new interface '%s' IP %s\n", iface.Name, address.String())
|
||||
|
||||
networksNew := nets.InterfaceStart(iface, []net.Addr{address})
|
||||
|
||||
for _, network := range networksNew {
|
||||
go network.upnpAuto()
|
||||
}
|
||||
|
||||
go nets.backend.nodesDHT.RefreshBuckets(0)
|
||||
}
|
||||
|
||||
// networkChangeIPRemove is called when an existing interface removes an IP
|
||||
func (nets *Networks) networkChangeIPRemove(iface net.Interface, address net.Addr) {
|
||||
nets.RLock()
|
||||
defer nets.RUnlock()
|
||||
|
||||
nets.backend.LogError("networkChangeIPRemove", "remove interface '%s' IP %s\n", iface.Name, address.String())
|
||||
|
||||
for n, network := range nets.networks6 {
|
||||
if network.address.IP.Equal(address.(*net.IPNet).IP) {
|
||||
network.Terminate()
|
||||
|
||||
// remove from list
|
||||
networksNew := nets.networks6[:n]
|
||||
if n < len(nets.networks6)-1 {
|
||||
networksNew = append(networksNew, nets.networks6[n+1:]...)
|
||||
}
|
||||
nets.networks6 = networksNew
|
||||
}
|
||||
}
|
||||
|
||||
for n, network := range nets.networks4 {
|
||||
if network.address.IP.Equal(address.(*net.IPNet).IP) {
|
||||
network.Terminate()
|
||||
|
||||
// remove from list
|
||||
networksNew := nets.networks4[:n]
|
||||
if n < len(nets.networks4)-1 {
|
||||
networksNew = append(networksNew, nets.networks4[n+1:]...)
|
||||
}
|
||||
nets.networks4 = networksNew
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IsIPLocal reports whether ip is a private (local) address.
|
||||
// The identification of private, or local, unicast addresses uses address type
|
||||
// indentification as defined in RFC 1918 for ip4 and RFC 4193 (fc00::/7) for ip6 with the exception of ip4 directed broadcast addresses.
|
||||
// Unassigned, reserved, multicast and limited-broadcast addresses are not handled and will return false.
|
||||
// IPv6 link-local addresses (fe80::/10) are included in this check.
|
||||
func IsIPLocal(ip net.IP) bool {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
return ip4[0] == 10 || (ip4[0] == 172 && ip4[1]&0xf0 == 16) || (ip4[0] == 192 && ip4[1] == 168)
|
||||
}
|
||||
return len(ip) == net.IPv6len &&
|
||||
(ip[0]&0xfe == 0xfc || // fc00::/7
|
||||
(ip[0] == 0xfe && ip[1]&0xC0 == 0x80)) // fe80::/10
|
||||
}
|
||||
181
vendor/github.com/PeernetOfficial/core/Network IPv4 Broadcast.go
generated
vendored
Normal file
181
vendor/github.com/PeernetOfficial/core/Network IPv4 Broadcast.go
generated
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
File Name: Network IPv4 Broadcast.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
IPv4 Multicast just sucks (can't use socket bound to 0.0.0.0:PortMain and send to 224.0.0.1:PortMulticast), so we rely on Broadcast instead.
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
"github.com/PeernetOfficial/core/reuseport"
|
||||
)
|
||||
|
||||
const ipv4BroadcastPort = 12912
|
||||
|
||||
// special Public-Private Key pair for local discovery
|
||||
var ipv4BroadcastPrivateKey *btcec.PrivateKey
|
||||
var ipv4BroadcastPublicKey *btcec.PublicKey
|
||||
|
||||
const ipv4BroadcastPrivateKeyH = "5e27ecc8e54a24e71dca9ba84a9bf465400e27b8c46a977d34962d3d88558c8e"
|
||||
|
||||
func initBroadcastIPv4() {
|
||||
if configPK, err := hex.DecodeString(ipv4BroadcastPrivateKeyH); err == nil {
|
||||
ipv4BroadcastPrivateKey, ipv4BroadcastPublicKey = btcec.PrivKeyFromBytes(btcec.S256(), configPK)
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastIPv4 prepares sending Broadcasts
|
||||
func (network *Network) BroadcastIPv4() (err error) {
|
||||
if ipv4BroadcastPrivateKey == nil || ipv4BroadcastPublicKey == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// listen on a special socket
|
||||
network.broadcastSocket, err = reuseport.ListenPacket("udp4", net.JoinHostPort(network.address.IP.String(), strconv.Itoa(ipv4BroadcastPort)))
|
||||
if err != nil {
|
||||
network.backend.LogError("BroadcastIPv4", "broadcast socket listen on IP '%s' port '%d': %v\n", network.address.IP.String(), ipv4BroadcastPort, err)
|
||||
return err
|
||||
}
|
||||
|
||||
network.broadcastIPv4 = networkToIPv4BroadcastIPs(network.ipnet)
|
||||
|
||||
go network.BroadcastIPv4Listen()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BroadcastIPv4Listen listens for incoming broadcast packets
|
||||
// Fork from network.Listen! Keep any changes synced.
|
||||
func (network *Network) BroadcastIPv4Listen() {
|
||||
for {
|
||||
// Buffer: Must be created for each packet as it is passed as pointer.
|
||||
// If the buffer is too small, ReadFromUDP only reads until its length and returns this error: "wsarecvfrom: A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram into was smaller than the datagram itself."
|
||||
buffer := make([]byte, maxPacketSize)
|
||||
length, sender, err := network.broadcastSocket.ReadFrom(buffer)
|
||||
|
||||
if err != nil {
|
||||
network.backend.LogError("BroadcastIPv4Listen", "receiving UDP message: %v\n", err) // Only log for debug purposes.
|
||||
time.Sleep(time.Millisecond * 50) // In case of endless errors, prevent ddos of CPU.
|
||||
continue
|
||||
}
|
||||
|
||||
if network.networkGroup.ipListen.IsAddressSelf(sender.(*net.UDPAddr)) {
|
||||
continue
|
||||
}
|
||||
|
||||
// For good network practice (and reducing amount of parallel connections), do not allow link-local to talk to non-link-local addresses.
|
||||
if sender.(*net.UDPAddr).IP.IsLinkLocalUnicast() != network.address.IP.IsLinkLocalUnicast() {
|
||||
continue
|
||||
}
|
||||
|
||||
//fmt.Printf("BroadcastIPv4Listen from %s at network %s\n", sender.String(), network.address.String())
|
||||
|
||||
if length < protocol.PacketLengthMin {
|
||||
// Discard packets that do not meet the minimum length.
|
||||
continue
|
||||
}
|
||||
|
||||
// send the packet to a channel which is processed by multiple workers.
|
||||
network.networkGroup.rawPacketsIncoming <- networkWire{network: network, sender: sender.(*net.UDPAddr), raw: buffer[:length], receiverPublicKey: ipv4BroadcastPublicKey, unicast: false}
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastIPv4Send sends out a single broadcast messages to discover peers
|
||||
func (network *Network) BroadcastIPv4Send() (err error) {
|
||||
_, blockchainHeight, blockchainVersion := network.backend.UserBlockchain.Header()
|
||||
packets := protocol.EncodeAnnouncement(true, true, nil, nil, nil, network.backend.FeatureSupport(), blockchainHeight, blockchainVersion, network.backend.userAgent)
|
||||
if len(packets) == 0 {
|
||||
return errors.New("error encoding broadcast announcement")
|
||||
}
|
||||
|
||||
raw, err := protocol.PacketEncrypt(network.backend.peerPrivateKey, ipv4BroadcastPublicKey, &protocol.PacketRaw{Protocol: protocol.ProtocolVersion, Command: protocol.CommandLocalDiscovery, Payload: packets[0]})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// send out the wire
|
||||
for _, ip := range network.broadcastIPv4 {
|
||||
err = network.send(ip, ipv4BroadcastPort, raw)
|
||||
if err != nil {
|
||||
network.backend.LogError("BroadcastIPv4Send", "sending UDP packet: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// networkToIPv4BroadcastIPs generates the IPv4 addresses to send out the broadcast to
|
||||
func networkToIPv4BroadcastIPs(ipnet *net.IPNet) (broadcastIPs []net.IP) {
|
||||
broadcastIPs = append(broadcastIPs, net.IPv4bcast)
|
||||
|
||||
if ipnet != nil {
|
||||
if ip2 := ipv4DirectedBroadcast(ipnet); ip2 != nil {
|
||||
broadcastIPs = append(broadcastIPs, ip2)
|
||||
}
|
||||
} else {
|
||||
interfaceList, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, iface := range interfaceList {
|
||||
addresses, err := iface.Addrs()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, address := range addresses {
|
||||
net1 := address.(*net.IPNet)
|
||||
|
||||
// TODO: Does the rfc3927Net make sense?
|
||||
if !IsIPv4(net1.IP) || rfc3927Net.Contains(net1.IP) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ip2 := ipv4DirectedBroadcast(net1); ip2 != nil {
|
||||
broadcastIPs = append(broadcastIPs, ip2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Result could contain duplicates, filter them out
|
||||
|
||||
return broadcastIPs
|
||||
}
|
||||
|
||||
func ipv4DirectedBroadcast(n *net.IPNet) net.IP {
|
||||
ip4 := n.IP.To4()
|
||||
if ip4 == nil {
|
||||
return nil
|
||||
}
|
||||
last := make(net.IP, len(ip4))
|
||||
copy(last, ip4)
|
||||
for i := range ip4 {
|
||||
last[i] |= ^n.Mask[i]
|
||||
}
|
||||
return last
|
||||
}
|
||||
|
||||
var (
|
||||
// rfc3927Net specifies the IPv4 auto configuration address block as
|
||||
// defined by RFC3927 (169.254.0.0/16).
|
||||
rfc3927Net = ipNet("169.254.0.0", 16, 32)
|
||||
)
|
||||
|
||||
// ipNet returns a net.IPNet struct given the passed IP address string, number
|
||||
// of one bits to include at the start of the mask, and the total number of bits
|
||||
// for the mask.
|
||||
func ipNet(ip string, ones, bits int) net.IPNet {
|
||||
return net.IPNet{IP: net.ParseIP(ip), Mask: net.CIDRMask(ones, bits)}
|
||||
}
|
||||
153
vendor/github.com/PeernetOfficial/core/Network IPv6 Multicast.go
generated
vendored
Normal file
153
vendor/github.com/PeernetOfficial/core/Network IPv6 Multicast.go
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
File Name: Network IPv6 Multicast.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
IPv6 Multicast implementation to support discovery of peers within the same network (Site-local).
|
||||
Loopback is enabled, which means that Multicast packets sent will be looped back and received by any local listeners. This allows to connect local processes with each other.
|
||||
|
||||
Using the separate Multicast port, it allows sending unsolicited announcements without knowing the target's public key. Instead, a hard-coded key is used.
|
||||
|
||||
The Multicast listener opens port 12912 with SO_REUSEADDR to allow multiple processes receive the incoming Multicast packets.
|
||||
[1] mentions "If two sockets are bound to the same interface and port and are members of the same multicast group, data will be delivered to both sockets, rather than an arbitrarily chosen one."
|
||||
|
||||
[1] https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
"github.com/PeernetOfficial/core/reuseport"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
// Multicast group is site-local. Group ID is 112.
|
||||
const ipv6MulticastGroup = "ff05::112"
|
||||
const ipv6MulticastPort = 12912
|
||||
|
||||
// special Public-Private Key pair for local discovery
|
||||
var ipv6MulticastPrivateKey *btcec.PrivateKey
|
||||
var ipv6MulticastPublicKey *btcec.PublicKey
|
||||
|
||||
const ipv6MulticastPrivateKeyH = "016ad30bfb369926523bf18d136298b6c31d0817e9fb6c21feed89ae22cad788"
|
||||
|
||||
func initMulticastIPv6() {
|
||||
if configPK, err := hex.DecodeString(ipv6MulticastPrivateKeyH); err == nil {
|
||||
ipv6MulticastPrivateKey, ipv6MulticastPublicKey = btcec.PrivKeyFromBytes(btcec.S256(), configPK)
|
||||
}
|
||||
}
|
||||
|
||||
// MulticastIPv6Join joins the Multicast group
|
||||
func (network *Network) MulticastIPv6Join() (err error) {
|
||||
if ipv6MulticastPrivateKey == nil || ipv6MulticastPublicKey == nil {
|
||||
return
|
||||
}
|
||||
|
||||
network.multicastIP = net.ParseIP(ipv6MulticastGroup)
|
||||
|
||||
// listen on a special socket
|
||||
network.multicastSocket, err = reuseport.ListenPacket("udp6", net.JoinHostPort(network.address.IP.String(), strconv.Itoa(ipv6MulticastPort)))
|
||||
if err != nil {
|
||||
network.backend.LogError("MulticastIPv6Join", "multicast socket listen on IP '%s' port '%d': %v\n", network.address.IP.String(), ipv6MulticastPort, err)
|
||||
return err
|
||||
}
|
||||
|
||||
joinMulticastGroup := func(iface *net.Interface) (err error) {
|
||||
pc := ipv6.NewPacketConn(network.multicastSocket)
|
||||
if err := pc.JoinGroup(iface, &net.UDPAddr{IP: network.multicastIP}); err != nil {
|
||||
//LogError("MulticastIPv6Join", "join multicast group iface '%s' multicast IP '%s' listen on IP '%s' port '%d': %v\n", iface.Name, network.multicastIP.String(), network.address.IP.String(), ipv6MulticastPort, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// receive messages from self or other processes running on the same computer
|
||||
if loop, err := pc.MulticastLoopback(); err == nil && !loop {
|
||||
if err := pc.SetMulticastLoopback(true); err != nil {
|
||||
network.backend.LogError("MulticastIPv6Join", "setting multicast loopback status: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// specific interface or join all?
|
||||
if network.iface != nil {
|
||||
if err = joinMulticastGroup(network.iface); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
interfaceList, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, ifaceSingle := range interfaceList {
|
||||
joinMulticastGroup(&ifaceSingle)
|
||||
}
|
||||
}
|
||||
|
||||
go network.MulticastIPv6Listen()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MulticastIPv6Listen listens for incoming multicast packets
|
||||
// Fork from network.Listen! Keep any changes synced.
|
||||
func (network *Network) MulticastIPv6Listen() {
|
||||
for {
|
||||
// Buffer: Must be created for each packet as it is passed as pointer.
|
||||
// If the buffer is too small, ReadFromUDP only reads until its length and returns this error: "wsarecvfrom: A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram into was smaller than the datagram itself."
|
||||
buffer := make([]byte, maxPacketSize)
|
||||
length, sender, err := network.multicastSocket.ReadFrom(buffer)
|
||||
|
||||
if err != nil {
|
||||
network.backend.LogError("MulticastIPv6Listen", "receiving UDP message: %v\n", err) // Only log for debug purposes.
|
||||
time.Sleep(time.Millisecond * 50) // In case of endless errors, prevent ddos of CPU.
|
||||
continue
|
||||
}
|
||||
|
||||
// skip incoming packets that were looped back
|
||||
if network.networkGroup.ipListen.IsAddressSelf(sender.(*net.UDPAddr)) {
|
||||
continue
|
||||
}
|
||||
|
||||
// For good network practice (and reducing amount of parallel connections), do not allow link-local to talk to non-link-local addresses.
|
||||
if sender.(*net.UDPAddr).IP.IsLinkLocalUnicast() != network.address.IP.IsLinkLocalUnicast() {
|
||||
continue
|
||||
}
|
||||
|
||||
//fmt.Printf("MulticastIPv6Listen from %s at network %s\n", sender.String(), network.address.String())
|
||||
|
||||
if length < protocol.PacketLengthMin {
|
||||
// Discard packets that do not meet the minimum length.
|
||||
continue
|
||||
}
|
||||
|
||||
// send the packet to a channel which is processed by multiple workers.
|
||||
network.networkGroup.rawPacketsIncoming <- networkWire{network: network, sender: sender.(*net.UDPAddr), raw: buffer[:length], receiverPublicKey: ipv6MulticastPublicKey, unicast: false}
|
||||
}
|
||||
}
|
||||
|
||||
// MulticastIPv6Send sends out a single multicast messages to discover peers at the same site
|
||||
func (network *Network) MulticastIPv6Send() (err error) {
|
||||
_, blockchainHeight, blockchainVersion := network.backend.UserBlockchain.Header()
|
||||
packets := protocol.EncodeAnnouncement(true, true, nil, nil, nil, network.backend.FeatureSupport(), blockchainHeight, blockchainVersion, network.backend.userAgent)
|
||||
if len(packets) == 0 {
|
||||
return errors.New("error encoding multicast announcement")
|
||||
}
|
||||
|
||||
raw, err := protocol.PacketEncrypt(network.backend.peerPrivateKey, ipv6MulticastPublicKey, &protocol.PacketRaw{Protocol: protocol.ProtocolVersion, Command: protocol.CommandLocalDiscovery, Payload: packets[0]})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// send out the wire
|
||||
return network.send(network.multicastIP, ipv6MulticastPort, raw)
|
||||
}
|
||||
221
vendor/github.com/PeernetOfficial/core/Network Init.go
generated
vendored
Normal file
221
vendor/github.com/PeernetOfficial/core/Network Init.go
generated
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
File Name: Network Init.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Magic 🪄 to start the network configuration with 0 manual input. Users may specify the list of IPs (and optional ports) to listen; otherwise it listens on all.
|
||||
IPv6 is always preferred.
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
)
|
||||
|
||||
// networkWire is an incoming packet
|
||||
type networkWire struct {
|
||||
network *Network // network which received the packet
|
||||
sender *net.UDPAddr // sender of the packet
|
||||
receiverPublicKey *btcec.PublicKey // public key associated with the receiver
|
||||
raw []byte // buffer
|
||||
unicast bool // True if the message was sent via unicast. False if sent via IPv4 broadcast or IPv6 multicast.
|
||||
}
|
||||
|
||||
// initNetwork sets up the network configuration and starts listening.
|
||||
func (backend *Backend) initNetwork() {
|
||||
rand.Seed(time.Now().UnixNano()) // we are not using "crypto/rand" for speed tradeoff
|
||||
|
||||
// start listen workers
|
||||
if backend.Config.ListenWorkers == 0 {
|
||||
backend.Config.ListenWorkers = 2
|
||||
}
|
||||
if backend.Config.ListenWorkersLite == 0 {
|
||||
backend.Config.ListenWorkersLite = 2
|
||||
}
|
||||
for n := 0; n < backend.Config.ListenWorkers; n++ {
|
||||
go backend.networks.packetWorker()
|
||||
}
|
||||
for n := 0; n < backend.Config.ListenWorkersLite; n++ {
|
||||
go backend.networks.packetWorkerLite()
|
||||
}
|
||||
|
||||
// check if user specified where to listen
|
||||
if len(backend.Config.Listen) > 0 {
|
||||
for _, listenA := range backend.Config.Listen {
|
||||
host, portA, err := net.SplitHostPort(listenA)
|
||||
if err != nil && strings.Contains(err.Error(), "missing port in address") { // port is optional
|
||||
host = listenA
|
||||
portA = "0"
|
||||
} else if err != nil {
|
||||
backend.LogError("initNetwork", "invalid input listen address '%s': %s\n", listenA, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
portI, _ := strconv.Atoi(portA)
|
||||
|
||||
if _, err := backend.networks.PrepareListen(host, portI); err != nil {
|
||||
backend.LogError("initNetwork", "listen on '%s': %s\n", listenA, err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Listen on all IPv4 and IPv6 addresses
|
||||
//if _, err := networks.PrepareListen("0.0.0.0", 0); err != nil {
|
||||
// LogError("initNetwork", "listen on all IPv4 addresses (0.0.0.0): %s\n", err.Error())
|
||||
//}
|
||||
//if _, err := networks.PrepareListen("::", 0); err != nil {
|
||||
// LogError("initNetwork", "listen on all IPv6 addresses (::): %s\n", err.Error())
|
||||
//}
|
||||
|
||||
// Listen on each network adapter on each IP. This guarantees the highest deliverability, even though it brings on additional challenges such as:
|
||||
// * Packet duplicates on IPv6 Multicast (listening on multiple IPs and joining the group on the same adapter) and IPv4 Broadcast (listening on multiple IPs on the same adapter).
|
||||
// * Local peers are more likely to connect on the same adapter via multiple IPs (i.e. link-local and others, including public IPv6 and temporary public IPv6).
|
||||
// * Network adapters and IPs might change. Simplest case is if someone changes Wifi network.
|
||||
interfaceList, err := net.Interfaces()
|
||||
if err != nil {
|
||||
backend.LogError("initNetwork", "enumerating network adapters failed: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for _, iface := range interfaceList {
|
||||
addresses, err := iface.Addrs()
|
||||
if err != nil {
|
||||
backend.LogError("initNetwork", "enumerating IPs for network adapter '%s': %s\n", iface.Name, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
backend.networks.ipListen.ifacesExist[iface.Name] = addresses
|
||||
|
||||
backend.networks.InterfaceStart(iface, addresses)
|
||||
}
|
||||
}
|
||||
|
||||
// InterfaceStart will start the listeners on all the IP addresses for the network
|
||||
func (nets *Networks) InterfaceStart(iface net.Interface, addresses []net.Addr) (networksNew []*Network) {
|
||||
for _, address := range addresses {
|
||||
net1 := address.(*net.IPNet)
|
||||
|
||||
// Do not listen on lookpback IPs. They are not even needed for discovery of machine-local peers (they will be discovered via regular multicast/broadcast).
|
||||
if net1.IP.IsLoopback() {
|
||||
continue
|
||||
}
|
||||
|
||||
networkNew, err := nets.PrepareListen(net1.IP.String(), 0)
|
||||
|
||||
if err != nil {
|
||||
// Do not log common errors:
|
||||
// * "listen udp4 169.254.X.X:X: bind: The requested address is not valid in its context."
|
||||
// Windows reports link-local addresses for inactive network adapters.
|
||||
if net1.IP.IsLinkLocalUnicast() {
|
||||
continue
|
||||
}
|
||||
|
||||
nets.backend.LogError("networks.InterfaceStart", "listening on network adapter '%s' IPv4 '%s': %s\n", iface.Name, net1.IP.String(), err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
nets.ipListen.Add(networkNew.address)
|
||||
|
||||
nets.backend.LogError("networks.InterfaceStart", "listen on network '%s' UDP %s\n", iface.Name, networkNew.address.String())
|
||||
|
||||
networksNew = append(networksNew, networkNew)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareListen creates a new network and prepares to listen on the given IP address. If port is 0, one is chosen automatically.
|
||||
func (nets *Networks) PrepareListen(ipA string, port int) (network *Network, err error) {
|
||||
ip := net.ParseIP(ipA)
|
||||
if ip == nil {
|
||||
return nil, errors.New("invalid input IP")
|
||||
}
|
||||
|
||||
network = &Network{backend: nets.backend, networkGroup: nets}
|
||||
network.terminateSignal = make(chan interface{})
|
||||
|
||||
// get the network interface that belongs to the IP
|
||||
if !ip.IsUnspecified() { // checks for IPv4 "0.0.0.0" and IPv6 "::"
|
||||
network.iface, network.ipnet = FindInterfaceByIP(ip)
|
||||
if network.iface == nil {
|
||||
return nil, errors.New("error finding the network interface belonging to IP")
|
||||
}
|
||||
}
|
||||
|
||||
// open up the port
|
||||
if err = network.AutoAssignPort(ip, port); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nets.Lock()
|
||||
|
||||
// Success - port is open. Add to the list and start accepting incoming messages.
|
||||
if IsIPv4(ip) {
|
||||
nets.networks4 = append(nets.networks4, network)
|
||||
nets.Unlock()
|
||||
network.BroadcastIPv4()
|
||||
} else {
|
||||
nets.networks6 = append(nets.networks6, network)
|
||||
nets.Unlock()
|
||||
network.MulticastIPv6Join()
|
||||
}
|
||||
|
||||
go network.Listen()
|
||||
|
||||
return network, nil
|
||||
}
|
||||
|
||||
// ipList keeps track of listened IP addresses and observed interfaces
|
||||
type ipList struct {
|
||||
ipListen map[string]struct{} // list of IPs currently listening on
|
||||
sync.RWMutex // Mutex for list
|
||||
ifacesExist map[string][]net.Addr // list of currently known interfaces with list of IP addresses
|
||||
}
|
||||
|
||||
// NewIPList creates a new list
|
||||
func NewIPList() (list *ipList) {
|
||||
return &ipList{
|
||||
ipListen: make(map[string]struct{}),
|
||||
ifacesExist: make(map[string][]net.Addr),
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a listening IP:Port to the list.
|
||||
func (list *ipList) Add(addr *net.UDPAddr) {
|
||||
list.Lock()
|
||||
list.ipListen[net.JoinHostPort(addr.IP.String(), strconv.Itoa(addr.Port))] = struct{}{}
|
||||
list.Unlock()
|
||||
}
|
||||
|
||||
// Remove removes a listening address from the list
|
||||
func (list *ipList) Remove(addr *net.UDPAddr) {
|
||||
list.Lock()
|
||||
delete(list.ipListen, net.JoinHostPort(addr.IP.String(), strconv.Itoa(addr.Port)))
|
||||
list.Unlock()
|
||||
}
|
||||
|
||||
// IsAddressSelf checks if the senders address is actually listening address. This prevents loopback packets from being considered.
|
||||
// Note: This does not work when listening on 0.0.0.0 or ::1 and binding the sending socket to that.
|
||||
func (list *ipList) IsAddressSelf(addr *net.UDPAddr) bool {
|
||||
if addr == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// do not use addr.String() since it addds the Zone for IPv6 which may be ambiguous (can be adapter name or address literal).
|
||||
list.RLock()
|
||||
_, ok := list.ipListen[net.JoinHostPort(addr.IP.String(), strconv.Itoa(addr.Port))]
|
||||
list.RUnlock()
|
||||
return ok
|
||||
}
|
||||
191
vendor/github.com/PeernetOfficial/core/Network UPnP.go
generated
vendored
Normal file
191
vendor/github.com/PeernetOfficial/core/Network UPnP.go
generated
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
File Name: Network UPnP.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Currently only supports IPv4 networks.
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/upnp"
|
||||
)
|
||||
|
||||
func (nets *Networks) startUPnP() {
|
||||
nets.upnpListInterfaces = make(map[string]struct{})
|
||||
|
||||
for _, cidr := range []string{
|
||||
"10.0.0.0/8", // RFC1918
|
||||
"172.16.0.0/12", // RFC1918
|
||||
"192.168.0.0/16", // RFC1918
|
||||
} {
|
||||
if _, block, err := net.ParseCIDR(cidr); err == nil {
|
||||
privateIPv4Blocks = append(privateIPv4Blocks, block)
|
||||
}
|
||||
}
|
||||
|
||||
if nets.backend.Config.PortForward > 0 {
|
||||
nets.backend.Config.EnableUPnP = false
|
||||
}
|
||||
if !nets.backend.Config.EnableUPnP {
|
||||
return
|
||||
}
|
||||
|
||||
for _, network := range nets.networks4 {
|
||||
go network.upnpAuto()
|
||||
}
|
||||
}
|
||||
|
||||
// upnpIsEligible checks if the network is eligible for UPnP
|
||||
func (network *Network) upnpIsEligible() bool {
|
||||
// IPv4 only for now.
|
||||
if !IsIPv4(network.address.IP) {
|
||||
return false
|
||||
}
|
||||
|
||||
// The network interface must be known which indicates that the IP address is NOT a wildcard.
|
||||
// Port forwarding requires to specify a local IP. In case of listening on a wildcard that would not be known and guessing (looking at you btcd) is not a solution.
|
||||
if network.iface == nil || network.address.IP.IsUnspecified() {
|
||||
return false
|
||||
}
|
||||
|
||||
// IPv4/IPv6: No link-local addresses, no loopback. Multicast would be invalid anyway.
|
||||
if network.address.IP.IsLinkLocalUnicast() || network.address.IP.IsLoopback() || network.address.IP.IsMulticast() {
|
||||
return false
|
||||
}
|
||||
|
||||
// IPv4: Must be private IP.
|
||||
if IsIPv4(network.address.IP) && !isPrivateIP(network.address.IP) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
var privateIPv4Blocks []*net.IPNet
|
||||
|
||||
func isPrivateIP(ip net.IP) bool {
|
||||
for _, block := range privateIPv4Blocks {
|
||||
if block.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// upnpAuto runs a UPnP daemon to forward the port, refresh the forwarding and continuously monitor if the forwarding remains valid.
|
||||
func (network *Network) upnpAuto() {
|
||||
if !network.backend.Config.EnableUPnP || !network.upnpIsEligible() {
|
||||
return
|
||||
}
|
||||
|
||||
nat, err := upnp.Discover(network.address.IP)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
network.nat = nat
|
||||
|
||||
externalIP, err := nat.GetExternalAddress()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
network.ipExternal = externalIP
|
||||
|
||||
// Only allow 1 UPnP worker at a time for registering the adapter.
|
||||
network.networkGroup.upnpMutex.Lock()
|
||||
defer network.networkGroup.upnpMutex.Unlock()
|
||||
|
||||
// If there is already a running UPnP on the adapter, skip.
|
||||
if _, ok := network.networkGroup.upnpListInterfaces[network.GetAdapterName()]; ok {
|
||||
return
|
||||
}
|
||||
|
||||
if err := network.upnpTryPortForward(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
network.networkGroup.upnpListInterfaces[network.GetAdapterName()] = struct{}{}
|
||||
|
||||
go network.upnpMonitorPortForward()
|
||||
}
|
||||
|
||||
// upnpMonitorPortForward monitors the port forwarding status
|
||||
func (network *Network) upnpMonitorPortForward() {
|
||||
ticker := time.NewTicker(time.Second * 10)
|
||||
|
||||
monitorLoop:
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
case <-network.terminateSignal:
|
||||
// Remove port mapping. Note that in case the network is unavailable this is likely to fail.
|
||||
network.nat.DeletePortMapping("UDP", network.portExternal)
|
||||
|
||||
network.portExternal = 0
|
||||
network.ipExternal = net.IP{}
|
||||
|
||||
break monitorLoop
|
||||
}
|
||||
|
||||
// 3 tries
|
||||
var err error
|
||||
for n := 0; n < 3; n++ {
|
||||
if err = network.upnpValidate(); err == nil {
|
||||
continue monitorLoop
|
||||
}
|
||||
}
|
||||
|
||||
// invalid :(
|
||||
network.backend.LogError("upnpMonitorPortForward", "port forwarding invalidated for local IP %s (adapter %s) external IP %s port %d\n", network.address.String(), network.iface.Name, network.ipExternal.String(), network.portExternal)
|
||||
|
||||
network.portExternal = 0
|
||||
network.ipExternal = net.IP{}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
ticker.Stop()
|
||||
|
||||
network.networkGroup.upnpMutex.Lock()
|
||||
delete(network.networkGroup.upnpListInterfaces, network.GetAdapterName())
|
||||
network.networkGroup.upnpMutex.Unlock()
|
||||
}
|
||||
|
||||
func (network *Network) upnpTryPortForward() (err error) {
|
||||
// Try forwarding the port. First to the same one listening, otherwise random.
|
||||
mappedExternalPort, err := network.nat.AddPortMapping("UDP", network.address.IP, uint16(network.address.Port), uint16(network.address.Port), "Peernet", 0)
|
||||
if err != nil {
|
||||
mappedExternalPort, err = network.nat.AddPortMapping("UDP", network.address.IP, uint16(network.address.Port), uint16(randInt(1024, 65535)), "Peernet", 0)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validate
|
||||
if err := network.upnpValidate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// valid!
|
||||
network.portExternal = mappedExternalPort
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func randInt(min int, max int) int {
|
||||
return min + rand.Intn(max-min)
|
||||
}
|
||||
|
||||
func (network *Network) upnpValidate() (err error) {
|
||||
// TODO: Send special message which validates the UPnP mapping
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Function to check if there is an existing port forward
|
||||
434
vendor/github.com/PeernetOfficial/core/Network.go
generated
vendored
Normal file
434
vendor/github.com/PeernetOfficial/core/Network.go
generated
vendored
Normal file
@@ -0,0 +1,434 @@
|
||||
/*
|
||||
File Name: Network.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
"github.com/PeernetOfficial/core/upnp"
|
||||
)
|
||||
|
||||
// Network is a connection adapter through one network interface (adapter).
|
||||
// Note that for each IP on the same adapter separate network entries are created.
|
||||
type Network struct {
|
||||
iface *net.Interface // Network interface belonging to the IP. May not be set.
|
||||
ipnet *net.IPNet // IP network the listening address belongs to. May not be set.
|
||||
address *net.UDPAddr // IP:Port where the server listens
|
||||
socket *net.UDPConn // active socket for send/receive
|
||||
multicastIP net.IP // Multicast IP, IPv6 only.
|
||||
multicastSocket net.PacketConn // Multicast socket, IPv6 only.
|
||||
broadcastSocket net.PacketConn // Broadcast socket, IPv4 only.
|
||||
broadcastIPv4 []net.IP // Broadcast IPs, IPv4 only.
|
||||
portExternal uint16 // External port. 0 if not known.
|
||||
ipExternal net.IP // External IP of the network. Usually not known.
|
||||
nat upnp.NAT // UPnP: NAT information
|
||||
isTerminated bool // If true, the network was signaled for termination
|
||||
terminateSignal chan interface{} // gets closed on termination signal, can be used in select via "case _ = <- network.terminateSignal:"
|
||||
sync.RWMutex // for sychronized closing
|
||||
networkGroup *Networks // Pointer to the pool of networks that this is part of
|
||||
backend *Backend
|
||||
}
|
||||
|
||||
// Default ports to use. This may be randomized in the future to prevent fingerprinting (and subsequent blocking) by corporate and ISP firewalls.
|
||||
const defaultPort = 'p' // 112
|
||||
|
||||
// AutoAssignPort assigns a port for the given IP. Use port 0 for zero configuration.
|
||||
func (network *Network) AutoAssignPort(ip net.IP, port int) (err error) {
|
||||
networkA := "udp6"
|
||||
if IsIPv4(ip) {
|
||||
networkA = "udp4"
|
||||
}
|
||||
|
||||
// A common error return is "bind: The requested address is not valid in its context.".
|
||||
// This error was observed when the network interface might not be ready after boot but also when listening on a link-local IPv4 (169.254.) for an inactive adapter.
|
||||
// Previously the algorithm retried up to n times, but this would unnecessarily delay startup in case the IP is actual unlistenable.
|
||||
connectPortTry := func(port int) (address *net.UDPAddr, socket *net.UDPConn, err error) {
|
||||
address = &net.UDPAddr{IP: ip, Port: port}
|
||||
if socket, err = net.ListenUDP(networkA, address); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if port == 0 {
|
||||
localAddr := socket.LocalAddr()
|
||||
if localAddr == nil {
|
||||
return nil, nil, errors.New("invalid port assignment")
|
||||
}
|
||||
address.Port = localAddr.(*net.UDPAddr).Port
|
||||
}
|
||||
|
||||
return address, socket, nil
|
||||
}
|
||||
|
||||
if port != 0 {
|
||||
network.address, network.socket, err = connectPortTry(port)
|
||||
return err
|
||||
}
|
||||
|
||||
// try default main port, then random
|
||||
if network.address, network.socket, err = connectPortTry(defaultPort); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if network.address, network.socket, err = connectPortTry(0); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// send sends a message
|
||||
func (network *Network) send(IP net.IP, port int, raw []byte) (err error) {
|
||||
_, err = network.socket.WriteTo(raw, &net.UDPAddr{IP: IP, Port: port})
|
||||
return err
|
||||
}
|
||||
|
||||
// Max packet size is 64 KB.
|
||||
const maxPacketSize = 65536
|
||||
|
||||
// Listen starts listening for incoming packets on the given UDP connection
|
||||
func (network *Network) Listen() {
|
||||
if !network.address.IP.IsLinkLocalUnicast() {
|
||||
if IsIPv4(network.address.IP) {
|
||||
atomic.AddInt64(&network.networkGroup.countListen4, 1)
|
||||
} else {
|
||||
atomic.AddInt64(&network.networkGroup.countListen6, 1)
|
||||
}
|
||||
}
|
||||
|
||||
for !network.isTerminated {
|
||||
// Buffer: Must be created for each packet as it is passed as pointer.
|
||||
// If the buffer is too small, ReadFromUDP only reads until its length and returns this error: "wsarecvfrom: A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram into was smaller than the datagram itself."
|
||||
buffer := make([]byte, maxPacketSize)
|
||||
length, sender, err := network.socket.ReadFromUDP(buffer)
|
||||
|
||||
if err != nil {
|
||||
// Exit on closed socket. Error will be "use of closed network connection".
|
||||
if network.isTerminated {
|
||||
return
|
||||
}
|
||||
|
||||
network.backend.LogError("Listen", "receiving UDP message: %v\n", err) // Only log for debug purposes.
|
||||
time.Sleep(time.Millisecond * 50) // In case of endless errors, prevent ddos of CPU.
|
||||
continue
|
||||
}
|
||||
|
||||
// handle lite packets before regular ones
|
||||
if isLite, err := network.networkGroup.LiteRouter.IsPacketLite(buffer[:length]); isLite && err != nil {
|
||||
continue
|
||||
} else if isLite {
|
||||
network.networkGroup.litePacketsIncoming <- networkWire{network: network, sender: sender, raw: buffer[:length], receiverPublicKey: network.backend.peerPublicKey, unicast: true}
|
||||
continue
|
||||
}
|
||||
|
||||
if length < protocol.PacketLengthMin {
|
||||
// Discard packets that do not meet the minimum length.
|
||||
continue
|
||||
}
|
||||
|
||||
// send the packet to a channel which is processed by multiple workers.
|
||||
network.networkGroup.rawPacketsIncoming <- networkWire{network: network, sender: sender, raw: buffer[:length], receiverPublicKey: network.backend.peerPublicKey, unicast: true}
|
||||
}
|
||||
}
|
||||
|
||||
// packetWorker handles incoming packets.
|
||||
func (nets *Networks) packetWorker() {
|
||||
for packet := range nets.rawPacketsIncoming {
|
||||
decoded, senderPublicKey, err := protocol.PacketDecrypt(packet.raw, packet.receiverPublicKey)
|
||||
if err != nil {
|
||||
//LogError("packetWorker", "decrypting packet from '%s': %s\n", packet.sender.String(), err.Error()) // Only log for debug purposes.
|
||||
continue
|
||||
}
|
||||
|
||||
// immediately discard message if sender = self
|
||||
if senderPublicKey.IsEqual(nets.backend.peerPublicKey) {
|
||||
continue
|
||||
}
|
||||
|
||||
// supported protocol version
|
||||
if decoded.Protocol != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
connection := &Connection{backend: nets.backend, Network: packet.network, Address: packet.sender, Status: ConnectionActive}
|
||||
|
||||
nets.backend.Filters.PacketIn(decoded, senderPublicKey, connection)
|
||||
|
||||
// A peer structure will always be returned, even if the peer won't be added to the peer list.
|
||||
peer, added := nets.backend.PeerlistAdd(senderPublicKey, connection)
|
||||
if !added {
|
||||
connection = peer.registerConnection(connection)
|
||||
}
|
||||
|
||||
atomic.AddUint64(&peer.StatsPacketReceived, 1)
|
||||
connection.LastPacketIn = time.Now()
|
||||
|
||||
// process the packet
|
||||
raw := &protocol.MessageRaw{SenderPublicKey: senderPublicKey, PacketRaw: *decoded}
|
||||
|
||||
switch decoded.Command {
|
||||
case protocol.CommandAnnouncement: // Announce
|
||||
if announce, _ := protocol.DecodeAnnouncement(raw); announce != nil {
|
||||
// Update known internal/external port and User Agent
|
||||
connection.PortInternal = announce.PortInternal
|
||||
connection.PortExternal = announce.PortExternal
|
||||
connection.Firewall = announce.Features&(1<<protocol.FeatureFirewall) > 0
|
||||
if len(announce.UserAgent) > 0 {
|
||||
peer.UserAgent = announce.UserAgent
|
||||
}
|
||||
peer.Features = announce.Features
|
||||
|
||||
isBlockchainUpdate := peer.BlockchainHeight != announce.BlockchainHeight || peer.BlockchainVersion != announce.BlockchainVersion
|
||||
peer.BlockchainHeight = announce.BlockchainHeight
|
||||
peer.BlockchainVersion = announce.BlockchainVersion
|
||||
peer.blockchainLastRefresh = time.Now()
|
||||
|
||||
nets.backend.Filters.MessageIn(peer, raw, announce)
|
||||
|
||||
peer.cmdAnouncement(announce, connection)
|
||||
|
||||
if isBlockchainUpdate {
|
||||
peer.remoteBlockchainUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
case protocol.CommandResponse: // Response
|
||||
if response, _ := protocol.DecodeResponse(raw); response != nil {
|
||||
// Validate sequence number which prevents unsolicited responses.
|
||||
isLast := response.IsLast()
|
||||
sequenceInfo, valid, rtt := nets.Sequences.ValidateSequence(raw.SenderPublicKey, raw.Sequence, isLast, !isLast)
|
||||
if !valid {
|
||||
//LogError("packetWorker", "message with invalid sequence %d command %d from %s\n", raw.Sequence, raw.Command, raw.connection.Address.String()) // Only log for debug purposes.
|
||||
continue
|
||||
} else if rtt > 0 {
|
||||
connection.RoundTripTime = rtt
|
||||
}
|
||||
raw.SequenceInfo = sequenceInfo
|
||||
|
||||
// Update known internal/external port and User Agent
|
||||
connection.PortInternal = response.PortInternal
|
||||
connection.PortExternal = response.PortExternal
|
||||
connection.Firewall = response.Features&(1<<protocol.FeatureFirewall) > 0
|
||||
if len(response.UserAgent) > 0 {
|
||||
peer.UserAgent = response.UserAgent
|
||||
}
|
||||
peer.Features = response.Features
|
||||
|
||||
isBlockchainUpdate := peer.BlockchainHeight != response.BlockchainHeight || peer.BlockchainVersion != response.BlockchainVersion
|
||||
peer.BlockchainHeight = response.BlockchainHeight
|
||||
peer.BlockchainVersion = response.BlockchainVersion
|
||||
peer.blockchainLastRefresh = time.Now()
|
||||
|
||||
nets.backend.Filters.MessageIn(peer, raw, response)
|
||||
|
||||
peer.cmdResponse(response, connection)
|
||||
|
||||
if isBlockchainUpdate {
|
||||
peer.remoteBlockchainUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
case protocol.CommandLocalDiscovery: // Local discovery, sent via IPv4 broadcast and IPv6 multicast
|
||||
if announce, _ := protocol.DecodeAnnouncement(raw); announce != nil {
|
||||
if len(announce.UserAgent) > 0 {
|
||||
peer.UserAgent = announce.UserAgent
|
||||
}
|
||||
peer.Features = announce.Features
|
||||
|
||||
isBlockchainUpdate := peer.BlockchainHeight != announce.BlockchainHeight || peer.BlockchainVersion != announce.BlockchainVersion
|
||||
peer.BlockchainHeight = announce.BlockchainHeight
|
||||
peer.BlockchainVersion = announce.BlockchainVersion
|
||||
peer.blockchainLastRefresh = time.Now()
|
||||
|
||||
nets.backend.Filters.MessageIn(peer, raw, announce)
|
||||
|
||||
peer.cmdLocalDiscovery(announce, connection)
|
||||
|
||||
if isBlockchainUpdate {
|
||||
peer.remoteBlockchainUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
case protocol.CommandPing: // Ping
|
||||
nets.backend.Filters.MessageIn(peer, raw, nil)
|
||||
peer.cmdPing(raw, connection)
|
||||
|
||||
case protocol.CommandPong: // Ping
|
||||
// Validate sequence number which prevents unsolicited responses.
|
||||
sequenceInfo, valid, rtt := nets.Sequences.ValidateSequence(raw.SenderPublicKey, raw.Sequence, true, false)
|
||||
if !valid {
|
||||
//LogError("packetWorker", "message with invalid sequence %d command %d from %s\n", raw.Sequence, raw.Command, raw.connection.Address.String()) // Only log for debug purposes.
|
||||
continue
|
||||
} else if rtt > 0 {
|
||||
connection.RoundTripTime = rtt
|
||||
}
|
||||
raw.SequenceInfo = sequenceInfo
|
||||
|
||||
nets.backend.Filters.MessageIn(peer, raw, nil)
|
||||
|
||||
peer.cmdPong(raw, connection)
|
||||
|
||||
case protocol.CommandChat: // Chat [debug]
|
||||
nets.backend.Filters.MessageIn(peer, raw, nil)
|
||||
peer.cmdChat(raw, connection)
|
||||
|
||||
case protocol.CommandTraverse:
|
||||
if traverse, _ := protocol.DecodeTraverse(raw); traverse != nil {
|
||||
nets.backend.Filters.MessageIn(peer, raw, traverse)
|
||||
if traverse.TargetPeer.IsEqual(nets.backend.peerPublicKey) && traverse.AuthorizedRelayPeer.IsEqual(peer.PublicKey) {
|
||||
peer.cmdTraverseReceive(traverse)
|
||||
} else if traverse.AuthorizedRelayPeer.IsEqual(nets.backend.peerPublicKey) {
|
||||
peer.cmdTraverseForward(traverse)
|
||||
}
|
||||
}
|
||||
|
||||
case protocol.CommandTransfer:
|
||||
if msg, _ := protocol.DecodeTransfer(raw); msg != nil {
|
||||
// Validate sequence number which prevents unsolicited responses.
|
||||
isLast := msg.IsLast()
|
||||
sequenceInfo, valid, rtt := nets.Sequences.ValidateSequenceBi(raw.SenderPublicKey, raw.Sequence, isLast)
|
||||
if msg.Control != protocol.TransferControlRequestStart && !valid {
|
||||
//LogError("packetWorker", "message with invalid sequence %d command %d from %s\n", raw.Sequence, raw.Command, raw.connection.Address.String()) // Only log for debug purposes.
|
||||
continue
|
||||
} else if rtt > 0 {
|
||||
connection.RoundTripTime = rtt
|
||||
}
|
||||
raw.SequenceInfo = sequenceInfo
|
||||
|
||||
peer.cmdTransfer(msg, connection)
|
||||
}
|
||||
|
||||
case protocol.CommandGetBlock:
|
||||
if msg, _ := protocol.DecodeGetBlock(raw); msg != nil {
|
||||
// Validate sequence number which prevents unsolicited responses.
|
||||
isLast := msg.IsLast()
|
||||
sequenceInfo, valid, rtt := nets.Sequences.ValidateSequenceBi(raw.SenderPublicKey, raw.Sequence, isLast)
|
||||
if msg.Control != protocol.GetBlockControlRequestStart && !valid {
|
||||
//LogError("packetWorker", "message with invalid sequence %d command %d from %s\n", raw.Sequence, raw.Command, raw.connection.Address.String()) // Only log for debug purposes.
|
||||
continue
|
||||
} else if rtt > 0 {
|
||||
connection.RoundTripTime = rtt
|
||||
}
|
||||
raw.SequenceInfo = sequenceInfo
|
||||
|
||||
peer.cmdGetBlock(msg, connection)
|
||||
}
|
||||
|
||||
default: // Unknown command
|
||||
nets.backend.Filters.MessageIn(peer, raw, nil)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// GetNetworks returns the list of connected networks
|
||||
func (backend *Backend) GetNetworks(networkType int) (networksConnected []*Network) {
|
||||
switch networkType {
|
||||
case 4:
|
||||
return backend.networks.networks4
|
||||
case 6:
|
||||
return backend.networks.networks6
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetListen returns connectivity information
|
||||
func (network *Network) GetListen() (listen *net.UDPAddr, multicastIPv6 net.IP, broadcastIPv4 []net.IP, ipExternal net.IP, portExternal uint16) {
|
||||
return network.address, network.multicastIP, network.broadcastIPv4, network.ipExternal, network.portExternal
|
||||
}
|
||||
|
||||
// GetAdapterName returns the adapter name, if available
|
||||
func (network *Network) GetAdapterName() string {
|
||||
if network.iface != nil {
|
||||
return network.iface.Name
|
||||
}
|
||||
return "[unknown adapter]"
|
||||
}
|
||||
|
||||
// Terminate sends the termination signal to all workers. It is safe to call Terminate multiple times.
|
||||
func (network *Network) Terminate() {
|
||||
network.Lock()
|
||||
defer network.Unlock()
|
||||
|
||||
if network.isTerminated {
|
||||
return
|
||||
}
|
||||
|
||||
if !network.address.IP.IsLinkLocalUnicast() {
|
||||
if IsIPv4(network.address.IP) {
|
||||
atomic.AddInt64(&network.networkGroup.countListen4, -1)
|
||||
} else {
|
||||
atomic.AddInt64(&network.networkGroup.countListen6, -1)
|
||||
}
|
||||
}
|
||||
|
||||
// set the termination signal
|
||||
network.isTerminated = true
|
||||
close(network.terminateSignal) // safety guaranteed via lock
|
||||
network.socket.Close() // Will stop the listener from blocking on network.socket.ReadFromUDP
|
||||
|
||||
network.networkGroup.ipListen.Remove(network.address)
|
||||
}
|
||||
|
||||
// SelfReportedPorts returns the internal and external ports as self-reported by the peer to others.
|
||||
func (network *Network) SelfReportedPorts() (portI, portE uint16) {
|
||||
// The internal port is set to where the network listens on.
|
||||
// Datacenter: This should usually be the same as the outgoing port.
|
||||
// NAT: The internal port will be different than the outgoing one.
|
||||
portI = uint16(network.address.Port)
|
||||
|
||||
// External port: This is usually unknown, except in these 2 cases:
|
||||
// UPnP: The port is forwarded automatically.
|
||||
// Manual override in config: The user can specify a (global) incoming port that must be open on all listening IPs.
|
||||
// This external port will be then passed onto other peers who will use it to connect.
|
||||
portE = network.portExternal
|
||||
|
||||
if network.backend.Config.PortForward > 0 {
|
||||
portE = network.backend.Config.PortForward
|
||||
}
|
||||
|
||||
return portI, portE
|
||||
}
|
||||
|
||||
// FeatureSupport returns supported features by this peer
|
||||
func (backend *Backend) FeatureSupport() (feature byte) {
|
||||
if backend.networks.countListen4 > 0 {
|
||||
feature |= 1 << protocol.FeatureIPv4Listen
|
||||
}
|
||||
if backend.networks.countListen6 > 0 {
|
||||
feature |= 1 << protocol.FeatureIPv6Listen
|
||||
}
|
||||
if backend.networks.localFirewall {
|
||||
feature |= 1 << protocol.FeatureFirewall
|
||||
}
|
||||
return feature
|
||||
}
|
||||
|
||||
// Handles incoming lite packets. It will decrypt them as needed.
|
||||
func (nets *Networks) packetWorkerLite() {
|
||||
for wire := range nets.litePacketsIncoming {
|
||||
packet, err := nets.LiteRouter.PacketLiteDecode(wire.raw)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle the received data. Note this is called in the same Go routine.
|
||||
// The underlying data receiver must not stall.
|
||||
if v, ok := packet.Session.Data.(*VirtualPacketConn); ok {
|
||||
// update stats TODO
|
||||
//atomic.AddUint64(&packet.Session.Data.(*VirtualPacketConn).peer.StatsPacketReceived, 1)
|
||||
//connection.LastPacketIn = time.Now()
|
||||
|
||||
v.receiveData(packet.Payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
94
vendor/github.com/PeernetOfficial/core/Networks.go
generated
vendored
Normal file
94
vendor/github.com/PeernetOfficial/core/Networks.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
File Name: Networks.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
)
|
||||
|
||||
// Networks is the collection of all connected networks
|
||||
type Networks struct {
|
||||
// networks is a list of all connected networks
|
||||
networks4, networks6 []*Network
|
||||
|
||||
// Mutex for both network lists. Higher granularity currently not needed.
|
||||
sync.RWMutex
|
||||
|
||||
// countListenX is the number of networks listened to, excluding link-local only listeners. This number might be different than len(networksN).
|
||||
// This is useful to determine if there are any IPv4 or IPv6 listeners for potential external connections. This can be used to determine IPv4_LISTEN and IPv6_LISTEN.
|
||||
countListen4, countListen6 int64
|
||||
|
||||
// channel for processing incoming decoded packets by workers, across all networks
|
||||
rawPacketsIncoming chan networkWire
|
||||
litePacketsIncoming chan networkWire
|
||||
|
||||
// Sequences keeps track of all message sequence number, regardless of the network connection.
|
||||
Sequences *protocol.SequenceManager
|
||||
|
||||
// Keep track of valid IDs for lite packets.
|
||||
LiteRouter *protocol.LiteRouter
|
||||
|
||||
// ipListen keeps a simple list of IPs listened to. This allows quickly identifying if an IP matches with a listened one.
|
||||
ipListen *ipList
|
||||
|
||||
// localFirewall indicates if a local firewall may drop unsolicited incoming packets
|
||||
localFirewall bool
|
||||
|
||||
// UPnP data
|
||||
upnpListInterfaces map[string]struct{}
|
||||
upnpMutex sync.RWMutex
|
||||
|
||||
// backend
|
||||
backend *Backend
|
||||
}
|
||||
|
||||
// ReplyTimeout is the round-trip timeout for message sequences.
|
||||
const ReplyTimeout = 20
|
||||
|
||||
func (backend *Backend) initMessageSequence() {
|
||||
backend.networks = &Networks{backend: backend}
|
||||
|
||||
backend.networks.rawPacketsIncoming = make(chan networkWire, 1000) // buffer up to 1000 UDP packets before they get buffered by the OS network stack and eventually dropped
|
||||
backend.networks.litePacketsIncoming = make(chan networkWire, 1000) // buffer up to 1000 UDP packets before they get buffered by the OS network stack and eventually dropped
|
||||
|
||||
backend.networks.Sequences = protocol.NewSequenceManager(ReplyTimeout)
|
||||
backend.networks.LiteRouter = protocol.NewLiteRouter()
|
||||
|
||||
backend.networks.ipListen = NewIPList()
|
||||
|
||||
// There is currently no suitable live firewall detection code. Instead, there is the config flag.
|
||||
// Windows: If the user runs as non-admin, it can be assumed that the Windows Firewall creates a rule to drop unsolicited incoming packets.
|
||||
// Changing the Windows Firewall (via netsh or otherwise) requires elevated admin rights.
|
||||
// This flag will be passed on to other peers to indicate that uncontacted peers shall use the Traverse message for establishing connections.
|
||||
backend.firewallDetectIndicatorFile()
|
||||
backend.networks.localFirewall = backend.Config.LocalFirewall
|
||||
}
|
||||
|
||||
const firewallIndicatorFile = "firewallnotset"
|
||||
|
||||
// Detects the file "firewallnotset". It may be set by an Peernet client installer to indicate the presence of a local firewall.
|
||||
func (backend *Backend) firewallDetectIndicatorFile() {
|
||||
if _, err := os.Stat(firewallIndicatorFile); err == nil {
|
||||
// If the config firewall flag isn't set, set it.
|
||||
if !backend.Config.LocalFirewall {
|
||||
backend.Config.LocalFirewall = true
|
||||
|
||||
backend.SaveConfig()
|
||||
}
|
||||
|
||||
// the indicator is one-time only, remove
|
||||
os.Remove(firewallIndicatorFile)
|
||||
}
|
||||
}
|
||||
|
||||
// List of all lite sessions
|
||||
func (backend *Backend) LiteSessions() (sessions []*protocol.LiteID) {
|
||||
return backend.networks.LiteRouter.All()
|
||||
}
|
||||
308
vendor/github.com/PeernetOfficial/core/Peer ID.go
generated
vendored
Normal file
308
vendor/github.com/PeernetOfficial/core/Peer ID.go
generated
vendored
Normal file
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
File Name: Peer ID.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
"github.com/PeernetOfficial/core/dht"
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
)
|
||||
|
||||
func (backend *Backend) initPeerID() {
|
||||
backend.peerList = make(map[[btcec.PubKeyBytesLenCompressed]byte]*PeerInfo)
|
||||
backend.nodeList = make(map[[protocol.HashSize]byte]*PeerInfo)
|
||||
|
||||
// load existing key from config, if available
|
||||
if len(backend.Config.PrivateKey) > 0 {
|
||||
configPK, err := hex.DecodeString(backend.Config.PrivateKey)
|
||||
if err == nil {
|
||||
backend.peerPrivateKey, backend.peerPublicKey = btcec.PrivKeyFromBytes(btcec.S256(), configPK)
|
||||
backend.nodeID = protocol.PublicKey2NodeID(backend.peerPublicKey)
|
||||
|
||||
if backend.Config.AutoUpdateSeedList {
|
||||
backend.configUpdateSeedList()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
backend.LogError("initPeerID", "private key in config is corrupted! Error: %s\n", err.Error())
|
||||
os.Exit(ExitPrivateKeyCorrupt)
|
||||
}
|
||||
|
||||
// if the peer ID is empty, create a new user public-private key pair
|
||||
var err error
|
||||
backend.peerPrivateKey, backend.peerPublicKey, err = Secp256k1NewPrivateKey()
|
||||
if err != nil {
|
||||
backend.LogError("initPeerID", "generating public-private key pairs: %s\n", err.Error())
|
||||
os.Exit(ExitPrivateKeyCreate)
|
||||
}
|
||||
backend.nodeID = protocol.PublicKey2NodeID(backend.peerPublicKey)
|
||||
|
||||
// save the newly generated private key into the config
|
||||
backend.Config.PrivateKey = hex.EncodeToString(backend.peerPrivateKey.Serialize())
|
||||
|
||||
backend.SaveConfig()
|
||||
}
|
||||
|
||||
// Secp256k1NewPrivateKey creates a new public-private key pair
|
||||
func Secp256k1NewPrivateKey() (privateKey *btcec.PrivateKey, publicKey *btcec.PublicKey, err error) {
|
||||
key, err := btcec.NewPrivateKey(btcec.S256())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return key, (*btcec.PublicKey)(&key.PublicKey), nil
|
||||
}
|
||||
|
||||
// ExportPrivateKey returns the peers public and private key
|
||||
func (backend *Backend) ExportPrivateKey() (privateKey *btcec.PrivateKey, publicKey *btcec.PublicKey) {
|
||||
return backend.peerPrivateKey, backend.peerPublicKey
|
||||
}
|
||||
|
||||
// SelfNodeID returns the node ID used for DHT
|
||||
func (backend *Backend) SelfNodeID() []byte {
|
||||
return backend.nodeID
|
||||
}
|
||||
|
||||
// SelfUserAgent returns the User Agent
|
||||
func (backend *Backend) SelfUserAgent() string {
|
||||
return backend.userAgent
|
||||
}
|
||||
|
||||
// PeerInfo stores information about a single remote peer
|
||||
type PeerInfo struct {
|
||||
PublicKey *btcec.PublicKey // Public key
|
||||
NodeID []byte // Node ID in Kademlia network = blake3(Public Key).
|
||||
connectionActive []*Connection // List of active established connections to the peer.
|
||||
connectionInactive []*Connection // List of former connections that are no longer valid. They may be removed after a while.
|
||||
connectionLatest *Connection // Latest valid connection.
|
||||
sync.RWMutex // Mutex for access to list of connections.
|
||||
messageSequence uint32 // Sequence number. Increased with every message.
|
||||
IsRootPeer bool // Whether the peer is a trusted root peer.
|
||||
UserAgent string // User Agent reported by remote peer. Empty if no Announcement/Response message was yet received.
|
||||
Features uint8 // Feature bit array. 0 = IPv4_LISTEN, 1 = IPv6_LISTEN, 1 = FIREWALL
|
||||
isVirtual bool // Whether it is a virtual peer for establishing a connection.
|
||||
targetAddresses []*peerAddress // Virtual peer: Addresses to send any replies.
|
||||
traversePeer *PeerInfo // Virtual peer: Same field as in connection.
|
||||
BlockchainHeight uint64 // Blockchain height
|
||||
BlockchainVersion uint64 // Blockchain version
|
||||
blockchainLastRefresh time.Time // Last refresh of the blockchain info.
|
||||
|
||||
// statistics
|
||||
StatsPacketSent uint64 // Count of packets sent
|
||||
StatsPacketReceived uint64 // Count of packets received
|
||||
|
||||
Backend *Backend
|
||||
}
|
||||
|
||||
type peerAddress struct {
|
||||
IP net.IP
|
||||
Port uint16
|
||||
PortInternal uint16
|
||||
}
|
||||
|
||||
// PeerlistAdd adds a new peer to the peer list. It does not validate the peer info. If the peer is already added, it does nothing. Connections must be live.
|
||||
func (backend *Backend) PeerlistAdd(PublicKey *btcec.PublicKey, connections ...*Connection) (peer *PeerInfo, added bool) {
|
||||
if len(connections) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
publicKeyCompressed := publicKey2Compressed(PublicKey)
|
||||
|
||||
backend.peerlistMutex.Lock()
|
||||
defer backend.peerlistMutex.Unlock()
|
||||
|
||||
peer, ok := backend.peerList[publicKeyCompressed]
|
||||
if ok {
|
||||
return peer, false
|
||||
}
|
||||
|
||||
peer = &PeerInfo{Backend: backend, PublicKey: PublicKey, connectionActive: connections, connectionLatest: connections[0], NodeID: protocol.PublicKey2NodeID(PublicKey), messageSequence: rand.Uint32()}
|
||||
_, peer.IsRootPeer = rootPeers[publicKeyCompressed]
|
||||
|
||||
backend.peerList[publicKeyCompressed] = peer
|
||||
|
||||
// also add to mirrored nodeList
|
||||
var nodeID [protocol.HashSize]byte
|
||||
copy(nodeID[:], peer.NodeID)
|
||||
backend.nodeList[nodeID] = peer
|
||||
|
||||
// add to Kademlia
|
||||
backend.nodesDHT.AddNode(&dht.Node{ID: peer.NodeID, Info: peer})
|
||||
|
||||
// TODO: If the node isn't added to Kademlia, it should be either added temporarily to the peerList with an expiration, or to a temp list, or not at all.
|
||||
|
||||
// send to all channels non-blocking
|
||||
for _, monitor := range backend.peerMonitor {
|
||||
select {
|
||||
case monitor <- peer:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
backend.Filters.NewPeer(peer, connections[0])
|
||||
backend.Filters.NewPeerConnection(peer, connections[0])
|
||||
|
||||
return peer, true
|
||||
}
|
||||
|
||||
// PeerlistRemove removes a peer from the peer list.
|
||||
func (backend *Backend) PeerlistRemove(peer *PeerInfo) {
|
||||
backend.peerlistMutex.Lock()
|
||||
defer backend.peerlistMutex.Unlock()
|
||||
|
||||
// remove from Kademlia
|
||||
backend.nodesDHT.RemoveNode(peer.NodeID)
|
||||
|
||||
delete(backend.peerList, publicKey2Compressed(peer.PublicKey))
|
||||
|
||||
var nodeID [protocol.HashSize]byte
|
||||
copy(nodeID[:], peer.NodeID)
|
||||
|
||||
delete(backend.nodeList, nodeID)
|
||||
}
|
||||
|
||||
// PeerlistGet returns the full peer list
|
||||
func (backend *Backend) PeerlistGet() (peers []*PeerInfo) {
|
||||
backend.peerlistMutex.RLock()
|
||||
defer backend.peerlistMutex.RUnlock()
|
||||
|
||||
for _, peer := range backend.peerList {
|
||||
peers = append(peers, peer)
|
||||
}
|
||||
|
||||
return peers
|
||||
}
|
||||
|
||||
// PeerlistLookup returns the peer from the list with the public key
|
||||
func (backend *Backend) PeerlistLookup(publicKey *btcec.PublicKey) (peer *PeerInfo) {
|
||||
backend.peerlistMutex.RLock()
|
||||
defer backend.peerlistMutex.RUnlock()
|
||||
|
||||
return backend.peerList[publicKey2Compressed(publicKey)]
|
||||
}
|
||||
|
||||
// NodelistLookup returns the peer from the list with the node ID
|
||||
func (backend *Backend) NodelistLookup(nodeID []byte) (peer *PeerInfo) {
|
||||
backend.peerlistMutex.RLock()
|
||||
defer backend.peerlistMutex.RUnlock()
|
||||
|
||||
var nodeID2 [protocol.HashSize]byte
|
||||
copy(nodeID2[:], nodeID)
|
||||
|
||||
return backend.nodeList[nodeID2]
|
||||
}
|
||||
|
||||
// PeerlistCount returns the current count of peers in the peer list
|
||||
func (backend *Backend) PeerlistCount() (count int) {
|
||||
backend.peerlistMutex.RLock()
|
||||
defer backend.peerlistMutex.RUnlock()
|
||||
|
||||
return len(backend.peerList)
|
||||
}
|
||||
|
||||
func publicKey2Compressed(publicKey *btcec.PublicKey) [btcec.PubKeyBytesLenCompressed]byte {
|
||||
var key [btcec.PubKeyBytesLenCompressed]byte
|
||||
copy(key[:], publicKey.SerializeCompressed())
|
||||
return key
|
||||
}
|
||||
|
||||
// records2Nodes translates infoPeer structures to nodes reported by the peer. If the reported nodes are not in the peer table, it will create temporary PeerInfo structures.
|
||||
// LastContact is passed on in the Node.LastSeen field.
|
||||
func (peerSource *PeerInfo) records2Nodes(records []protocol.PeerRecord) (nodes []*dht.Node) {
|
||||
for _, record := range records {
|
||||
if peerSource.Backend.isReturnedPeerBadQuality(&record) {
|
||||
continue
|
||||
}
|
||||
|
||||
var peer *PeerInfo
|
||||
if record.PublicKey.IsEqual(peerSource.PublicKey) {
|
||||
// Special case if peer that stores info = sender. In that case IP:Port in the record would be empty anyway.
|
||||
peer = peerSource
|
||||
} else if peer = peerSource.Backend.PeerlistLookup(record.PublicKey); peer == nil {
|
||||
// Create temporary peer which is not added to the global list and not added to Kademlia.
|
||||
// traversePeer is set to the peer who provided the node information.
|
||||
addresses := peerRecordToAddresses(&record)
|
||||
if len(addresses) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
peer = &PeerInfo{Backend: peerSource.Backend, PublicKey: record.PublicKey, connectionActive: nil, connectionLatest: nil, NodeID: protocol.PublicKey2NodeID(record.PublicKey), messageSequence: rand.Uint32(), isVirtual: true, targetAddresses: addresses, traversePeer: peerSource, Features: record.Features}
|
||||
}
|
||||
|
||||
nodes = append(nodes, &dht.Node{ID: peer.NodeID, LastSeen: record.LastContactT, Info: peer})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// selfPeerRecord returns self as peer record
|
||||
func (backend *Backend) selfPeerRecord() (result protocol.PeerRecord) {
|
||||
return protocol.PeerRecord{
|
||||
PublicKey: backend.peerPublicKey,
|
||||
NodeID: backend.nodeID,
|
||||
//IP: network.address.IP,
|
||||
//Port: uint16(network.address.Port),
|
||||
LastContact: 0,
|
||||
Features: backend.FeatureSupport(),
|
||||
}
|
||||
}
|
||||
|
||||
// registerPeerMonitor registers a channel to receive all new peers
|
||||
func (backend *Backend) registerPeerMonitor(channel chan<- *PeerInfo) {
|
||||
backend.peerlistMutex.Lock()
|
||||
defer backend.peerlistMutex.Unlock()
|
||||
|
||||
backend.peerMonitor = append(backend.peerMonitor, channel)
|
||||
}
|
||||
|
||||
// unregisterPeerMonitor unregisters a channel
|
||||
func (backend *Backend) unregisterPeerMonitor(channel chan<- *PeerInfo) {
|
||||
backend.peerlistMutex.Lock()
|
||||
defer backend.peerlistMutex.Unlock()
|
||||
|
||||
for n, channel2 := range backend.peerMonitor {
|
||||
if channel == channel2 {
|
||||
peerMonitorNew := backend.peerMonitor[:n]
|
||||
if n < len(backend.peerMonitor)-1 {
|
||||
peerMonitorNew = append(peerMonitorNew, backend.peerMonitor[n+1:]...)
|
||||
}
|
||||
backend.peerMonitor = peerMonitorNew
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteAccount deletes the account
|
||||
func (backend *Backend) DeleteAccount() {
|
||||
// delete the blockchain
|
||||
backend.UserBlockchain.DeleteBlockchain()
|
||||
|
||||
// delete the warehouse
|
||||
backend.UserWarehouse.DeleteWarehouse()
|
||||
|
||||
// delete the private key
|
||||
backend.Config.PrivateKey = ""
|
||||
backend.SaveConfig()
|
||||
}
|
||||
|
||||
// PublicKeyFromPeerID decodes the peer ID (hex encoded) into a public key.
|
||||
func PublicKeyFromPeerID(peerID string) (publicKey *btcec.PublicKey, err error) {
|
||||
hash, err := hex.DecodeString(peerID)
|
||||
if err != nil || len(hash) != 33 {
|
||||
return nil, errors.New("invalid peer ID length")
|
||||
}
|
||||
|
||||
return btcec.ParsePubKey(hash, btcec.S256())
|
||||
}
|
||||
122
vendor/github.com/PeernetOfficial/core/Peernet.go
generated
vendored
Normal file
122
vendor/github.com/PeernetOfficial/core/Peernet.go
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
File Name: Peernet.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/PeernetOfficial/core/blockchain"
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
"github.com/PeernetOfficial/core/dht"
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
"github.com/PeernetOfficial/core/search"
|
||||
"github.com/PeernetOfficial/core/store"
|
||||
"github.com/PeernetOfficial/core/warehouse"
|
||||
)
|
||||
|
||||
// Init initializes the client. If the config file does not exist or is empty, a default one will be created.
|
||||
// The User Agent must be provided in the form "Application Name/1.0".
|
||||
// The returned status is of type ExitX. Anything other than ExitSuccess indicates a fatal failure.
|
||||
func Init(UserAgent string, ConfigFilename string, Filters *Filters, ConfigOut interface{}) (backend *Backend, status int, err error) {
|
||||
if UserAgent == "" {
|
||||
return
|
||||
}
|
||||
|
||||
backend = &Backend{
|
||||
ConfigFilename: ConfigFilename,
|
||||
userAgent: UserAgent,
|
||||
Stdout: newMultiWriter(),
|
||||
}
|
||||
|
||||
if Filters != nil {
|
||||
backend.Filters = *Filters
|
||||
}
|
||||
|
||||
// The configuration and log init are fatal events if they fail.
|
||||
if status, err = LoadConfig(ConfigFilename, &backend.Config); status != ExitSuccess {
|
||||
return nil, status, err
|
||||
}
|
||||
if ConfigOut != nil {
|
||||
if status, err = LoadConfig(ConfigFilename, ConfigOut); status != ExitSuccess {
|
||||
return nil, status, err
|
||||
}
|
||||
backend.ConfigClient = ConfigOut
|
||||
}
|
||||
|
||||
if err = backend.initLog(); err != nil {
|
||||
return nil, ExitErrorLogInit, err
|
||||
}
|
||||
|
||||
backend.initFilters()
|
||||
backend.initPeerID()
|
||||
backend.initUserBlockchain()
|
||||
backend.initUserWarehouse()
|
||||
backend.initKademlia()
|
||||
backend.initMessageSequence()
|
||||
backend.initSeedList()
|
||||
initMulticastIPv6()
|
||||
initBroadcastIPv4()
|
||||
backend.initStore()
|
||||
backend.initNetwork()
|
||||
backend.initBlockchainCache()
|
||||
|
||||
if backend.SearchIndex, err = search.InitSearchIndexStore(backend.Config.SearchIndex); err != nil {
|
||||
backend.LogError("Init", "search index '%s' init: %s", backend.Config.SearchIndex, err.Error())
|
||||
} else {
|
||||
backend.userBlockchainUpdateSearchIndex()
|
||||
}
|
||||
|
||||
return backend, ExitSuccess, nil
|
||||
}
|
||||
|
||||
// Connect starts bootstrapping and local peer discovery.
|
||||
func (backend *Backend) Connect() {
|
||||
go backend.bootstrapKademlia()
|
||||
go backend.bootstrap()
|
||||
go backend.networks.autoMulticastBroadcast()
|
||||
go backend.autoPingAll()
|
||||
go backend.networks.networkChangeMonitor()
|
||||
go backend.networks.startUPnP()
|
||||
go backend.autoBucketRefresh()
|
||||
}
|
||||
|
||||
// The Backend represents an instance of a Peernet client to be used by a frontend.
|
||||
// Global variables and init functions are to be merged.
|
||||
type Backend struct {
|
||||
ConfigFilename string // Filename of the configuration file.
|
||||
Config *Config // Core configuration
|
||||
ConfigClient interface{} // Custom configuration from the client
|
||||
Filters Filters // Filters allow to install hooks.
|
||||
userAgent string // User Agent
|
||||
GlobalBlockchainCache *BlockchainCache // Caches blockchains of other peers.
|
||||
SearchIndex *search.SearchIndexStore // Search index of blockchain records.
|
||||
networks *Networks // All connected networks.
|
||||
dhtStore store.Store // dhtStore contains all key-value data served via DHT
|
||||
UserBlockchain *blockchain.Blockchain // UserBlockchain is the user's blockchain and exports functions to directly read and write it
|
||||
UserWarehouse *warehouse.Warehouse // UserWarehouse is the user's warehouse for storing files that are shared
|
||||
nodesDHT *dht.DHT // Nodes connected in the DHT.
|
||||
|
||||
// peerID is the current peer's ID. It is a ECDSA (secp256k1) 257-bit public key.
|
||||
peerPrivateKey *btcec.PrivateKey
|
||||
peerPublicKey *btcec.PublicKey
|
||||
|
||||
// The node ID is the blake3 hash of the public key compressed form.
|
||||
nodeID []byte
|
||||
|
||||
// peerList keeps track of all peers
|
||||
peerList map[[btcec.PubKeyBytesLenCompressed]byte]*PeerInfo
|
||||
peerlistMutex sync.RWMutex
|
||||
|
||||
// nodeList is a mirror of peerList but using the node ID
|
||||
nodeList map[[protocol.HashSize]byte]*PeerInfo
|
||||
|
||||
// peerMonitor is a list of channels receiving information about new peers
|
||||
peerMonitor []chan<- *PeerInfo
|
||||
|
||||
// Stdout bundles any output for the end-user. Writers may subscribe/unsubscribe.
|
||||
Stdout *multiWriter
|
||||
}
|
||||
78
vendor/github.com/PeernetOfficial/core/Ping.go
generated
vendored
Normal file
78
vendor/github.com/PeernetOfficial/core/Ping.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
File Name: Ping.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// pingTime is the time in seconds to send out ping messages
|
||||
const pingTime = 10
|
||||
|
||||
// thresholdBlockchainRefresh is the threshold to refresh the blockchain information by sending an Announcement (and expecting the Response message).
|
||||
// This helps for keeping the global blockchain cache up to date.
|
||||
const thresholdBlockchainRefresh = 60 * time.Second
|
||||
|
||||
// connectionInvalidate is the threshold in seconds to invalidate formerly active connections that no longer receive incoming packets.
|
||||
const connectionInvalidate = 22
|
||||
|
||||
// connectionRemove is the threshold in seconds to remove inactive connections in case there is at least one active connection known.
|
||||
const connectionRemove = 2 * 60
|
||||
|
||||
// autoPingAll sends out regular ping messages to all connections of all peers. This allows to detect invalid connections and eventually drop them.
|
||||
func (backend *Backend) autoPingAll() {
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
thresholdInvalidate1 := time.Now().Add(-connectionInvalidate * time.Second)
|
||||
thresholdInvalidate2 := time.Now().Add(-connectionInvalidate * time.Second * 4)
|
||||
thresholdPingOut1 := time.Now().Add(-pingTime * time.Second)
|
||||
thresholdPingOut2 := time.Now().Add(-pingTime * time.Second * 4)
|
||||
thresholdBlockchainRefresh := time.Now().Add(-thresholdBlockchainRefresh)
|
||||
|
||||
for _, peer := range backend.PeerlistGet() {
|
||||
// first handle active connections
|
||||
for _, connection := range peer.GetConnections(true) {
|
||||
thresholdPing := thresholdPingOut1
|
||||
thresholdInv := thresholdInvalidate1
|
||||
|
||||
if connection.Status == ConnectionRedundant {
|
||||
thresholdPing = thresholdPingOut2
|
||||
thresholdInv = thresholdInvalidate2
|
||||
}
|
||||
|
||||
if connection.LastPacketIn.Before(thresholdInv) {
|
||||
peer.invalidateActiveConnection(connection)
|
||||
continue
|
||||
}
|
||||
|
||||
if connection.LastPacketIn.Before(thresholdPing) && connection.LastPingOut.Before(thresholdPing) {
|
||||
if connection.Status == ConnectionActive && peer.blockchainLastRefresh.Before(thresholdBlockchainRefresh) {
|
||||
peer.pingConnectionAnnouncement(connection)
|
||||
} else {
|
||||
// just a regular ping otherwise
|
||||
peer.pingConnection(connection)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// handle inactive connections
|
||||
for _, connection := range peer.GetConnections(false) {
|
||||
// If the inactive connection is expired, remove it; although only if there is at least one active connection, or two other inactive ones.
|
||||
if (len(peer.connectionActive) >= 1 || len(peer.connectionInactive) > 2) && connection.Expires.Before(time.Now()) {
|
||||
peer.removeInactiveConnection(connection)
|
||||
continue
|
||||
}
|
||||
|
||||
// if no ping was sent recently, send one now
|
||||
if connection.LastPingOut.Before(thresholdPingOut1) {
|
||||
peer.pingConnection(connection)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
127
vendor/github.com/PeernetOfficial/core/README.md
generated
vendored
Normal file
127
vendor/github.com/PeernetOfficial/core/README.md
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
# Peernet Core
|
||||
|
||||
The core library which is needed for any Peernet application. It provides connectivity to the network and all basic functions. For details about Peernet see https://peernet.org/.
|
||||
|
||||
Current version: Alpha 6
|
||||
|
||||
## Use
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/PeernetOfficial/core"
|
||||
)
|
||||
|
||||
func main() {
|
||||
backend, status, err := core.Init("Your application/1.0", "Config.yaml", nil, nil)
|
||||
if status != core.ExitSuccess {
|
||||
fmt.Printf("Error %d initializing backend: %s\n", status, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
backend.Connect()
|
||||
|
||||
// Use the backend functions for example to search for files or download them.
|
||||
// The webapi package exports some high-level functions that can be used directly (without calling the HTTP API).
|
||||
}
|
||||
```
|
||||
|
||||
## Encryption and Hashing functions
|
||||
|
||||
* Salsa20 is used for encrypting the packets.
|
||||
* secp256k1 is used to generate the peer IDs (public keys).
|
||||
* blake3 is used for hashing the packets when signing and as hashing algorithm for the DHT.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Go 1.16 or higher is required. All dependencies are automatically downloaded via Go modules.
|
||||
|
||||
## Configuration
|
||||
|
||||
Peernet follows a "zeroconf" approach, meaning there is no manual configuration required. However, in certain cases such as providing root peers that shall listen on a fixed IP and port, it is desirable to create a config file. See inline documentation in the file [Config Default.yaml](Config%20Default.yaml).
|
||||
|
||||
The name of the config file is passed to the function `LoadConfig`. If it does not exist, it will be created with the values from the file `Config Default.yaml`. It uses the YAML format. Any public/private keys in the config are hex encoded. Here are some notable settings:
|
||||
|
||||
* `PrivateKey` The users Private Key hex encoded. The users public key is derived from it.
|
||||
* `Listen` defines IP:Port combinations to listen on. If not specified, it will listen on all IPs. You can specify an IP but port 0 for auto port selection. IPv6 addresses must be in the format "[IPv6]:Port".
|
||||
|
||||
Root peer = A peer operated by a known trusted entity. They allow to speed up the network including discovery of peers and data.
|
||||
|
||||
### Private Key
|
||||
|
||||
The Private Key is required to make any changes to the user's blockchain, including deleting, renaming, and adding files on Peernet, or nuking the blockchain. If the private key is lost, no write access will be possible. Users should always create a secure backup of their private key.
|
||||
|
||||
## Connectivity
|
||||
|
||||
### Bootstrap Strategy
|
||||
|
||||
* Connection to root peers (initial seed list):
|
||||
* Immediate contact to all root peers.
|
||||
* Phase 1: First 10 minutes. Try every 7 seconds to connect to all root peers until at least 2 peers connected.
|
||||
* Phase 2: After that (if not 2 peers), try every 5 minutes to connect to remaining root peers for a maximum of 1 hour.
|
||||
* Local peer discovery via IPv4 Broadcast and IPv6 Multicast:
|
||||
* Send out immediately when starting.
|
||||
* Phase 1: Resend every 10 seconds until at least 1 peer in the peer list.
|
||||
* Phase 2: Every 10 minutes.
|
||||
* Network adapter changes:
|
||||
* Check every 10 seconds for new/removed network interfaces and for new/removed IPs.
|
||||
* Automatically listen on any newly detected network interfaces and IPs.
|
||||
* Start a full Kademlia bucket refresh in case of a new network interface or IP.
|
||||
* Kademlia bootstrap:
|
||||
* As soon as there are at least 2 peers, keep refreshing buckets (target number is alpha) every 10 seconds 3 times.
|
||||
|
||||
### Ping
|
||||
|
||||
The Ping/Pong commands are used to verify whether connections remain valid. They are only sent in absence of any other commands in the defined timeframe.
|
||||
|
||||
* Active connections
|
||||
* Invalidate if 'Last packet in' was older than 22 seconds ago.
|
||||
* Send ping if last ping and 'Last packet in' was earlier than 10 seconds.
|
||||
* Redundant connections to the same peer (= any connections exceeding the 1 main active one): Mentioned times are multiplied by 4.
|
||||
* Inactive connections
|
||||
* If inactive for 120 seconds, remove the connection.
|
||||
* If there are no connections (neither active/inactive) to the peer, remove it.
|
||||
* Send ping if last ping was earlier than 10 seconds.
|
||||
|
||||
Above limits are constants and can be adjusted in the code via `pingTime`, `connectionInvalidate`, and `connectionRemove`.
|
||||
|
||||
### Kademlia
|
||||
|
||||
The routing table has a bucket size of 20 and the size of keys 256 bits (blake3 hash). Nodes within buckets are sorted by least recently seen. The number of nodes to contact concurrently in DHT lookups (also known as alpha number) is set to 5.
|
||||
|
||||
If a bucket is full when a new peer connects `ShouldEvict` is called. It compares the RTTs (favoring smaller one) and in absence of the RTT time it will favor the node which is closer by XOR distance. Refresh of buckets is done every 5 minutes and queries a random ID in that bucket if there are not at least alpha nodes. A full refresh of all buckets is done every hour.
|
||||
|
||||
### Timeouts
|
||||
|
||||
* The default reply timeout (round-trip time) is 20 seconds set in `ReplyTimeout`. This applies to Response and Pong messages. The RTT timeout implies an average minimum connection speed between peers of about 6.4 KB/s for files of 64 KB size.
|
||||
* Separate timeouts for file transfers will be established.
|
||||
|
||||
### MTU
|
||||
|
||||
The default MTU is set to 1280 bytes (see `internetSafeMTU` constant). This value (and by extension the lower value `TransferMaxEmbedSize` for file transfer) is chosen for safe transfer, not for highest performance. Different environments (IPv4, IPv6, Ethernet, Internet) have different smallest and common supported MTUs. MTU negotiation or detection is currently not implemented.
|
||||
|
||||
### File Transfer Performance
|
||||
|
||||
The packet encryption/signing overhead appears to require significant CPU overhead during file transfer. This can be improved in the future by defining special file transfer packets that start with a UUID and not the regular protocol header. It would reduce processing time, increase payload data per packet, and therefore the overall transfer speed. A symmetric encryption algorithm (and key negotiation during file transfer initiation) would be required to not lose the security benefit.
|
||||
|
||||
### Network Listen
|
||||
|
||||
Unless specified in the config via `Listen`, it will listen on all network adapters. The default port is 112, but that may be randomized in the future.
|
||||
|
||||
* Traffic between link-local unicast IPs and non link-local IPs is not allowed.
|
||||
* UPnP is supported on IPv4 only for now.
|
||||
|
||||
## OS Support
|
||||
|
||||
The code is compatible and tested on Windows, Linux, Mac, and Android.
|
||||
|
||||
ARM32 is not supported due to a [Go compiler bug](https://github.com/golang/go/issues/36606) affecting [64-bit atomic access](https://pkg.go.dev/sync/atomic#pkg-note-BUG). This may affect old Raspberry Pis.
|
||||
|
||||
## Contributing
|
||||
|
||||
Please note that by contributing code, documentation, ideas, snippets, or any other intellectual property you agree that you have all the necessary rights and you agree that we, the Peernet organization, may use it for any purpose.
|
||||
|
||||
© 2021 Peernet s.r.o.
|
||||
183
vendor/github.com/PeernetOfficial/core/Transfer Block.go
generated
vendored
Normal file
183
vendor/github.com/PeernetOfficial/core/Transfer Block.go
generated
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
File Name: Transfer Block.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/blockchain"
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
"github.com/PeernetOfficial/core/udt"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// blockSequenceTimeout is the timeout for a follow-up message to appear, otherwise the transfer will be terminated.
|
||||
var blockSequenceTimeout = time.Second * 10
|
||||
|
||||
// Whether to use the lite protocol for transfer of data.
|
||||
const blockTransferLite = true
|
||||
|
||||
// startBlockTransfer starts the transfer of blocks. Currently it only serves the user's blockchain.
|
||||
func (peer *PeerInfo) startBlockTransfer(BlockchainPublicKey *btcec.PublicKey, LimitBlockCount uint64, MaxBlockSize uint64, TargetBlocks []protocol.BlockRange, sequenceNumber uint32, transferID uuid.UUID) (err error) {
|
||||
virtualConn := newVirtualPacketConn(peer, func(data []byte, sequenceNumber uint32, transferID uuid.UUID) {
|
||||
peer.sendGetBlock(data, protocol.GetBlockControlActive, BlockchainPublicKey, 0, 0, nil, sequenceNumber, transferID, blockTransferLite)
|
||||
})
|
||||
virtualConn.Stats = &BlockTransferStats{BlockchainPublicKey: BlockchainPublicKey, Direction: DirectionOut, LimitBlockCount: LimitBlockCount, MaxBlockSize: MaxBlockSize, TargetBlocks: TargetBlocks}
|
||||
|
||||
// use the transfer ID indicated by the remote peer
|
||||
// 17.01.2021: Due to using lite IDs, the sequence termination function in RegisterSequenceBi is no longer used, as data packets are only sent via lite packets.
|
||||
virtualConn.transferID = transferID
|
||||
peer.Backend.networks.LiteRouter.RegisterLiteID(transferID, virtualConn, blockSequenceTimeout, virtualConn.sequenceTerminate)
|
||||
|
||||
// register the sequence since packets are sent bi-directional
|
||||
virtualConn.sequenceNumber = sequenceNumber
|
||||
peer.Backend.networks.Sequences.RegisterSequenceBi(peer.PublicKey, sequenceNumber, virtualConn, blockSequenceTimeout, nil)
|
||||
|
||||
udtConfig := udt.DefaultConfig()
|
||||
udtConfig.MaxPacketSize = protocol.TransferMaxEmbedSizeLite
|
||||
udtConfig.MaxFlowWinSize = maxFlowWinSize
|
||||
|
||||
// start UDT sender
|
||||
// Set streaming to true, otherwise udtSocket.Read returns the error "Message truncated" in case the reader has a smaller buffer.
|
||||
udtConn, err := udt.DialUDT(udtConfig, virtualConn, virtualConn.incomingData, virtualConn.outgoingData, virtualConn.terminationSignal, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer udtConn.Close()
|
||||
virtualConn.Stats.(*BlockTransferStats).UDTConn = udtConn
|
||||
|
||||
// loop through the requested TargetBlocks range.
|
||||
sentBlocks := uint64(0)
|
||||
|
||||
for _, target := range TargetBlocks {
|
||||
for blockN := target.Offset; blockN < target.Offset+target.Limit; blockN++ {
|
||||
blockData, status, err := peer.Backend.UserBlockchain.GetBlockRaw(blockN)
|
||||
if err != nil {
|
||||
protocol.BlockTransferWriteHeader(udtConn, protocol.GetBlockStatusNotAvailable, protocol.BlockRange{Offset: blockN, Limit: 1}, 0)
|
||||
continue
|
||||
}
|
||||
blockSize := uint64(len(blockData))
|
||||
|
||||
if status != blockchain.StatusOK {
|
||||
protocol.BlockTransferWriteHeader(udtConn, protocol.GetBlockStatusNotAvailable, protocol.BlockRange{Offset: blockN, Limit: 1}, 0)
|
||||
continue
|
||||
} else if blockSize > MaxBlockSize {
|
||||
protocol.BlockTransferWriteHeader(udtConn, protocol.GetBlockStatusSizeExceed, protocol.BlockRange{Offset: blockN, Limit: 1}, blockSize)
|
||||
continue
|
||||
}
|
||||
|
||||
protocol.BlockTransferWriteHeader(udtConn, protocol.GetBlockStatusAvailable, protocol.BlockRange{Offset: blockN, Limit: 1}, blockSize)
|
||||
udtConn.Write(blockData)
|
||||
|
||||
sentBlocks++
|
||||
if sentBlocks >= LimitBlockCount {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// BlockTransferRequest requests blocks from the peer.
|
||||
// The caller must call udtConn.Close() when done. Do not use any of the closing functions of virtualConn.
|
||||
func (peer *PeerInfo) BlockTransferRequest(BlockchainPublicKey *btcec.PublicKey, LimitBlockCount uint64, MaxBlockSize uint64, TargetBlocks []protocol.BlockRange) (udtConn *udt.UDTSocket, virtualConn *VirtualPacketConn, err error) {
|
||||
virtualConn = newVirtualPacketConn(peer, func(data []byte, sequenceNumber uint32, transferID uuid.UUID) {
|
||||
peer.sendGetBlock(data, protocol.GetBlockControlActive, BlockchainPublicKey, 0, 0, nil, sequenceNumber, transferID, blockTransferLite)
|
||||
})
|
||||
virtualConn.Stats = &BlockTransferStats{BlockchainPublicKey: BlockchainPublicKey, Direction: DirectionIn, LimitBlockCount: LimitBlockCount, MaxBlockSize: MaxBlockSize, TargetBlocks: TargetBlocks}
|
||||
|
||||
// new lite ID
|
||||
liteID := peer.Backend.networks.LiteRouter.NewLiteID(virtualConn, blockSequenceTimeout, virtualConn.sequenceTerminate)
|
||||
virtualConn.transferID = liteID.ID
|
||||
|
||||
// new sequence
|
||||
sequence := peer.Backend.networks.Sequences.NewSequenceBi(peer.PublicKey, &peer.messageSequence, virtualConn, blockSequenceTimeout, nil)
|
||||
if sequence == nil {
|
||||
return nil, nil, errors.New("cannot acquire sequence")
|
||||
}
|
||||
virtualConn.sequenceNumber = sequence.SequenceNumber
|
||||
|
||||
udtConfig := udt.DefaultConfig()
|
||||
udtConfig.MaxPacketSize = protocol.TransferMaxEmbedSizeLite
|
||||
udtConfig.MaxFlowWinSize = maxFlowWinSize
|
||||
|
||||
// start UDT receiver
|
||||
udtListener := udt.ListenUDT(udtConfig, virtualConn, virtualConn.incomingData, virtualConn.outgoingData, virtualConn.terminationSignal)
|
||||
|
||||
// request block transfer
|
||||
err = peer.sendGetBlock(nil, protocol.GetBlockControlRequestStart, BlockchainPublicKey, LimitBlockCount, MaxBlockSize, TargetBlocks, virtualConn.sequenceNumber, virtualConn.transferID, false)
|
||||
if err != nil {
|
||||
udtListener.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// accept the connection
|
||||
udtConn, err = udtListener.Accept() // TODO: Add timeout!
|
||||
if err != nil {
|
||||
udtListener.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
virtualConn.Stats.(*BlockTransferStats).UDTConn = udtConn
|
||||
|
||||
// We do not close the UDT listener here. It should automatically close after udtConn is closed.
|
||||
|
||||
return udtConn, virtualConn, nil
|
||||
}
|
||||
|
||||
// Downloads the requested blocks for the selected blockchain from the remote peer. The callback is called for each result.
|
||||
func (peer *PeerInfo) BlockDownload(BlockchainPublicKey *btcec.PublicKey, LimitBlockCount, MaxBlockSize uint64, TargetBlocks []protocol.BlockRange, callback func(data []byte, targetBlock protocol.BlockRange, blockSize uint64, availability uint8)) (err error) {
|
||||
conn, _, err := peer.BlockTransferRequest(BlockchainPublicKey, LimitBlockCount, MaxBlockSize, TargetBlocks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
var limit uint64
|
||||
for _, target := range TargetBlocks {
|
||||
limit += target.Limit
|
||||
}
|
||||
|
||||
for n := uint64(0); n < limit; {
|
||||
data, targetBlock, blockSize, availability, err := protocol.BlockTransferReadBlock(conn, MaxBlockSize)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !isTargetInRange(TargetBlocks, targetBlock.Offset, targetBlock.Limit) {
|
||||
return errors.New("invalid returned block range")
|
||||
}
|
||||
|
||||
// TODO: Check if the block was already returned in case the block is available. This can be done via simple map.
|
||||
|
||||
callback(data, targetBlock, blockSize, availability)
|
||||
|
||||
n += targetBlock.Limit
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isTargetInRange(targets []protocol.BlockRange, offset, limit uint64) (valid bool) {
|
||||
for _, target := range targets {
|
||||
if offset >= target.Offset && offset+limit <= target.Offset+target.Limit {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type BlockTransferStats struct {
|
||||
BlockchainPublicKey *btcec.PublicKey // Target blockchain
|
||||
Direction int // Direction of the data transfer
|
||||
LimitBlockCount uint64 // Max count of blocks to be transferred
|
||||
MaxBlockSize uint64 // Max single block size to transfer
|
||||
TargetBlocks []protocol.BlockRange // List of blocks to transfer
|
||||
UDTConn *udt.UDTSocket // Underlying UDT connection
|
||||
}
|
||||
135
vendor/github.com/PeernetOfficial/core/Transfer UDT.go
generated
vendored
Normal file
135
vendor/github.com/PeernetOfficial/core/Transfer UDT.go
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
File Name: Transfer UDT.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
TODO: Add timeouts for listening and sending.
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
"github.com/PeernetOfficial/core/udt"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// transferSequenceTimeout is the timeout for a follow-up message to appear, otherwise the transfer will be terminated.
|
||||
var transferSequenceTimeout = time.Minute * 1
|
||||
|
||||
// maxFlowWinSize is the maximum number of unacknowledged packets to permit. A higher number means using more memory, but reduces potential overhead since it does not stop so quickly for missing packets.
|
||||
// Each unacknowledged packet may store protocol.TransferMaxEmbedSize (currently 1121 bytes) payload data in memory. A too high number may impact the speed of real-time streaming in case of lost packets.
|
||||
// The actual used number will be negotiated through the UDT handshake and must be a minimum of 32.
|
||||
const maxFlowWinSize = 64
|
||||
|
||||
// Whether to use the lite protocol for transfer of data.
|
||||
const transferLite = true
|
||||
|
||||
// startFileTransferUDT starts a file transfer from the local warehouse to the remote peer.
|
||||
// It creates a virtual UDT client to transfer data to a remote peer. Counterintuitively, this will be the "file server" peer.
|
||||
func (peer *PeerInfo) startFileTransferUDT(hash []byte, fileSize uint64, offset, limit uint64, sequenceNumber uint32, transferID uuid.UUID, transferProtocol uint8) (err error) {
|
||||
if limit > 0 && offset+limit > fileSize {
|
||||
return errors.New("invalid limit")
|
||||
} else if offset > fileSize {
|
||||
return errors.New("invalid offset")
|
||||
} else if limit == 0 {
|
||||
limit = fileSize - offset
|
||||
}
|
||||
|
||||
virtualConn := newVirtualPacketConn(peer, func(data []byte, sequenceNumber uint32, transferID uuid.UUID) {
|
||||
peer.sendTransfer(data, protocol.TransferControlActive, 0, hash, offset, limit, sequenceNumber, transferID, transferLite)
|
||||
})
|
||||
virtualConn.Stats = &FileTransferStats{Hash: hash, Direction: DirectionOut, FileSize: fileSize, Offset: offset, Limit: limit}
|
||||
|
||||
// use the transfer ID indicated by the remote peer
|
||||
// 17.01.2021: Due to using lite IDs, the sequence termination function in RegisterSequenceBi is no longer used, as data packets are only sent via lite packets.
|
||||
virtualConn.transferID = transferID
|
||||
peer.Backend.networks.LiteRouter.RegisterLiteID(transferID, virtualConn, transferSequenceTimeout, virtualConn.sequenceTerminate)
|
||||
|
||||
// register the sequence since packets are sent bi-directional
|
||||
virtualConn.sequenceNumber = sequenceNumber
|
||||
peer.Backend.networks.Sequences.RegisterSequenceBi(peer.PublicKey, sequenceNumber, virtualConn, transferSequenceTimeout, nil)
|
||||
|
||||
udtConfig := udt.DefaultConfig()
|
||||
udtConfig.MaxPacketSize = protocol.TransferMaxEmbedSizeLite
|
||||
udtConfig.MaxFlowWinSize = maxFlowWinSize
|
||||
|
||||
// start UDT sender
|
||||
// Set streaming to true, otherwise udtSocket.Read returns the error "Message truncated" in case the reader has a smaller buffer.
|
||||
udtConn, err := udt.DialUDT(udtConfig, virtualConn, virtualConn.incomingData, virtualConn.outgoingData, virtualConn.terminationSignal, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer udtConn.Close()
|
||||
virtualConn.Stats.(*FileTransferStats).UDTConn = udtConn
|
||||
|
||||
// First send the header (Total File Size, Transfer Size) and then the file data.
|
||||
protocol.FileTransferWriteHeader(udtConn, fileSize, limit)
|
||||
|
||||
_, _, err = peer.Backend.UserWarehouse.ReadFile(hash, int64(offset), int64(limit), udtConn)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// FileTransferRequestUDT creates a UDT server listening for incoming data transfer via the lite protocol and requests a file transfer from a remote peer.
|
||||
// The caller must call udtConn.Close() when done. Do not use any of the closing functions of virtualConn.
|
||||
// Limit is optional. 0 means the entire file.
|
||||
func (peer *PeerInfo) FileTransferRequestUDT(hash []byte, offset, limit uint64) (udtConn *udt.UDTSocket, virtualConn *VirtualPacketConn, err error) {
|
||||
virtualConn = newVirtualPacketConn(peer, func(data []byte, sequenceNumber uint32, transferID uuid.UUID) {
|
||||
peer.sendTransfer(data, protocol.TransferControlActive, protocol.TransferProtocolUDT, hash, offset, limit, sequenceNumber, transferID, transferLite)
|
||||
})
|
||||
|
||||
// new lite ID
|
||||
liteID := peer.Backend.networks.LiteRouter.NewLiteID(virtualConn, transferSequenceTimeout, virtualConn.sequenceTerminate)
|
||||
virtualConn.transferID = liteID.ID
|
||||
virtualConn.Stats = &FileTransferStats{Hash: hash, Direction: DirectionIn, Offset: offset, Limit: limit}
|
||||
|
||||
// new sequence
|
||||
sequence := peer.Backend.networks.Sequences.NewSequenceBi(peer.PublicKey, &peer.messageSequence, virtualConn, transferSequenceTimeout, nil)
|
||||
if sequence == nil {
|
||||
return nil, nil, errors.New("cannot acquire sequence")
|
||||
}
|
||||
virtualConn.sequenceNumber = sequence.SequenceNumber
|
||||
|
||||
udtConfig := udt.DefaultConfig()
|
||||
udtConfig.MaxPacketSize = protocol.TransferMaxEmbedSizeLite
|
||||
udtConfig.MaxFlowWinSize = maxFlowWinSize
|
||||
|
||||
// start UDT receiver
|
||||
udtListener := udt.ListenUDT(udtConfig, virtualConn, virtualConn.incomingData, virtualConn.outgoingData, virtualConn.terminationSignal)
|
||||
|
||||
// request file transfer
|
||||
peer.sendTransfer(nil, protocol.TransferControlRequestStart, protocol.TransferProtocolUDT, hash, offset, limit, virtualConn.sequenceNumber, virtualConn.transferID, false)
|
||||
|
||||
// accept the connection
|
||||
udtConn, err = udtListener.Accept()
|
||||
if err != nil {
|
||||
udtListener.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
virtualConn.Stats.(*FileTransferStats).UDTConn = udtConn
|
||||
|
||||
// We do not close the UDT listener here. It should automatically close after udtConn is closed.
|
||||
|
||||
return udtConn, virtualConn, nil
|
||||
}
|
||||
|
||||
type FileTransferStats struct {
|
||||
Hash []byte // Hash of the file to transfer
|
||||
Direction int // Direction of the data transfer
|
||||
FileSize uint64 // File size if known
|
||||
Offset uint64 // Offset to start the transfer
|
||||
Limit uint64 // Limit in bytes to transfer
|
||||
UDTConn *udt.UDTSocket // Underlying UDT connection
|
||||
}
|
||||
|
||||
// Transfer directions
|
||||
const (
|
||||
DirectionIn = 0
|
||||
DirectionOut = 1
|
||||
DirectionBi = 2
|
||||
)
|
||||
133
vendor/github.com/PeernetOfficial/core/Transfer Virtual Connection.go
generated
vendored
Normal file
133
vendor/github.com/PeernetOfficial/core/Transfer Virtual Connection.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
File Name: Transfer Virtual Connection.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
This file defines a virtual connection between a transfer protocol and Peernet messages.
|
||||
If either the downstream transfer protocol or upstream Peernet messages indicate termination, the virtual connection ceases to exist.
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// VirtualPacketConn is a virtual connection.
|
||||
type VirtualPacketConn struct {
|
||||
Peer *PeerInfo
|
||||
|
||||
// Stats are maintained by the caller
|
||||
Stats interface{}
|
||||
|
||||
// function to send data to the remote peer
|
||||
sendData func(data []byte, sequenceNumber uint32, transferID uuid.UUID)
|
||||
|
||||
// Sequence number from the first outgoing or incoming packet.
|
||||
sequenceNumber uint32
|
||||
|
||||
// Transfer ID represents a session ID valid only for the duration of the transfer.
|
||||
transferID uuid.UUID
|
||||
|
||||
// data channel
|
||||
incomingData chan []byte
|
||||
outgoingData chan []byte
|
||||
|
||||
// internal data
|
||||
closed bool
|
||||
terminationSignal chan struct{} // The termination signal shall be used by the underlying protocol to detect upstream termination.
|
||||
reason int // Reason why it was closed
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// newVirtualPacketConn creates a new virtual connection (both incoming and outgoing).
|
||||
func newVirtualPacketConn(peer *PeerInfo, sendData func(data []byte, sequenceNumber uint32, transferID uuid.UUID)) (v *VirtualPacketConn) {
|
||||
v = &VirtualPacketConn{
|
||||
Peer: peer,
|
||||
sendData: sendData,
|
||||
incomingData: make(chan []byte, 512),
|
||||
outgoingData: make(chan []byte),
|
||||
terminationSignal: make(chan struct{}),
|
||||
}
|
||||
|
||||
go v.writeForward()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// writeForward forwards outgoing messages
|
||||
func (v *VirtualPacketConn) writeForward() {
|
||||
for {
|
||||
select {
|
||||
case data := <-v.outgoingData:
|
||||
v.sendData(data, v.sequenceNumber, v.transferID)
|
||||
|
||||
case <-v.terminationSignal:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// receiveData receives incoming data via an external message. Non-blocking.
|
||||
func (v *VirtualPacketConn) receiveData(data []byte) {
|
||||
if v.IsTerminated() {
|
||||
return
|
||||
}
|
||||
|
||||
// pass the data on
|
||||
select {
|
||||
case v.incomingData <- data:
|
||||
case <-v.terminationSignal:
|
||||
default:
|
||||
// packet lost
|
||||
}
|
||||
}
|
||||
|
||||
// Terminate closes the connection. Do not call this function manually. Use the underlying protocol's function to close the connection.
|
||||
// Reason: 404 = Remote peer does not store file (upstream), 2 = Remote termination signal (upstream), 3 = Sequence invalidation or expiration (upstream), 1000+ = Transfer protocol indicated closing (downstream)
|
||||
func (v *VirtualPacketConn) Terminate(reason int) (err error) {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
|
||||
if v.closed { // if already closed, take no action
|
||||
return
|
||||
}
|
||||
|
||||
v.closed = true
|
||||
v.reason = reason
|
||||
close(v.terminationSignal)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// IsTerminated checks if the connection is terminated
|
||||
func (v *VirtualPacketConn) IsTerminated() bool {
|
||||
return v.closed
|
||||
}
|
||||
|
||||
// sequenceTerminate is a wrapper for sequenece termination (invalidation or expiration)
|
||||
func (v *VirtualPacketConn) sequenceTerminate() {
|
||||
v.Terminate(3)
|
||||
}
|
||||
|
||||
// Close provides a Close function to be called by the underlying transfer protocol.
|
||||
// Do not call the function manually; otherwise the underlying transfer protocol may not have time to send a termination message (and the remote peer would subsequently try to reconnect).
|
||||
// Rather, use the underlying transfer protocol's close function.
|
||||
func (v *VirtualPacketConn) Close(reason int) (err error) {
|
||||
v.Peer.Backend.networks.Sequences.InvalidateSequence(v.Peer.PublicKey, v.sequenceNumber, true)
|
||||
return v.Terminate(reason)
|
||||
}
|
||||
|
||||
// CloseLinger is to be called by the underlying transfer protocol when it will close the socket soon after lingering around.
|
||||
// Lingering happens to resend packets at the end of transfer, when it is not immediately known whether the remote peer received all packets.
|
||||
func (v *VirtualPacketConn) CloseLinger(reason int) (err error) {
|
||||
v.reason = reason
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTerminateReason returns the termination reason. 0 = Not yet terminated.
|
||||
func (v *VirtualPacketConn) GetTerminateReason() int {
|
||||
return v.reason
|
||||
}
|
||||
20
vendor/github.com/PeernetOfficial/core/Warehouse.go
generated
vendored
Normal file
20
vendor/github.com/PeernetOfficial/core/Warehouse.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
File Name: Warehouse.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/PeernetOfficial/core/warehouse"
|
||||
)
|
||||
|
||||
func (backend *Backend) initUserWarehouse() {
|
||||
var err error
|
||||
backend.UserWarehouse, err = warehouse.Init(backend.Config.WarehouseMain)
|
||||
|
||||
if err != nil {
|
||||
backend.LogError("initUserWarehouse", "error: %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
246
vendor/github.com/PeernetOfficial/core/blockchain/Block Record File.go
generated
vendored
Normal file
246
vendor/github.com/PeernetOfficial/core/blockchain/Block Record File.go
generated
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
File Name: Block Record File.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
File records:
|
||||
Offset Size Info
|
||||
0 32 Hash blake3 of the file content
|
||||
32 16 File ID
|
||||
48 32 Merkle Root Hash
|
||||
80 8 Fragment Size
|
||||
88 1 File Type
|
||||
89 2 File Format
|
||||
91 8 File Size
|
||||
99 2 Count of Tags
|
||||
101 ? Tags
|
||||
|
||||
Each file tag provides additional optional information:
|
||||
Offset Size Info
|
||||
0 2 Type
|
||||
2 4 Size of data that follows
|
||||
6 ? Data according to the tag type
|
||||
|
||||
Tag data record contains only raw data and may be referenced by Tags in File records.
|
||||
This is a basic embedded way of compression when tags are repetitive in multiple files within the same block.
|
||||
|
||||
*/
|
||||
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math"
|
||||
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// BlockRecordFile is the metadata of a file published on the blockchain
|
||||
type BlockRecordFile struct {
|
||||
Hash []byte // Hash of the file data
|
||||
ID uuid.UUID // ID of the file
|
||||
MerkleRootHash []byte // Merkle Root Hash
|
||||
FragmentSize uint64 // Fragment Size
|
||||
Type uint8 // File Type
|
||||
Format uint16 // File Format
|
||||
Size uint64 // Size of the file data
|
||||
NodeID []byte // Node ID, owner of the file
|
||||
Tags []BlockRecordFileTag // Tags provide additional metadata
|
||||
}
|
||||
|
||||
// BlockRecordFileTag provides metadata about the file.
|
||||
type BlockRecordFileTag struct {
|
||||
Type uint16 // See TagX constants.
|
||||
Data []byte // Data
|
||||
|
||||
// If top bit of Type is set, then Data must be 2, 4, or 8 bytes representing the distance number (positive or negative) of raw record in the block that will be used as data.
|
||||
// This is an embedded basic compression algorithm for repetitive tag. For example directory tags or album tags might be heavily repetitive among files.
|
||||
}
|
||||
|
||||
const blockRecordFileMinSize = 101
|
||||
|
||||
// decodeBlockRecordFiles decodes only file records. Other records are ignored.
|
||||
func decodeBlockRecordFiles(recordsRaw []BlockRecordRaw, nodeID []byte) (files []BlockRecordFile, err error) {
|
||||
for i, record := range recordsRaw {
|
||||
switch record.Type {
|
||||
case RecordTypeFile:
|
||||
if len(record.Data) < blockRecordFileMinSize {
|
||||
return nil, errors.New("file record invalid size")
|
||||
}
|
||||
|
||||
file := BlockRecordFile{NodeID: nodeID}
|
||||
file.Hash = make([]byte, protocol.HashSize)
|
||||
copy(file.Hash, record.Data[0:0+protocol.HashSize])
|
||||
copy(file.ID[:], record.Data[32:32+16])
|
||||
|
||||
file.MerkleRootHash = make([]byte, protocol.HashSize)
|
||||
copy(file.MerkleRootHash, record.Data[48:48+protocol.HashSize])
|
||||
file.FragmentSize = binary.LittleEndian.Uint64(record.Data[80 : 80+8])
|
||||
|
||||
file.Type = record.Data[88]
|
||||
file.Format = binary.LittleEndian.Uint16(record.Data[89 : 89+2])
|
||||
file.Size = binary.LittleEndian.Uint64(record.Data[91 : 91+8])
|
||||
|
||||
countTags := binary.LittleEndian.Uint16(record.Data[99 : 99+2])
|
||||
|
||||
index := blockRecordFileMinSize
|
||||
|
||||
for n := uint16(0); n < countTags; n++ {
|
||||
if index+6 > len(record.Data) {
|
||||
return nil, errors.New("file record tags invalid size")
|
||||
}
|
||||
|
||||
tag := BlockRecordFileTag{}
|
||||
tag.Type = binary.LittleEndian.Uint16(record.Data[index:index+2]) & 0x7FFF
|
||||
tagSize := binary.LittleEndian.Uint32(record.Data[index+2 : index+2+4])
|
||||
isDataReference := record.Data[index+1]&0x80 != 0
|
||||
|
||||
if index+6+int(tagSize) > len(record.Data) {
|
||||
return nil, errors.New("file record tag data invalid size")
|
||||
}
|
||||
|
||||
if isDataReference { // reference to RecordTypeTagData record?
|
||||
var refRecordNumber int
|
||||
if tagSize == 2 {
|
||||
refRecordNumber = i + int(int16(binary.LittleEndian.Uint16(record.Data[index+6:index+6+2])))
|
||||
} else if tagSize == 4 {
|
||||
refRecordNumber = i + int(int32(binary.LittleEndian.Uint32(record.Data[index+6:index+6+4])))
|
||||
} else if tagSize == 8 {
|
||||
refRecordNumber = i + int(int64(binary.LittleEndian.Uint64(record.Data[index+6:index+6+8])))
|
||||
} else {
|
||||
return nil, errors.New("file record tag reference invalid size")
|
||||
}
|
||||
|
||||
if refRecordNumber < 0 || refRecordNumber >= len(recordsRaw) {
|
||||
return nil, errors.New("file record tag reference not available")
|
||||
} else if recordsRaw[refRecordNumber].Type != RecordTypeTagData {
|
||||
return nil, errors.New("file record tag reference invalid")
|
||||
}
|
||||
|
||||
tag.Data = recordsRaw[refRecordNumber].Data
|
||||
|
||||
} else {
|
||||
tag.Data = record.Data[index+6 : index+6+int(tagSize)]
|
||||
}
|
||||
|
||||
file.Tags = append(file.Tags, tag)
|
||||
|
||||
index += 6 + int(tagSize)
|
||||
}
|
||||
|
||||
file.Tags = append(file.Tags, TagFromDate(TagDateShared, record.Date))
|
||||
|
||||
files = append(files, file)
|
||||
}
|
||||
}
|
||||
|
||||
return files, err
|
||||
}
|
||||
|
||||
// encodeBlockRecordFiles encodes files into the block record data
|
||||
// This function should be called grouped with all files in the same folder. The folder name is deduplicated; only unique folder records will be returned.
|
||||
// Note that this function only stores the folder names as tags; it does not create separate TypeFolder file records.
|
||||
func encodeBlockRecordFiles(files []BlockRecordFile) (recordsRaw []BlockRecordRaw, err error) {
|
||||
uniqueTagDataMap := make(map[string]struct{})
|
||||
duplicateTagDataMap := make(map[string]int) // list of tag data that appeared twice. Number in recordsRaw.
|
||||
|
||||
// loop through all tags to encode them and create list of duplicates that will be replaced by references
|
||||
for n := range files {
|
||||
for _, tag := range files[n].Tags {
|
||||
if len(tag.Data) > 4 {
|
||||
if _, ok := uniqueTagDataMap[string(tag.Data)]; !ok {
|
||||
uniqueTagDataMap[string(tag.Data)] = struct{}{}
|
||||
} else if _, ok := duplicateTagDataMap[string(tag.Data)]; !ok {
|
||||
recordsRaw = append(recordsRaw, BlockRecordRaw{Type: RecordTypeTagData, Data: tag.Data})
|
||||
duplicateTagDataMap[string(tag.Data)] = len(recordsRaw) - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// then encode all files as records
|
||||
for n := range files {
|
||||
data := make([]byte, blockRecordFileMinSize)
|
||||
|
||||
if len(files[n].Hash) != protocol.HashSize {
|
||||
return nil, errors.New("encodeBlockRecords invalid file hash")
|
||||
} else if len(files[n].MerkleRootHash) != protocol.HashSize {
|
||||
return nil, errors.New("encodeBlockRecords invalid merkle root hash")
|
||||
}
|
||||
|
||||
copy(data[0:32], files[n].Hash[0:32])
|
||||
copy(data[32:32+16], files[n].ID[:])
|
||||
copy(data[48:48+32], files[n].MerkleRootHash[0:32])
|
||||
binary.LittleEndian.PutUint64(data[80:80+8], files[n].FragmentSize)
|
||||
|
||||
data[88] = files[n].Type
|
||||
binary.LittleEndian.PutUint16(data[89:89+2], files[n].Format)
|
||||
binary.LittleEndian.PutUint64(data[91:91+8], files[n].Size)
|
||||
|
||||
var tagCount uint16
|
||||
|
||||
for _, tag := range files[n].Tags {
|
||||
// Some tags are virtual and never stored on the blockchain. If attempted to write, ignore.
|
||||
if tag.IsVirtual() {
|
||||
continue
|
||||
}
|
||||
tagCount++
|
||||
|
||||
if len(tag.Data) > 4 {
|
||||
if refNumber, ok := duplicateTagDataMap[string(tag.Data)]; ok {
|
||||
// In case the data is duplicated, use reference to the RecordTypeTagData instead
|
||||
tag.Type |= 0x8000
|
||||
tag.Data = intToBytes(-(len(recordsRaw) - refNumber))
|
||||
}
|
||||
}
|
||||
|
||||
var tempTag [6]byte
|
||||
|
||||
binary.LittleEndian.PutUint16(tempTag[0:2], tag.Type)
|
||||
binary.LittleEndian.PutUint32(tempTag[2:2+4], uint32(len(tag.Data)))
|
||||
|
||||
data = append(data, tempTag[:]...)
|
||||
data = append(data, tag.Data...)
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint16(data[99:99+2], tagCount)
|
||||
|
||||
recordsRaw = append(recordsRaw, BlockRecordRaw{Type: RecordTypeFile, Data: data})
|
||||
}
|
||||
|
||||
return recordsRaw, nil
|
||||
}
|
||||
|
||||
// intToBytes encodes int to little endian byte array as it fits to 16, 32 or 64 bit.
|
||||
func intToBytes(number int) (buffer []byte) {
|
||||
buffer = make([]byte, 4)
|
||||
|
||||
if number <= math.MaxInt16 && number >= math.MinInt16 {
|
||||
binary.LittleEndian.PutUint16(buffer[0:2], uint16(number))
|
||||
return buffer[0:2]
|
||||
} else if number <= math.MaxInt32 && number >= math.MinInt32 {
|
||||
binary.LittleEndian.PutUint32(buffer[0:4], uint32(number))
|
||||
return buffer[0:4]
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint64(buffer[0:8], uint64(number))
|
||||
return buffer[0:8]
|
||||
}
|
||||
|
||||
// SizeInBlock returns the full size this file takes up in a single block. (i.e., the record size)
|
||||
// If paired with other files in a single block, compression (via tag references) may reduce the actual size.
|
||||
func (file *BlockRecordFile) SizeInBlock() (size uint64) {
|
||||
size = blockRecordHeaderSize + blockRecordFileMinSize
|
||||
|
||||
for _, tag := range file.Tags {
|
||||
if tag.IsVirtual() {
|
||||
continue
|
||||
}
|
||||
|
||||
size += 6 + uint64(len(tag.Data))
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
75
vendor/github.com/PeernetOfficial/core/blockchain/Block Record Profile.go
generated
vendored
Normal file
75
vendor/github.com/PeernetOfficial/core/blockchain/Block Record Profile.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
File Name: Block Record Profile.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Profile records:
|
||||
Offset Size Info
|
||||
0 2 Type
|
||||
2 ? Data according to the type
|
||||
|
||||
*/
|
||||
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math"
|
||||
)
|
||||
|
||||
// BlockRecordProfile provides information about the end user.
|
||||
type BlockRecordProfile struct {
|
||||
Type uint16 // See ProfileX constants.
|
||||
Data []byte // Data
|
||||
}
|
||||
|
||||
// decodeBlockRecordProfile decodes only profile records. Other records are ignored.
|
||||
func decodeBlockRecordProfile(recordsRaw []BlockRecordRaw) (fields []BlockRecordProfile, err error) {
|
||||
fieldMap := make(map[uint16][]byte)
|
||||
|
||||
for _, record := range recordsRaw {
|
||||
if record.Type != RecordTypeProfile {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(record.Data) < 2 {
|
||||
return nil, errors.New("profile record invalid size")
|
||||
}
|
||||
|
||||
fieldType := binary.LittleEndian.Uint16(record.Data[0:2])
|
||||
fieldMap[fieldType] = record.Data[2:]
|
||||
}
|
||||
|
||||
for fieldType, fieldData := range fieldMap {
|
||||
fields = append(fields, BlockRecordProfile{Type: fieldType, Data: fieldData})
|
||||
}
|
||||
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// encodeBlockRecordProfile encodes the profile record.
|
||||
func encodeBlockRecordProfile(fields []BlockRecordProfile) (recordsRaw []BlockRecordRaw, err error) {
|
||||
if len(fields) > math.MaxUint16 {
|
||||
return nil, errors.New("exceeding max count of fields")
|
||||
}
|
||||
|
||||
for n := range fields {
|
||||
if uint64(len(fields[n].Data)) > math.MaxUint32 {
|
||||
return nil, errors.New("exceeding max field size")
|
||||
}
|
||||
|
||||
data := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(data[0:2], fields[n].Type)
|
||||
data = append(data, fields[n].Data...)
|
||||
|
||||
recordsRaw = append(recordsRaw, BlockRecordRaw{Type: RecordTypeProfile, Data: data})
|
||||
}
|
||||
|
||||
return recordsRaw, nil
|
||||
}
|
||||
|
||||
// SizeInBlock returns the full size this file takes up in a single block. (i.e., the record size)
|
||||
func (field *BlockRecordProfile) SizeInBlock() (size uint64) {
|
||||
return blockRecordHeaderSize + 2 + uint64(len(field.Data))
|
||||
}
|
||||
55
vendor/github.com/PeernetOfficial/core/blockchain/Block Record.go
generated
vendored
Normal file
55
vendor/github.com/PeernetOfficial/core/blockchain/Block Record.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
File Name: Block Record.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Each record inside the block has this basic structure:
|
||||
Offset Size Info
|
||||
0 1 Record type
|
||||
1 8 Date created. This remains the same in case of block refactoring.
|
||||
9 4 Size of data
|
||||
13 ? Data (encoding depends on record type)
|
||||
|
||||
*/
|
||||
|
||||
package blockchain
|
||||
|
||||
// RecordTypeX defines the type of the record
|
||||
const (
|
||||
RecordTypeProfile = 0 // Profile data about the end user.
|
||||
RecordTypeTagData = 1 // Tag data record to be referenced by one or multiple tags. Only valid in the context of the current block.
|
||||
RecordTypeFile = 2 // File
|
||||
RecordTypeInvalid1 = 3 // Do not use.
|
||||
RecordTypeCertificate = 4 // Certificate to certify provided information in the blockchain issued by a trusted 3rd party.
|
||||
RecordTypeContentRating = 5 // Content rating (positive).
|
||||
RecordTypeContentReport = 6 // Content report (negative).
|
||||
)
|
||||
|
||||
// BlockDecoded contains the decoded records from a block
|
||||
type BlockDecoded struct {
|
||||
Block
|
||||
RecordsDecoded []interface{} // Decoded records. See BlockRecordX structures.
|
||||
}
|
||||
|
||||
// decodeBlockRecords decodes all raw records in the block and returns a high-level decoded structure
|
||||
// Use decodeBlockRecordX instead for specific record decoding.
|
||||
func decodeBlockRecords(block *Block) (decoded *BlockDecoded, err error) {
|
||||
decoded = &BlockDecoded{Block: *block}
|
||||
|
||||
files, err := decodeBlockRecordFiles(block.RecordsRaw, block.NodeID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
decoded.RecordsDecoded = append(decoded.RecordsDecoded, file)
|
||||
}
|
||||
|
||||
if profileFields, err := decodeBlockRecordProfile(block.RecordsRaw); err != nil {
|
||||
return nil, err
|
||||
} else if len(profileFields) > 0 {
|
||||
decoded.RecordsDecoded = append(decoded.RecordsDecoded, profileFields)
|
||||
}
|
||||
|
||||
return decoded, nil
|
||||
}
|
||||
162
vendor/github.com/PeernetOfficial/core/blockchain/Block.go
generated
vendored
Normal file
162
vendor/github.com/PeernetOfficial/core/blockchain/Block.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
File Name: Block.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Encoding of a block (it is the same stored in the database and shared in a message):
|
||||
Offset Size Info
|
||||
0 65 Signature of entire block
|
||||
65 32 Hash (blake3) of last block. 0 for first one.
|
||||
97 8 Blockchain version number
|
||||
105 8 Block number
|
||||
113 4 Size of entire block including this header
|
||||
117 2 Count of records that follow
|
||||
|
||||
*/
|
||||
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
)
|
||||
|
||||
// Block is a single block containing a set of records (metadata).
|
||||
// It has no upper size limit, although a soft limit of 64 KB - overhead is encouraged for efficiency.
|
||||
type Block struct {
|
||||
OwnerPublicKey *btcec.PublicKey // Owner Public Key, ECDSA (secp256k1) 257-bit
|
||||
NodeID []byte // Node ID of the owner (derived from the public key)
|
||||
LastBlockHash []byte // Hash of the last block. Blake3.
|
||||
BlockchainVersion uint64 // Blockchain version
|
||||
Number uint64 // Block number
|
||||
RecordsRaw []BlockRecordRaw // Block records raw
|
||||
}
|
||||
|
||||
// BlockRecordRaw is a single block record (not decoded)
|
||||
type BlockRecordRaw struct {
|
||||
Type uint8 // Record Type. See RecordTypeX.
|
||||
Date time.Time // Date created. This remains the same in case of block refactoring.
|
||||
Data []byte // Data according to the type
|
||||
}
|
||||
|
||||
const blockHeaderSize = 119
|
||||
const blockRecordHeaderSize = 13
|
||||
|
||||
// decodeBlock decodes a single block
|
||||
func decodeBlock(raw []byte) (block *Block, err error) {
|
||||
if len(raw) < blockHeaderSize {
|
||||
return nil, errors.New("decodeBlock invalid block size")
|
||||
}
|
||||
|
||||
block = &Block{}
|
||||
|
||||
signature := raw[0 : 0+65]
|
||||
|
||||
block.OwnerPublicKey, _, err = btcec.RecoverCompact(btcec.S256(), signature, protocol.HashData(raw[65:]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block.NodeID = protocol.PublicKey2NodeID(block.OwnerPublicKey)
|
||||
|
||||
block.LastBlockHash = make([]byte, protocol.HashSize)
|
||||
copy(block.LastBlockHash, raw[65:65+protocol.HashSize])
|
||||
|
||||
block.BlockchainVersion = binary.LittleEndian.Uint64(raw[97 : 97+8])
|
||||
block.Number = binary.LittleEndian.Uint64(raw[105 : 105+8])
|
||||
|
||||
blockSize := binary.LittleEndian.Uint32(raw[113 : 113+4])
|
||||
if blockSize != uint32(len(raw)) {
|
||||
return nil, errors.New("decodeBlock invalid block size")
|
||||
}
|
||||
|
||||
// decode on a low-level all block records
|
||||
countRecords := binary.LittleEndian.Uint16(raw[117 : 117+2])
|
||||
index := blockHeaderSize
|
||||
|
||||
for n := uint16(0); n < countRecords; n++ {
|
||||
if index+blockRecordHeaderSize > len(raw) {
|
||||
return nil, errors.New("decodeBlock record exceeds block size")
|
||||
}
|
||||
|
||||
recordType := raw[index]
|
||||
recordDate := int64(binary.LittleEndian.Uint64(raw[index+1 : index+9])) // Unix time int64, the number of seconds elapsed since January 1, 1970 UTC
|
||||
recordSize := binary.LittleEndian.Uint32(raw[index+9 : index+9+4])
|
||||
index += blockRecordHeaderSize
|
||||
|
||||
if index+int(recordSize) > len(raw) {
|
||||
return nil, errors.New("decodeBlock record exceeds block size")
|
||||
}
|
||||
|
||||
block.RecordsRaw = append(block.RecordsRaw, BlockRecordRaw{Type: recordType, Data: raw[index : index+int(recordSize)], Date: time.Unix(recordDate, 0)})
|
||||
|
||||
index += int(recordSize)
|
||||
}
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func encodeBlock(block *Block, ownerPrivateKey *btcec.PrivateKey) (raw []byte, err error) {
|
||||
var buffer bytes.Buffer
|
||||
buffer.Write(make([]byte, 65)) // Signature, filled at the end
|
||||
|
||||
if block.Number > 0 && len(block.LastBlockHash) != protocol.HashSize {
|
||||
return nil, errors.New("encodeBlock invalid last block hash")
|
||||
} else if block.Number == 0 { // Block 0: Empty last hash
|
||||
block.LastBlockHash = make([]byte, 32)
|
||||
}
|
||||
buffer.Write(block.LastBlockHash)
|
||||
|
||||
var temp [8]byte
|
||||
binary.LittleEndian.PutUint64(temp[0:8], block.BlockchainVersion)
|
||||
buffer.Write(temp[:8])
|
||||
|
||||
binary.LittleEndian.PutUint64(temp[0:8], block.Number)
|
||||
buffer.Write(temp[:8])
|
||||
|
||||
buffer.Write(make([]byte, 4)) // Size of block, filled later
|
||||
buffer.Write(make([]byte, 2)) // Count of records, filled later
|
||||
|
||||
// write all records
|
||||
countRecords := uint16(0)
|
||||
|
||||
for _, record := range block.RecordsRaw {
|
||||
if record.Date == (time.Time{}) { // Always set date if not already set
|
||||
record.Date = time.Now()
|
||||
}
|
||||
|
||||
var tempSize, tempDate [8]byte
|
||||
binary.LittleEndian.PutUint32(tempSize[0:4], uint32(len(record.Data)))
|
||||
binary.LittleEndian.PutUint64(tempDate[0:8], uint64(record.Date.UTC().Unix()))
|
||||
|
||||
buffer.Write([]byte{record.Type}) // Record Type
|
||||
buffer.Write(tempDate[:8]) // Date created
|
||||
buffer.Write(tempSize[:4]) // Size of data
|
||||
buffer.Write(record.Data) // Data
|
||||
|
||||
countRecords++
|
||||
}
|
||||
|
||||
// finalize the block
|
||||
raw = buffer.Bytes()
|
||||
if len(raw) < blockHeaderSize {
|
||||
return nil, errors.New("encodeBlock invalid block size")
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint32(raw[113:113+4], uint32(len(raw))) // Size of block
|
||||
binary.LittleEndian.PutUint16(raw[117:117+2], countRecords) // Count of records
|
||||
|
||||
// signature is last
|
||||
signature, err := btcec.SignCompact(btcec.S256(), ownerPrivateKey, protocol.HashData(raw[65:]), true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(raw[0:0+65], signature)
|
||||
|
||||
return raw, nil
|
||||
}
|
||||
443
vendor/github.com/PeernetOfficial/core/blockchain/Blockchain.go
generated
vendored
Normal file
443
vendor/github.com/PeernetOfficial/core/blockchain/Blockchain.go
generated
vendored
Normal file
@@ -0,0 +1,443 @@
|
||||
/*
|
||||
File Name: Blockchain.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
All blocks and the blockchain header are stored in a key-value database.
|
||||
The key for the blockchain header is keyHeader and for each block is the block number as 64-bit unsigned integer little endian.
|
||||
|
||||
Encoding of the blockchain header:
|
||||
Offset Size Info
|
||||
0 8 Height of the blockchain
|
||||
8 8 Version of the blockchain
|
||||
16 2 Format of the blockchain. This provides backward compatibility.
|
||||
18 65 Signature
|
||||
|
||||
*/
|
||||
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
"github.com/PeernetOfficial/core/protocol"
|
||||
"github.com/PeernetOfficial/core/store"
|
||||
)
|
||||
|
||||
// TargetBlockSize is the target size that a generated block shall not exceed. This ensures the block will be transferred via blockchain exchange and cached in DHT.
|
||||
// Large blocks may be ignored by clients for size and spam reasons, resulting in decreased discoverability.
|
||||
var TargetBlockSize = uint64(4096)
|
||||
|
||||
// MinAcceptableBlockSize is the minimum block size peers must accept.
|
||||
const MinAcceptableBlockSize = uint64(1024)
|
||||
|
||||
// Blockchain stores the blockchain's header in memory. Any changes must be synced to disk!
|
||||
type Blockchain struct {
|
||||
// header
|
||||
height uint64 // Height is exchanged as uint32 in the protocol, but stored as uint64.
|
||||
version uint64 // Version is always uint64.
|
||||
format uint16 // Format is only locally used.
|
||||
|
||||
// internals
|
||||
publicKey *btcec.PublicKey // Public Key of the owner. This must match the ones used on disk.
|
||||
privateKey *btcec.PrivateKey // Private Key of the owner. This must match the ones used on disk.
|
||||
path string // Path of the blockchain on disk. Depends on key-value store whether a filename or folder.
|
||||
database store.Store // The database storing the blockchain.
|
||||
sync.Mutex // synchronized access to the header
|
||||
|
||||
// callback
|
||||
BlockchainUpdate func(blockchain *Blockchain, oldHeight, oldVersion, newHeight, newVersion uint64)
|
||||
}
|
||||
|
||||
// Init initializes the given blockchain. It creates the blockchain file if it does not exist already.
|
||||
func Init(privateKey *btcec.PrivateKey, path string) (blockchain *Blockchain, err error) {
|
||||
blockchain = &Blockchain{privateKey: privateKey, path: path}
|
||||
publicKey := privateKey.PubKey()
|
||||
|
||||
// open existing blockchain file or create new one
|
||||
if blockchain.database, err = store.NewPogrebStore(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// verify header
|
||||
var found bool
|
||||
|
||||
found, err = blockchain.headerRead()
|
||||
if err != nil {
|
||||
return blockchain, err // likely corrupt blockchain database
|
||||
} else if !found {
|
||||
// First run: create header signature!
|
||||
blockchain.publicKey = publicKey
|
||||
|
||||
if err := blockchain.headerWrite(0, 0); err != nil {
|
||||
return blockchain, err
|
||||
}
|
||||
} else if !blockchain.publicKey.IsEqual(publicKey) {
|
||||
return blockchain, errors.New("corrupt user blockchain database. Public key mismatch")
|
||||
}
|
||||
|
||||
return blockchain, nil
|
||||
}
|
||||
|
||||
// the key names in the key-value database are constant and must not collide with block numbers (i.e. they must be >64 bit)
|
||||
const keyHeader = "header blockchain"
|
||||
|
||||
// headerRead reads the header from the blockchain and decodes it.
|
||||
func (blockchain *Blockchain) headerRead() (found bool, err error) {
|
||||
buffer, found := blockchain.database.Get([]byte(keyHeader))
|
||||
if !found {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if len(buffer) != 83 {
|
||||
return true, errors.New("blockchain header size mismatch")
|
||||
}
|
||||
|
||||
blockchain.height = binary.LittleEndian.Uint64(buffer[0:8])
|
||||
blockchain.version = binary.LittleEndian.Uint64(buffer[8:16])
|
||||
blockchain.format = binary.LittleEndian.Uint16(buffer[16:18])
|
||||
signature := buffer[18 : 18+65]
|
||||
|
||||
if blockchain.format != 0 {
|
||||
return true, errors.New("future blockchain format not supported. You must go back to the future!")
|
||||
}
|
||||
|
||||
blockchain.publicKey, _, err = btcec.RecoverCompact(btcec.S256(), signature, protocol.HashData(buffer[0:18]))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// headerWrite writes the header to the blockchain and signs it.
|
||||
func (blockchain *Blockchain) headerWrite(height, version uint64) (err error) {
|
||||
oldHeight := blockchain.height
|
||||
oldVersion := blockchain.version
|
||||
|
||||
blockchain.height = height
|
||||
blockchain.version = version
|
||||
|
||||
var buffer [83]byte
|
||||
binary.LittleEndian.PutUint64(buffer[0:8], height)
|
||||
binary.LittleEndian.PutUint64(buffer[8:16], version)
|
||||
binary.LittleEndian.PutUint16(buffer[16:18], 0) // Current format is 0
|
||||
|
||||
signature, err := btcec.SignCompact(btcec.S256(), blockchain.privateKey, protocol.HashData(buffer[0:18]), true)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if len(signature) != 65 {
|
||||
return errors.New("signature length invalid")
|
||||
}
|
||||
|
||||
copy(buffer[18:18+65], signature)
|
||||
|
||||
err = blockchain.database.Set([]byte(keyHeader), buffer[:])
|
||||
|
||||
// call the callback, if any
|
||||
if blockchain.BlockchainUpdate != nil {
|
||||
blockchain.BlockchainUpdate(blockchain, oldHeight, oldVersion, blockchain.height, blockchain.version)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// StatusX provides information about the blockchain status. Some errors codes indicate a corruption.
|
||||
const (
|
||||
StatusOK = 0 // No problems in the blockchain detected.
|
||||
StatusBlockNotFound = 1 // Missing block in the blockchain.
|
||||
StatusCorruptBlock = 2 // Error block encoding
|
||||
StatusCorruptBlockRecord = 3 // Error block record encoding
|
||||
StatusDataNotFound = 4 // Requested data not available in the blockchain
|
||||
StatusNotInWarehouse = 5 // File to be added to blockchain does not exist in the Warehouse
|
||||
)
|
||||
|
||||
// blockNumberToKey returns the database key for the given block number
|
||||
func blockNumberToKey(number uint64) (key []byte) {
|
||||
var target [8]byte
|
||||
binary.LittleEndian.PutUint64(target[:], number)
|
||||
|
||||
return target[:]
|
||||
}
|
||||
|
||||
// Iterate iterates over the blockchain. Status is StatusX.
|
||||
// If the callback returns non-zero, the function aborts and returns the inner status code.
|
||||
func (blockchain *Blockchain) Iterate(callback func(block *Block) int) (status int) {
|
||||
// read all blocks until height is reached
|
||||
height := blockchain.height
|
||||
|
||||
for blockN := uint64(0); blockN < height; blockN++ {
|
||||
blockRaw, found := blockchain.database.Get(blockNumberToKey(blockN))
|
||||
if !found || len(blockRaw) == 0 {
|
||||
return StatusBlockNotFound
|
||||
}
|
||||
|
||||
block, err := decodeBlock(blockRaw)
|
||||
if err != nil {
|
||||
return StatusCorruptBlock
|
||||
}
|
||||
|
||||
if statusI := callback(block); statusI != StatusOK {
|
||||
return statusI
|
||||
}
|
||||
}
|
||||
|
||||
return StatusOK
|
||||
}
|
||||
|
||||
// IterateDeleteRecord iterates over the blockchain to find records to delete. Status is StatusX.
|
||||
// deleteAction is 0 = no action on record, 1 = delete record, 2 = replace record, 3 = error blockchain corrupt
|
||||
// If the callback returns true, the record will be deleted. The blockchain will be automatically refactored and height and version updated.
|
||||
func (blockchain *Blockchain) IterateDeleteRecord(callbackFile func(file *BlockRecordFile) (deleteAction int), callbackOther func(record *BlockRecordRaw) (deleteAction int)) (newHeight, newVersion uint64, status int) {
|
||||
blockchain.Lock()
|
||||
defer blockchain.Unlock()
|
||||
|
||||
// New blockchain keeps track of the new blocks. If anything changes in the blockchain, it must be recalculated and the version number increased.
|
||||
var blockchainNew []Block
|
||||
refactorBlockchain := false
|
||||
refactorVersion := blockchain.version + 1
|
||||
|
||||
// Read all blocks until height is reached. At the end the height and version might be different if blocks are deleted.
|
||||
height := blockchain.height
|
||||
|
||||
for blockN := uint64(0); blockN < height; blockN++ {
|
||||
blockRaw, found := blockchain.database.Get(blockNumberToKey(blockN))
|
||||
if !found || len(blockRaw) == 0 {
|
||||
return 0, 0, StatusBlockNotFound
|
||||
}
|
||||
|
||||
block, err := decodeBlock(blockRaw)
|
||||
if err != nil {
|
||||
return 0, 0, StatusCorruptBlock
|
||||
}
|
||||
|
||||
refactorBlock := false
|
||||
|
||||
// Decode all file records at once. This is needed due to potential referenced tags.
|
||||
// If a file is deleted or referenced tag data changed, it would corrupt the blockchain if the other records were not updated.
|
||||
filesD, err := decodeBlockRecordFiles(block.RecordsRaw, block.NodeID)
|
||||
if err != nil {
|
||||
return 0, 0, StatusCorruptBlock
|
||||
}
|
||||
|
||||
// loop through all file records in this block
|
||||
var newFileRecords []BlockRecordFile
|
||||
|
||||
if callbackFile == nil {
|
||||
newFileRecords = filesD
|
||||
} else {
|
||||
for n := range filesD {
|
||||
switch callbackFile(&filesD[n]) {
|
||||
case 0: // no action on record
|
||||
newFileRecords = append(newFileRecords, filesD[n])
|
||||
|
||||
case 1: // delete record
|
||||
refactorBlock = true
|
||||
refactorBlockchain = true
|
||||
|
||||
case 2: // replace record
|
||||
newFileRecords = append(newFileRecords, filesD[n])
|
||||
refactorBlock = true
|
||||
refactorBlockchain = true
|
||||
|
||||
case 3: // error blockchain corrupt
|
||||
return 0, 0, StatusCorruptBlockRecord
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loop through all other (non-file) records in this block
|
||||
var newRecordsRaw []BlockRecordRaw
|
||||
|
||||
for n := range block.RecordsRaw {
|
||||
// File and Tag records were already handled in above loop.
|
||||
if block.RecordsRaw[n].Type == RecordTypeFile || block.RecordsRaw[n].Type == RecordTypeTagData {
|
||||
continue
|
||||
}
|
||||
|
||||
if callbackOther == nil {
|
||||
newRecordsRaw = append(newRecordsRaw, block.RecordsRaw[n])
|
||||
} else {
|
||||
switch callbackOther(&block.RecordsRaw[n]) {
|
||||
case 0: // no action on record
|
||||
newRecordsRaw = append(newRecordsRaw, block.RecordsRaw[n])
|
||||
|
||||
case 1: // delete record
|
||||
refactorBlock = true
|
||||
refactorBlockchain = true
|
||||
|
||||
case 2: // replace record
|
||||
newRecordsRaw = append(newRecordsRaw, block.RecordsRaw[n])
|
||||
refactorBlock = true
|
||||
refactorBlockchain = true
|
||||
|
||||
case 3: // error blockchain corrupt
|
||||
return 0, 0, StatusCorruptBlockRecord
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If refactor, re-calculate the block. All later blocks need to be re-encoded due to change of previous block hash. The version number needs to change.
|
||||
// Note: Deleting records may leave referenced records orphaned, such as RecordTypeTagData for deleted file records.
|
||||
if refactorBlock {
|
||||
// re-encode the block
|
||||
filesRecords, err := encodeBlockRecordFiles(newFileRecords)
|
||||
if err != nil {
|
||||
return 0, 0, StatusCorruptBlock
|
||||
}
|
||||
|
||||
newRecordsRaw = append(newRecordsRaw, filesRecords...)
|
||||
|
||||
if len(newRecordsRaw) > 0 {
|
||||
blockchainNew = append(blockchainNew, Block{OwnerPublicKey: blockchain.publicKey, RecordsRaw: newRecordsRaw, BlockchainVersion: refactorVersion, Number: uint64(len(blockchainNew))})
|
||||
}
|
||||
} else {
|
||||
blockchainNew = append(blockchainNew, Block{OwnerPublicKey: blockchain.publicKey, RecordsRaw: block.RecordsRaw, BlockchainVersion: refactorVersion, Number: uint64(len(blockchainNew))})
|
||||
}
|
||||
}
|
||||
|
||||
if refactorBlockchain {
|
||||
var lastBlockHash []byte
|
||||
|
||||
for _, block := range blockchainNew {
|
||||
block.LastBlockHash = lastBlockHash
|
||||
|
||||
raw, err := encodeBlock(&block, blockchain.privateKey)
|
||||
if err != nil {
|
||||
return 0, 0, StatusCorruptBlock
|
||||
}
|
||||
|
||||
// store the block
|
||||
blockchain.database.Set(blockNumberToKey(block.Number), raw)
|
||||
|
||||
lastBlockHash = protocol.HashData(raw)
|
||||
}
|
||||
|
||||
// update the blockchain header in the database
|
||||
blockchain.headerWrite(uint64(len(blockchainNew)), refactorVersion)
|
||||
|
||||
// delete orphaned blocks
|
||||
for n := blockchain.height; n < height; n++ {
|
||||
blockchain.database.Delete(blockNumberToKey(n))
|
||||
}
|
||||
}
|
||||
|
||||
return blockchain.height, blockchain.version, StatusOK
|
||||
}
|
||||
|
||||
// ---- blockchain manipulation functions ----
|
||||
|
||||
// Header returns the users blockchain header which stores the height and version number.
|
||||
func (blockchain *Blockchain) Header() (publicKey *btcec.PublicKey, height uint64, version uint64) {
|
||||
blockchain.Lock()
|
||||
defer blockchain.Unlock()
|
||||
|
||||
return blockchain.publicKey, blockchain.height, blockchain.version
|
||||
}
|
||||
|
||||
// Append appends a new block to the blockchain based on the provided raw records. Status is StatusX.
|
||||
func (blockchain *Blockchain) Append(RecordsRaw []BlockRecordRaw) (newHeight, newVersion uint64, status int) {
|
||||
blockchain.Lock()
|
||||
defer blockchain.Unlock()
|
||||
|
||||
if len(RecordsRaw) == 0 {
|
||||
return blockchain.height, blockchain.version, StatusOK
|
||||
}
|
||||
|
||||
block := &Block{OwnerPublicKey: blockchain.publicKey, RecordsRaw: RecordsRaw}
|
||||
|
||||
// set the last block hash first
|
||||
if blockchain.height > 0 {
|
||||
previousBlockRaw, found := blockchain.database.Get(blockNumberToKey(blockchain.height - 1))
|
||||
if !found || len(previousBlockRaw) == 0 {
|
||||
return 0, 0, StatusBlockNotFound
|
||||
}
|
||||
|
||||
block.LastBlockHash = protocol.HashData(previousBlockRaw)
|
||||
}
|
||||
|
||||
block.Number = blockchain.height
|
||||
block.BlockchainVersion = blockchain.version
|
||||
|
||||
raw, err := encodeBlock(block, blockchain.privateKey)
|
||||
if err != nil {
|
||||
return 0, 0, StatusCorruptBlock
|
||||
}
|
||||
|
||||
// store the block
|
||||
blockchain.database.Set(blockNumberToKey(block.Number), raw)
|
||||
|
||||
// update the blockchain header in the database, increase blockchain height
|
||||
blockchain.headerWrite(blockchain.height+1, blockchain.version)
|
||||
|
||||
return blockchain.height, blockchain.version, StatusOK
|
||||
}
|
||||
|
||||
// Read reads the block number from the blockchain. Status is StatusX.
|
||||
func (blockchain *Blockchain) Read(number uint64) (decoded *BlockDecoded, status int, err error) {
|
||||
if number >= blockchain.height {
|
||||
return nil, StatusBlockNotFound, errors.New("block number exceeds blockchain height")
|
||||
}
|
||||
|
||||
blockRaw, found := blockchain.database.Get(blockNumberToKey(number))
|
||||
if !found || len(blockRaw) == 0 {
|
||||
return nil, StatusBlockNotFound, errors.New("block not found")
|
||||
}
|
||||
|
||||
block, err := decodeBlock(blockRaw)
|
||||
if err != nil {
|
||||
return nil, StatusCorruptBlock, err
|
||||
}
|
||||
|
||||
decoded, err = decodeBlockRecords(block)
|
||||
if err != nil {
|
||||
return nil, StatusCorruptBlock, err
|
||||
}
|
||||
|
||||
return decoded, StatusOK, nil
|
||||
}
|
||||
|
||||
// DeleteBlockchain deletes the entire blockchain
|
||||
func (blockchain *Blockchain) DeleteBlockchain() (status int, err error) {
|
||||
blockchain.Lock()
|
||||
defer blockchain.Unlock()
|
||||
|
||||
for n := uint64(0); n < blockchain.height; n++ {
|
||||
blockchain.database.Delete(blockNumberToKey(n))
|
||||
}
|
||||
|
||||
// update the blockchain header in the database, reset height, increase version
|
||||
blockchain.headerWrite(0, blockchain.version+1)
|
||||
|
||||
return StatusOK, nil
|
||||
}
|
||||
|
||||
// GetBlockRaw returns the encoded block from the blockchain. Status is StatusX.
|
||||
func (blockchain *Blockchain) GetBlockRaw(number uint64) (data []byte, status int, err error) {
|
||||
if number >= blockchain.height {
|
||||
return nil, StatusBlockNotFound, errors.New("block number exceeds blockchain height")
|
||||
}
|
||||
|
||||
blockRaw, found := blockchain.database.Get(blockNumberToKey(number))
|
||||
if !found || len(blockRaw) == 0 {
|
||||
return nil, StatusBlockNotFound, errors.New("block not found")
|
||||
}
|
||||
|
||||
return blockRaw, StatusOK, nil
|
||||
}
|
||||
|
||||
// DecodeBlockRaw decodes the raw block. Status is StatusX.
|
||||
func DecodeBlockRaw(blockRaw []byte) (decoded *BlockDecoded, status int, err error) {
|
||||
block, err := decodeBlock(blockRaw)
|
||||
if err != nil {
|
||||
return nil, StatusCorruptBlock, err
|
||||
}
|
||||
|
||||
decoded, err = decodeBlockRecords(block)
|
||||
if err != nil {
|
||||
return nil, StatusCorruptBlock, err
|
||||
}
|
||||
|
||||
return decoded, StatusOK, nil
|
||||
}
|
||||
104
vendor/github.com/PeernetOfficial/core/blockchain/File Tag.go
generated
vendored
Normal file
104
vendor/github.com/PeernetOfficial/core/blockchain/File Tag.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
File Name: File Tag.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Metadata tags provide meta information about files.
|
||||
*/
|
||||
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// List of defined file tags. Virtual tags are generated at runtime and are read-only. They cannot be stored on the blockchain.
|
||||
const (
|
||||
TagName = 0 // Name of file.
|
||||
TagFolder = 1 // Folder name.
|
||||
TagDescription = 2 // Arbitrary description of the file. May contain hashtags.
|
||||
TagDateShared = 3 // When the file was published on the blockchain. Virtual.
|
||||
TagDateCreated = 4 // Date when the file was originally created. This may differ from the date in the block record, which indicates when the file was shared.
|
||||
TagSharedByCount = 5 // Count of peers that share the file. Virtual.
|
||||
TagSharedByGeoIP = 6 // GeoIP data of peers that are sharing the file. CSV encoded with header "latitude,longitude". Virtual.
|
||||
)
|
||||
|
||||
// Future tags to be defined for audio/video: Artist, Album, Title, Length, Bitrate, Codec
|
||||
// Windows list: https://docs.microsoft.com/en-us/windows/win32/wmdm/metadata-constants
|
||||
|
||||
// ---- encoding ----
|
||||
|
||||
// Date returns the tags data as date encoded
|
||||
func (tag *BlockRecordFileTag) Date() (time.Time, error) {
|
||||
if tag == nil {
|
||||
return time.Time{}, errors.New("tag not available")
|
||||
} else if len(tag.Data) != 8 {
|
||||
return time.Time{}, errors.New("file tag date invalid size")
|
||||
}
|
||||
|
||||
timeB := int64(binary.LittleEndian.Uint64(tag.Data[0:8]))
|
||||
return time.Unix(timeB, 0).UTC(), nil
|
||||
}
|
||||
|
||||
// Text returns the tags data as text encoded
|
||||
func (tag *BlockRecordFileTag) Text() string {
|
||||
return string(tag.Data)
|
||||
}
|
||||
|
||||
// Number returns the tags data as uint64. It returns 0 if the data cannot be decoded.
|
||||
func (tag *BlockRecordFileTag) Number() uint64 {
|
||||
if len(tag.Data) != 8 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return binary.LittleEndian.Uint64(tag.Data[0:8])
|
||||
}
|
||||
|
||||
// IsVirtual checks if the tag is virtual.
|
||||
func (tag *BlockRecordFileTag) IsVirtual() bool {
|
||||
return IsTagVirtual(tag.Type)
|
||||
}
|
||||
|
||||
// TagFromDate returns a tag from date
|
||||
func TagFromDate(Type uint16, Date time.Time) BlockRecordFileTag {
|
||||
var tempDate [8]byte
|
||||
binary.LittleEndian.PutUint64(tempDate[0:8], uint64(Date.UTC().Unix()))
|
||||
|
||||
return BlockRecordFileTag{Type: Type, Data: tempDate[:]}
|
||||
}
|
||||
|
||||
// TagFromText returns a tag from text
|
||||
func TagFromText(Type uint16, Text string) BlockRecordFileTag {
|
||||
return BlockRecordFileTag{Type: Type, Data: []byte(Text)}
|
||||
}
|
||||
|
||||
// TagFromNumber returns a tag from a number
|
||||
func TagFromNumber(Type uint16, Number uint64) BlockRecordFileTag {
|
||||
var tempDate [8]byte
|
||||
binary.LittleEndian.PutUint64(tempDate[0:8], Number)
|
||||
|
||||
return BlockRecordFileTag{Type: Type, Data: tempDate[:]}
|
||||
}
|
||||
|
||||
// IsTagVirtual checks if the tag is a virtual one.
|
||||
func IsTagVirtual(Type uint16) bool {
|
||||
switch Type {
|
||||
case TagDateShared, TagSharedByCount, TagSharedByGeoIP:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// GetTag returns the tag with the type or nil if not available.
|
||||
func (file *BlockRecordFile) GetTag(Type uint16) (tag *BlockRecordFileTag) {
|
||||
for n := range file.Tags {
|
||||
if file.Tags[n].Type == Type {
|
||||
return &file.Tags[n]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
117
vendor/github.com/PeernetOfficial/core/blockchain/File.go
generated
vendored
Normal file
117
vendor/github.com/PeernetOfficial/core/blockchain/File.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
File Name: File.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Note that files include virtual folders as well for any operation.
|
||||
*/
|
||||
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// AddFiles adds files to the blockchain. Status is StatusX.
|
||||
// It makes sense to group all files in the same directory into one call, since only one directory record will be created per unique directory per block.
|
||||
func (blockchain *Blockchain) AddFiles(files []BlockRecordFile) (newHeight, newVersion uint64, status int) {
|
||||
encodeFilesAppend := func(files []BlockRecordFile) (newHeight, newVersion uint64, status int) {
|
||||
encoded, err := encodeBlockRecordFiles(files)
|
||||
if err != nil {
|
||||
return 0, 0, StatusCorruptBlockRecord
|
||||
}
|
||||
|
||||
return blockchain.Append(encoded)
|
||||
}
|
||||
|
||||
blockSize := uint64(blockHeaderSize)
|
||||
var recordFiles []BlockRecordFile
|
||||
|
||||
for _, file := range files {
|
||||
recordSize := file.SizeInBlock()
|
||||
|
||||
// need to create a new block due to target block size?
|
||||
if len(recordFiles) > 0 && blockSize+recordSize > TargetBlockSize {
|
||||
if newHeight, newVersion, status = encodeFilesAppend(recordFiles); status != StatusOK {
|
||||
return newHeight, newVersion, status
|
||||
}
|
||||
|
||||
blockSize = blockHeaderSize
|
||||
recordFiles = nil
|
||||
}
|
||||
|
||||
blockSize += recordSize
|
||||
recordFiles = append(recordFiles, file)
|
||||
}
|
||||
|
||||
return encodeFilesAppend(recordFiles)
|
||||
}
|
||||
|
||||
// ListFiles returns a list of all files. Status is StatusX.
|
||||
// If there is a corruption in the blockchain it will stop reading but return the files parsed so far.
|
||||
func (blockchain *Blockchain) ListFiles() (files []BlockRecordFile, status int) {
|
||||
status = blockchain.Iterate(func(block *Block) (statusI int) {
|
||||
filesMore, err := decodeBlockRecordFiles(block.RecordsRaw, block.NodeID)
|
||||
if err != nil {
|
||||
return StatusCorruptBlockRecord
|
||||
}
|
||||
files = append(files, filesMore...)
|
||||
|
||||
return StatusOK
|
||||
})
|
||||
|
||||
return files, status
|
||||
}
|
||||
|
||||
// FileExists checks if the file (identified via its hash) exists.
|
||||
// If there is a corruption in the blockchain it will stop reading but return the files found so far.
|
||||
func (blockchain *Blockchain) FileExists(hash []byte) (files []BlockRecordFile, status int) {
|
||||
status = blockchain.Iterate(func(block *Block) (statusI int) {
|
||||
filesD, err := decodeBlockRecordFiles(block.RecordsRaw, block.NodeID)
|
||||
if err != nil {
|
||||
return StatusCorruptBlockRecord
|
||||
}
|
||||
for _, file := range filesD {
|
||||
if bytes.Equal(file.Hash, hash) {
|
||||
files = append(files, file)
|
||||
}
|
||||
}
|
||||
|
||||
return StatusOK
|
||||
})
|
||||
|
||||
return files, status
|
||||
}
|
||||
|
||||
// DeleteFiles deletes files from the blockchain. Status is StatusX.
|
||||
func (blockchain *Blockchain) DeleteFiles(IDs []uuid.UUID) (newHeight, newVersion uint64, deletedFiles []*BlockRecordFile, status int) {
|
||||
newHeight, newVersion, status = blockchain.IterateDeleteRecord(func(file *BlockRecordFile) (deleteAction int) {
|
||||
for _, id := range IDs {
|
||||
if file.ID == id { // found a file ID to delete?
|
||||
deletedFiles = append(deletedFiles, file)
|
||||
return 1 // delete record
|
||||
}
|
||||
}
|
||||
|
||||
return 0 // no action on record
|
||||
}, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ReplaceFiles is a convenience wrapper to replace files in the blockchain identified via their IDs. Status is StatusX.
|
||||
// If a file does not exist on the blockchain, it acts as add.
|
||||
func (blockchain *Blockchain) ReplaceFiles(files []BlockRecordFile) (newHeight, newVersion uint64, status int) {
|
||||
var deleteIDs []uuid.UUID
|
||||
for n := range files {
|
||||
deleteIDs = append(deleteIDs, files[n].ID)
|
||||
}
|
||||
|
||||
if newHeight, newVersion, _, status = blockchain.DeleteFiles(deleteIDs); status != StatusOK {
|
||||
return
|
||||
}
|
||||
|
||||
return blockchain.AddFiles(files)
|
||||
}
|
||||
292
vendor/github.com/PeernetOfficial/core/blockchain/Multi.go
generated
vendored
Normal file
292
vendor/github.com/PeernetOfficial/core/blockchain/Multi.go
generated
vendored
Normal file
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
File Name: Multi.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Multi-blockchain store implementation.
|
||||
|
||||
Keys used in the key-value store:
|
||||
1. Key: Public key compressed, Value: Header
|
||||
2. Key: Public key compressed + version + block number, Value: Block
|
||||
|
||||
*/
|
||||
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
"github.com/PeernetOfficial/core/store"
|
||||
)
|
||||
|
||||
const (
|
||||
MultiStatusOK = 0 // Success.
|
||||
MultiStatusErrorReadHeader = 1 // Error reading header for blockchain.
|
||||
MultiStatusHeaderNA = 2 // Header not available. This indicates nothing is stored for the blockchain.
|
||||
MultiStatusInvalidRemote = 3 // Invalid remote reported blockchain version and height. (local cache is "newer" which should not happen)
|
||||
MultiStatusEqual = 4 // Remote blockchain and local cache are equal.
|
||||
MultiStatusNewVersion = 5 // Local cache is out of date. A new version of the blockchain is available.
|
||||
MultiStatusNewBlocks = 6 // Local cache is out of date. Additional blocks are available.
|
||||
)
|
||||
|
||||
// MultiStore stores multiple blockchains.
|
||||
type MultiStore struct {
|
||||
path string // Path of the blockchain on disk. Depends on key-value store whether a filename or folder.
|
||||
Database store.Store // The database storing the blockchain.
|
||||
|
||||
// callbacks
|
||||
FilterStatisticUpdate func(multi *MultiStore, header *MultiBlockchainHeader, statsOld BlockchainStats)
|
||||
FilterBlockchainDelete func(multi *MultiStore, header *MultiBlockchainHeader)
|
||||
}
|
||||
|
||||
func InitMultiStore(path string) (multi *MultiStore, err error) {
|
||||
multi = &MultiStore{path: path}
|
||||
|
||||
// open existing blockchain file or create new one
|
||||
if multi.Database, err = store.NewPogrebStore(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return multi, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Header for blockchains:
|
||||
|
||||
Offset Size Info
|
||||
0 8 Version of the blockchain
|
||||
8 8 Height of the blockchain
|
||||
16 8 Count of blocks
|
||||
24 8 Date first block added
|
||||
32 8 Date last block added
|
||||
40 8 Stats: Count of file records (from available blocks)
|
||||
48 8 Stats: Size of all files combined (from available blocks)
|
||||
56 8 * n List of block numbers that are stored
|
||||
|
||||
Note: The statistics fields only count available stored blocks.
|
||||
|
||||
*/
|
||||
|
||||
const multiBlockchainHeaderSize = 56
|
||||
|
||||
// This is a header for a single blockchain stored in a multi store.
|
||||
type MultiBlockchainHeader struct {
|
||||
PublicKey *btcec.PublicKey // Public Key of the blockchain
|
||||
Height uint64 // Height is exchanged as uint32 in the protocol, but stored as uint64.
|
||||
Version uint64 // Version is always uint64.
|
||||
DateFirstBlockAdded time.Time // Date the first block was added
|
||||
DateLastBlockAdded time.Time // Date the last block was added
|
||||
ListBlocks []uint64 // List of block numbers that are stored
|
||||
Stats BlockchainStats // Statistics about the blockchain (only about stored blocks)
|
||||
}
|
||||
|
||||
// Keep statistics about blocks stored for a blockchain
|
||||
type BlockchainStats struct {
|
||||
CountFileRecords uint64 // Count of file records in stored blocks
|
||||
SizeAllFiles uint64 // Size of all files combined in stored blocks
|
||||
}
|
||||
|
||||
func decodeBlockchainHeader(publicKey *btcec.PublicKey, buffer []byte) (header *MultiBlockchainHeader, err error) {
|
||||
if len(buffer) < multiBlockchainHeaderSize {
|
||||
return nil, errors.New("header length too small")
|
||||
}
|
||||
|
||||
header = &MultiBlockchainHeader{PublicKey: publicKey}
|
||||
header.Version = binary.LittleEndian.Uint64(buffer[0:8])
|
||||
header.Height = binary.LittleEndian.Uint64(buffer[8:16])
|
||||
countBlocks := binary.LittleEndian.Uint64(buffer[16:24])
|
||||
header.DateFirstBlockAdded = time.Unix(int64(binary.LittleEndian.Uint64(buffer[24:32])), 0)
|
||||
header.DateLastBlockAdded = time.Unix(int64(binary.LittleEndian.Uint64(buffer[32:40])), 0)
|
||||
header.Stats.CountFileRecords = binary.LittleEndian.Uint64(buffer[40:48])
|
||||
header.Stats.SizeAllFiles = binary.LittleEndian.Uint64(buffer[48:56])
|
||||
|
||||
if uint64(len(buffer)) < multiBlockchainHeaderSize+8*countBlocks {
|
||||
return nil, errors.New("header length too small")
|
||||
}
|
||||
|
||||
index := multiBlockchainHeaderSize
|
||||
|
||||
for n := uint64(0); n < countBlocks; n++ {
|
||||
blockN := binary.LittleEndian.Uint64(buffer[index : index+8])
|
||||
header.ListBlocks = append(header.ListBlocks, blockN)
|
||||
index += 8
|
||||
}
|
||||
|
||||
return header, nil
|
||||
}
|
||||
|
||||
// Reads a blockchains header if available.
|
||||
func (multi *MultiStore) ReadBlockchainHeader(publicKey *btcec.PublicKey) (header *MultiBlockchainHeader, found bool, err error) {
|
||||
buffer, found := multi.Database.Get(publicKey.SerializeCompressed())
|
||||
if !found {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
header, err = decodeBlockchainHeader(publicKey, buffer)
|
||||
return header, err == nil, err
|
||||
}
|
||||
|
||||
// WriteBlockchainHeader writes a blockchain header. If one exists, it will be overwritten.
|
||||
func (multi *MultiStore) WriteBlockchainHeader(header *MultiBlockchainHeader) (err error) {
|
||||
raw := make([]byte, multiBlockchainHeaderSize+8*len(header.ListBlocks))
|
||||
|
||||
binary.LittleEndian.PutUint64(raw[0:8], header.Version)
|
||||
binary.LittleEndian.PutUint64(raw[8:16], header.Height)
|
||||
binary.LittleEndian.PutUint64(raw[16:24], uint64(len(header.ListBlocks)))
|
||||
binary.LittleEndian.PutUint64(raw[24:32], uint64(header.DateFirstBlockAdded.UTC().Unix()))
|
||||
binary.LittleEndian.PutUint64(raw[32:40], uint64(header.DateLastBlockAdded.UTC().Unix()))
|
||||
binary.LittleEndian.PutUint64(raw[40:48], header.Stats.CountFileRecords)
|
||||
binary.LittleEndian.PutUint64(raw[48:56], header.Stats.SizeAllFiles)
|
||||
|
||||
index := multiBlockchainHeaderSize
|
||||
|
||||
for _, blockN := range header.ListBlocks {
|
||||
binary.LittleEndian.PutUint64(raw[index:index+8], blockN)
|
||||
index += 8
|
||||
}
|
||||
|
||||
return multi.Database.Set(header.PublicKey.SerializeCompressed(), raw)
|
||||
}
|
||||
|
||||
func lookupKeyForBlock(publicKey *btcec.PublicKey, version, blockNumber uint64) (key []byte) {
|
||||
var buffer [16]byte
|
||||
binary.LittleEndian.PutUint64(buffer[0:8], version)
|
||||
binary.LittleEndian.PutUint64(buffer[8:16], blockNumber)
|
||||
|
||||
key = append(key, publicKey.SerializeCompressed()...)
|
||||
key = append(key, buffer[:]...)
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
// ReadBlock reads a raw block
|
||||
func (multi *MultiStore) ReadBlock(publicKey *btcec.PublicKey, version, blockNumber uint64) (raw []byte, found bool) {
|
||||
return multi.Database.Get(lookupKeyForBlock(publicKey, version, blockNumber))
|
||||
}
|
||||
|
||||
// WriteBlock writes a raw block. It does not update the blockchain header.
|
||||
func (multi *MultiStore) WriteBlock(publicKey *btcec.PublicKey, version, blockNumber uint64, raw []byte) (err error) {
|
||||
return multi.Database.Set(lookupKeyForBlock(publicKey, version, blockNumber), raw)
|
||||
}
|
||||
|
||||
// AssessBlockchainHeader reads the blockchain header, if available, and assesses the status.
|
||||
func (multi *MultiStore) AssessBlockchainHeader(publicKey *btcec.PublicKey, version, height uint64) (header *MultiBlockchainHeader, status int, err error) {
|
||||
// check if there is an existing header for the blockchain
|
||||
header, found, err := multi.ReadBlockchainHeader(publicKey)
|
||||
if err != nil {
|
||||
return nil, MultiStatusErrorReadHeader, err
|
||||
} else if !found {
|
||||
return nil, MultiStatusHeaderNA, nil
|
||||
}
|
||||
|
||||
if header.Version == version && header.Height == height {
|
||||
return header, MultiStatusEqual, nil
|
||||
}
|
||||
|
||||
// Check if existing version is newer than reported - indicates illegal behavior by the remote peer.
|
||||
// Improper refactoring could happen if the local blockchain folder is deleted and automatically recreated.
|
||||
if header.Version > version || (header.Version == version && header.Height > height) {
|
||||
return header, MultiStatusInvalidRemote, nil
|
||||
}
|
||||
|
||||
if version > header.Version {
|
||||
return header, MultiStatusNewVersion, nil
|
||||
}
|
||||
|
||||
return header, MultiStatusNewBlocks, nil
|
||||
}
|
||||
|
||||
// Deletes an entire blockchain from the store. It will delete each block individually and then the header.
|
||||
func (multi *MultiStore) DeleteBlockchain(header *MultiBlockchainHeader) {
|
||||
// first delete all blocks
|
||||
for _, blockN := range header.ListBlocks {
|
||||
multi.Database.Delete(lookupKeyForBlock(header.PublicKey, header.Version, blockN))
|
||||
}
|
||||
|
||||
// delete the header
|
||||
multi.Database.Delete(header.PublicKey.SerializeCompressed())
|
||||
|
||||
if multi.FilterBlockchainDelete != nil {
|
||||
multi.FilterBlockchainDelete(multi, header)
|
||||
}
|
||||
}
|
||||
|
||||
func (multi *MultiStore) NewBlockchainHeader(publicKey *btcec.PublicKey, version, height uint64) (header *MultiBlockchainHeader, err error) {
|
||||
timeN := time.Now().UTC()
|
||||
header = &MultiBlockchainHeader{
|
||||
PublicKey: publicKey,
|
||||
Height: height,
|
||||
Version: version,
|
||||
DateFirstBlockAdded: timeN,
|
||||
DateLastBlockAdded: timeN,
|
||||
}
|
||||
|
||||
return header, multi.WriteBlockchainHeader(header)
|
||||
}
|
||||
|
||||
// Updates the statistics fields of the blockchain header based on the new decoded block records.
|
||||
// It does not write the blockchain header; multi.WriteBlockchainHeader must called to store the changes.
|
||||
// The caller must make sure not to call this function on records already processed.
|
||||
func (multi *MultiStore) UpdateBlockchainStatistics(header *MultiBlockchainHeader, recordsDecoded []interface{}) {
|
||||
updatedStats := false
|
||||
statsOld := header.Stats
|
||||
|
||||
for _, decodedR := range recordsDecoded {
|
||||
if file, ok := decodedR.(BlockRecordFile); ok {
|
||||
header.Stats.SizeAllFiles += file.Size
|
||||
header.Stats.CountFileRecords++
|
||||
|
||||
updatedStats = true
|
||||
}
|
||||
}
|
||||
|
||||
if updatedStats && multi.FilterStatisticUpdate != nil {
|
||||
multi.FilterStatisticUpdate(multi, header, statsOld)
|
||||
}
|
||||
}
|
||||
|
||||
// Iterates over all blockchains stored in the cache
|
||||
func (multi *MultiStore) IterateBlockchains(callback func(header *MultiBlockchainHeader)) {
|
||||
multi.Database.Iterate(func(key, value []byte) {
|
||||
if len(key) == 33 {
|
||||
// Length 33 indicates key = Public key compressed, value = Header
|
||||
if blockchainPublicKey, err := btcec.ParsePubKey(key, btcec.S256()); err == nil {
|
||||
if header, err := decodeBlockchainHeader(blockchainPublicKey, value); err == nil {
|
||||
callback(header)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// IngestBlock ingests a new block into the store. It fails if a block is already stored for the given blockchain and block number.
|
||||
// It will update the blockchain header including the statistics.
|
||||
func (multi *MultiStore) IngestBlock(header *MultiBlockchainHeader, blockNumber uint64, raw []byte, failIfInvalid bool) (decoded *BlockDecoded, err error) {
|
||||
// check if already exists
|
||||
if _, found := multi.ReadBlock(header.PublicKey, header.Version, blockNumber); found {
|
||||
return nil, errors.New("already exists")
|
||||
}
|
||||
|
||||
// decode it
|
||||
decoded, status, err := DecodeBlockRaw(raw)
|
||||
if failIfInvalid && err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// store the transferred block in the cache
|
||||
multi.WriteBlock(header.PublicKey, header.Version, blockNumber, raw)
|
||||
header.ListBlocks = append(header.ListBlocks, blockNumber)
|
||||
|
||||
// update blockchain header stats if records were decoded
|
||||
if status == StatusOK {
|
||||
multi.UpdateBlockchainStatistics(header, decoded.RecordsDecoded)
|
||||
}
|
||||
|
||||
// update the blockchain header
|
||||
multi.WriteBlockchainHeader(header)
|
||||
|
||||
return decoded, nil
|
||||
}
|
||||
32
vendor/github.com/PeernetOfficial/core/blockchain/Profile Data.go
generated
vendored
Normal file
32
vendor/github.com/PeernetOfficial/core/blockchain/Profile Data.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
File Name: Profile Data.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package blockchain
|
||||
|
||||
// List of recognized profile fields.
|
||||
const (
|
||||
ProfileName = 0 // Arbitrary username
|
||||
ProfileEmail = 1 // Email address
|
||||
ProfileWebsite = 2 // Website address
|
||||
ProfileTwitter = 3 // Twitter account without the @
|
||||
ProfileYouTube = 4 // YouTube channel URL
|
||||
ProfileAddress = 5 // Physical address
|
||||
ProfilePicture = 6 // Profile picture, blob
|
||||
)
|
||||
|
||||
// The encoding of profile fields depends on the field. Text data is always UTF-8 text encoded.
|
||||
// Note that all profile data is arbitrary and shall be considered untrusted and unverified.
|
||||
// To establish trust, the user must load Certificates into the blockchain that validate certain data.
|
||||
|
||||
// Text returns the profile field as text encoded
|
||||
func (info *BlockRecordProfile) Text() string {
|
||||
return string(info.Data)
|
||||
}
|
||||
|
||||
// ProfileFieldFromText returns a profile field from text
|
||||
func ProfileFieldFromText(Type uint16, Text string) BlockRecordProfile {
|
||||
return BlockRecordProfile{Type: Type, Data: []byte(Text)}
|
||||
}
|
||||
119
vendor/github.com/PeernetOfficial/core/blockchain/Profile.go
generated
vendored
Normal file
119
vendor/github.com/PeernetOfficial/core/blockchain/Profile.go
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
File Name: Profile.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package blockchain
|
||||
|
||||
// ProfileReadField reads the specified profile field. See ProfileX for the list of recognized fields. The encoding depends on the field type. Status is StatusX.
|
||||
func (blockchain *Blockchain) ProfileReadField(index uint16) (data []byte, status int) {
|
||||
found := false
|
||||
|
||||
status = blockchain.Iterate(func(block *Block) (statusI int) {
|
||||
fields, err := decodeBlockRecordProfile(block.RecordsRaw)
|
||||
if err != nil {
|
||||
return StatusCorruptBlockRecord
|
||||
} else if len(fields) == 0 {
|
||||
return StatusOK
|
||||
}
|
||||
|
||||
// Check if the field is available in the profile record. If there are multiple records, only return the latest one.
|
||||
for n := range fields {
|
||||
if fields[n].Type == index {
|
||||
data = fields[n].Data
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
return StatusOK
|
||||
})
|
||||
|
||||
if status != StatusOK {
|
||||
return nil, status
|
||||
} else if !found {
|
||||
return nil, StatusDataNotFound
|
||||
}
|
||||
|
||||
return data, StatusOK
|
||||
}
|
||||
|
||||
// ProfileList lists all profile fields. Status is StatusX.
|
||||
func (blockchain *Blockchain) ProfileList() (fields []BlockRecordProfile, status int) {
|
||||
uniqueFields := make(map[uint16][]byte)
|
||||
|
||||
status = blockchain.Iterate(func(block *Block) (statusI int) {
|
||||
fields, err := decodeBlockRecordProfile(block.RecordsRaw)
|
||||
if err != nil {
|
||||
return StatusCorruptBlockRecord
|
||||
}
|
||||
|
||||
for n := range fields {
|
||||
uniqueFields[fields[n].Type] = fields[n].Data
|
||||
}
|
||||
|
||||
return StatusOK
|
||||
})
|
||||
|
||||
for key, value := range uniqueFields {
|
||||
fields = append(fields, BlockRecordProfile{Type: key, Data: value})
|
||||
}
|
||||
|
||||
return fields, status
|
||||
}
|
||||
|
||||
// ProfileWrite writes profile fields and blobs to the blockchain. Status is StatusX.
|
||||
func (blockchain *Blockchain) ProfileWrite(fields []BlockRecordProfile) (newHeight, newVersion uint64, status int) {
|
||||
encodeProfileAppend := func(fields []BlockRecordProfile) (newHeight, newVersion uint64, status int) {
|
||||
encoded, err := encodeBlockRecordProfile(fields)
|
||||
if err != nil {
|
||||
return 0, 0, StatusCorruptBlockRecord
|
||||
}
|
||||
|
||||
return blockchain.Append(encoded)
|
||||
}
|
||||
|
||||
blockSize := uint64(blockHeaderSize)
|
||||
var recordFields []BlockRecordProfile
|
||||
|
||||
for _, field := range fields {
|
||||
recordSize := field.SizeInBlock()
|
||||
|
||||
// need to create a new block due to target block size?
|
||||
if len(recordFields) > 0 && blockSize+recordSize > TargetBlockSize {
|
||||
if newHeight, newVersion, status = encodeProfileAppend(recordFields); status != StatusOK {
|
||||
return newHeight, newVersion, status
|
||||
}
|
||||
|
||||
blockSize = blockHeaderSize
|
||||
recordFields = nil
|
||||
}
|
||||
|
||||
blockSize += recordSize
|
||||
recordFields = append(recordFields, field)
|
||||
}
|
||||
|
||||
return encodeProfileAppend(recordFields)
|
||||
}
|
||||
|
||||
// ProfileDelete deletes fields and blobs from the blockchain. Status is StatusX.
|
||||
func (blockchain *Blockchain) ProfileDelete(fields []uint16) (newHeight, newVersion uint64, status int) {
|
||||
return blockchain.IterateDeleteRecord(nil, func(record *BlockRecordRaw) (deleteAction int) {
|
||||
if record.Type != RecordTypeProfile {
|
||||
return 0 // no action
|
||||
}
|
||||
|
||||
existingFields, err := decodeBlockRecordProfile([]BlockRecordRaw{*record})
|
||||
if err != nil || len(existingFields) != 1 {
|
||||
return 3 // error blockchain corrupt
|
||||
}
|
||||
|
||||
for _, i := range fields {
|
||||
if i == existingFields[0].Type { // found a file ID to delete?
|
||||
return 1 // delete record
|
||||
}
|
||||
}
|
||||
|
||||
return 0 // no action on record
|
||||
})
|
||||
}
|
||||
66
vendor/github.com/PeernetOfficial/core/blockchain/readme.md
generated
vendored
Normal file
66
vendor/github.com/PeernetOfficial/core/blockchain/readme.md
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
# Blockchain
|
||||
|
||||
The blockchain stores the metadata of files published by the user, profile data, and social interactions. The blockchain is implemented according to the Peernet Whitepaper published at [peernet.org](https://peernet.org).
|
||||
|
||||
The blockchain is a consecutive sequence of blocks linked together by their previous hash. Each block may contain one or multiple records.
|
||||
|
||||
All blocks and the blockchain header are stored locally in a key-value database.
|
||||
|
||||
# Encoding
|
||||
|
||||
## Header
|
||||
|
||||
The blockchain header is not part of the Peernet specification. Below is the encoding of the blockchain header. The public key can be extracted from the signature.
|
||||
|
||||
```
|
||||
Offset Size Info
|
||||
0 8 Height of the blockchain
|
||||
8 8 Version of the blockchain
|
||||
16 2 Format of the blockchain. This provides backward compatibility.
|
||||
18 65 Signature
|
||||
```
|
||||
|
||||
## Block
|
||||
|
||||
Encoding of a block (it is the same stored in the database and shared in a message):
|
||||
|
||||
```
|
||||
Offset Size Info
|
||||
0 65 Signature of entire block
|
||||
65 32 Hash (blake3) of last block. 0 for first one.
|
||||
97 8 Blockchain version number
|
||||
105 4 Block number
|
||||
109 4 Size of entire block including this header
|
||||
113 2 Count of records that follow
|
||||
```
|
||||
|
||||
Each record inside the block has this basic structure:
|
||||
|
||||
```
|
||||
Offset Size Info
|
||||
0 1 Record type
|
||||
1 8 Date created. This remains the same in case of block refactoring.
|
||||
9 4 Size of data
|
||||
13 ? Data (encoding depends on record type)
|
||||
```
|
||||
|
||||
# Internals
|
||||
|
||||
## Block Size
|
||||
|
||||
Peers must accept a minimum block size of 1 KB.
|
||||
|
||||
The target block size (for generating new blocks) is defined via `TargetBlockSize`. If records cannot fit within that target size, they are added into a new block.
|
||||
|
||||
Small block sizes ensure that the block will be transferred via blockchain exchange and cached in DHT.
|
||||
Large blocks may be ignored by clients for size and spam reasons, resulting in decreased discoverability.
|
||||
|
||||
## Edge Cases
|
||||
|
||||
### Deleting vs Replacing Records
|
||||
|
||||
If a specific record shall be replaced, it should be deleted and a new block containing the replacement record shall be created.
|
||||
|
||||
Inline replacement of a record in a block would lead to problems:
|
||||
* The block size could increase which could push the block size above the recommended limit.
|
||||
* In case of `RecordTypeFile` records, they may use `RecordTypeTagData` records for compression. If a single record is to be replaced 1:1 with another record, this could not take advantage of this embedded compression algorithm.
|
||||
25
vendor/github.com/PeernetOfficial/core/btcec/README.md
generated
vendored
Normal file
25
vendor/github.com/PeernetOfficial/core/btcec/README.md
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# btcec
|
||||
|
||||
This is a fork of `https://github.com/btcsuite/btcd/tree/master/btcec`. Last sync of changes: 14.11.2021
|
||||
|
||||
Package btcec implements elliptic curve cryptography needed for working with
|
||||
Bitcoin (secp256k1 only for now). It is designed so that it may be used with the
|
||||
standard crypto/ecdsa packages provided with go. A comprehensive suite of test
|
||||
is provided to ensure proper functionality. Package btcec was originally based
|
||||
on work from ThePiachu which is licensed under the same terms as Go, but it has
|
||||
signficantly diverged since then. The btcsuite developers original is licensed
|
||||
under the liberal ISC license.
|
||||
|
||||
Although this package was primarily written for btcd, it has intentionally been
|
||||
designed so it can be used as a standalone package for any projects needing to
|
||||
use secp256k1 elliptic curve cryptography.
|
||||
|
||||
## Examples
|
||||
|
||||
* Sign Message: Demonstrates signing a message with a secp256k1 private key that is first parsed form raw bytes and serializing the generated signature.
|
||||
|
||||
* Verify Signature: Demonstrates verifying a secp256k1 signature against a public key that is first parsed from raw bytes. The signature is also parsed from raw bytes.
|
||||
|
||||
* Encryption: Demonstrates encrypting a message for a public key that is first parsed from raw bytes, then decrypting it using the corresponding private key.
|
||||
|
||||
* Decryption: Demonstrates decrypting a message using a private key that is first parsed from raw bytes.
|
||||
978
vendor/github.com/PeernetOfficial/core/btcec/btcec.go
generated
vendored
Normal file
978
vendor/github.com/PeernetOfficial/core/btcec/btcec.go
generated
vendored
Normal file
@@ -0,0 +1,978 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Copyright 2011 ThePiachu. All rights reserved.
|
||||
// Copyright 2013-2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcec
|
||||
|
||||
// References:
|
||||
// [SECG]: Recommended Elliptic Curve Domain Parameters
|
||||
// http://www.secg.org/sec2-v2.pdf
|
||||
//
|
||||
// [GECC]: Guide to Elliptic Curve Cryptography (Hankerson, Menezes, Vanstone)
|
||||
|
||||
// This package operates, internally, on Jacobian coordinates. For a given
|
||||
// (x, y) position on the curve, the Jacobian coordinates are (x1, y1, z1)
|
||||
// where x = x1/z1² and y = y1/z1³. The greatest speedups come when the whole
|
||||
// calculation can be performed within the transform (as in ScalarMult and
|
||||
// ScalarBaseMult). But even for Add and Double, it's faster to apply and
|
||||
// reverse the transform than to operate in affine coordinates.
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"math/big"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
// fieldOne is simply the integer 1 in field representation. It is
|
||||
// used to avoid needing to create it multiple times during the internal
|
||||
// arithmetic.
|
||||
fieldOne = new(fieldVal).SetInt(1)
|
||||
)
|
||||
|
||||
// KoblitzCurve supports a koblitz curve implementation that fits the ECC Curve
|
||||
// interface from crypto/elliptic.
|
||||
type KoblitzCurve struct {
|
||||
*elliptic.CurveParams
|
||||
|
||||
// q is the value (P+1)/4 used to compute the square root of field
|
||||
// elements.
|
||||
q *big.Int
|
||||
|
||||
H int // cofactor of the curve.
|
||||
halfOrder *big.Int // half the order N
|
||||
|
||||
// fieldB is the constant B of the curve as a fieldVal.
|
||||
fieldB *fieldVal
|
||||
|
||||
// byteSize is simply the bit size / 8 and is provided for convenience
|
||||
// since it is calculated repeatedly.
|
||||
byteSize int
|
||||
|
||||
// bytePoints
|
||||
bytePoints *[32][256][3]fieldVal
|
||||
|
||||
// The next 6 values are used specifically for endomorphism
|
||||
// optimizations in ScalarMult.
|
||||
|
||||
// lambda must fulfill lambda^3 = 1 mod N where N is the order of G.
|
||||
lambda *big.Int
|
||||
|
||||
// beta must fulfill beta^3 = 1 mod P where P is the prime field of the
|
||||
// curve.
|
||||
beta *fieldVal
|
||||
|
||||
// See the EndomorphismVectors in gensecp256k1.go to see how these are
|
||||
// derived.
|
||||
a1 *big.Int
|
||||
b1 *big.Int
|
||||
a2 *big.Int
|
||||
b2 *big.Int
|
||||
}
|
||||
|
||||
// Params returns the parameters for the curve.
|
||||
func (curve *KoblitzCurve) Params() *elliptic.CurveParams {
|
||||
return curve.CurveParams
|
||||
}
|
||||
|
||||
// bigAffineToField takes an affine point (x, y) as big integers and converts
|
||||
// it to an affine point as field values.
|
||||
func (curve *KoblitzCurve) bigAffineToField(x, y *big.Int) (*fieldVal, *fieldVal) {
|
||||
x3, y3 := new(fieldVal), new(fieldVal)
|
||||
x3.SetByteSlice(x.Bytes())
|
||||
y3.SetByteSlice(y.Bytes())
|
||||
|
||||
return x3, y3
|
||||
}
|
||||
|
||||
// fieldJacobianToBigAffine takes a Jacobian point (x, y, z) as field values and
|
||||
// converts it to an affine point as big integers.
|
||||
func (curve *KoblitzCurve) fieldJacobianToBigAffine(x, y, z *fieldVal) (*big.Int, *big.Int) {
|
||||
// Inversions are expensive and both point addition and point doubling
|
||||
// are faster when working with points that have a z value of one. So,
|
||||
// if the point needs to be converted to affine, go ahead and normalize
|
||||
// the point itself at the same time as the calculation is the same.
|
||||
var zInv, tempZ fieldVal
|
||||
zInv.Set(z).Inverse() // zInv = Z^-1
|
||||
tempZ.SquareVal(&zInv) // tempZ = Z^-2
|
||||
x.Mul(&tempZ) // X = X/Z^2 (mag: 1)
|
||||
y.Mul(tempZ.Mul(&zInv)) // Y = Y/Z^3 (mag: 1)
|
||||
z.SetInt(1) // Z = 1 (mag: 1)
|
||||
|
||||
// Normalize the x and y values.
|
||||
x.Normalize()
|
||||
y.Normalize()
|
||||
|
||||
// Convert the field values for the now affine point to big.Ints.
|
||||
x3, y3 := new(big.Int), new(big.Int)
|
||||
x3.SetBytes(x.Bytes()[:])
|
||||
y3.SetBytes(y.Bytes()[:])
|
||||
return x3, y3
|
||||
}
|
||||
|
||||
// IsOnCurve returns boolean if the point (x,y) is on the curve.
|
||||
// Part of the elliptic.Curve interface. This function differs from the
|
||||
// crypto/elliptic algorithm since a = 0 not -3.
|
||||
func (curve *KoblitzCurve) IsOnCurve(x, y *big.Int) bool {
|
||||
// Convert big ints to field values for faster arithmetic.
|
||||
fx, fy := curve.bigAffineToField(x, y)
|
||||
|
||||
// Elliptic curve equation for secp256k1 is: y^2 = x^3 + 7
|
||||
y2 := new(fieldVal).SquareVal(fy).Normalize()
|
||||
result := new(fieldVal).SquareVal(fx).Mul(fx).AddInt(7).Normalize()
|
||||
return y2.Equals(result)
|
||||
}
|
||||
|
||||
// addZ1AndZ2EqualsOne adds two Jacobian points that are already known to have
|
||||
// z values of 1 and stores the result in (x3, y3, z3). That is to say
|
||||
// (x1, y1, 1) + (x2, y2, 1) = (x3, y3, z3). It performs faster addition than
|
||||
// the generic add routine since less arithmetic is needed due to the ability to
|
||||
// avoid the z value multiplications.
|
||||
func (curve *KoblitzCurve) addZ1AndZ2EqualsOne(x1, y1, z1, x2, y2, x3, y3, z3 *fieldVal) {
|
||||
// To compute the point addition efficiently, this implementation splits
|
||||
// the equation into intermediate elements which are used to minimize
|
||||
// the number of field multiplications using the method shown at:
|
||||
// http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-mmadd-2007-bl
|
||||
//
|
||||
// In particular it performs the calculations using the following:
|
||||
// H = X2-X1, HH = H^2, I = 4*HH, J = H*I, r = 2*(Y2-Y1), V = X1*I
|
||||
// X3 = r^2-J-2*V, Y3 = r*(V-X3)-2*Y1*J, Z3 = 2*H
|
||||
//
|
||||
// This results in a cost of 4 field multiplications, 2 field squarings,
|
||||
// 6 field additions, and 5 integer multiplications.
|
||||
|
||||
// When the x coordinates are the same for two points on the curve, the
|
||||
// y coordinates either must be the same, in which case it is point
|
||||
// doubling, or they are opposite and the result is the point at
|
||||
// infinity per the group law for elliptic curve cryptography.
|
||||
x1.Normalize()
|
||||
y1.Normalize()
|
||||
x2.Normalize()
|
||||
y2.Normalize()
|
||||
if x1.Equals(x2) {
|
||||
if y1.Equals(y2) {
|
||||
// Since x1 == x2 and y1 == y2, point doubling must be
|
||||
// done, otherwise the addition would end up dividing
|
||||
// by zero.
|
||||
curve.doubleJacobian(x1, y1, z1, x3, y3, z3)
|
||||
return
|
||||
}
|
||||
|
||||
// Since x1 == x2 and y1 == -y2, the sum is the point at
|
||||
// infinity per the group law.
|
||||
x3.SetInt(0)
|
||||
y3.SetInt(0)
|
||||
z3.SetInt(0)
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate X3, Y3, and Z3 according to the intermediate elements
|
||||
// breakdown above.
|
||||
var h, i, j, r, v fieldVal
|
||||
var negJ, neg2V, negX3 fieldVal
|
||||
h.Set(x1).Negate(1).Add(x2) // H = X2-X1 (mag: 3)
|
||||
i.SquareVal(&h).MulInt(4) // I = 4*H^2 (mag: 4)
|
||||
j.Mul2(&h, &i) // J = H*I (mag: 1)
|
||||
r.Set(y1).Negate(1).Add(y2).MulInt(2) // r = 2*(Y2-Y1) (mag: 6)
|
||||
v.Mul2(x1, &i) // V = X1*I (mag: 1)
|
||||
negJ.Set(&j).Negate(1) // negJ = -J (mag: 2)
|
||||
neg2V.Set(&v).MulInt(2).Negate(2) // neg2V = -(2*V) (mag: 3)
|
||||
x3.Set(&r).Square().Add(&negJ).Add(&neg2V) // X3 = r^2-J-2*V (mag: 6)
|
||||
negX3.Set(x3).Negate(6) // negX3 = -X3 (mag: 7)
|
||||
j.Mul(y1).MulInt(2).Negate(2) // J = -(2*Y1*J) (mag: 3)
|
||||
y3.Set(&v).Add(&negX3).Mul(&r).Add(&j) // Y3 = r*(V-X3)-2*Y1*J (mag: 4)
|
||||
z3.Set(&h).MulInt(2) // Z3 = 2*H (mag: 6)
|
||||
|
||||
// Normalize the resulting field values to a magnitude of 1 as needed.
|
||||
x3.Normalize()
|
||||
y3.Normalize()
|
||||
z3.Normalize()
|
||||
}
|
||||
|
||||
// addZ1EqualsZ2 adds two Jacobian points that are already known to have the
|
||||
// same z value and stores the result in (x3, y3, z3). That is to say
|
||||
// (x1, y1, z1) + (x2, y2, z1) = (x3, y3, z3). It performs faster addition than
|
||||
// the generic add routine since less arithmetic is needed due to the known
|
||||
// equivalence.
|
||||
func (curve *KoblitzCurve) addZ1EqualsZ2(x1, y1, z1, x2, y2, x3, y3, z3 *fieldVal) {
|
||||
// To compute the point addition efficiently, this implementation splits
|
||||
// the equation into intermediate elements which are used to minimize
|
||||
// the number of field multiplications using a slightly modified version
|
||||
// of the method shown at:
|
||||
// http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-mmadd-2007-bl
|
||||
//
|
||||
// In particular it performs the calculations using the following:
|
||||
// A = X2-X1, B = A^2, C=Y2-Y1, D = C^2, E = X1*B, F = X2*B
|
||||
// X3 = D-E-F, Y3 = C*(E-X3)-Y1*(F-E), Z3 = Z1*A
|
||||
//
|
||||
// This results in a cost of 5 field multiplications, 2 field squarings,
|
||||
// 9 field additions, and 0 integer multiplications.
|
||||
|
||||
// When the x coordinates are the same for two points on the curve, the
|
||||
// y coordinates either must be the same, in which case it is point
|
||||
// doubling, or they are opposite and the result is the point at
|
||||
// infinity per the group law for elliptic curve cryptography.
|
||||
x1.Normalize()
|
||||
y1.Normalize()
|
||||
x2.Normalize()
|
||||
y2.Normalize()
|
||||
if x1.Equals(x2) {
|
||||
if y1.Equals(y2) {
|
||||
// Since x1 == x2 and y1 == y2, point doubling must be
|
||||
// done, otherwise the addition would end up dividing
|
||||
// by zero.
|
||||
curve.doubleJacobian(x1, y1, z1, x3, y3, z3)
|
||||
return
|
||||
}
|
||||
|
||||
// Since x1 == x2 and y1 == -y2, the sum is the point at
|
||||
// infinity per the group law.
|
||||
x3.SetInt(0)
|
||||
y3.SetInt(0)
|
||||
z3.SetInt(0)
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate X3, Y3, and Z3 according to the intermediate elements
|
||||
// breakdown above.
|
||||
var a, b, c, d, e, f fieldVal
|
||||
var negX1, negY1, negE, negX3 fieldVal
|
||||
negX1.Set(x1).Negate(1) // negX1 = -X1 (mag: 2)
|
||||
negY1.Set(y1).Negate(1) // negY1 = -Y1 (mag: 2)
|
||||
a.Set(&negX1).Add(x2) // A = X2-X1 (mag: 3)
|
||||
b.SquareVal(&a) // B = A^2 (mag: 1)
|
||||
c.Set(&negY1).Add(y2) // C = Y2-Y1 (mag: 3)
|
||||
d.SquareVal(&c) // D = C^2 (mag: 1)
|
||||
e.Mul2(x1, &b) // E = X1*B (mag: 1)
|
||||
negE.Set(&e).Negate(1) // negE = -E (mag: 2)
|
||||
f.Mul2(x2, &b) // F = X2*B (mag: 1)
|
||||
x3.Add2(&e, &f).Negate(3).Add(&d) // X3 = D-E-F (mag: 5)
|
||||
negX3.Set(x3).Negate(5).Normalize() // negX3 = -X3 (mag: 1)
|
||||
y3.Set(y1).Mul(f.Add(&negE)).Negate(3) // Y3 = -(Y1*(F-E)) (mag: 4)
|
||||
y3.Add(e.Add(&negX3).Mul(&c)) // Y3 = C*(E-X3)+Y3 (mag: 5)
|
||||
z3.Mul2(z1, &a) // Z3 = Z1*A (mag: 1)
|
||||
|
||||
// Normalize the resulting field values to a magnitude of 1 as needed.
|
||||
x3.Normalize()
|
||||
y3.Normalize()
|
||||
}
|
||||
|
||||
// addZ2EqualsOne adds two Jacobian points when the second point is already
|
||||
// known to have a z value of 1 (and the z value for the first point is not 1)
|
||||
// and stores the result in (x3, y3, z3). That is to say (x1, y1, z1) +
|
||||
// (x2, y2, 1) = (x3, y3, z3). It performs faster addition than the generic
|
||||
// add routine since less arithmetic is needed due to the ability to avoid
|
||||
// multiplications by the second point's z value.
|
||||
func (curve *KoblitzCurve) addZ2EqualsOne(x1, y1, z1, x2, y2, x3, y3, z3 *fieldVal) {
|
||||
// To compute the point addition efficiently, this implementation splits
|
||||
// the equation into intermediate elements which are used to minimize
|
||||
// the number of field multiplications using the method shown at:
|
||||
// http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-madd-2007-bl
|
||||
//
|
||||
// In particular it performs the calculations using the following:
|
||||
// Z1Z1 = Z1^2, U2 = X2*Z1Z1, S2 = Y2*Z1*Z1Z1, H = U2-X1, HH = H^2,
|
||||
// I = 4*HH, J = H*I, r = 2*(S2-Y1), V = X1*I
|
||||
// X3 = r^2-J-2*V, Y3 = r*(V-X3)-2*Y1*J, Z3 = (Z1+H)^2-Z1Z1-HH
|
||||
//
|
||||
// This results in a cost of 7 field multiplications, 4 field squarings,
|
||||
// 9 field additions, and 4 integer multiplications.
|
||||
|
||||
// When the x coordinates are the same for two points on the curve, the
|
||||
// y coordinates either must be the same, in which case it is point
|
||||
// doubling, or they are opposite and the result is the point at
|
||||
// infinity per the group law for elliptic curve cryptography. Since
|
||||
// any number of Jacobian coordinates can represent the same affine
|
||||
// point, the x and y values need to be converted to like terms. Due to
|
||||
// the assumption made for this function that the second point has a z
|
||||
// value of 1 (z2=1), the first point is already "converted".
|
||||
var z1z1, u2, s2 fieldVal
|
||||
x1.Normalize()
|
||||
y1.Normalize()
|
||||
z1z1.SquareVal(z1) // Z1Z1 = Z1^2 (mag: 1)
|
||||
u2.Set(x2).Mul(&z1z1).Normalize() // U2 = X2*Z1Z1 (mag: 1)
|
||||
s2.Set(y2).Mul(&z1z1).Mul(z1).Normalize() // S2 = Y2*Z1*Z1Z1 (mag: 1)
|
||||
if x1.Equals(&u2) {
|
||||
if y1.Equals(&s2) {
|
||||
// Since x1 == x2 and y1 == y2, point doubling must be
|
||||
// done, otherwise the addition would end up dividing
|
||||
// by zero.
|
||||
curve.doubleJacobian(x1, y1, z1, x3, y3, z3)
|
||||
return
|
||||
}
|
||||
|
||||
// Since x1 == x2 and y1 == -y2, the sum is the point at
|
||||
// infinity per the group law.
|
||||
x3.SetInt(0)
|
||||
y3.SetInt(0)
|
||||
z3.SetInt(0)
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate X3, Y3, and Z3 according to the intermediate elements
|
||||
// breakdown above.
|
||||
var h, hh, i, j, r, rr, v fieldVal
|
||||
var negX1, negY1, negX3 fieldVal
|
||||
negX1.Set(x1).Negate(1) // negX1 = -X1 (mag: 2)
|
||||
h.Add2(&u2, &negX1) // H = U2-X1 (mag: 3)
|
||||
hh.SquareVal(&h) // HH = H^2 (mag: 1)
|
||||
i.Set(&hh).MulInt(4) // I = 4 * HH (mag: 4)
|
||||
j.Mul2(&h, &i) // J = H*I (mag: 1)
|
||||
negY1.Set(y1).Negate(1) // negY1 = -Y1 (mag: 2)
|
||||
r.Set(&s2).Add(&negY1).MulInt(2) // r = 2*(S2-Y1) (mag: 6)
|
||||
rr.SquareVal(&r) // rr = r^2 (mag: 1)
|
||||
v.Mul2(x1, &i) // V = X1*I (mag: 1)
|
||||
x3.Set(&v).MulInt(2).Add(&j).Negate(3) // X3 = -(J+2*V) (mag: 4)
|
||||
x3.Add(&rr) // X3 = r^2+X3 (mag: 5)
|
||||
negX3.Set(x3).Negate(5) // negX3 = -X3 (mag: 6)
|
||||
y3.Set(y1).Mul(&j).MulInt(2).Negate(2) // Y3 = -(2*Y1*J) (mag: 3)
|
||||
y3.Add(v.Add(&negX3).Mul(&r)) // Y3 = r*(V-X3)+Y3 (mag: 4)
|
||||
z3.Add2(z1, &h).Square() // Z3 = (Z1+H)^2 (mag: 1)
|
||||
z3.Add(z1z1.Add(&hh).Negate(2)) // Z3 = Z3-(Z1Z1+HH) (mag: 4)
|
||||
|
||||
// Normalize the resulting field values to a magnitude of 1 as needed.
|
||||
x3.Normalize()
|
||||
y3.Normalize()
|
||||
z3.Normalize()
|
||||
}
|
||||
|
||||
// addGeneric adds two Jacobian points (x1, y1, z1) and (x2, y2, z2) without any
|
||||
// assumptions about the z values of the two points and stores the result in
|
||||
// (x3, y3, z3). That is to say (x1, y1, z1) + (x2, y2, z2) = (x3, y3, z3). It
|
||||
// is the slowest of the add routines due to requiring the most arithmetic.
|
||||
func (curve *KoblitzCurve) addGeneric(x1, y1, z1, x2, y2, z2, x3, y3, z3 *fieldVal) {
|
||||
// To compute the point addition efficiently, this implementation splits
|
||||
// the equation into intermediate elements which are used to minimize
|
||||
// the number of field multiplications using the method shown at:
|
||||
// http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl
|
||||
//
|
||||
// In particular it performs the calculations using the following:
|
||||
// Z1Z1 = Z1^2, Z2Z2 = Z2^2, U1 = X1*Z2Z2, U2 = X2*Z1Z1, S1 = Y1*Z2*Z2Z2
|
||||
// S2 = Y2*Z1*Z1Z1, H = U2-U1, I = (2*H)^2, J = H*I, r = 2*(S2-S1)
|
||||
// V = U1*I
|
||||
// X3 = r^2-J-2*V, Y3 = r*(V-X3)-2*S1*J, Z3 = ((Z1+Z2)^2-Z1Z1-Z2Z2)*H
|
||||
//
|
||||
// This results in a cost of 11 field multiplications, 5 field squarings,
|
||||
// 9 field additions, and 4 integer multiplications.
|
||||
|
||||
// When the x coordinates are the same for two points on the curve, the
|
||||
// y coordinates either must be the same, in which case it is point
|
||||
// doubling, or they are opposite and the result is the point at
|
||||
// infinity. Since any number of Jacobian coordinates can represent the
|
||||
// same affine point, the x and y values need to be converted to like
|
||||
// terms.
|
||||
var z1z1, z2z2, u1, u2, s1, s2 fieldVal
|
||||
z1z1.SquareVal(z1) // Z1Z1 = Z1^2 (mag: 1)
|
||||
z2z2.SquareVal(z2) // Z2Z2 = Z2^2 (mag: 1)
|
||||
u1.Set(x1).Mul(&z2z2).Normalize() // U1 = X1*Z2Z2 (mag: 1)
|
||||
u2.Set(x2).Mul(&z1z1).Normalize() // U2 = X2*Z1Z1 (mag: 1)
|
||||
s1.Set(y1).Mul(&z2z2).Mul(z2).Normalize() // S1 = Y1*Z2*Z2Z2 (mag: 1)
|
||||
s2.Set(y2).Mul(&z1z1).Mul(z1).Normalize() // S2 = Y2*Z1*Z1Z1 (mag: 1)
|
||||
if u1.Equals(&u2) {
|
||||
if s1.Equals(&s2) {
|
||||
// Since x1 == x2 and y1 == y2, point doubling must be
|
||||
// done, otherwise the addition would end up dividing
|
||||
// by zero.
|
||||
curve.doubleJacobian(x1, y1, z1, x3, y3, z3)
|
||||
return
|
||||
}
|
||||
|
||||
// Since x1 == x2 and y1 == -y2, the sum is the point at
|
||||
// infinity per the group law.
|
||||
x3.SetInt(0)
|
||||
y3.SetInt(0)
|
||||
z3.SetInt(0)
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate X3, Y3, and Z3 according to the intermediate elements
|
||||
// breakdown above.
|
||||
var h, i, j, r, rr, v fieldVal
|
||||
var negU1, negS1, negX3 fieldVal
|
||||
negU1.Set(&u1).Negate(1) // negU1 = -U1 (mag: 2)
|
||||
h.Add2(&u2, &negU1) // H = U2-U1 (mag: 3)
|
||||
i.Set(&h).MulInt(2).Square() // I = (2*H)^2 (mag: 2)
|
||||
j.Mul2(&h, &i) // J = H*I (mag: 1)
|
||||
negS1.Set(&s1).Negate(1) // negS1 = -S1 (mag: 2)
|
||||
r.Set(&s2).Add(&negS1).MulInt(2) // r = 2*(S2-S1) (mag: 6)
|
||||
rr.SquareVal(&r) // rr = r^2 (mag: 1)
|
||||
v.Mul2(&u1, &i) // V = U1*I (mag: 1)
|
||||
x3.Set(&v).MulInt(2).Add(&j).Negate(3) // X3 = -(J+2*V) (mag: 4)
|
||||
x3.Add(&rr) // X3 = r^2+X3 (mag: 5)
|
||||
negX3.Set(x3).Negate(5) // negX3 = -X3 (mag: 6)
|
||||
y3.Mul2(&s1, &j).MulInt(2).Negate(2) // Y3 = -(2*S1*J) (mag: 3)
|
||||
y3.Add(v.Add(&negX3).Mul(&r)) // Y3 = r*(V-X3)+Y3 (mag: 4)
|
||||
z3.Add2(z1, z2).Square() // Z3 = (Z1+Z2)^2 (mag: 1)
|
||||
z3.Add(z1z1.Add(&z2z2).Negate(2)) // Z3 = Z3-(Z1Z1+Z2Z2) (mag: 4)
|
||||
z3.Mul(&h) // Z3 = Z3*H (mag: 1)
|
||||
|
||||
// Normalize the resulting field values to a magnitude of 1 as needed.
|
||||
x3.Normalize()
|
||||
y3.Normalize()
|
||||
}
|
||||
|
||||
// addJacobian adds the passed Jacobian points (x1, y1, z1) and (x2, y2, z2)
|
||||
// together and stores the result in (x3, y3, z3).
|
||||
func (curve *KoblitzCurve) addJacobian(x1, y1, z1, x2, y2, z2, x3, y3, z3 *fieldVal) {
|
||||
// A point at infinity is the identity according to the group law for
|
||||
// elliptic curve cryptography. Thus, ∞ + P = P and P + ∞ = P.
|
||||
if (x1.IsZero() && y1.IsZero()) || z1.IsZero() {
|
||||
x3.Set(x2)
|
||||
y3.Set(y2)
|
||||
z3.Set(z2)
|
||||
return
|
||||
}
|
||||
if (x2.IsZero() && y2.IsZero()) || z2.IsZero() {
|
||||
x3.Set(x1)
|
||||
y3.Set(y1)
|
||||
z3.Set(z1)
|
||||
return
|
||||
}
|
||||
|
||||
// Faster point addition can be achieved when certain assumptions are
|
||||
// met. For example, when both points have the same z value, arithmetic
|
||||
// on the z values can be avoided. This section thus checks for these
|
||||
// conditions and calls an appropriate add function which is accelerated
|
||||
// by using those assumptions.
|
||||
z1.Normalize()
|
||||
z2.Normalize()
|
||||
isZ1One := z1.Equals(fieldOne)
|
||||
isZ2One := z2.Equals(fieldOne)
|
||||
switch {
|
||||
case isZ1One && isZ2One:
|
||||
curve.addZ1AndZ2EqualsOne(x1, y1, z1, x2, y2, x3, y3, z3)
|
||||
return
|
||||
case z1.Equals(z2):
|
||||
curve.addZ1EqualsZ2(x1, y1, z1, x2, y2, x3, y3, z3)
|
||||
return
|
||||
case isZ2One:
|
||||
curve.addZ2EqualsOne(x1, y1, z1, x2, y2, x3, y3, z3)
|
||||
return
|
||||
}
|
||||
|
||||
// None of the above assumptions are true, so fall back to generic
|
||||
// point addition.
|
||||
curve.addGeneric(x1, y1, z1, x2, y2, z2, x3, y3, z3)
|
||||
}
|
||||
|
||||
// Add returns the sum of (x1,y1) and (x2,y2). Part of the elliptic.Curve
|
||||
// interface.
|
||||
func (curve *KoblitzCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) {
|
||||
// A point at infinity is the identity according to the group law for
|
||||
// elliptic curve cryptography. Thus, ∞ + P = P and P + ∞ = P.
|
||||
if x1.Sign() == 0 && y1.Sign() == 0 {
|
||||
return x2, y2
|
||||
}
|
||||
if x2.Sign() == 0 && y2.Sign() == 0 {
|
||||
return x1, y1
|
||||
}
|
||||
|
||||
// Convert the affine coordinates from big integers to field values
|
||||
// and do the point addition in Jacobian projective space.
|
||||
fx1, fy1 := curve.bigAffineToField(x1, y1)
|
||||
fx2, fy2 := curve.bigAffineToField(x2, y2)
|
||||
fx3, fy3, fz3 := new(fieldVal), new(fieldVal), new(fieldVal)
|
||||
fOne := new(fieldVal).SetInt(1)
|
||||
curve.addJacobian(fx1, fy1, fOne, fx2, fy2, fOne, fx3, fy3, fz3)
|
||||
|
||||
// Convert the Jacobian coordinate field values back to affine big
|
||||
// integers.
|
||||
return curve.fieldJacobianToBigAffine(fx3, fy3, fz3)
|
||||
}
|
||||
|
||||
// doubleZ1EqualsOne performs point doubling on the passed Jacobian point
|
||||
// when the point is already known to have a z value of 1 and stores
|
||||
// the result in (x3, y3, z3). That is to say (x3, y3, z3) = 2*(x1, y1, 1). It
|
||||
// performs faster point doubling than the generic routine since less arithmetic
|
||||
// is needed due to the ability to avoid multiplication by the z value.
|
||||
func (curve *KoblitzCurve) doubleZ1EqualsOne(x1, y1, x3, y3, z3 *fieldVal) {
|
||||
// This function uses the assumptions that z1 is 1, thus the point
|
||||
// doubling formulas reduce to:
|
||||
//
|
||||
// X3 = (3*X1^2)^2 - 8*X1*Y1^2
|
||||
// Y3 = (3*X1^2)*(4*X1*Y1^2 - X3) - 8*Y1^4
|
||||
// Z3 = 2*Y1
|
||||
//
|
||||
// To compute the above efficiently, this implementation splits the
|
||||
// equation into intermediate elements which are used to minimize the
|
||||
// number of field multiplications in favor of field squarings which
|
||||
// are roughly 35% faster than field multiplications with the current
|
||||
// implementation at the time this was written.
|
||||
//
|
||||
// This uses a slightly modified version of the method shown at:
|
||||
// http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-mdbl-2007-bl
|
||||
//
|
||||
// In particular it performs the calculations using the following:
|
||||
// A = X1^2, B = Y1^2, C = B^2, D = 2*((X1+B)^2-A-C)
|
||||
// E = 3*A, F = E^2, X3 = F-2*D, Y3 = E*(D-X3)-8*C
|
||||
// Z3 = 2*Y1
|
||||
//
|
||||
// This results in a cost of 1 field multiplication, 5 field squarings,
|
||||
// 6 field additions, and 5 integer multiplications.
|
||||
var a, b, c, d, e, f fieldVal
|
||||
z3.Set(y1).MulInt(2) // Z3 = 2*Y1 (mag: 2)
|
||||
a.SquareVal(x1) // A = X1^2 (mag: 1)
|
||||
b.SquareVal(y1) // B = Y1^2 (mag: 1)
|
||||
c.SquareVal(&b) // C = B^2 (mag: 1)
|
||||
b.Add(x1).Square() // B = (X1+B)^2 (mag: 1)
|
||||
d.Set(&a).Add(&c).Negate(2) // D = -(A+C) (mag: 3)
|
||||
d.Add(&b).MulInt(2) // D = 2*(B+D)(mag: 8)
|
||||
e.Set(&a).MulInt(3) // E = 3*A (mag: 3)
|
||||
f.SquareVal(&e) // F = E^2 (mag: 1)
|
||||
x3.Set(&d).MulInt(2).Negate(16) // X3 = -(2*D) (mag: 17)
|
||||
x3.Add(&f) // X3 = F+X3 (mag: 18)
|
||||
f.Set(x3).Negate(18).Add(&d).Normalize() // F = D-X3 (mag: 1)
|
||||
y3.Set(&c).MulInt(8).Negate(8) // Y3 = -(8*C) (mag: 9)
|
||||
y3.Add(f.Mul(&e)) // Y3 = E*F+Y3 (mag: 10)
|
||||
|
||||
// Normalize the field values back to a magnitude of 1.
|
||||
x3.Normalize()
|
||||
y3.Normalize()
|
||||
z3.Normalize()
|
||||
}
|
||||
|
||||
// doubleGeneric performs point doubling on the passed Jacobian point without
|
||||
// any assumptions about the z value and stores the result in (x3, y3, z3).
|
||||
// That is to say (x3, y3, z3) = 2*(x1, y1, z1). It is the slowest of the point
|
||||
// doubling routines due to requiring the most arithmetic.
|
||||
func (curve *KoblitzCurve) doubleGeneric(x1, y1, z1, x3, y3, z3 *fieldVal) {
|
||||
// Point doubling formula for Jacobian coordinates for the secp256k1
|
||||
// curve:
|
||||
// X3 = (3*X1^2)^2 - 8*X1*Y1^2
|
||||
// Y3 = (3*X1^2)*(4*X1*Y1^2 - X3) - 8*Y1^4
|
||||
// Z3 = 2*Y1*Z1
|
||||
//
|
||||
// To compute the above efficiently, this implementation splits the
|
||||
// equation into intermediate elements which are used to minimize the
|
||||
// number of field multiplications in favor of field squarings which
|
||||
// are roughly 35% faster than field multiplications with the current
|
||||
// implementation at the time this was written.
|
||||
//
|
||||
// This uses a slightly modified version of the method shown at:
|
||||
// http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
|
||||
//
|
||||
// In particular it performs the calculations using the following:
|
||||
// A = X1^2, B = Y1^2, C = B^2, D = 2*((X1+B)^2-A-C)
|
||||
// E = 3*A, F = E^2, X3 = F-2*D, Y3 = E*(D-X3)-8*C
|
||||
// Z3 = 2*Y1*Z1
|
||||
//
|
||||
// This results in a cost of 1 field multiplication, 5 field squarings,
|
||||
// 6 field additions, and 5 integer multiplications.
|
||||
var a, b, c, d, e, f fieldVal
|
||||
z3.Mul2(y1, z1).MulInt(2) // Z3 = 2*Y1*Z1 (mag: 2)
|
||||
a.SquareVal(x1) // A = X1^2 (mag: 1)
|
||||
b.SquareVal(y1) // B = Y1^2 (mag: 1)
|
||||
c.SquareVal(&b) // C = B^2 (mag: 1)
|
||||
b.Add(x1).Square() // B = (X1+B)^2 (mag: 1)
|
||||
d.Set(&a).Add(&c).Negate(2) // D = -(A+C) (mag: 3)
|
||||
d.Add(&b).MulInt(2) // D = 2*(B+D)(mag: 8)
|
||||
e.Set(&a).MulInt(3) // E = 3*A (mag: 3)
|
||||
f.SquareVal(&e) // F = E^2 (mag: 1)
|
||||
x3.Set(&d).MulInt(2).Negate(16) // X3 = -(2*D) (mag: 17)
|
||||
x3.Add(&f) // X3 = F+X3 (mag: 18)
|
||||
f.Set(x3).Negate(18).Add(&d).Normalize() // F = D-X3 (mag: 1)
|
||||
y3.Set(&c).MulInt(8).Negate(8) // Y3 = -(8*C) (mag: 9)
|
||||
y3.Add(f.Mul(&e)) // Y3 = E*F+Y3 (mag: 10)
|
||||
|
||||
// Normalize the field values back to a magnitude of 1.
|
||||
x3.Normalize()
|
||||
y3.Normalize()
|
||||
z3.Normalize()
|
||||
}
|
||||
|
||||
// doubleJacobian doubles the passed Jacobian point (x1, y1, z1) and stores the
|
||||
// result in (x3, y3, z3).
|
||||
func (curve *KoblitzCurve) doubleJacobian(x1, y1, z1, x3, y3, z3 *fieldVal) {
|
||||
// Doubling a point at infinity is still infinity.
|
||||
if y1.IsZero() || z1.IsZero() {
|
||||
x3.SetInt(0)
|
||||
y3.SetInt(0)
|
||||
z3.SetInt(0)
|
||||
return
|
||||
}
|
||||
|
||||
// Slightly faster point doubling can be achieved when the z value is 1
|
||||
// by avoiding the multiplication on the z value. This section calls
|
||||
// a point doubling function which is accelerated by using that
|
||||
// assumption when possible.
|
||||
if z1.Normalize().Equals(fieldOne) {
|
||||
curve.doubleZ1EqualsOne(x1, y1, x3, y3, z3)
|
||||
return
|
||||
}
|
||||
|
||||
// Fall back to generic point doubling which works with arbitrary z
|
||||
// values.
|
||||
curve.doubleGeneric(x1, y1, z1, x3, y3, z3)
|
||||
}
|
||||
|
||||
// Double returns 2*(x1,y1). Part of the elliptic.Curve interface.
|
||||
func (curve *KoblitzCurve) Double(x1, y1 *big.Int) (*big.Int, *big.Int) {
|
||||
if y1.Sign() == 0 {
|
||||
return new(big.Int), new(big.Int)
|
||||
}
|
||||
|
||||
// Convert the affine coordinates from big integers to field values
|
||||
// and do the point doubling in Jacobian projective space.
|
||||
fx1, fy1 := curve.bigAffineToField(x1, y1)
|
||||
fx3, fy3, fz3 := new(fieldVal), new(fieldVal), new(fieldVal)
|
||||
fOne := new(fieldVal).SetInt(1)
|
||||
curve.doubleJacobian(fx1, fy1, fOne, fx3, fy3, fz3)
|
||||
|
||||
// Convert the Jacobian coordinate field values back to affine big
|
||||
// integers.
|
||||
return curve.fieldJacobianToBigAffine(fx3, fy3, fz3)
|
||||
}
|
||||
|
||||
// splitK returns a balanced length-two representation of k and their signs.
|
||||
// This is algorithm 3.74 from [GECC].
|
||||
//
|
||||
// One thing of note about this algorithm is that no matter what c1 and c2 are,
|
||||
// the final equation of k = k1 + k2 * lambda (mod n) will hold. This is
|
||||
// provable mathematically due to how a1/b1/a2/b2 are computed.
|
||||
//
|
||||
// c1 and c2 are chosen to minimize the max(k1,k2).
|
||||
func (curve *KoblitzCurve) splitK(k []byte) ([]byte, []byte, int, int) {
|
||||
// All math here is done with big.Int, which is slow.
|
||||
// At some point, it might be useful to write something similar to
|
||||
// fieldVal but for N instead of P as the prime field if this ends up
|
||||
// being a bottleneck.
|
||||
bigIntK := new(big.Int)
|
||||
c1, c2 := new(big.Int), new(big.Int)
|
||||
tmp1, tmp2 := new(big.Int), new(big.Int)
|
||||
k1, k2 := new(big.Int), new(big.Int)
|
||||
|
||||
bigIntK.SetBytes(k)
|
||||
// c1 = round(b2 * k / n) from step 4.
|
||||
// Rounding isn't really necessary and costs too much, hence skipped
|
||||
c1.Mul(curve.b2, bigIntK)
|
||||
c1.Div(c1, curve.N)
|
||||
// c2 = round(b1 * k / n) from step 4 (sign reversed to optimize one step)
|
||||
// Rounding isn't really necessary and costs too much, hence skipped
|
||||
c2.Mul(curve.b1, bigIntK)
|
||||
c2.Div(c2, curve.N)
|
||||
// k1 = k - c1 * a1 - c2 * a2 from step 5 (note c2's sign is reversed)
|
||||
tmp1.Mul(c1, curve.a1)
|
||||
tmp2.Mul(c2, curve.a2)
|
||||
k1.Sub(bigIntK, tmp1)
|
||||
k1.Add(k1, tmp2)
|
||||
// k2 = - c1 * b1 - c2 * b2 from step 5 (note c2's sign is reversed)
|
||||
tmp1.Mul(c1, curve.b1)
|
||||
tmp2.Mul(c2, curve.b2)
|
||||
k2.Sub(tmp2, tmp1)
|
||||
|
||||
// Note Bytes() throws out the sign of k1 and k2. This matters
|
||||
// since k1 and/or k2 can be negative. Hence, we pass that
|
||||
// back separately.
|
||||
return k1.Bytes(), k2.Bytes(), k1.Sign(), k2.Sign()
|
||||
}
|
||||
|
||||
// moduloReduce reduces k from more than 32 bytes to 32 bytes and under. This
|
||||
// is done by doing a simple modulo curve.N. We can do this since G^N = 1 and
|
||||
// thus any other valid point on the elliptic curve has the same order.
|
||||
func (curve *KoblitzCurve) moduloReduce(k []byte) []byte {
|
||||
// Since the order of G is curve.N, we can use a much smaller number
|
||||
// by doing modulo curve.N
|
||||
if len(k) > curve.byteSize {
|
||||
// Reduce k by performing modulo curve.N.
|
||||
tmpK := new(big.Int).SetBytes(k)
|
||||
tmpK.Mod(tmpK, curve.N)
|
||||
return tmpK.Bytes()
|
||||
}
|
||||
|
||||
return k
|
||||
}
|
||||
|
||||
// NAF takes a positive integer k and returns the Non-Adjacent Form (NAF) as two
|
||||
// byte slices. The first is where 1s will be. The second is where -1s will
|
||||
// be. NAF is convenient in that on average, only 1/3rd of its values are
|
||||
// non-zero. This is algorithm 3.30 from [GECC].
|
||||
//
|
||||
// Essentially, this makes it possible to minimize the number of operations
|
||||
// since the resulting ints returned will be at least 50% 0s.
|
||||
func NAF(k []byte) ([]byte, []byte) {
|
||||
// The essence of this algorithm is that whenever we have consecutive 1s
|
||||
// in the binary, we want to put a -1 in the lowest bit and get a bunch
|
||||
// of 0s up to the highest bit of consecutive 1s. This is due to this
|
||||
// identity:
|
||||
// 2^n + 2^(n-1) + 2^(n-2) + ... + 2^(n-k) = 2^(n+1) - 2^(n-k)
|
||||
//
|
||||
// The algorithm thus may need to go 1 more bit than the length of the
|
||||
// bits we actually have, hence bits being 1 bit longer than was
|
||||
// necessary. Since we need to know whether adding will cause a carry,
|
||||
// we go from right-to-left in this addition.
|
||||
var carry, curIsOne, nextIsOne bool
|
||||
// these default to zero
|
||||
retPos := make([]byte, len(k)+1)
|
||||
retNeg := make([]byte, len(k)+1)
|
||||
for i := len(k) - 1; i >= 0; i-- {
|
||||
curByte := k[i]
|
||||
for j := uint(0); j < 8; j++ {
|
||||
curIsOne = curByte&1 == 1
|
||||
if j == 7 {
|
||||
if i == 0 {
|
||||
nextIsOne = false
|
||||
} else {
|
||||
nextIsOne = k[i-1]&1 == 1
|
||||
}
|
||||
} else {
|
||||
nextIsOne = curByte&2 == 2
|
||||
}
|
||||
if carry {
|
||||
if curIsOne {
|
||||
// This bit is 1, so continue to carry
|
||||
// and don't need to do anything.
|
||||
} else {
|
||||
// We've hit a 0 after some number of
|
||||
// 1s.
|
||||
if nextIsOne {
|
||||
// Start carrying again since
|
||||
// a new sequence of 1s is
|
||||
// starting.
|
||||
retNeg[i+1] += 1 << j
|
||||
} else {
|
||||
// Stop carrying since 1s have
|
||||
// stopped.
|
||||
carry = false
|
||||
retPos[i+1] += 1 << j
|
||||
}
|
||||
}
|
||||
} else if curIsOne {
|
||||
if nextIsOne {
|
||||
// If this is the start of at least 2
|
||||
// consecutive 1s, set the current one
|
||||
// to -1 and start carrying.
|
||||
retNeg[i+1] += 1 << j
|
||||
carry = true
|
||||
} else {
|
||||
// This is a singleton, not consecutive
|
||||
// 1s.
|
||||
retPos[i+1] += 1 << j
|
||||
}
|
||||
}
|
||||
curByte >>= 1
|
||||
}
|
||||
}
|
||||
if carry {
|
||||
retPos[0] = 1
|
||||
return retPos, retNeg
|
||||
}
|
||||
return retPos[1:], retNeg[1:]
|
||||
}
|
||||
|
||||
// ScalarMult returns k*(Bx, By) where k is a big endian integer.
|
||||
// Part of the elliptic.Curve interface.
|
||||
func (curve *KoblitzCurve) ScalarMult(Bx, By *big.Int, k []byte) (*big.Int, *big.Int) {
|
||||
// Point Q = ∞ (point at infinity).
|
||||
qx, qy, qz := new(fieldVal), new(fieldVal), new(fieldVal)
|
||||
|
||||
// Decompose K into k1 and k2 in order to halve the number of EC ops.
|
||||
// See Algorithm 3.74 in [GECC].
|
||||
k1, k2, signK1, signK2 := curve.splitK(curve.moduloReduce(k))
|
||||
|
||||
// The main equation here to remember is:
|
||||
// k * P = k1 * P + k2 * ϕ(P)
|
||||
//
|
||||
// P1 below is P in the equation, P2 below is ϕ(P) in the equation
|
||||
p1x, p1y := curve.bigAffineToField(Bx, By)
|
||||
p1yNeg := new(fieldVal).NegateVal(p1y, 1)
|
||||
p1z := new(fieldVal).SetInt(1)
|
||||
|
||||
// NOTE: ϕ(x,y) = (βx,y). The Jacobian z coordinate is 1, so this math
|
||||
// goes through.
|
||||
p2x := new(fieldVal).Mul2(p1x, curve.beta)
|
||||
p2y := new(fieldVal).Set(p1y)
|
||||
p2yNeg := new(fieldVal).NegateVal(p2y, 1)
|
||||
p2z := new(fieldVal).SetInt(1)
|
||||
|
||||
// Flip the positive and negative values of the points as needed
|
||||
// depending on the signs of k1 and k2. As mentioned in the equation
|
||||
// above, each of k1 and k2 are multiplied by the respective point.
|
||||
// Since -k * P is the same thing as k * -P, and the group law for
|
||||
// elliptic curves states that P(x, y) = -P(x, -y), it's faster and
|
||||
// simplifies the code to just make the point negative.
|
||||
if signK1 == -1 {
|
||||
p1y, p1yNeg = p1yNeg, p1y
|
||||
}
|
||||
if signK2 == -1 {
|
||||
p2y, p2yNeg = p2yNeg, p2y
|
||||
}
|
||||
|
||||
// NAF versions of k1 and k2 should have a lot more zeros.
|
||||
//
|
||||
// The Pos version of the bytes contain the +1s and the Neg versions
|
||||
// contain the -1s.
|
||||
k1PosNAF, k1NegNAF := NAF(k1)
|
||||
k2PosNAF, k2NegNAF := NAF(k2)
|
||||
k1Len := len(k1PosNAF)
|
||||
k2Len := len(k2PosNAF)
|
||||
|
||||
m := k1Len
|
||||
if m < k2Len {
|
||||
m = k2Len
|
||||
}
|
||||
|
||||
// Add left-to-right using the NAF optimization. See algorithm 3.77
|
||||
// from [GECC]. This should be faster overall since there will be a lot
|
||||
// more instances of 0, hence reducing the number of Jacobian additions
|
||||
// at the cost of 1 possible extra doubling.
|
||||
var k1BytePos, k1ByteNeg, k2BytePos, k2ByteNeg byte
|
||||
for i := 0; i < m; i++ {
|
||||
// Since we're going left-to-right, pad the front with 0s.
|
||||
if i < m-k1Len {
|
||||
k1BytePos = 0
|
||||
k1ByteNeg = 0
|
||||
} else {
|
||||
k1BytePos = k1PosNAF[i-(m-k1Len)]
|
||||
k1ByteNeg = k1NegNAF[i-(m-k1Len)]
|
||||
}
|
||||
if i < m-k2Len {
|
||||
k2BytePos = 0
|
||||
k2ByteNeg = 0
|
||||
} else {
|
||||
k2BytePos = k2PosNAF[i-(m-k2Len)]
|
||||
k2ByteNeg = k2NegNAF[i-(m-k2Len)]
|
||||
}
|
||||
|
||||
for j := 7; j >= 0; j-- {
|
||||
// Q = 2 * Q
|
||||
curve.doubleJacobian(qx, qy, qz, qx, qy, qz)
|
||||
|
||||
if k1BytePos&0x80 == 0x80 {
|
||||
curve.addJacobian(qx, qy, qz, p1x, p1y, p1z,
|
||||
qx, qy, qz)
|
||||
} else if k1ByteNeg&0x80 == 0x80 {
|
||||
curve.addJacobian(qx, qy, qz, p1x, p1yNeg, p1z,
|
||||
qx, qy, qz)
|
||||
}
|
||||
|
||||
if k2BytePos&0x80 == 0x80 {
|
||||
curve.addJacobian(qx, qy, qz, p2x, p2y, p2z,
|
||||
qx, qy, qz)
|
||||
} else if k2ByteNeg&0x80 == 0x80 {
|
||||
curve.addJacobian(qx, qy, qz, p2x, p2yNeg, p2z,
|
||||
qx, qy, qz)
|
||||
}
|
||||
k1BytePos <<= 1
|
||||
k1ByteNeg <<= 1
|
||||
k2BytePos <<= 1
|
||||
k2ByteNeg <<= 1
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the Jacobian coordinate field values back to affine big.Ints.
|
||||
return curve.fieldJacobianToBigAffine(qx, qy, qz)
|
||||
}
|
||||
|
||||
// ScalarBaseMult returns k*G where G is the base point of the group and k is a
|
||||
// big endian integer.
|
||||
// Part of the elliptic.Curve interface.
|
||||
func (curve *KoblitzCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) {
|
||||
newK := curve.moduloReduce(k)
|
||||
diff := len(curve.bytePoints) - len(newK)
|
||||
|
||||
// Point Q = ∞ (point at infinity).
|
||||
qx, qy, qz := new(fieldVal), new(fieldVal), new(fieldVal)
|
||||
|
||||
// curve.bytePoints has all 256 byte points for each 8-bit window. The
|
||||
// strategy is to add up the byte points. This is best understood by
|
||||
// expressing k in base-256 which it already sort of is.
|
||||
// Each "digit" in the 8-bit window can be looked up using bytePoints
|
||||
// and added together.
|
||||
for i, byteVal := range newK {
|
||||
p := curve.bytePoints[diff+i][byteVal]
|
||||
curve.addJacobian(qx, qy, qz, &p[0], &p[1], &p[2], qx, qy, qz)
|
||||
}
|
||||
return curve.fieldJacobianToBigAffine(qx, qy, qz)
|
||||
}
|
||||
|
||||
// QPlus1Div4 returns the (P+1)/4 constant for the curve for use in calculating
|
||||
// square roots via exponentiation.
|
||||
//
|
||||
// DEPRECATED: The actual value returned is (P+1)/4, where as the original
|
||||
// method name implies that this value is (((P+1)/4)+1)/4. This method is kept
|
||||
// to maintain backwards compatibility of the API. Use Q() instead.
|
||||
func (curve *KoblitzCurve) QPlus1Div4() *big.Int {
|
||||
return curve.q
|
||||
}
|
||||
|
||||
// Q returns the (P+1)/4 constant for the curve for use in calculating square
|
||||
// roots via exponentiation.
|
||||
func (curve *KoblitzCurve) Q() *big.Int {
|
||||
return curve.q
|
||||
}
|
||||
|
||||
var initonce sync.Once
|
||||
var secp256k1 KoblitzCurve
|
||||
|
||||
func initAll() {
|
||||
initS256()
|
||||
}
|
||||
|
||||
// fromHex converts the passed hex string into a big integer pointer and will
|
||||
// panic is there is an error. This is only provided for the hard-coded
|
||||
// constants so errors in the source code can bet detected. It will only (and
|
||||
// must only) be called for initialization purposes.
|
||||
func fromHex(s string) *big.Int {
|
||||
r, ok := new(big.Int).SetString(s, 16)
|
||||
if !ok {
|
||||
panic("invalid hex in source file: " + s)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func initS256() {
|
||||
// Curve parameters taken from [SECG] section 2.4.1.
|
||||
secp256k1.CurveParams = new(elliptic.CurveParams)
|
||||
secp256k1.P = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F")
|
||||
secp256k1.N = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141")
|
||||
secp256k1.B = fromHex("0000000000000000000000000000000000000000000000000000000000000007")
|
||||
secp256k1.Gx = fromHex("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798")
|
||||
secp256k1.Gy = fromHex("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8")
|
||||
secp256k1.BitSize = 256
|
||||
// Curve name taken from https://safecurves.cr.yp.to/.
|
||||
secp256k1.Name = "secp256k1"
|
||||
secp256k1.q = new(big.Int).Div(new(big.Int).Add(secp256k1.P,
|
||||
big.NewInt(1)), big.NewInt(4))
|
||||
secp256k1.H = 1
|
||||
secp256k1.halfOrder = new(big.Int).Rsh(secp256k1.N, 1)
|
||||
secp256k1.fieldB = new(fieldVal).SetByteSlice(secp256k1.B.Bytes())
|
||||
|
||||
// Provided for convenience since this gets computed repeatedly.
|
||||
secp256k1.byteSize = secp256k1.BitSize / 8
|
||||
|
||||
// Deserialize and set the pre-computed table used to accelerate scalar
|
||||
// base multiplication. This is hard-coded data, so any errors are
|
||||
// panics because it means something is wrong in the source code.
|
||||
if err := loadS256BytePoints(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Next 6 constants are from Hal Finney's bitcointalk.org post:
|
||||
// https://bitcointalk.org/index.php?topic=3238.msg45565#msg45565
|
||||
// May he rest in peace.
|
||||
//
|
||||
// They have also been independently derived from the code in the
|
||||
// EndomorphismVectors function in gensecp256k1.go.
|
||||
secp256k1.lambda = fromHex("5363AD4CC05C30E0A5261C028812645A122E22EA20816678DF02967C1B23BD72")
|
||||
secp256k1.beta = new(fieldVal).SetHex("7AE96A2B657C07106E64479EAC3434E99CF0497512F58995C1396C28719501EE")
|
||||
secp256k1.a1 = fromHex("3086D221A7D46BCDE86C90E49284EB15")
|
||||
secp256k1.b1 = fromHex("-E4437ED6010E88286F547FA90ABFE4C3")
|
||||
secp256k1.a2 = fromHex("114CA50F7A8E2F3F657C1108D9D44CFD8")
|
||||
secp256k1.b2 = fromHex("3086D221A7D46BCDE86C90E49284EB15")
|
||||
|
||||
// Alternatively, we can use the parameters below, however, they seem
|
||||
// to be about 8% slower.
|
||||
// secp256k1.lambda = fromHex("AC9C52B33FA3CF1F5AD9E3FD77ED9BA4A880B9FC8EC739C2E0CFC810B51283CE")
|
||||
// secp256k1.beta = new(fieldVal).SetHex("851695D49A83F8EF919BB86153CBCB16630FB68AED0A766A3EC693D68E6AFA40")
|
||||
// secp256k1.a1 = fromHex("E4437ED6010E88286F547FA90ABFE4C3")
|
||||
// secp256k1.b1 = fromHex("-3086D221A7D46BCDE86C90E49284EB15")
|
||||
// secp256k1.a2 = fromHex("3086D221A7D46BCDE86C90E49284EB15")
|
||||
// secp256k1.b2 = fromHex("114CA50F7A8E2F3F657C1108D9D44CFD8")
|
||||
}
|
||||
|
||||
// S256 returns a Curve which implements secp256k1.
|
||||
func S256() *KoblitzCurve {
|
||||
initonce.Do(initAll)
|
||||
return &secp256k1
|
||||
}
|
||||
216
vendor/github.com/PeernetOfficial/core/btcec/ciphering.go
generated
vendored
Normal file
216
vendor/github.com/PeernetOfficial/core/btcec/ciphering.go
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidMAC occurs when Message Authentication Check (MAC) fails
|
||||
// during decryption. This happens because of either invalid private key or
|
||||
// corrupt ciphertext.
|
||||
ErrInvalidMAC = errors.New("invalid mac hash")
|
||||
|
||||
// errInputTooShort occurs when the input ciphertext to the Decrypt
|
||||
// function is less than 134 bytes long.
|
||||
errInputTooShort = errors.New("ciphertext too short")
|
||||
|
||||
// errUnsupportedCurve occurs when the first two bytes of the encrypted
|
||||
// text aren't 0x02CA (= 712 = secp256k1, from OpenSSL).
|
||||
errUnsupportedCurve = errors.New("unsupported curve")
|
||||
|
||||
errInvalidXLength = errors.New("invalid X length, must be 32")
|
||||
errInvalidYLength = errors.New("invalid Y length, must be 32")
|
||||
errInvalidPadding = errors.New("invalid PKCS#7 padding")
|
||||
|
||||
// 0x02CA = 714
|
||||
ciphCurveBytes = [2]byte{0x02, 0xCA}
|
||||
// 0x20 = 32
|
||||
ciphCoordLength = [2]byte{0x00, 0x20}
|
||||
)
|
||||
|
||||
// GenerateSharedSecret generates a shared secret based on a private key and a
|
||||
// public key using Diffie-Hellman key exchange (ECDH) (RFC 4753).
|
||||
// RFC5903 Section 9 states we should only return x.
|
||||
func GenerateSharedSecret(privkey *PrivateKey, pubkey *PublicKey) []byte {
|
||||
x, _ := pubkey.Curve.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes())
|
||||
return x.Bytes()
|
||||
}
|
||||
|
||||
// Encrypt encrypts data for the target public key using AES-256-CBC. It also
|
||||
// generates a private key (the pubkey of which is also in the output). The only
|
||||
// supported curve is secp256k1. The `structure' that it encodes everything into
|
||||
// is:
|
||||
//
|
||||
// struct {
|
||||
// // Initialization Vector used for AES-256-CBC
|
||||
// IV [16]byte
|
||||
// // Public Key: curve(2) + len_of_pubkeyX(2) + pubkeyX +
|
||||
// // len_of_pubkeyY(2) + pubkeyY (curve = 714)
|
||||
// PublicKey [70]byte
|
||||
// // Cipher text
|
||||
// Data []byte
|
||||
// // HMAC-SHA-256 Message Authentication Code
|
||||
// HMAC [32]byte
|
||||
// }
|
||||
//
|
||||
// The primary aim is to ensure byte compatibility with Pyelliptic. Also, refer
|
||||
// to section 5.8.1 of ANSI X9.63 for rationale on this format.
|
||||
func Encrypt(pubkey *PublicKey, in []byte) ([]byte, error) {
|
||||
ephemeral, err := NewPrivateKey(S256())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ecdhKey := GenerateSharedSecret(ephemeral, pubkey)
|
||||
derivedKey := sha512.Sum512(ecdhKey)
|
||||
keyE := derivedKey[:32]
|
||||
keyM := derivedKey[32:]
|
||||
|
||||
paddedIn := addPKCSPadding(in)
|
||||
// IV + Curve params/X/Y + padded plaintext/ciphertext + HMAC-256
|
||||
out := make([]byte, aes.BlockSize+70+len(paddedIn)+sha256.Size)
|
||||
iv := out[:aes.BlockSize]
|
||||
if _, err = io.ReadFull(rand.Reader, iv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// start writing public key
|
||||
pb := ephemeral.PubKey().SerializeUncompressed()
|
||||
offset := aes.BlockSize
|
||||
|
||||
// curve and X length
|
||||
copy(out[offset:offset+4], append(ciphCurveBytes[:], ciphCoordLength[:]...))
|
||||
offset += 4
|
||||
// X
|
||||
copy(out[offset:offset+32], pb[1:33])
|
||||
offset += 32
|
||||
// Y length
|
||||
copy(out[offset:offset+2], ciphCoordLength[:])
|
||||
offset += 2
|
||||
// Y
|
||||
copy(out[offset:offset+32], pb[33:])
|
||||
offset += 32
|
||||
|
||||
// start encryption
|
||||
block, err := aes.NewCipher(keyE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
mode.CryptBlocks(out[offset:len(out)-sha256.Size], paddedIn)
|
||||
|
||||
// start HMAC-SHA-256
|
||||
hm := hmac.New(sha256.New, keyM)
|
||||
hm.Write(out[:len(out)-sha256.Size]) // everything is hashed
|
||||
copy(out[len(out)-sha256.Size:], hm.Sum(nil)) // write checksum
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Decrypt decrypts data that was encrypted using the Encrypt function.
|
||||
func Decrypt(priv *PrivateKey, in []byte) ([]byte, error) {
|
||||
// IV + Curve params/X/Y + 1 block + HMAC-256
|
||||
if len(in) < aes.BlockSize+70+aes.BlockSize+sha256.Size {
|
||||
return nil, errInputTooShort
|
||||
}
|
||||
|
||||
// read iv
|
||||
iv := in[:aes.BlockSize]
|
||||
offset := aes.BlockSize
|
||||
|
||||
// start reading pubkey
|
||||
if !bytes.Equal(in[offset:offset+2], ciphCurveBytes[:]) {
|
||||
return nil, errUnsupportedCurve
|
||||
}
|
||||
offset += 2
|
||||
|
||||
if !bytes.Equal(in[offset:offset+2], ciphCoordLength[:]) {
|
||||
return nil, errInvalidXLength
|
||||
}
|
||||
offset += 2
|
||||
|
||||
xBytes := in[offset : offset+32]
|
||||
offset += 32
|
||||
|
||||
if !bytes.Equal(in[offset:offset+2], ciphCoordLength[:]) {
|
||||
return nil, errInvalidYLength
|
||||
}
|
||||
offset += 2
|
||||
|
||||
yBytes := in[offset : offset+32]
|
||||
offset += 32
|
||||
|
||||
pb := make([]byte, 65)
|
||||
pb[0] = byte(0x04) // uncompressed
|
||||
copy(pb[1:33], xBytes)
|
||||
copy(pb[33:], yBytes)
|
||||
// check if (X, Y) lies on the curve and create a Pubkey if it does
|
||||
pubkey, err := ParsePubKey(pb, S256())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check for cipher text length
|
||||
if (len(in)-aes.BlockSize-offset-sha256.Size)%aes.BlockSize != 0 {
|
||||
return nil, errInvalidPadding // not padded to 16 bytes
|
||||
}
|
||||
|
||||
// read hmac
|
||||
messageMAC := in[len(in)-sha256.Size:]
|
||||
|
||||
// generate shared secret
|
||||
ecdhKey := GenerateSharedSecret(priv, pubkey)
|
||||
derivedKey := sha512.Sum512(ecdhKey)
|
||||
keyE := derivedKey[:32]
|
||||
keyM := derivedKey[32:]
|
||||
|
||||
// verify mac
|
||||
hm := hmac.New(sha256.New, keyM)
|
||||
hm.Write(in[:len(in)-sha256.Size]) // everything is hashed
|
||||
expectedMAC := hm.Sum(nil)
|
||||
if !hmac.Equal(messageMAC, expectedMAC) {
|
||||
return nil, ErrInvalidMAC
|
||||
}
|
||||
|
||||
// start decryption
|
||||
block, err := aes.NewCipher(keyE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mode := cipher.NewCBCDecrypter(block, iv)
|
||||
// same length as ciphertext
|
||||
plaintext := make([]byte, len(in)-offset-sha256.Size)
|
||||
mode.CryptBlocks(plaintext, in[offset:len(in)-sha256.Size])
|
||||
|
||||
return removePKCSPadding(plaintext)
|
||||
}
|
||||
|
||||
// Implement PKCS#7 padding with block size of 16 (AES block size).
|
||||
|
||||
// addPKCSPadding adds padding to a block of data
|
||||
func addPKCSPadding(src []byte) []byte {
|
||||
padding := aes.BlockSize - len(src)%aes.BlockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(src, padtext...)
|
||||
}
|
||||
|
||||
// removePKCSPadding removes padding from data that was added with addPKCSPadding
|
||||
func removePKCSPadding(src []byte) ([]byte, error) {
|
||||
length := len(src)
|
||||
padLength := int(src[length-1])
|
||||
if padLength > aes.BlockSize || length < aes.BlockSize {
|
||||
return nil, errInvalidPadding
|
||||
}
|
||||
|
||||
return src[:length-padLength], nil
|
||||
}
|
||||
21
vendor/github.com/PeernetOfficial/core/btcec/doc.go
generated
vendored
Normal file
21
vendor/github.com/PeernetOfficial/core/btcec/doc.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2013-2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package btcec implements support for the elliptic curves needed for bitcoin.
|
||||
|
||||
Bitcoin uses elliptic curve cryptography using koblitz curves
|
||||
(specifically secp256k1) for cryptographic functions. See
|
||||
http://www.secg.org/collateral/sec2_final.pdf for details on the
|
||||
standard.
|
||||
|
||||
This package provides the data structures and functions implementing the
|
||||
crypto/elliptic Curve interface in order to permit using these curves
|
||||
with the standard crypto/ecdsa package provided with go. Helper
|
||||
functionality is provided to parse signatures and public keys from
|
||||
standard formats. It was designed for use with btcd, but should be
|
||||
general enough for other uses of elliptic curve crypto. It was originally based
|
||||
on some initial work by ThePiachu, but has significantly diverged since then.
|
||||
*/
|
||||
package btcec
|
||||
1356
vendor/github.com/PeernetOfficial/core/btcec/field.go
generated
vendored
Normal file
1356
vendor/github.com/PeernetOfficial/core/btcec/field.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
203
vendor/github.com/PeernetOfficial/core/btcec/gensecp256k1.go
generated
vendored
Normal file
203
vendor/github.com/PeernetOfficial/core/btcec/gensecp256k1.go
generated
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
// Copyright (c) 2014-2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file is ignored during the regular build due to the following build tag.
|
||||
// This build tag is set during go generate.
|
||||
// +build gensecp256k1
|
||||
|
||||
package btcec
|
||||
|
||||
// References:
|
||||
// [GECC]: Guide to Elliptic Curve Cryptography (Hankerson, Menezes, Vanstone)
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// secp256k1BytePoints are dummy points used so the code which generates the
|
||||
// real values can compile.
|
||||
var secp256k1BytePoints = ""
|
||||
|
||||
// getDoublingPoints returns all the possible G^(2^i) for i in
|
||||
// 0..n-1 where n is the curve's bit size (256 in the case of secp256k1)
|
||||
// the coordinates are recorded as Jacobian coordinates.
|
||||
func (curve *KoblitzCurve) getDoublingPoints() [][3]fieldVal {
|
||||
doublingPoints := make([][3]fieldVal, curve.BitSize)
|
||||
|
||||
// initialize px, py, pz to the Jacobian coordinates for the base point
|
||||
px, py := curve.bigAffineToField(curve.Gx, curve.Gy)
|
||||
pz := new(fieldVal).SetInt(1)
|
||||
for i := 0; i < curve.BitSize; i++ {
|
||||
doublingPoints[i] = [3]fieldVal{*px, *py, *pz}
|
||||
// P = 2*P
|
||||
curve.doubleJacobian(px, py, pz, px, py, pz)
|
||||
}
|
||||
return doublingPoints
|
||||
}
|
||||
|
||||
// SerializedBytePoints returns a serialized byte slice which contains all of
|
||||
// the possible points per 8-bit window. This is used to when generating
|
||||
// secp256k1.go.
|
||||
func (curve *KoblitzCurve) SerializedBytePoints() []byte {
|
||||
doublingPoints := curve.getDoublingPoints()
|
||||
|
||||
// Segregate the bits into byte-sized windows
|
||||
serialized := make([]byte, curve.byteSize*256*3*10*4)
|
||||
offset := 0
|
||||
for byteNum := 0; byteNum < curve.byteSize; byteNum++ {
|
||||
// Grab the 8 bits that make up this byte from doublingPoints.
|
||||
startingBit := 8 * (curve.byteSize - byteNum - 1)
|
||||
computingPoints := doublingPoints[startingBit : startingBit+8]
|
||||
|
||||
// Compute all points in this window and serialize them.
|
||||
for i := 0; i < 256; i++ {
|
||||
px, py, pz := new(fieldVal), new(fieldVal), new(fieldVal)
|
||||
for j := 0; j < 8; j++ {
|
||||
if i>>uint(j)&1 == 1 {
|
||||
curve.addJacobian(px, py, pz, &computingPoints[j][0],
|
||||
&computingPoints[j][1], &computingPoints[j][2], px, py, pz)
|
||||
}
|
||||
}
|
||||
for i := 0; i < 10; i++ {
|
||||
binary.LittleEndian.PutUint32(serialized[offset:], px.n[i])
|
||||
offset += 4
|
||||
}
|
||||
for i := 0; i < 10; i++ {
|
||||
binary.LittleEndian.PutUint32(serialized[offset:], py.n[i])
|
||||
offset += 4
|
||||
}
|
||||
for i := 0; i < 10; i++ {
|
||||
binary.LittleEndian.PutUint32(serialized[offset:], pz.n[i])
|
||||
offset += 4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return serialized
|
||||
}
|
||||
|
||||
// sqrt returns the square root of the provided big integer using Newton's
|
||||
// method. It's only compiled and used during generation of pre-computed
|
||||
// values, so speed is not a huge concern.
|
||||
func sqrt(n *big.Int) *big.Int {
|
||||
// Initial guess = 2^(log_2(n)/2)
|
||||
guess := big.NewInt(2)
|
||||
guess.Exp(guess, big.NewInt(int64(n.BitLen()/2)), nil)
|
||||
|
||||
// Now refine using Newton's method.
|
||||
big2 := big.NewInt(2)
|
||||
prevGuess := big.NewInt(0)
|
||||
for {
|
||||
prevGuess.Set(guess)
|
||||
guess.Add(guess, new(big.Int).Div(n, guess))
|
||||
guess.Div(guess, big2)
|
||||
if guess.Cmp(prevGuess) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return guess
|
||||
}
|
||||
|
||||
// EndomorphismVectors runs the first 3 steps of algorithm 3.74 from [GECC] to
|
||||
// generate the linearly independent vectors needed to generate a balanced
|
||||
// length-two representation of a multiplier such that k = k1 + k2λ (mod N) and
|
||||
// returns them. Since the values will always be the same given the fact that N
|
||||
// and λ are fixed, the final results can be accelerated by storing the
|
||||
// precomputed values with the curve.
|
||||
func (curve *KoblitzCurve) EndomorphismVectors() (a1, b1, a2, b2 *big.Int) {
|
||||
bigMinus1 := big.NewInt(-1)
|
||||
|
||||
// This section uses an extended Euclidean algorithm to generate a
|
||||
// sequence of equations:
|
||||
// s[i] * N + t[i] * λ = r[i]
|
||||
|
||||
nSqrt := sqrt(curve.N)
|
||||
u, v := new(big.Int).Set(curve.N), new(big.Int).Set(curve.lambda)
|
||||
x1, y1 := big.NewInt(1), big.NewInt(0)
|
||||
x2, y2 := big.NewInt(0), big.NewInt(1)
|
||||
q, r := new(big.Int), new(big.Int)
|
||||
qu, qx1, qy1 := new(big.Int), new(big.Int), new(big.Int)
|
||||
s, t := new(big.Int), new(big.Int)
|
||||
ri, ti := new(big.Int), new(big.Int)
|
||||
a1, b1, a2, b2 = new(big.Int), new(big.Int), new(big.Int), new(big.Int)
|
||||
found, oneMore := false, false
|
||||
for u.Sign() != 0 {
|
||||
// q = v/u
|
||||
q.Div(v, u)
|
||||
|
||||
// r = v - q*u
|
||||
qu.Mul(q, u)
|
||||
r.Sub(v, qu)
|
||||
|
||||
// s = x2 - q*x1
|
||||
qx1.Mul(q, x1)
|
||||
s.Sub(x2, qx1)
|
||||
|
||||
// t = y2 - q*y1
|
||||
qy1.Mul(q, y1)
|
||||
t.Sub(y2, qy1)
|
||||
|
||||
// v = u, u = r, x2 = x1, x1 = s, y2 = y1, y1 = t
|
||||
v.Set(u)
|
||||
u.Set(r)
|
||||
x2.Set(x1)
|
||||
x1.Set(s)
|
||||
y2.Set(y1)
|
||||
y1.Set(t)
|
||||
|
||||
// As soon as the remainder is less than the sqrt of n, the
|
||||
// values of a1 and b1 are known.
|
||||
if !found && r.Cmp(nSqrt) < 0 {
|
||||
// When this condition executes ri and ti represent the
|
||||
// r[i] and t[i] values such that i is the greatest
|
||||
// index for which r >= sqrt(n). Meanwhile, the current
|
||||
// r and t values are r[i+1] and t[i+1], respectively.
|
||||
|
||||
// a1 = r[i+1], b1 = -t[i+1]
|
||||
a1.Set(r)
|
||||
b1.Mul(t, bigMinus1)
|
||||
found = true
|
||||
oneMore = true
|
||||
|
||||
// Skip to the next iteration so ri and ti are not
|
||||
// modified.
|
||||
continue
|
||||
|
||||
} else if oneMore {
|
||||
// When this condition executes ri and ti still
|
||||
// represent the r[i] and t[i] values while the current
|
||||
// r and t are r[i+2] and t[i+2], respectively.
|
||||
|
||||
// sum1 = r[i]^2 + t[i]^2
|
||||
rSquared := new(big.Int).Mul(ri, ri)
|
||||
tSquared := new(big.Int).Mul(ti, ti)
|
||||
sum1 := new(big.Int).Add(rSquared, tSquared)
|
||||
|
||||
// sum2 = r[i+2]^2 + t[i+2]^2
|
||||
r2Squared := new(big.Int).Mul(r, r)
|
||||
t2Squared := new(big.Int).Mul(t, t)
|
||||
sum2 := new(big.Int).Add(r2Squared, t2Squared)
|
||||
|
||||
// if (r[i]^2 + t[i]^2) <= (r[i+2]^2 + t[i+2]^2)
|
||||
if sum1.Cmp(sum2) <= 0 {
|
||||
// a2 = r[i], b2 = -t[i]
|
||||
a2.Set(ri)
|
||||
b2.Mul(ti, bigMinus1)
|
||||
} else {
|
||||
// a2 = r[i+2], b2 = -t[i+2]
|
||||
a2.Set(r)
|
||||
b2.Mul(t, bigMinus1)
|
||||
}
|
||||
|
||||
// All done.
|
||||
break
|
||||
}
|
||||
|
||||
ri.Set(r)
|
||||
ti.Set(t)
|
||||
}
|
||||
|
||||
return a1, b1, a2, b2
|
||||
}
|
||||
67
vendor/github.com/PeernetOfficial/core/btcec/precompute.go
generated
vendored
Normal file
67
vendor/github.com/PeernetOfficial/core/btcec/precompute.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcec
|
||||
|
||||
import (
|
||||
"compress/zlib"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:generate go run -tags gensecp256k1 genprecomps.go
|
||||
|
||||
// loadS256BytePoints decompresses and deserializes the pre-computed byte points
|
||||
// used to accelerate scalar base multiplication for the secp256k1 curve. This
|
||||
// approach is used since it allows the compile to use significantly less ram
|
||||
// and be performed much faster than it is with hard-coding the final in-memory
|
||||
// data structure. At the same time, it is quite fast to generate the in-memory
|
||||
// data structure at init time with this approach versus computing the table.
|
||||
func loadS256BytePoints() error {
|
||||
// There will be no byte points to load when generating them.
|
||||
bp := secp256k1BytePoints
|
||||
if len(bp) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decompress the pre-computed table used to accelerate scalar base
|
||||
// multiplication.
|
||||
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(bp))
|
||||
r, err := zlib.NewReader(decoder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serialized, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Deserialize the precomputed byte points and set the curve to them.
|
||||
offset := 0
|
||||
var bytePoints [32][256][3]fieldVal
|
||||
for byteNum := 0; byteNum < 32; byteNum++ {
|
||||
// All points in this window.
|
||||
for i := 0; i < 256; i++ {
|
||||
px := &bytePoints[byteNum][i][0]
|
||||
py := &bytePoints[byteNum][i][1]
|
||||
pz := &bytePoints[byteNum][i][2]
|
||||
for i := 0; i < 10; i++ {
|
||||
px.n[i] = binary.LittleEndian.Uint32(serialized[offset:])
|
||||
offset += 4
|
||||
}
|
||||
for i := 0; i < 10; i++ {
|
||||
py.n[i] = binary.LittleEndian.Uint32(serialized[offset:])
|
||||
offset += 4
|
||||
}
|
||||
for i := 0; i < 10; i++ {
|
||||
pz.n[i] = binary.LittleEndian.Uint32(serialized[offset:])
|
||||
offset += 4
|
||||
}
|
||||
}
|
||||
}
|
||||
secp256k1.bytePoints = &bytePoints
|
||||
return nil
|
||||
}
|
||||
73
vendor/github.com/PeernetOfficial/core/btcec/privkey.go
generated
vendored
Normal file
73
vendor/github.com/PeernetOfficial/core/btcec/privkey.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcec
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// PrivateKey wraps an ecdsa.PrivateKey as a convenience mainly for signing
|
||||
// things with the the private key without having to directly import the ecdsa
|
||||
// package.
|
||||
type PrivateKey ecdsa.PrivateKey
|
||||
|
||||
// PrivKeyFromBytes returns a private and public key for `curve' based on the
|
||||
// private key passed as an argument as a byte slice.
|
||||
func PrivKeyFromBytes(curve elliptic.Curve, pk []byte) (*PrivateKey,
|
||||
*PublicKey) {
|
||||
x, y := curve.ScalarBaseMult(pk)
|
||||
|
||||
priv := &ecdsa.PrivateKey{
|
||||
PublicKey: ecdsa.PublicKey{
|
||||
Curve: curve,
|
||||
X: x,
|
||||
Y: y,
|
||||
},
|
||||
D: new(big.Int).SetBytes(pk),
|
||||
}
|
||||
|
||||
return (*PrivateKey)(priv), (*PublicKey)(&priv.PublicKey)
|
||||
}
|
||||
|
||||
// NewPrivateKey is a wrapper for ecdsa.GenerateKey that returns a PrivateKey
|
||||
// instead of the normal ecdsa.PrivateKey.
|
||||
func NewPrivateKey(curve elliptic.Curve) (*PrivateKey, error) {
|
||||
key, err := ecdsa.GenerateKey(curve, rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (*PrivateKey)(key), nil
|
||||
}
|
||||
|
||||
// PubKey returns the PublicKey corresponding to this private key.
|
||||
func (p *PrivateKey) PubKey() *PublicKey {
|
||||
return (*PublicKey)(&p.PublicKey)
|
||||
}
|
||||
|
||||
// ToECDSA returns the private key as a *ecdsa.PrivateKey.
|
||||
func (p *PrivateKey) ToECDSA() *ecdsa.PrivateKey {
|
||||
return (*ecdsa.PrivateKey)(p)
|
||||
}
|
||||
|
||||
// Sign generates an ECDSA signature for the provided hash (which should be the result
|
||||
// of hashing a larger message) using the private key. Produced signature
|
||||
// is deterministic (same message and same key yield the same signature) and canonical
|
||||
// in accordance with RFC6979 and BIP0062.
|
||||
func (p *PrivateKey) Sign(hash []byte) (*Signature, error) {
|
||||
return signRFC6979(p, hash)
|
||||
}
|
||||
|
||||
// PrivKeyBytesLen defines the length in bytes of a serialized private key.
|
||||
const PrivKeyBytesLen = 32
|
||||
|
||||
// Serialize returns the private key number d as a big-endian binary-encoded
|
||||
// number, padded to a length of 32 bytes.
|
||||
func (p *PrivateKey) Serialize() []byte {
|
||||
b := make([]byte, 0, PrivKeyBytesLen)
|
||||
return paddedAppend(PrivKeyBytesLen, b, p.ToECDSA().D.Bytes())
|
||||
}
|
||||
194
vendor/github.com/PeernetOfficial/core/btcec/pubkey.go
generated
vendored
Normal file
194
vendor/github.com/PeernetOfficial/core/btcec/pubkey.go
generated
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
// Copyright (c) 2013-2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcec
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// These constants define the lengths of serialized public keys.
|
||||
const (
|
||||
PubKeyBytesLenCompressed = 33
|
||||
PubKeyBytesLenUncompressed = 65
|
||||
PubKeyBytesLenHybrid = 65
|
||||
)
|
||||
|
||||
func isOdd(a *big.Int) bool {
|
||||
return a.Bit(0) == 1
|
||||
}
|
||||
|
||||
// decompressPoint decompresses a point on the secp256k1 curve given the X point and
|
||||
// the solution to use.
|
||||
func decompressPoint(curve *KoblitzCurve, bigX *big.Int, ybit bool) (*big.Int, error) {
|
||||
var x fieldVal
|
||||
x.SetByteSlice(bigX.Bytes())
|
||||
|
||||
// Compute x^3 + B mod p.
|
||||
var x3 fieldVal
|
||||
x3.SquareVal(&x).Mul(&x)
|
||||
x3.Add(curve.fieldB).Normalize()
|
||||
|
||||
// Now calculate sqrt mod p of x^3 + B
|
||||
// This code used to do a full sqrt based on tonelli/shanks,
|
||||
// but this was replaced by the algorithms referenced in
|
||||
// https://bitcointalk.org/index.php?topic=162805.msg1712294#msg1712294
|
||||
var y fieldVal
|
||||
y.SqrtVal(&x3).Normalize()
|
||||
if ybit != y.IsOdd() {
|
||||
y.Negate(1).Normalize()
|
||||
}
|
||||
|
||||
// Check that y is a square root of x^3 + B.
|
||||
var y2 fieldVal
|
||||
y2.SquareVal(&y).Normalize()
|
||||
if !y2.Equals(&x3) {
|
||||
return nil, fmt.Errorf("invalid square root")
|
||||
}
|
||||
|
||||
// Verify that y-coord has expected parity.
|
||||
if ybit != y.IsOdd() {
|
||||
return nil, fmt.Errorf("ybit doesn't match oddness")
|
||||
}
|
||||
|
||||
return new(big.Int).SetBytes(y.Bytes()[:]), nil
|
||||
}
|
||||
|
||||
const (
|
||||
pubkeyCompressed byte = 0x2 // y_bit + x coord
|
||||
pubkeyUncompressed byte = 0x4 // x coord + y coord
|
||||
pubkeyHybrid byte = 0x6 // y_bit + x coord + y coord
|
||||
)
|
||||
|
||||
// IsCompressedPubKey returns true the the passed serialized public key has
|
||||
// been encoded in compressed format, and false otherwise.
|
||||
func IsCompressedPubKey(pubKey []byte) bool {
|
||||
// The public key is only compressed if it is the correct length and
|
||||
// the format (first byte) is one of the compressed pubkey values.
|
||||
return len(pubKey) == PubKeyBytesLenCompressed &&
|
||||
(pubKey[0]&^byte(0x1) == pubkeyCompressed)
|
||||
}
|
||||
|
||||
// ParsePubKey parses a public key for a koblitz curve from a bytestring into a
|
||||
// ecdsa.Publickey, verifying that it is valid. It supports compressed,
|
||||
// uncompressed and hybrid signature formats.
|
||||
func ParsePubKey(pubKeyStr []byte, curve *KoblitzCurve) (key *PublicKey, err error) {
|
||||
pubkey := PublicKey{}
|
||||
pubkey.Curve = curve
|
||||
|
||||
if len(pubKeyStr) == 0 {
|
||||
return nil, errors.New("pubkey string is empty")
|
||||
}
|
||||
|
||||
format := pubKeyStr[0]
|
||||
ybit := (format & 0x1) == 0x1
|
||||
format &= ^byte(0x1)
|
||||
|
||||
switch len(pubKeyStr) {
|
||||
case PubKeyBytesLenUncompressed:
|
||||
if format != pubkeyUncompressed && format != pubkeyHybrid {
|
||||
return nil, fmt.Errorf("invalid magic in pubkey str: "+
|
||||
"%d", pubKeyStr[0])
|
||||
}
|
||||
|
||||
pubkey.X = new(big.Int).SetBytes(pubKeyStr[1:33])
|
||||
pubkey.Y = new(big.Int).SetBytes(pubKeyStr[33:])
|
||||
// hybrid keys have extra information, make use of it.
|
||||
if format == pubkeyHybrid && ybit != isOdd(pubkey.Y) {
|
||||
return nil, fmt.Errorf("ybit doesn't match oddness")
|
||||
}
|
||||
|
||||
if pubkey.X.Cmp(pubkey.Curve.Params().P) >= 0 {
|
||||
return nil, fmt.Errorf("pubkey X parameter is >= to P")
|
||||
}
|
||||
if pubkey.Y.Cmp(pubkey.Curve.Params().P) >= 0 {
|
||||
return nil, fmt.Errorf("pubkey Y parameter is >= to P")
|
||||
}
|
||||
if !pubkey.Curve.IsOnCurve(pubkey.X, pubkey.Y) {
|
||||
return nil, fmt.Errorf("pubkey isn't on secp256k1 curve")
|
||||
}
|
||||
|
||||
case PubKeyBytesLenCompressed:
|
||||
// format is 0x2 | solution, <X coordinate>
|
||||
// solution determines which solution of the curve we use.
|
||||
/// y^2 = x^3 + Curve.B
|
||||
if format != pubkeyCompressed {
|
||||
return nil, fmt.Errorf("invalid magic in compressed "+
|
||||
"pubkey string: %d", pubKeyStr[0])
|
||||
}
|
||||
pubkey.X = new(big.Int).SetBytes(pubKeyStr[1:33])
|
||||
pubkey.Y, err = decompressPoint(curve, pubkey.X, ybit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
default: // wrong!
|
||||
return nil, fmt.Errorf("invalid pub key length %d",
|
||||
len(pubKeyStr))
|
||||
}
|
||||
|
||||
return &pubkey, nil
|
||||
}
|
||||
|
||||
// PublicKey is an ecdsa.PublicKey with additional functions to
|
||||
// serialize in uncompressed, compressed, and hybrid formats.
|
||||
type PublicKey ecdsa.PublicKey
|
||||
|
||||
// ToECDSA returns the public key as a *ecdsa.PublicKey.
|
||||
func (p *PublicKey) ToECDSA() *ecdsa.PublicKey {
|
||||
return (*ecdsa.PublicKey)(p)
|
||||
}
|
||||
|
||||
// SerializeUncompressed serializes a public key in a 65-byte uncompressed
|
||||
// format.
|
||||
func (p *PublicKey) SerializeUncompressed() []byte {
|
||||
b := make([]byte, 0, PubKeyBytesLenUncompressed)
|
||||
b = append(b, pubkeyUncompressed)
|
||||
b = paddedAppend(32, b, p.X.Bytes())
|
||||
return paddedAppend(32, b, p.Y.Bytes())
|
||||
}
|
||||
|
||||
// SerializeCompressed serializes a public key in a 33-byte compressed format.
|
||||
func (p *PublicKey) SerializeCompressed() []byte {
|
||||
b := make([]byte, 0, PubKeyBytesLenCompressed)
|
||||
format := pubkeyCompressed
|
||||
if isOdd(p.Y) {
|
||||
format |= 0x1
|
||||
}
|
||||
b = append(b, format)
|
||||
return paddedAppend(32, b, p.X.Bytes())
|
||||
}
|
||||
|
||||
// SerializeHybrid serializes a public key in a 65-byte hybrid format.
|
||||
func (p *PublicKey) SerializeHybrid() []byte {
|
||||
b := make([]byte, 0, PubKeyBytesLenHybrid)
|
||||
format := pubkeyHybrid
|
||||
if isOdd(p.Y) {
|
||||
format |= 0x1
|
||||
}
|
||||
b = append(b, format)
|
||||
b = paddedAppend(32, b, p.X.Bytes())
|
||||
return paddedAppend(32, b, p.Y.Bytes())
|
||||
}
|
||||
|
||||
// IsEqual compares this PublicKey instance to the one passed, returning true if
|
||||
// both PublicKeys are equivalent. A PublicKey is equivalent to another, if they
|
||||
// both have the same X and Y coordinate.
|
||||
func (p *PublicKey) IsEqual(otherPubKey *PublicKey) bool {
|
||||
return p.X.Cmp(otherPubKey.X) == 0 &&
|
||||
p.Y.Cmp(otherPubKey.Y) == 0
|
||||
}
|
||||
|
||||
// paddedAppend appends the src byte slice to dst, returning the new slice.
|
||||
// If the length of the source is smaller than the passed size, leading zero
|
||||
// bytes are appended to the dst slice before appending src.
|
||||
func paddedAppend(size uint, dst, src []byte) []byte {
|
||||
for i := 0; i < int(size)-len(src); i++ {
|
||||
dst = append(dst, 0)
|
||||
}
|
||||
return append(dst, src...)
|
||||
}
|
||||
10
vendor/github.com/PeernetOfficial/core/btcec/secp256k1.go
generated
vendored
Normal file
10
vendor/github.com/PeernetOfficial/core/btcec/secp256k1.go
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
563
vendor/github.com/PeernetOfficial/core/btcec/signature.go
generated
vendored
Normal file
563
vendor/github.com/PeernetOfficial/core/btcec/signature.go
generated
vendored
Normal file
@@ -0,0 +1,563 @@
|
||||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// Errors returned by canonicalPadding.
|
||||
var (
|
||||
errNegativeValue = errors.New("value may be interpreted as negative")
|
||||
errExcessivelyPaddedValue = errors.New("value is excessively padded")
|
||||
)
|
||||
|
||||
// Signature is a type representing an ecdsa signature.
|
||||
type Signature struct {
|
||||
R *big.Int
|
||||
S *big.Int
|
||||
}
|
||||
|
||||
var (
|
||||
// Used in RFC6979 implementation when testing the nonce for correctness
|
||||
one = big.NewInt(1)
|
||||
|
||||
// oneInitializer is used to fill a byte slice with byte 0x01. It is provided
|
||||
// here to avoid the need to create it multiple times.
|
||||
oneInitializer = []byte{0x01}
|
||||
)
|
||||
|
||||
// Serialize returns the ECDSA signature in the more strict DER format. Note
|
||||
// that the serialized bytes returned do not include the appended hash type
|
||||
// used in Bitcoin signature scripts.
|
||||
//
|
||||
// encoding/asn1 is broken so we hand roll this output:
|
||||
//
|
||||
// 0x30 <length> 0x02 <length r> r 0x02 <length s> s
|
||||
func (sig *Signature) Serialize() []byte {
|
||||
// low 'S' malleability breaker
|
||||
sigS := sig.S
|
||||
if sigS.Cmp(S256().halfOrder) == 1 {
|
||||
sigS = new(big.Int).Sub(S256().N, sigS)
|
||||
}
|
||||
// Ensure the encoded bytes for the r and s values are canonical and
|
||||
// thus suitable for DER encoding.
|
||||
rb := canonicalizeInt(sig.R)
|
||||
sb := canonicalizeInt(sigS)
|
||||
|
||||
// total length of returned signature is 1 byte for each magic and
|
||||
// length (6 total), plus lengths of r and s
|
||||
length := 6 + len(rb) + len(sb)
|
||||
b := make([]byte, length)
|
||||
|
||||
b[0] = 0x30
|
||||
b[1] = byte(length - 2)
|
||||
b[2] = 0x02
|
||||
b[3] = byte(len(rb))
|
||||
offset := copy(b[4:], rb) + 4
|
||||
b[offset] = 0x02
|
||||
b[offset+1] = byte(len(sb))
|
||||
copy(b[offset+2:], sb)
|
||||
return b
|
||||
}
|
||||
|
||||
// Verify calls ecdsa.Verify to verify the signature of hash using the public
|
||||
// key. It returns true if the signature is valid, false otherwise.
|
||||
func (sig *Signature) Verify(hash []byte, pubKey *PublicKey) bool {
|
||||
return ecdsa.Verify(pubKey.ToECDSA(), hash, sig.R, sig.S)
|
||||
}
|
||||
|
||||
// IsEqual compares this Signature instance to the one passed, returning true
|
||||
// if both Signatures are equivalent. A signature is equivalent to another, if
|
||||
// they both have the same scalar value for R and S.
|
||||
func (sig *Signature) IsEqual(otherSig *Signature) bool {
|
||||
return sig.R.Cmp(otherSig.R) == 0 &&
|
||||
sig.S.Cmp(otherSig.S) == 0
|
||||
}
|
||||
|
||||
// MinSigLen is the minimum length of a DER encoded signature and is when both R
|
||||
// and S are 1 byte each.
|
||||
// 0x30 + <1-byte> + 0x02 + 0x01 + <byte> + 0x2 + 0x01 + <byte>
|
||||
const MinSigLen = 8
|
||||
|
||||
func parseSig(sigStr []byte, curve elliptic.Curve, der bool) (*Signature, error) {
|
||||
// Originally this code used encoding/asn1 in order to parse the
|
||||
// signature, but a number of problems were found with this approach.
|
||||
// Despite the fact that signatures are stored as DER, the difference
|
||||
// between go's idea of a bignum (and that they have sign) doesn't agree
|
||||
// with the openssl one (where they do not). The above is true as of
|
||||
// Go 1.1. In the end it was simpler to rewrite the code to explicitly
|
||||
// understand the format which is this:
|
||||
// 0x30 <length of whole message> <0x02> <length of R> <R> 0x2
|
||||
// <length of S> <S>.
|
||||
|
||||
signature := &Signature{}
|
||||
|
||||
if len(sigStr) < MinSigLen {
|
||||
return nil, errors.New("malformed signature: too short")
|
||||
}
|
||||
// 0x30
|
||||
index := 0
|
||||
if sigStr[index] != 0x30 {
|
||||
return nil, errors.New("malformed signature: no header magic")
|
||||
}
|
||||
index++
|
||||
// length of remaining message
|
||||
siglen := sigStr[index]
|
||||
index++
|
||||
|
||||
// siglen should be less than the entire message and greater than
|
||||
// the minimal message size.
|
||||
if int(siglen+2) > len(sigStr) || int(siglen+2) < MinSigLen {
|
||||
return nil, errors.New("malformed signature: bad length")
|
||||
}
|
||||
// trim the slice we're working on so we only look at what matters.
|
||||
sigStr = sigStr[:siglen+2]
|
||||
|
||||
// 0x02
|
||||
if sigStr[index] != 0x02 {
|
||||
return nil,
|
||||
errors.New("malformed signature: no 1st int marker")
|
||||
}
|
||||
index++
|
||||
|
||||
// Length of signature R.
|
||||
rLen := int(sigStr[index])
|
||||
// must be positive, must be able to fit in another 0x2, <len> <s>
|
||||
// hence the -3. We assume that the length must be at least one byte.
|
||||
index++
|
||||
if rLen <= 0 || rLen > len(sigStr)-index-3 {
|
||||
return nil, errors.New("malformed signature: bogus R length")
|
||||
}
|
||||
|
||||
// Then R itself.
|
||||
rBytes := sigStr[index : index+rLen]
|
||||
if der {
|
||||
switch err := canonicalPadding(rBytes); err {
|
||||
case errNegativeValue:
|
||||
return nil, errors.New("signature R is negative")
|
||||
case errExcessivelyPaddedValue:
|
||||
return nil, errors.New("signature R is excessively padded")
|
||||
}
|
||||
}
|
||||
signature.R = new(big.Int).SetBytes(rBytes)
|
||||
index += rLen
|
||||
// 0x02. length already checked in previous if.
|
||||
if sigStr[index] != 0x02 {
|
||||
return nil, errors.New("malformed signature: no 2nd int marker")
|
||||
}
|
||||
index++
|
||||
|
||||
// Length of signature S.
|
||||
sLen := int(sigStr[index])
|
||||
index++
|
||||
// S should be the rest of the string.
|
||||
if sLen <= 0 || sLen > len(sigStr)-index {
|
||||
return nil, errors.New("malformed signature: bogus S length")
|
||||
}
|
||||
|
||||
// Then S itself.
|
||||
sBytes := sigStr[index : index+sLen]
|
||||
if der {
|
||||
switch err := canonicalPadding(sBytes); err {
|
||||
case errNegativeValue:
|
||||
return nil, errors.New("signature S is negative")
|
||||
case errExcessivelyPaddedValue:
|
||||
return nil, errors.New("signature S is excessively padded")
|
||||
}
|
||||
}
|
||||
signature.S = new(big.Int).SetBytes(sBytes)
|
||||
index += sLen
|
||||
|
||||
// sanity check length parsing
|
||||
if index != len(sigStr) {
|
||||
return nil, fmt.Errorf("malformed signature: bad final length %v != %v",
|
||||
index, len(sigStr))
|
||||
}
|
||||
|
||||
// Verify also checks this, but we can be more sure that we parsed
|
||||
// correctly if we verify here too.
|
||||
// FWIW the ecdsa spec states that R and S must be | 1, N - 1 |
|
||||
// but crypto/ecdsa only checks for Sign != 0. Mirror that.
|
||||
if signature.R.Sign() != 1 {
|
||||
return nil, errors.New("signature R isn't 1 or more")
|
||||
}
|
||||
if signature.S.Sign() != 1 {
|
||||
return nil, errors.New("signature S isn't 1 or more")
|
||||
}
|
||||
if signature.R.Cmp(curve.Params().N) >= 0 {
|
||||
return nil, errors.New("signature R is >= curve.N")
|
||||
}
|
||||
if signature.S.Cmp(curve.Params().N) >= 0 {
|
||||
return nil, errors.New("signature S is >= curve.N")
|
||||
}
|
||||
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
// ParseSignature parses a signature in BER format for the curve type `curve'
|
||||
// into a Signature type, perfoming some basic sanity checks. If parsing
|
||||
// according to the more strict DER format is needed, use ParseDERSignature.
|
||||
func ParseSignature(sigStr []byte, curve elliptic.Curve) (*Signature, error) {
|
||||
return parseSig(sigStr, curve, false)
|
||||
}
|
||||
|
||||
// ParseDERSignature parses a signature in DER format for the curve type
|
||||
// `curve` into a Signature type. If parsing according to the less strict
|
||||
// BER format is needed, use ParseSignature.
|
||||
func ParseDERSignature(sigStr []byte, curve elliptic.Curve) (*Signature, error) {
|
||||
return parseSig(sigStr, curve, true)
|
||||
}
|
||||
|
||||
// canonicalizeInt returns the bytes for the passed big integer adjusted as
|
||||
// necessary to ensure that a big-endian encoded integer can't possibly be
|
||||
// misinterpreted as a negative number. This can happen when the most
|
||||
// significant bit is set, so it is padded by a leading zero byte in this case.
|
||||
// Also, the returned bytes will have at least a single byte when the passed
|
||||
// value is 0. This is required for DER encoding.
|
||||
func canonicalizeInt(val *big.Int) []byte {
|
||||
b := val.Bytes()
|
||||
if len(b) == 0 {
|
||||
b = []byte{0x00}
|
||||
}
|
||||
if b[0]&0x80 != 0 {
|
||||
paddedBytes := make([]byte, len(b)+1)
|
||||
copy(paddedBytes[1:], b)
|
||||
b = paddedBytes
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// canonicalPadding checks whether a big-endian encoded integer could
|
||||
// possibly be misinterpreted as a negative number (even though OpenSSL
|
||||
// treats all numbers as unsigned), or if there is any unnecessary
|
||||
// leading zero padding.
|
||||
func canonicalPadding(b []byte) error {
|
||||
switch {
|
||||
case b[0]&0x80 == 0x80:
|
||||
return errNegativeValue
|
||||
case len(b) > 1 && b[0] == 0x00 && b[1]&0x80 != 0x80:
|
||||
return errExcessivelyPaddedValue
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// hashToInt converts a hash value to an integer. There is some disagreement
|
||||
// about how this is done. [NSA] suggests that this is done in the obvious
|
||||
// manner, but [SECG] truncates the hash to the bit-length of the curve order
|
||||
// first. We follow [SECG] because that's what OpenSSL does. Additionally,
|
||||
// OpenSSL right shifts excess bits from the number if the hash is too large
|
||||
// and we mirror that too.
|
||||
// This is borrowed from crypto/ecdsa.
|
||||
func hashToInt(hash []byte, c elliptic.Curve) *big.Int {
|
||||
orderBits := c.Params().N.BitLen()
|
||||
orderBytes := (orderBits + 7) / 8
|
||||
if len(hash) > orderBytes {
|
||||
hash = hash[:orderBytes]
|
||||
}
|
||||
|
||||
ret := new(big.Int).SetBytes(hash)
|
||||
excess := len(hash)*8 - orderBits
|
||||
if excess > 0 {
|
||||
ret.Rsh(ret, uint(excess))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// recoverKeyFromSignature recovers a public key from the signature "sig" on the
|
||||
// given message hash "msg". Based on the algorithm found in section 4.1.6 of
|
||||
// SEC 1 Ver 2.0, page 47-48 (53 and 54 in the pdf). This performs the details
|
||||
// in the inner loop in Step 1. The counter provided is actually the j parameter
|
||||
// of the loop * 2 - on the first iteration of j we do the R case, else the -R
|
||||
// case in step 1.6. This counter is used in the bitcoin compressed signature
|
||||
// format and thus we match bitcoind's behaviour here.
|
||||
func recoverKeyFromSignature(curve *KoblitzCurve, sig *Signature, msg []byte,
|
||||
iter int, doChecks bool) (*PublicKey, error) {
|
||||
// Parse and validate the R and S signature components.
|
||||
//
|
||||
// Fail if r and s are not in [1, N-1].
|
||||
if sig.R.Cmp(curve.Params().N) != -1 {
|
||||
return nil, errors.New("signature R is >= curve order")
|
||||
}
|
||||
|
||||
if sig.R.Sign() == 0 {
|
||||
return nil, errors.New("signature R is 0")
|
||||
}
|
||||
|
||||
if sig.S.Cmp(curve.Params().N) != -1 {
|
||||
return nil, errors.New("signature S is >= curve order")
|
||||
}
|
||||
|
||||
if sig.S.Sign() == 0 {
|
||||
return nil, errors.New("signature S is 0")
|
||||
}
|
||||
|
||||
// 1.1 x = (n * i) + r
|
||||
Rx := new(big.Int).Mul(curve.Params().N,
|
||||
new(big.Int).SetInt64(int64(iter/2)))
|
||||
Rx.Add(Rx, sig.R)
|
||||
if Rx.Cmp(curve.Params().P) != -1 {
|
||||
return nil, errors.New("calculated Rx is larger than curve P")
|
||||
}
|
||||
|
||||
// convert 02<Rx> to point R. (step 1.2 and 1.3). If we are on an odd
|
||||
// iteration then 1.6 will be done with -R, so we calculate the other
|
||||
// term when uncompressing the point.
|
||||
Ry, err := decompressPoint(curve, Rx, iter%2 == 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 1.4 Check n*R is point at infinity
|
||||
if doChecks {
|
||||
nRx, nRy := curve.ScalarMult(Rx, Ry, curve.Params().N.Bytes())
|
||||
if nRx.Sign() != 0 || nRy.Sign() != 0 {
|
||||
return nil, errors.New("n*R does not equal the point at infinity")
|
||||
}
|
||||
}
|
||||
|
||||
// 1.5 calculate e from message using the same algorithm as ecdsa
|
||||
// signature calculation.
|
||||
e := hashToInt(msg, curve)
|
||||
|
||||
// Step 1.6.1:
|
||||
// We calculate the two terms sR and eG separately multiplied by the
|
||||
// inverse of r (from the signature). We then add them to calculate
|
||||
// Q = r^-1(sR-eG)
|
||||
invr := new(big.Int).ModInverse(sig.R, curve.Params().N)
|
||||
|
||||
// first term.
|
||||
invrS := new(big.Int).Mul(invr, sig.S)
|
||||
invrS.Mod(invrS, curve.Params().N)
|
||||
sRx, sRy := curve.ScalarMult(Rx, Ry, invrS.Bytes())
|
||||
|
||||
// second term.
|
||||
e.Neg(e)
|
||||
e.Mod(e, curve.Params().N)
|
||||
e.Mul(e, invr)
|
||||
e.Mod(e, curve.Params().N)
|
||||
minuseGx, minuseGy := curve.ScalarBaseMult(e.Bytes())
|
||||
|
||||
// TODO: this would be faster if we did a mult and add in one
|
||||
// step to prevent the jacobian conversion back and forth.
|
||||
Qx, Qy := curve.Add(sRx, sRy, minuseGx, minuseGy)
|
||||
|
||||
if Qx.Sign() == 0 && Qy.Sign() == 0 {
|
||||
return nil, errors.New("point (Qx, Qy) equals the point at infinity")
|
||||
}
|
||||
|
||||
return &PublicKey{
|
||||
Curve: curve,
|
||||
X: Qx,
|
||||
Y: Qy,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SignCompact produces a compact signature of the data in hash with the given
|
||||
// private key on the given koblitz curve. The isCompressed parameter should
|
||||
// be used to detail if the given signature should reference a compressed
|
||||
// public key or not. If successful the bytes of the compact signature will be
|
||||
// returned in the format:
|
||||
// <(byte of 27+public key solution)+4 if compressed >< padded bytes for signature R><padded bytes for signature S>
|
||||
// where the R and S parameters are padde up to the bitlengh of the curve.
|
||||
func SignCompact(curve *KoblitzCurve, key *PrivateKey,
|
||||
hash []byte, isCompressedKey bool) ([]byte, error) {
|
||||
sig, err := key.Sign(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// bitcoind checks the bit length of R and S here. The ecdsa signature
|
||||
// algorithm returns R and S mod N therefore they will be the bitsize of
|
||||
// the curve, and thus correctly sized.
|
||||
for i := 0; i < (curve.H+1)*2; i++ {
|
||||
pk, err := recoverKeyFromSignature(curve, sig, hash, i, true)
|
||||
if err == nil && pk.X.Cmp(key.X) == 0 && pk.Y.Cmp(key.Y) == 0 {
|
||||
result := make([]byte, 1, 2*curve.byteSize+1)
|
||||
result[0] = 27 + byte(i)
|
||||
if isCompressedKey {
|
||||
result[0] += 4
|
||||
}
|
||||
// Not sure this needs rounding but safer to do so.
|
||||
curvelen := (curve.BitSize + 7) / 8
|
||||
|
||||
// Pad R and S to curvelen if needed.
|
||||
bytelen := (sig.R.BitLen() + 7) / 8
|
||||
if bytelen < curvelen {
|
||||
result = append(result,
|
||||
make([]byte, curvelen-bytelen)...)
|
||||
}
|
||||
result = append(result, sig.R.Bytes()...)
|
||||
|
||||
bytelen = (sig.S.BitLen() + 7) / 8
|
||||
if bytelen < curvelen {
|
||||
result = append(result,
|
||||
make([]byte, curvelen-bytelen)...)
|
||||
}
|
||||
result = append(result, sig.S.Bytes()...)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("no valid solution for pubkey found")
|
||||
}
|
||||
|
||||
// RecoverCompact verifies the compact signature "signature" of "hash" for the
|
||||
// Koblitz curve in "curve". If the signature matches then the recovered public
|
||||
// key will be returned as well as a boolean if the original key was compressed
|
||||
// or not, else an error will be returned.
|
||||
func RecoverCompact(curve *KoblitzCurve, signature,
|
||||
hash []byte) (*PublicKey, bool, error) {
|
||||
bitlen := (curve.BitSize + 7) / 8
|
||||
if len(signature) != 1+bitlen*2 {
|
||||
return nil, false, errors.New("invalid compact signature size")
|
||||
}
|
||||
|
||||
iteration := int((signature[0] - 27) & ^byte(4))
|
||||
|
||||
// format is <header byte><bitlen R><bitlen S>
|
||||
sig := &Signature{
|
||||
R: new(big.Int).SetBytes(signature[1 : bitlen+1]),
|
||||
S: new(big.Int).SetBytes(signature[bitlen+1:]),
|
||||
}
|
||||
// The iteration used here was encoded
|
||||
key, err := recoverKeyFromSignature(curve, sig, hash, iteration, false)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return key, ((signature[0] - 27) & 4) == 4, nil
|
||||
}
|
||||
|
||||
// signRFC6979 generates a deterministic ECDSA signature according to RFC 6979 and BIP 62.
|
||||
func signRFC6979(privateKey *PrivateKey, hash []byte) (*Signature, error) {
|
||||
|
||||
privkey := privateKey.ToECDSA()
|
||||
N := S256().N
|
||||
halfOrder := S256().halfOrder
|
||||
k := nonceRFC6979(privkey.D, hash)
|
||||
inv := new(big.Int).ModInverse(k, N)
|
||||
r, _ := privkey.Curve.ScalarBaseMult(k.Bytes())
|
||||
r.Mod(r, N)
|
||||
|
||||
if r.Sign() == 0 {
|
||||
return nil, errors.New("calculated R is zero")
|
||||
}
|
||||
|
||||
e := hashToInt(hash, privkey.Curve)
|
||||
s := new(big.Int).Mul(privkey.D, r)
|
||||
s.Add(s, e)
|
||||
s.Mul(s, inv)
|
||||
s.Mod(s, N)
|
||||
|
||||
if s.Cmp(halfOrder) == 1 {
|
||||
s.Sub(N, s)
|
||||
}
|
||||
if s.Sign() == 0 {
|
||||
return nil, errors.New("calculated S is zero")
|
||||
}
|
||||
return &Signature{R: r, S: s}, nil
|
||||
}
|
||||
|
||||
// nonceRFC6979 generates an ECDSA nonce (`k`) deterministically according to RFC 6979.
|
||||
// It takes a 32-byte hash as an input and returns 32-byte nonce to be used in ECDSA algorithm.
|
||||
func nonceRFC6979(privkey *big.Int, hash []byte) *big.Int {
|
||||
|
||||
curve := S256()
|
||||
q := curve.Params().N
|
||||
x := privkey
|
||||
alg := sha256.New
|
||||
|
||||
qlen := q.BitLen()
|
||||
holen := alg().Size()
|
||||
rolen := (qlen + 7) >> 3
|
||||
bx := append(int2octets(x, rolen), bits2octets(hash, curve, rolen)...)
|
||||
|
||||
// Step B
|
||||
v := bytes.Repeat(oneInitializer, holen)
|
||||
|
||||
// Step C (Go zeroes the all allocated memory)
|
||||
k := make([]byte, holen)
|
||||
|
||||
// Step D
|
||||
k = mac(alg, k, append(append(v, 0x00), bx...))
|
||||
|
||||
// Step E
|
||||
v = mac(alg, k, v)
|
||||
|
||||
// Step F
|
||||
k = mac(alg, k, append(append(v, 0x01), bx...))
|
||||
|
||||
// Step G
|
||||
v = mac(alg, k, v)
|
||||
|
||||
// Step H
|
||||
for {
|
||||
// Step H1
|
||||
var t []byte
|
||||
|
||||
// Step H2
|
||||
for len(t)*8 < qlen {
|
||||
v = mac(alg, k, v)
|
||||
t = append(t, v...)
|
||||
}
|
||||
|
||||
// Step H3
|
||||
secret := hashToInt(t, curve)
|
||||
if secret.Cmp(one) >= 0 && secret.Cmp(q) < 0 {
|
||||
return secret
|
||||
}
|
||||
k = mac(alg, k, append(v, 0x00))
|
||||
v = mac(alg, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// mac returns an HMAC of the given key and message.
|
||||
func mac(alg func() hash.Hash, k, m []byte) []byte {
|
||||
h := hmac.New(alg, k)
|
||||
h.Write(m)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc6979#section-2.3.3
|
||||
func int2octets(v *big.Int, rolen int) []byte {
|
||||
out := v.Bytes()
|
||||
|
||||
// left pad with zeros if it's too short
|
||||
if len(out) < rolen {
|
||||
out2 := make([]byte, rolen)
|
||||
copy(out2[rolen-len(out):], out)
|
||||
return out2
|
||||
}
|
||||
|
||||
// drop most significant bytes if it's too long
|
||||
if len(out) > rolen {
|
||||
out2 := make([]byte, rolen)
|
||||
copy(out2, out[len(out)-rolen:])
|
||||
return out2
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc6979#section-2.3.4
|
||||
func bits2octets(in []byte, curve elliptic.Curve, rolen int) []byte {
|
||||
z1 := hashToInt(in, curve)
|
||||
z2 := new(big.Int).Sub(z1, curve.Params().N)
|
||||
if z2.Sign() < 0 {
|
||||
return int2octets(z1, rolen)
|
||||
}
|
||||
return int2octets(z2, rolen)
|
||||
}
|
||||
211
vendor/github.com/PeernetOfficial/core/dht/DHT Lite.go
generated
vendored
Normal file
211
vendor/github.com/PeernetOfficial/core/dht/DHT Lite.go
generated
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
File Name: DHT Lite.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
A "lite" DHT implementation without any direct network and store code. There is really no reason for any of the heavy network implementation to be part of this.
|
||||
*/
|
||||
|
||||
package dht
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DHT represents the state of the local node in the distributed hash table
|
||||
type DHT struct {
|
||||
ht *hashTable
|
||||
|
||||
// A small number representing the degree of parallelism in network calls.
|
||||
// The alpha amount of nodes will be contacted in parallel for finding the target.
|
||||
alpha int
|
||||
|
||||
// Functions below must be set and provided by the caller.
|
||||
|
||||
// ShouldEvict determines whether node 1 shall be evicted in favor of node 2
|
||||
ShouldEvict func(node1, node2 *Node) bool
|
||||
|
||||
// SendRequestStore sends an announcement-store message to the remote node. It informs the remote node that the local one stores the given key-value.
|
||||
SendRequestStore func(node *Node, key []byte, dataSize uint64)
|
||||
|
||||
// SendRequestFindNode sends an information request to find a particular node. nodes are the nodes to send the request to.
|
||||
SendRequestFindNode func(request *InformationRequest)
|
||||
|
||||
// SendRequestFindValue sends an information request to find data. nodes are the nodes to send the request to.
|
||||
SendRequestFindValue func(request *InformationRequest)
|
||||
|
||||
// FilterSearchStatus is called with updates of searches in the DHT
|
||||
FilterSearchStatus func(client *SearchClient, function, format string, v ...interface{})
|
||||
|
||||
// TimeoutSearch is the maximum time a search may take.
|
||||
TimeoutSearch time.Duration
|
||||
|
||||
// TimeoutIR is the maximum an information request to a node may take.
|
||||
TimeoutIR time.Duration
|
||||
}
|
||||
|
||||
// NewDHT initializes a new DHT node with default values.
|
||||
func NewDHT(self *Node, bits, bucketSize, alpha int) *DHT {
|
||||
return &DHT{
|
||||
ht: newHashTable(self, bits, bucketSize),
|
||||
alpha: alpha,
|
||||
FilterSearchStatus: func(client *SearchClient, function, format string, v ...interface{}) {},
|
||||
TimeoutSearch: 10 * time.Second,
|
||||
TimeoutIR: 6 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// NumNodes returns the total number of nodes stored in the local routing table
|
||||
func (dht *DHT) NumNodes() int {
|
||||
return dht.ht.totalNodes()
|
||||
}
|
||||
|
||||
// Nodes returns the nodes themselves sotred in the routing table.
|
||||
func (dht *DHT) Nodes() []*Node {
|
||||
return dht.ht.Nodes()
|
||||
}
|
||||
|
||||
// GetSelfID returns the identifier of the local node
|
||||
func (dht *DHT) GetSelfID() []byte {
|
||||
return dht.ht.Self.ID
|
||||
}
|
||||
|
||||
// AddNode adds a node into the appropriate k bucket. These buckets are stored in big-endian order so we look at the bits from right to left in order to find the appropriate bucket.
|
||||
func (dht *DHT) AddNode(node *Node) {
|
||||
// The previous code made an immediate ping to the oldest node to "ping the oldest node to find out if it responds back in a reasonable amount of time. If not - remove it."
|
||||
// In DHT Lite, however, it will be up to the caller to determine nodes to remove.
|
||||
dht.ht.insertNode(node, dht.ShouldEvict)
|
||||
}
|
||||
|
||||
// RemoveNode removes a node
|
||||
func (dht *DHT) RemoveNode(ID []byte) {
|
||||
dht.ht.removeNode(ID)
|
||||
}
|
||||
|
||||
// GetClosestContacts returns the closes contacts in the hash table
|
||||
func (dht *DHT) GetClosestContacts(count int, target []byte, filterFunc NodeFilterFunc, ignoredNodes ...[]byte) []*Node {
|
||||
closest := dht.ht.getClosestContacts(count, target, filterFunc, ignoredNodes...)
|
||||
return closest.Nodes
|
||||
}
|
||||
|
||||
// MarkNodeAsSeen marks a node as seen, which pushes it to the top in the bucket list.
|
||||
func (dht *DHT) MarkNodeAsSeen(ID []byte) {
|
||||
dht.ht.markNodeAsSeen(dht.ht.getBucketIndexFromDifferingBit(ID), ID)
|
||||
}
|
||||
|
||||
// IsNodeCloser compares 2 nodes to self. If true, the first node is closer (= smaller distance) to self than the second.
|
||||
func (dht *DHT) IsNodeCloser(node1, node2 []byte) bool {
|
||||
iDist := getDistance(node1, dht.ht.Self.ID)
|
||||
jDist := getDistance(node2, dht.ht.Self.ID)
|
||||
|
||||
return iDist.Cmp(jDist) == -1
|
||||
}
|
||||
|
||||
// IsNodeContact checks if the given node is in the local routing table
|
||||
func (dht *DHT) IsNodeContact(ID []byte) (node *Node) {
|
||||
return dht.ht.doesNodeExist(ID)
|
||||
}
|
||||
|
||||
// ---- Synchronous network query functions below ----
|
||||
|
||||
// Store informs the network about data stored locally.
|
||||
// Data size informs how big the data is without sending the actual data. closestCount is the number of closest nodes to contact.
|
||||
func (dht *DHT) Store(key []byte, dataSize uint64, closestCount int) (err error) {
|
||||
if len(key)*8 != dht.ht.bBits {
|
||||
return errors.New("invalid key size")
|
||||
}
|
||||
|
||||
// TODO: Introduce ActionFindClosestNodes?
|
||||
|
||||
search := dht.NewSearch(ActionFindNode, key, dht.TimeoutSearch, dht.TimeoutIR, dht.alpha)
|
||||
search.LogStatus = func(function, format string, v ...interface{}) {
|
||||
dht.FilterSearchStatus(search, function, format, v...)
|
||||
}
|
||||
search.LogStatus("dht.Store", "Search for closest nodes to key %s. Full timeout %s, per node %s. Alpha = %d.\n", hex.EncodeToString(key), dht.TimeoutSearch.String(), dht.TimeoutIR.String(), dht.alpha)
|
||||
search.SearchAway()
|
||||
|
||||
// search.Results channel is ignored here. Only the closest nodes to the key are of interest. It is not expected to find a match of key and node ID.
|
||||
<-search.TerminateSignal
|
||||
|
||||
// Contact the closes nodes found.
|
||||
for n := 0; n < closestCount && n < len(search.list.Nodes); n++ {
|
||||
node := search.list.Nodes[n]
|
||||
search.LogStatus("dht.Store", "Send info-store message to node %s\n", hex.EncodeToString(node.ID))
|
||||
dht.SendRequestStore(node, key, dataSize)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get retrieves data from the network using key
|
||||
func (dht *DHT) Get(key []byte) (value []byte, senderID []byte, found bool, err error) {
|
||||
if len(key)*8 != dht.ht.bBits {
|
||||
return nil, nil, false, errors.New("invalid key size")
|
||||
}
|
||||
|
||||
search := dht.NewSearch(ActionFindValue, key, dht.TimeoutSearch, dht.TimeoutIR, dht.alpha)
|
||||
search.LogStatus = func(function, format string, v ...interface{}) {
|
||||
dht.FilterSearchStatus(search, function, format, v...)
|
||||
}
|
||||
search.LogStatus("dht.Get", "Search for node %s. Full timeout %s, per node %s. Alpha = %d.\n", hex.EncodeToString(key), dht.TimeoutSearch.String(), dht.TimeoutIR.String(), dht.alpha)
|
||||
search.SearchAway()
|
||||
|
||||
select {
|
||||
case <-search.TerminateSignal:
|
||||
return nil, nil, false, nil
|
||||
case result := <-search.Results:
|
||||
return result.Data, result.SenderID, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// FindNode finds the target node in the network. Blocking!
|
||||
// The caller may use dht.NewSearch directly and take advantage of the asynchronous response and custom timeouts.
|
||||
func (dht *DHT) FindNode(key []byte) (node *Node, err error) {
|
||||
if len(key)*8 != dht.ht.bBits {
|
||||
return nil, errors.New("invalid key size")
|
||||
}
|
||||
|
||||
search := dht.NewSearch(ActionFindNode, key, dht.TimeoutSearch, dht.TimeoutIR, dht.alpha)
|
||||
search.LogStatus = func(function, format string, v ...interface{}) {
|
||||
dht.FilterSearchStatus(search, function, format, v...)
|
||||
}
|
||||
search.LogStatus("dht.FindNode", "Search for node %s. Full timeout %s, per node %s. Alpha = %d.\n", hex.EncodeToString(key), dht.TimeoutSearch.String(), dht.TimeoutIR.String(), dht.alpha)
|
||||
search.SearchAway()
|
||||
|
||||
result, ok := <-search.Results
|
||||
if !ok { // Check if closed channel. Redundant with checking <-search.TerminateSignal.
|
||||
return nil, nil
|
||||
}
|
||||
return result.TargetNode, nil
|
||||
}
|
||||
|
||||
// ---- DHT Health ----
|
||||
|
||||
// DisableBucketRefresh is an option for debug purposes to reduce noise. It can be useful to disable bucket refresh when debugging outgoing DHT searches.
|
||||
var DisableBucketRefresh = false
|
||||
|
||||
// RefreshBuckets refreshes all buckets not meeting the target node number. 0 to refresh all.
|
||||
func (dht *DHT) RefreshBuckets(target int) {
|
||||
if DisableBucketRefresh {
|
||||
return
|
||||
}
|
||||
|
||||
for bucket, total := range dht.ht.getTotalNodesPerBucket() {
|
||||
if target == 0 || total < target {
|
||||
nodeR := dht.ht.getRandomIDFromBucket(bucket)
|
||||
|
||||
// Refreshing closest bucket? Use self ID instead of random one.
|
||||
if bucket == 0 {
|
||||
nodeR = dht.ht.Self.ID
|
||||
}
|
||||
|
||||
dht.FindNode(nodeR)
|
||||
}
|
||||
|
||||
if DisableBucketRefresh { // may be disabled while in full refresh which may take some time
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
314
vendor/github.com/PeernetOfficial/core/dht/Hash Table.go
generated
vendored
Normal file
314
vendor/github.com/PeernetOfficial/core/dht/Hash Table.go
generated
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
File Name: Hash Table.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package dht
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// hashTable represents the hashtable state
|
||||
type hashTable struct {
|
||||
// The ID of the local node
|
||||
Self *Node
|
||||
|
||||
// the size in bits of the keys used to identify nodes and store and
|
||||
// retrieve data; in basic Kademlia this is 160, the length of a SHA1
|
||||
bBits int
|
||||
|
||||
// the maximum number of contacts stored in a bucket
|
||||
bSize int
|
||||
|
||||
// Routing table a list of all known nodes in the network
|
||||
// Nodes within buckets are sorted by least recently seen e.g.
|
||||
// [ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
|
||||
// ^ ^
|
||||
// └ Least recently seen Most recently seen ┘
|
||||
RoutingTable [][]*Node // bBits x bSize
|
||||
|
||||
mutex *sync.RWMutex
|
||||
}
|
||||
|
||||
func newHashTable(self *Node, bits, bucketSize int) *hashTable {
|
||||
ht := &hashTable{
|
||||
bBits: bits,
|
||||
bSize: bucketSize,
|
||||
mutex: &sync.RWMutex{},
|
||||
Self: self,
|
||||
}
|
||||
|
||||
ht.RoutingTable = make([][]*Node, ht.bBits)
|
||||
return ht
|
||||
}
|
||||
|
||||
func (ht *hashTable) markNodeAsSeen(index int, ID []byte) {
|
||||
ht.mutex.Lock()
|
||||
defer ht.mutex.Unlock()
|
||||
bucket := ht.RoutingTable[index]
|
||||
nodeIndex := -1
|
||||
for i, v := range bucket {
|
||||
if bytes.Compare(v.ID, ID) == 0 {
|
||||
nodeIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if nodeIndex == -1 {
|
||||
//errors.New("Tried to mark nonexistent node as seen")
|
||||
return
|
||||
}
|
||||
|
||||
n := bucket[nodeIndex]
|
||||
n.LastSeen = time.Now().UTC()
|
||||
|
||||
bucket = append(bucket[:nodeIndex], bucket[nodeIndex+1:]...)
|
||||
bucket = append(bucket, n)
|
||||
ht.RoutingTable[index] = bucket
|
||||
}
|
||||
|
||||
func (ht *hashTable) doesNodeExistInBucket(bucket int, ID []byte) (node *Node) {
|
||||
ht.mutex.RLock()
|
||||
defer ht.mutex.RUnlock()
|
||||
for _, node = range ht.RoutingTable[bucket] {
|
||||
if bytes.Compare(node.ID, ID) == 0 {
|
||||
return node
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ht *hashTable) doesNodeExist(ID []byte) (node *Node) {
|
||||
return ht.doesNodeExistInBucket(ht.getBucketIndexFromDifferingBit(ID), ID)
|
||||
}
|
||||
|
||||
// getClosestContacts returns the closest nodes to the target. filterFunc is optional and allows the caller to filter the nodes.
|
||||
func (ht *hashTable) getClosestContacts(num int, target []byte, filterFunc NodeFilterFunc, ignoredNodes ...[]byte) *shortList {
|
||||
ht.mutex.RLock()
|
||||
defer ht.mutex.RUnlock()
|
||||
|
||||
// First we need to build the list of adjacent indices to our target in order
|
||||
index := ht.getBucketIndexFromDifferingBit(target)
|
||||
indexList := []int{index}
|
||||
for i, j := index-1, index+1; len(indexList) < ht.bBits; i, j = i-1, j+1 {
|
||||
if j < ht.bBits {
|
||||
indexList = append(indexList, j)
|
||||
}
|
||||
if i >= 0 {
|
||||
indexList = append(indexList, i)
|
||||
}
|
||||
}
|
||||
|
||||
sl := newShortList()
|
||||
|
||||
leftToAdd := num
|
||||
|
||||
// Next we select alpha contacts and add them to the short list
|
||||
for leftToAdd > 0 && len(indexList) > 0 {
|
||||
index, indexList = indexList[0], indexList[1:]
|
||||
bucketContacts := len(ht.RoutingTable[index])
|
||||
bucketLoop:
|
||||
for i := 0; i < bucketContacts; i++ {
|
||||
for j := 0; j < len(ignoredNodes); j++ {
|
||||
if bytes.Compare(ht.RoutingTable[index][i].ID, ignoredNodes[j]) == 0 {
|
||||
continue bucketLoop
|
||||
}
|
||||
}
|
||||
|
||||
// Use the filter function if set. It allows the caller to only accept certain nodes.
|
||||
if filterFunc != nil && !filterFunc(ht.RoutingTable[index][i]) {
|
||||
continue
|
||||
}
|
||||
|
||||
sl.AppendUniqueNodes(ht.RoutingTable[index][i])
|
||||
leftToAdd--
|
||||
if leftToAdd == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(sl)
|
||||
|
||||
return sl
|
||||
}
|
||||
|
||||
func (ht *hashTable) insertNode(node *Node, shouldEvict func(nodeOld *Node, nodeNew *Node) bool) {
|
||||
index := ht.getBucketIndexFromDifferingBit(node.ID)
|
||||
|
||||
// If the node already exist, mark it as seen
|
||||
if ht.doesNodeExistInBucket(index, node.ID) != nil {
|
||||
ht.markNodeAsSeen(index, node.ID)
|
||||
return
|
||||
}
|
||||
|
||||
node.LastSeen = time.Now().UTC()
|
||||
|
||||
ht.mutex.Lock()
|
||||
defer ht.mutex.Unlock()
|
||||
|
||||
bucket := ht.RoutingTable[index]
|
||||
|
||||
if len(bucket) == ht.bSize {
|
||||
if shouldEvict(bucket[0], node) {
|
||||
bucket = append(bucket, node)
|
||||
bucket = bucket[1:]
|
||||
}
|
||||
} else {
|
||||
bucket = append(bucket, node)
|
||||
}
|
||||
|
||||
ht.RoutingTable[index] = bucket
|
||||
}
|
||||
|
||||
func (ht *hashTable) removeNode(ID []byte) {
|
||||
ht.mutex.Lock()
|
||||
defer ht.mutex.Unlock()
|
||||
|
||||
index := ht.getBucketIndexFromDifferingBit(ID)
|
||||
bucket := ht.RoutingTable[index]
|
||||
|
||||
for i, v := range bucket {
|
||||
if bytes.Compare(v.ID, ID) == 0 {
|
||||
bucket = append(bucket[:i], bucket[i+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
ht.RoutingTable[index] = bucket
|
||||
}
|
||||
|
||||
func (ht *hashTable) getTotalNodesInBucket(bucket int) int {
|
||||
ht.mutex.RLock()
|
||||
defer ht.mutex.RUnlock()
|
||||
return len(ht.RoutingTable[bucket])
|
||||
}
|
||||
|
||||
func (ht *hashTable) getRandomIDFromBucket(bucket int) []byte {
|
||||
ht.mutex.RLock()
|
||||
defer ht.mutex.RUnlock()
|
||||
// Set the new ID to to be equal in every byte up to
|
||||
// the byte of the first differing bit in the bucket
|
||||
|
||||
byteIndex := bucket / 8
|
||||
var id []byte
|
||||
for i := 0; i < byteIndex; i++ {
|
||||
id = append(id, ht.Self.ID[i])
|
||||
}
|
||||
differingBitStart := bucket % 8
|
||||
|
||||
var firstByte byte
|
||||
// check each bit from left to right in order
|
||||
for i := 0; i < 8; i++ {
|
||||
// Set the value of the bit to be the same as the ID
|
||||
// up to the differing bit. Then begin randomizing
|
||||
var bit bool
|
||||
if i < differingBitStart {
|
||||
bit = hasBit(ht.Self.ID[byteIndex], uint(i))
|
||||
} else {
|
||||
bit = rand.Intn(2) == 1
|
||||
}
|
||||
|
||||
if bit {
|
||||
firstByte += byte(math.Pow(2, float64(7-i)))
|
||||
}
|
||||
}
|
||||
|
||||
id = append(id, firstByte)
|
||||
|
||||
// Randomize each remaining byte
|
||||
for i := byteIndex + 1; i < 20; i++ {
|
||||
randomByte := byte(rand.Intn(256))
|
||||
id = append(id, randomByte)
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func (ht *hashTable) lastSeenBefore(cutoff time.Time) (nodes []*Node) {
|
||||
ht.mutex.RLock()
|
||||
defer ht.mutex.RUnlock()
|
||||
nodes = make([]*Node, 0, ht.bSize)
|
||||
for _, v := range ht.RoutingTable {
|
||||
for _, n := range v {
|
||||
if n.LastSeen.Before(cutoff) {
|
||||
nodes = append(nodes, n)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
func (ht *hashTable) getBucketIndexFromDifferingBit(id1 []byte) int {
|
||||
// Look at each byte from left to right
|
||||
for j := 0; j < len(id1); j++ {
|
||||
// xor the byte
|
||||
xor := id1[j] ^ ht.Self.ID[j]
|
||||
|
||||
// check each bit on the xored result from left to right in order
|
||||
for i := 0; i < 8; i++ {
|
||||
if hasBit(xor, uint(i)) {
|
||||
byteIndex := j * 8
|
||||
bitIndex := i
|
||||
return ht.bBits - (byteIndex + bitIndex) - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the ids must be the same
|
||||
// this should only happen during bootstrapping
|
||||
return 0
|
||||
}
|
||||
|
||||
func (ht *hashTable) totalNodes() int {
|
||||
ht.mutex.RLock()
|
||||
defer ht.mutex.RUnlock()
|
||||
var total int
|
||||
for _, v := range ht.RoutingTable {
|
||||
total += len(v)
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func (ht *hashTable) Nodes() (nodes []*Node) {
|
||||
ht.mutex.RLock()
|
||||
defer ht.mutex.RUnlock()
|
||||
nodes = make([]*Node, 0, ht.bSize)
|
||||
for _, v := range ht.RoutingTable {
|
||||
nodes = append(nodes, v...)
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
// Simple helper function to determine the value of a particular bit in a byte by index
|
||||
|
||||
// Example:
|
||||
// number: 1
|
||||
// bits: 00000001
|
||||
// pos: 01234567
|
||||
func hasBit(n byte, pos uint) bool {
|
||||
pos = 7 - pos
|
||||
val := n & (1 << pos)
|
||||
return (val > 0)
|
||||
}
|
||||
|
||||
// getTotalNodesPerBucket returns the count of nodes in all buckets
|
||||
func (ht *hashTable) getTotalNodesPerBucket() (total []int) {
|
||||
ht.mutex.RLock()
|
||||
defer ht.mutex.RUnlock()
|
||||
|
||||
for n, _ := range ht.RoutingTable {
|
||||
total = append(total, len(ht.RoutingTable[n]))
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
113
vendor/github.com/PeernetOfficial/core/dht/Information Request.go
generated
vendored
Normal file
113
vendor/github.com/PeernetOfficial/core/dht/Information Request.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
File Name: Information Request.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Information requests are asynchronous queries sent to nodes.
|
||||
*/
|
||||
|
||||
package dht
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// InformationRequest is an asynchronous request sent to nodes. It tracks any asynchronous replies and handles timeouts.
|
||||
type InformationRequest struct {
|
||||
Action int // ActionX
|
||||
Key []byte // Key that is being queried
|
||||
ResultChan chan *NodeMessage // Result channel
|
||||
ResultChanExt chan *NodeMessage // External result channel to use instead
|
||||
ActiveNodes uint64 // Number of nodes actively handling the request.
|
||||
Nodes []*Node // Nodes that are receiving the request.
|
||||
IsTerminated bool // If true, it was signaled for termination
|
||||
TerminateSignal chan struct{} // gets closed on termination signal, can be used in select via "case _ = <- network.terminateSignal:"
|
||||
sync.Mutex // for sychronized closing
|
||||
}
|
||||
|
||||
// Actions for performing the information request
|
||||
const (
|
||||
ActionFindNode = iota // Find a node
|
||||
ActionFindValue // Find a value
|
||||
)
|
||||
|
||||
const messageChannelSize = 100
|
||||
|
||||
// NewInformationRequest creates a new information request and adds it to the list.
|
||||
// It marks the count of nodes as active, meaning the caller should later decrease it via ActiveNodesSub.
|
||||
func (dht *DHT) NewInformationRequest(Action int, Key []byte, Nodes []*Node) (ir *InformationRequest) {
|
||||
ir = &InformationRequest{
|
||||
ResultChan: make(chan *NodeMessage, messageChannelSize),
|
||||
TerminateSignal: make(chan struct{}),
|
||||
Action: Action,
|
||||
Key: Key,
|
||||
Nodes: Nodes,
|
||||
ActiveNodes: uint64(len(Nodes)),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CollectResults collects all information request responses within the given timeout.
|
||||
func (ir *InformationRequest) CollectResults(timeout time.Duration) (results []*NodeMessage) {
|
||||
for {
|
||||
select {
|
||||
case result, ok := <-ir.ResultChan:
|
||||
if !ok { // channel closed?
|
||||
return
|
||||
}
|
||||
|
||||
results = append(results, result)
|
||||
|
||||
case <-time.After(timeout):
|
||||
ir.Terminate()
|
||||
return
|
||||
|
||||
case <-ir.TerminateSignal:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Terminate sends the termination signal to all workers. It is safe to call Terminate multiple times.
|
||||
func (ir *InformationRequest) Terminate() {
|
||||
ir.Lock()
|
||||
defer ir.Unlock()
|
||||
|
||||
if ir.IsTerminated {
|
||||
return
|
||||
}
|
||||
|
||||
// set the termination signal
|
||||
ir.IsTerminated = true
|
||||
close(ir.TerminateSignal) // safety guaranteed via lock
|
||||
|
||||
close(ir.ResultChan)
|
||||
}
|
||||
|
||||
// Done is called when a remote node is done.
|
||||
func (ir *InformationRequest) Done() {
|
||||
if atomic.AddUint64(&ir.ActiveNodes, ^uint64(0)) <= 0 {
|
||||
// If the counter reaches 0, it means no nodes are handling this request anymore -> terminate it.
|
||||
ir.Terminate()
|
||||
}
|
||||
}
|
||||
|
||||
// QueueResult accepts incoming results and queues them to the result channel. Non-blocking.
|
||||
func (ir *InformationRequest) QueueResult(message *NodeMessage) {
|
||||
ir.Lock()
|
||||
if !ir.IsTerminated {
|
||||
targetChan := ir.ResultChan
|
||||
if ir.ResultChanExt != nil {
|
||||
targetChan = ir.ResultChanExt
|
||||
}
|
||||
|
||||
select {
|
||||
case targetChan <- message:
|
||||
default:
|
||||
}
|
||||
}
|
||||
ir.Unlock()
|
||||
}
|
||||
133
vendor/github.com/PeernetOfficial/core/dht/Node.go
generated
vendored
Normal file
133
vendor/github.com/PeernetOfficial/core/dht/Node.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
File Name: Node.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package dht
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Node is the over-the-wire representation of a node
|
||||
type Node struct {
|
||||
// ID is the unique identifier
|
||||
ID []byte
|
||||
|
||||
// LastSeen when was this node last considered seen by the DHT
|
||||
LastSeen time.Time
|
||||
|
||||
// Info is an arbitrary pointer specified by the caller
|
||||
Info interface{}
|
||||
}
|
||||
|
||||
// shortList is used in order to sort a list of arbitrary nodes against a comparator. These nodes are sorted by xor distance
|
||||
type shortList struct {
|
||||
// Nodes are a list of nodes to be compared
|
||||
Nodes []*Node
|
||||
|
||||
// Comparator is the ID to compare to
|
||||
Comparator []byte
|
||||
|
||||
// Contacted is a list of nodes that are considered contacted
|
||||
Contacted map[string]bool
|
||||
}
|
||||
|
||||
func newShortList() *shortList {
|
||||
return &shortList{
|
||||
Contacted: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func areNodesEqual(n1 *Node, n2 *Node, allowNilID bool) bool {
|
||||
if n1 == nil || n2 == nil {
|
||||
return false
|
||||
}
|
||||
if !allowNilID {
|
||||
if n1.ID == nil || n2.ID == nil {
|
||||
return false
|
||||
}
|
||||
if bytes.Compare(n1.ID, n2.ID) != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (n *shortList) RemoveNode(ID []byte) {
|
||||
for i := 0; i < n.Len(); i++ {
|
||||
if bytes.Compare(n.Nodes[i].ID, ID) == 0 {
|
||||
n.Nodes = append(n.Nodes[:i], n.Nodes[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *shortList) AppendUniqueNodes(nodes ...*Node) {
|
||||
nodesLoop:
|
||||
for _, vv := range nodes {
|
||||
for _, v := range n.Nodes {
|
||||
if bytes.Compare(v.ID, vv.ID) == 0 {
|
||||
continue nodesLoop
|
||||
}
|
||||
}
|
||||
n.Nodes = append(n.Nodes, vv)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *shortList) Len() int {
|
||||
return len(n.Nodes)
|
||||
}
|
||||
|
||||
func (n *shortList) Swap(i, j int) {
|
||||
n.Nodes[i], n.Nodes[j] = n.Nodes[j], n.Nodes[i]
|
||||
}
|
||||
|
||||
func (n *shortList) Less(i, j int) bool {
|
||||
iDist := getDistance(n.Nodes[i].ID, n.Comparator)
|
||||
jDist := getDistance(n.Nodes[j].ID, n.Comparator)
|
||||
|
||||
return iDist.Cmp(jDist) == -1
|
||||
}
|
||||
|
||||
func getDistance(id1 []byte, id2 []byte) *big.Int {
|
||||
buf1 := new(big.Int).SetBytes(id1)
|
||||
buf2 := new(big.Int).SetBytes(id2)
|
||||
result := new(big.Int).Xor(buf1, buf2)
|
||||
return result
|
||||
}
|
||||
|
||||
// GetUncontacted returns a list of uncontacted nodes. Each returned node will be marked as contacted.
|
||||
func (n *shortList) GetUncontacted(count int, useCount bool) (Nodes []*Node) {
|
||||
for _, node := range n.Nodes {
|
||||
if useCount && count <= 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Don't contact nodes already contacted
|
||||
if n.Contacted[string(node.ID)] == true {
|
||||
continue
|
||||
}
|
||||
|
||||
n.Contacted[string(node.ID)] = true
|
||||
Nodes = append(Nodes, node)
|
||||
|
||||
count--
|
||||
}
|
||||
|
||||
return Nodes
|
||||
}
|
||||
|
||||
// NodeMessage is a message sent by a node
|
||||
type NodeMessage struct {
|
||||
SenderID []byte // Sender of the message
|
||||
Data []byte // FIND_VALUE: Actual data
|
||||
Closest []*Node // FIND_VALUE, FIND_NODE: Closest nodes to the requested key
|
||||
Storing []*Node // FIND_VALUE: Nodes known to store the value
|
||||
}
|
||||
|
||||
// NodeFilterFunc is called to filter nodes based on the callers choice
|
||||
type NodeFilterFunc func(node *Node) (accept bool)
|
||||
9
vendor/github.com/PeernetOfficial/core/dht/README.md
generated
vendored
Normal file
9
vendor/github.com/PeernetOfficial/core/dht/README.md
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# DHT Lite
|
||||
|
||||
This code is a fork from https://github.com/james-lawrence/kademlia and https://github.com/prettymuchbryce/kademlia with modifications for proper abstraction. All networking code was removed from the original one. This package shall only provide DHT fuctionality.
|
||||
|
||||
The following functions are not handled here and must be done by the caller, if desired:
|
||||
* Remove nodes that are deemed inactive via `dht.RemoveNode`.
|
||||
* Provide a function `ShouldEvict` to determine if a node shall be evicted in favor of another one.
|
||||
* Refresh buckets via `dht.RefreshBuckets`.
|
||||
* The actual store data functions (and associated replication/expiration) are not provided, only the functionality to traverse through the network.
|
||||
325
vendor/github.com/PeernetOfficial/core/dht/Search Client.go
generated
vendored
Normal file
325
vendor/github.com/PeernetOfficial/core/dht/Search Client.go
generated
vendored
Normal file
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
File Name: Search Client.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
A search client runs concurrent information requests for a single query. It solves the query efficiently by using levels.
|
||||
Any result that is closer to the target gets pushed down into a new lower level, which contacts nodes closer to the result.
|
||||
Level are running concurrently.
|
||||
*/
|
||||
|
||||
package dht
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MaxAcceptKnownStore is maximum count accepted of known peers that store the value
|
||||
const MaxAcceptKnownStore = 10
|
||||
|
||||
// MaxClosest is maximum number of closest peers accepted
|
||||
const MaxClosest = 3
|
||||
|
||||
// MaxLevel defines the max level.
|
||||
const MaxLevel = 10
|
||||
|
||||
// SearchClient defines a search in the distributed hash table involving multiple information requests.
|
||||
// The search can be created for a node or for a value, both identified by the hash (known as the key).
|
||||
type SearchClient struct {
|
||||
Action int // ActionX
|
||||
Key []byte // Key that is being queried
|
||||
IsTerminated bool // If true, it was signaled for termination
|
||||
TerminateSignal chan struct{} // gets closed on termination signal, can be used in select via "case _ = <- TerminateSignal:"
|
||||
sync.Mutex // for sychronized closing
|
||||
timeStart time.Time // When the search started.
|
||||
timeEnd time.Time // When the search ended.
|
||||
dht *DHT // DHT used
|
||||
timeoutTotal time.Duration // Timeout after the entire search will be terminated client-side.
|
||||
timeoutIR time.Duration // Timeout for information requests (entire roundtrip).
|
||||
alpha int // Count of concurrent information requests per level.
|
||||
Results chan *SearchResult // Result channel
|
||||
list *shortList // List of nodes to contact
|
||||
contactedNodesMap map[string]struct{} // List of nodes already contacted
|
||||
contactedNodesMutex sync.RWMutex // Sync map access
|
||||
storing chan []*Node // Internal channel to signal nodes that indicate storing the searched value.
|
||||
activeLevels uint64 // demo
|
||||
LogStatus func(function, format string, v ...interface{}) // Filter function for status output
|
||||
}
|
||||
|
||||
// SearchResult is a single result to the search. Depending on the search type and parameters, multiple results may be sent.
|
||||
type SearchResult struct {
|
||||
Key []byte // Original key that was searched for
|
||||
Action int // Original action
|
||||
SenderID []byte // Sender node ID of the result
|
||||
|
||||
// data for ActionFindNode
|
||||
TargetNode *Node // The node that was requested.
|
||||
|
||||
// data for ActionFindValue
|
||||
Data []byte // Actual data
|
||||
}
|
||||
|
||||
// NewSearch creates a new search client.
|
||||
// Action indicates the action to take (from ActionX constants), to either find a node, or a value.
|
||||
// Timeout is the total time the search may take, covering all information requests. TimeoutIR is the time an information request may take.
|
||||
// Alpha is the number of concurrent requests that will be performed.
|
||||
func (dht *DHT) NewSearch(Action int, Key []byte, Timeout, TimeoutIR time.Duration, Alpha int) (client *SearchClient) {
|
||||
client = &SearchClient{
|
||||
Action: Action,
|
||||
Key: Key,
|
||||
dht: dht,
|
||||
timeoutTotal: Timeout,
|
||||
timeoutIR: TimeoutIR,
|
||||
alpha: Alpha,
|
||||
contactedNodesMap: make(map[string]struct{}),
|
||||
storing: make(chan []*Node, Alpha*2),
|
||||
TerminateSignal: make(chan struct{}),
|
||||
Results: make(chan *SearchResult),
|
||||
LogStatus: func(function, format string, v ...interface{}) {},
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Terminate sends the termination signal to all workers. It is safe to call Terminate multiple times.
|
||||
func (client *SearchClient) Terminate() {
|
||||
client.Lock()
|
||||
defer client.Unlock()
|
||||
|
||||
if client.IsTerminated {
|
||||
return
|
||||
}
|
||||
|
||||
// set the termination signal
|
||||
client.IsTerminated = true
|
||||
close(client.TerminateSignal) // safety guaranteed via lock
|
||||
|
||||
client.timeEnd = time.Now()
|
||||
|
||||
close(client.Results)
|
||||
close(client.storing)
|
||||
}
|
||||
|
||||
// isContactedNode checks if a node was contacted
|
||||
func (client *SearchClient) isContactedNode(ID []byte, Set bool) (contacted bool) {
|
||||
client.contactedNodesMutex.Lock()
|
||||
_, contacted = client.contactedNodesMap[string(ID)]
|
||||
if Set {
|
||||
client.contactedNodesMap[string(ID)] = struct{}{}
|
||||
}
|
||||
client.contactedNodesMutex.Unlock()
|
||||
return contacted
|
||||
}
|
||||
|
||||
// filterUncontactedNodes returns only nodes that were not contacted so far. All nodes will be set to contacted. Limit is optional (0 for no limit).
|
||||
func (client *SearchClient) filterUncontactedNodes(input []*Node, limit int) (output []*Node) {
|
||||
client.contactedNodesMutex.Lock()
|
||||
|
||||
for _, node := range input {
|
||||
if _, ok := client.contactedNodesMap[string(node.ID)]; !ok {
|
||||
output = append(output, node)
|
||||
client.contactedNodesMap[string(node.ID)] = struct{}{}
|
||||
|
||||
if limit > 0 {
|
||||
limit--
|
||||
if limit == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.contactedNodesMutex.Unlock()
|
||||
return output
|
||||
}
|
||||
|
||||
// SearchAway starts the search. Non-blocking!
|
||||
func (client *SearchClient) SearchAway() {
|
||||
client.timeStart = time.Now()
|
||||
|
||||
// create the first search level and start it
|
||||
client.list = client.dht.ht.getClosestContacts(client.alpha, client.Key, nil)
|
||||
if len(client.list.Nodes) == 0 {
|
||||
client.Terminate()
|
||||
return
|
||||
}
|
||||
|
||||
go client.queryNodesKnownStore()
|
||||
|
||||
// start the first information request
|
||||
go client.startSearch(0)
|
||||
|
||||
// start an automated termination function for the timeout
|
||||
go func(client *SearchClient) {
|
||||
// sleep + watch for closing
|
||||
select {
|
||||
case <-client.TerminateSignal: // exit the function on other signal
|
||||
return
|
||||
case <-time.After(client.timeoutTotal):
|
||||
client.Terminate()
|
||||
}
|
||||
}(client)
|
||||
}
|
||||
|
||||
// sendInfoRequest sends out a new info request to the nodes
|
||||
func (client *SearchClient) sendInfoRequest(nodes []*Node, resultChan chan *NodeMessage) (info *InformationRequest) {
|
||||
if client.IsTerminated {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, node := range nodes {
|
||||
client.LogStatus("search.sendInfoRequest", "contact node %s\n", hex.EncodeToString(node.ID))
|
||||
}
|
||||
|
||||
info = client.dht.NewInformationRequest(client.Action, client.Key, nodes)
|
||||
info.ResultChanExt = resultChan
|
||||
|
||||
switch client.Action {
|
||||
case ActionFindNode:
|
||||
client.dht.SendRequestFindNode(info)
|
||||
case ActionFindValue:
|
||||
client.dht.SendRequestFindValue(info)
|
||||
}
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-client.TerminateSignal:
|
||||
case <-time.After(client.timeoutIR):
|
||||
}
|
||||
info.Terminate()
|
||||
}()
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
// queryNodesKnownStore queries nodes that are known to store the value. Only for ActionFindValue.
|
||||
// Returned 'closest nodes' are ignored, as the queried nodes are expected to store the value. This might be adjusted in the future.
|
||||
func (client *SearchClient) queryNodesKnownStore() {
|
||||
// all results are redirected to a single channel
|
||||
resultChan := make(chan *NodeMessage, client.alpha)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-client.TerminateSignal:
|
||||
return
|
||||
|
||||
case nodes := <-client.storing:
|
||||
client.sendInfoRequest(nodes, resultChan)
|
||||
|
||||
case result := <-resultChan:
|
||||
if len(result.Data) > 0 {
|
||||
client.Results <- &SearchResult{Key: client.Key, Action: client.Action, SenderID: result.SenderID, Data: result.Data}
|
||||
client.Terminate()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (client *SearchClient) startSearch(level int) {
|
||||
atomic.AddUint64(&client.activeLevels, 1)
|
||||
defer atomic.AddUint64(&client.activeLevels, ^uint64(0))
|
||||
nestedStarted := false
|
||||
|
||||
results := make(chan *NodeMessage, client.alpha*2)
|
||||
|
||||
closestNode := client.list.Nodes[0]
|
||||
|
||||
// start an info request
|
||||
startInfoRequest := func() (info *InformationRequest) {
|
||||
nodes := client.list.GetUncontacted(client.alpha, true)
|
||||
if len(nodes) == 0 {
|
||||
client.LogStatus("search.startSearch", "search in level %d aborted, no new nodes to contact\n", level)
|
||||
return nil
|
||||
}
|
||||
client.LogStatus("search.startSearch", "start search in level %d contacting %d nodes\n", level, len(nodes))
|
||||
return client.sendInfoRequest(nodes, results)
|
||||
}
|
||||
|
||||
info := startInfoRequest()
|
||||
if info == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-client.TerminateSignal:
|
||||
client.LogStatus("search.startSearch", "search in level %d aborted, search client termination signal\n", level)
|
||||
return
|
||||
|
||||
case result := <-results:
|
||||
|
||||
switch client.Action {
|
||||
case ActionFindValue:
|
||||
// search for value and it was found?
|
||||
if len(result.Data) > 0 {
|
||||
client.LogStatus("search.startSearch", "result: sender %s: data found (%d bytes)\n", hex.EncodeToString(result.SenderID), len(result.Data))
|
||||
client.Results <- &SearchResult{Key: client.Key, Action: client.Action, SenderID: result.SenderID, Data: result.Data}
|
||||
client.Terminate()
|
||||
return
|
||||
}
|
||||
|
||||
result.Storing = client.filterUncontactedNodes(result.Storing, MaxAcceptKnownStore)
|
||||
result.Closest = client.filterUncontactedNodes(result.Closest, MaxClosest)
|
||||
|
||||
client.LogStatus("search.startSearch", "result: sender %s: %d uncontacted nodes store and %d nodes are close to value\n", hex.EncodeToString(result.SenderID), len(result.Storing), len(result.Closest))
|
||||
|
||||
// Find value: Nodes known to store the value are queried in a separate function.
|
||||
if len(result.Storing) > 0 {
|
||||
client.storing <- result.Storing
|
||||
}
|
||||
|
||||
case ActionFindNode:
|
||||
// search for node and it was found?
|
||||
for _, closePeer := range result.Closest {
|
||||
if bytes.Equal(closePeer.ID, client.Key) {
|
||||
client.LogStatus("search.startSearch", "result: sender %s: node found!\n", hex.EncodeToString(result.SenderID))
|
||||
client.Results <- &SearchResult{Key: client.Key, Action: client.Action, SenderID: result.SenderID, TargetNode: closePeer}
|
||||
client.Terminate()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
result.Closest = client.filterUncontactedNodes(result.Closest, MaxClosest)
|
||||
|
||||
client.LogStatus("search.startSearch", "find node: sender %s: %d nodes are close to value\n", hex.EncodeToString(result.SenderID), len(result.Closest))
|
||||
|
||||
}
|
||||
|
||||
// Add closest to list
|
||||
client.list.AppendUniqueNodes(result.Closest...)
|
||||
|
||||
// If no subsequent level, and there's closer nodes, start one!
|
||||
if !nestedStarted && !bytes.Equal(client.list.Nodes[0].ID, closestNode.ID) && level < MaxLevel {
|
||||
nestedStarted = true
|
||||
go client.startSearch(level + 1)
|
||||
}
|
||||
|
||||
case <-info.TerminateSignal:
|
||||
// If highest level (= not nested), and there was no conclusive result, try one more round.
|
||||
// This helps against result poisoning.
|
||||
|
||||
if !nestedStarted {
|
||||
client.LogStatus("search.startSearch", "search in level %d aborted, info request termination signal. Final try.\n", level)
|
||||
if info = startInfoRequest(); info != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if client.activeLevels == 1 { // if this was the last level, no more results will appear
|
||||
client.LogStatus("search.startSearch", "level %d last active level, not found, terminate search\n", level)
|
||||
client.Terminate()
|
||||
} else {
|
||||
client.LogStatus("search.startSearch", "level %d end, info request termination signal\n", level)
|
||||
}
|
||||
return
|
||||
|
||||
//case <-time.After(time.Second):
|
||||
// Future todo: Launch another routine with the with uncontacted nodes if any, to speed up the query
|
||||
}
|
||||
}
|
||||
}
|
||||
57
vendor/github.com/PeernetOfficial/core/merkle/Fragment.go
generated
vendored
Normal file
57
vendor/github.com/PeernetOfficial/core/merkle/Fragment.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
File Name: Fragment.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package merkle
|
||||
|
||||
// MinimumFragmentSize is the minimum size a fragment must be. Merkle trees are not used equal or below that size.
|
||||
const MinimumFragmentSize = 256 * KB
|
||||
|
||||
const KB = 1024
|
||||
const MB = 1024 * KB
|
||||
const GB = 1024 * MB
|
||||
const TB = 1024 * GB
|
||||
const PB = 1024 * TB
|
||||
|
||||
// CalculateFragmentSize calculates the fragment size based on the file size.
|
||||
func CalculateFragmentSize(fileSize uint64) (fragmentSize uint64) {
|
||||
switch {
|
||||
case fileSize <= 256*MB:
|
||||
return 256 * KB
|
||||
|
||||
case fileSize <= 1*GB:
|
||||
return 512 * KB
|
||||
|
||||
case fileSize <= 2*GB:
|
||||
return 1 * MB
|
||||
|
||||
case fileSize <= 4*GB:
|
||||
return 2 * MB
|
||||
|
||||
case fileSize <= 8*GB:
|
||||
return 8 * MB
|
||||
|
||||
case fileSize <= 16*GB:
|
||||
return 16 * MB
|
||||
|
||||
case fileSize <= 32*GB:
|
||||
return 32 * MB
|
||||
|
||||
case fileSize <= 64*GB:
|
||||
return 64 * MB
|
||||
|
||||
case fileSize <= 1*TB:
|
||||
return 64 * MB
|
||||
|
||||
case fileSize <= 2*TB:
|
||||
return 128 * MB
|
||||
|
||||
case fileSize <= 1*PB:
|
||||
return 512 * MB
|
||||
|
||||
default: // 1 PB+
|
||||
return 1 * GB
|
||||
}
|
||||
}
|
||||
311
vendor/github.com/PeernetOfficial/core/merkle/Merkle Tree.go
generated
vendored
Normal file
311
vendor/github.com/PeernetOfficial/core/merkle/Merkle Tree.go
generated
vendored
Normal file
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
File Name: Merkle Tree.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Generates the merkle tree based on input data.
|
||||
In case of uneven number of fragments, the last uneven fragment is moved up a level.
|
||||
*/
|
||||
|
||||
package merkle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"lukechampine.com/blake3"
|
||||
)
|
||||
|
||||
// MerkleTree represents an entire merkle tree
|
||||
type MerkleTree struct {
|
||||
// information about the original file
|
||||
FileSize uint64
|
||||
FragmentSize uint64
|
||||
FragmentCount uint64
|
||||
|
||||
// list of hashes
|
||||
FragmentHashes [][]byte // List of hashes for each fragment
|
||||
MiddleHashes [][][]byte // All hashes in the middle, bottom up.
|
||||
RootHash []byte // Root hash.
|
||||
}
|
||||
|
||||
// NewMerkleTree creates a new merkle tree from the input
|
||||
func NewMerkleTree(fileSize, fragmentSize uint64, reader io.Reader) (tree *MerkleTree, err error) {
|
||||
if fragmentSize == 0 {
|
||||
return nil, errors.New("invalid fragment size")
|
||||
}
|
||||
|
||||
tree = &MerkleTree{
|
||||
FileSize: fileSize,
|
||||
FragmentSize: fragmentSize,
|
||||
FragmentCount: fileSizeToFragmentCount(fileSize, fragmentSize),
|
||||
}
|
||||
|
||||
// Special case: No fragments, in case of empty data.
|
||||
if tree.FragmentCount == 0 {
|
||||
hash := blake3.Sum256(nil)
|
||||
tree.RootHash = hash[:]
|
||||
|
||||
return tree, nil
|
||||
} else if tree.FragmentCount == 1 {
|
||||
// Special case: Single fragment.
|
||||
data := make([]byte, fileSize)
|
||||
if _, err := io.ReadAtLeast(reader, data, int(fileSize)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hash := blake3.Sum256(data)
|
||||
tree.RootHash = hash[:]
|
||||
|
||||
return tree, nil
|
||||
}
|
||||
|
||||
// calculate the hash per fragment
|
||||
data := make([]byte, fragmentSize)
|
||||
remaining := fileSize
|
||||
|
||||
for n := uint64(0); n < tree.FragmentCount; n++ {
|
||||
if fragmentSize > remaining {
|
||||
fragmentSize = remaining
|
||||
}
|
||||
|
||||
if _, err := io.ReadAtLeast(reader, data, int(fragmentSize)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// hash the fragment
|
||||
hash := blake3.Sum256(data[:fragmentSize])
|
||||
|
||||
tree.FragmentHashes = append(tree.FragmentHashes, hash[:])
|
||||
|
||||
remaining -= fragmentSize
|
||||
}
|
||||
|
||||
// calculate the intermediate hashes
|
||||
tree.calculateMiddleHashes(0)
|
||||
|
||||
return tree, nil
|
||||
}
|
||||
|
||||
func fileSizeToFragmentCount(fileSize, fragmentSize uint64) (count uint64) {
|
||||
return (fileSize + fragmentSize - 1) / fragmentSize
|
||||
}
|
||||
|
||||
func (tree *MerkleTree) calculateMiddleHashes(level uint64) {
|
||||
if len(tree.FragmentHashes) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var newHashes, inputHashes [][]byte
|
||||
|
||||
if level == 0 {
|
||||
inputHashes = tree.FragmentHashes
|
||||
} else {
|
||||
inputHashes = tree.MiddleHashes[level-1]
|
||||
}
|
||||
|
||||
for n := 0; n+1 <= len(inputHashes)-1; n += 2 {
|
||||
newHashes = append(newHashes, calculateMiddleHash(inputHashes[n], inputHashes[n+1]))
|
||||
}
|
||||
|
||||
// Uneven leafs? in this case the new hash is just a copy of the uneven one. No point in artifically recalcualting it with itself like Bitcoin does.
|
||||
// For other possible implementations see https://medium.com/coinmonks/merkle-trees-concepts-and-use-cases-5da873702318.
|
||||
if len(inputHashes)%2 != 0 {
|
||||
newHashes = append(newHashes, inputHashes[len(inputHashes)-1])
|
||||
}
|
||||
|
||||
if len(newHashes) == 1 {
|
||||
// Only one hash generated.
|
||||
tree.RootHash = newHashes[0]
|
||||
} else if len(newHashes) > 1 {
|
||||
tree.MiddleHashes = append(tree.MiddleHashes, newHashes)
|
||||
|
||||
tree.calculateMiddleHashes(level + 1)
|
||||
}
|
||||
}
|
||||
|
||||
func calculateMiddleHash(hash1 []byte, hash2 []byte) (newHash []byte) {
|
||||
var data []byte
|
||||
data = append(data, hash1...)
|
||||
data = append(data, hash2...)
|
||||
|
||||
hash := blake3.Sum256(data)
|
||||
|
||||
return hash[:]
|
||||
}
|
||||
|
||||
// CreateVerification returns the verification hashes for the given fragment number. The root hash itself is not included.
|
||||
// The result might be empty if there is no or a single fragment.
|
||||
// Each verification hash has a preceding left (= 0)/right (= 1) indicator that indicates where the verification is positioned.
|
||||
// This makes the algorithm future proof, in case uneven leafs will be handled differently.
|
||||
func (tree *MerkleTree) CreateVerification(fragment uint64) (verificationHashes [][]byte) {
|
||||
// 0 fragments: Empty data.
|
||||
// 1 fragment: The hash of the fragment is the root hash.
|
||||
if tree.FragmentCount <= 1 {
|
||||
return nil
|
||||
} else if fragment >= tree.FragmentCount {
|
||||
// invalid fragment index
|
||||
return nil
|
||||
}
|
||||
|
||||
// first hash it he neighbor fragment hash, if available
|
||||
if fragment == tree.FragmentCount-1 && fragment%2 == 0 {
|
||||
} else if fragment%2 == 0 {
|
||||
verificationHashes = append(verificationHashes, append([]byte{1}, tree.FragmentHashes[fragment+1]...))
|
||||
} else {
|
||||
verificationHashes = append(verificationHashes, append([]byte{0}, tree.FragmentHashes[fragment-1]...))
|
||||
}
|
||||
|
||||
// go through all middle hash levels
|
||||
for n := 0; n < len(tree.MiddleHashes); n++ {
|
||||
fragment = fragment / 2
|
||||
|
||||
if fragment == uint64(len(tree.MiddleHashes[n])-1) && fragment%2 == 0 {
|
||||
} else if fragment%2 == 0 {
|
||||
verificationHashes = append(verificationHashes, append([]byte{1}, tree.MiddleHashes[n][fragment+1]...))
|
||||
} else {
|
||||
verificationHashes = append(verificationHashes, append([]byte{0}, tree.MiddleHashes[n][fragment-1]...))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MerkleVerify validates the hashed data against the verification hashes and the known root hash.
|
||||
func MerkleVerify(rootHash []byte, dataHash []byte, verificationHashes [][]byte) (valid bool) {
|
||||
for _, verifyHash := range verificationHashes {
|
||||
if verifyHash[0] == 0 {
|
||||
dataHash = calculateMiddleHash(verifyHash[1:], dataHash)
|
||||
} else {
|
||||
dataHash = calculateMiddleHash(dataHash, verifyHash[1:])
|
||||
}
|
||||
}
|
||||
|
||||
return bytes.Equal(rootHash, dataHash)
|
||||
}
|
||||
|
||||
/*
|
||||
Export/Import of the merkle tree structure:
|
||||
|
||||
Offset Size Info
|
||||
0 8 File Size
|
||||
8 8 Fragment Size
|
||||
16 32 Merkle Root Hash
|
||||
48 32 * n Fragment Hashes
|
||||
? 32 * n Middle Hashes
|
||||
|
||||
*/
|
||||
|
||||
const MerkleTreeFileHeaderSize = 8 + 8 + 32
|
||||
|
||||
// calculateTotalHashCount returns the total number of fragment and middle hashes needed for the given count of fragments
|
||||
func calculateTotalHashCount(fragmentCount uint64) (count uint64) {
|
||||
// Special case no or 1 fragment: None needed, since the fragment hash is directly stored as root hash.
|
||||
if fragmentCount <= 1 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Equal count of fragment hashes needed
|
||||
count = fragmentCount
|
||||
|
||||
// Calculate middle hashes number
|
||||
for countHashesLast := fragmentCount; ; {
|
||||
countMiddleNew := (countHashesLast + 1) / 2 // round up
|
||||
if countMiddleNew <= 1 {
|
||||
break
|
||||
}
|
||||
|
||||
count += countMiddleNew
|
||||
countHashesLast = countMiddleNew
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
// Export stores the tree as blob
|
||||
func (tree *MerkleTree) Export() (data []byte) {
|
||||
data = make([]byte, MerkleTreeFileHeaderSize+calculateTotalHashCount(tree.FragmentCount)*32)
|
||||
|
||||
// header
|
||||
binary.LittleEndian.PutUint64(data[0:8], tree.FileSize)
|
||||
binary.LittleEndian.PutUint64(data[8:16], tree.FragmentSize)
|
||||
copy(data[16:16+32], tree.RootHash)
|
||||
|
||||
// fragment hashes
|
||||
offset := 48
|
||||
for _, hash := range tree.FragmentHashes {
|
||||
copy(data[offset:offset+32], hash)
|
||||
offset += 32
|
||||
}
|
||||
|
||||
// middle hashes
|
||||
for n := 0; n < len(tree.MiddleHashes); n++ {
|
||||
for _, hash := range tree.MiddleHashes[n] {
|
||||
copy(data[offset:offset+32], hash)
|
||||
offset += 32
|
||||
}
|
||||
}
|
||||
|
||||
return data[:offset]
|
||||
}
|
||||
|
||||
// Import reads the tree from the input data
|
||||
func ImportMerkleTree(data []byte) (tree *MerkleTree) {
|
||||
if tree = ReadMerkleTreeHeader(data); tree == nil || tree.FragmentCount <= 1 {
|
||||
return tree
|
||||
}
|
||||
|
||||
// verify size
|
||||
if uint64(len(data)) < MerkleTreeFileHeaderSize+calculateTotalHashCount(tree.FragmentCount)*32 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// fragment hashes
|
||||
offset := 48
|
||||
for n := 0; n < int(tree.FragmentCount); n++ {
|
||||
hash := data[offset : offset+32]
|
||||
tree.FragmentHashes = append(tree.FragmentHashes, hash)
|
||||
offset += 32
|
||||
}
|
||||
|
||||
// middle hashes
|
||||
n := tree.FragmentCount / 2
|
||||
if tree.FragmentCount > 2 && tree.FragmentCount%2 != 0 {
|
||||
n++
|
||||
}
|
||||
|
||||
for ; n > 1; n = n / 2 {
|
||||
var hashList [][]byte
|
||||
for m := uint64(0); m < n; m++ {
|
||||
hash := data[offset : offset+32]
|
||||
hashList = append(hashList, hash)
|
||||
offset += 32
|
||||
}
|
||||
|
||||
tree.MiddleHashes = append(tree.MiddleHashes, hashList)
|
||||
if len(hashList)%2 != 0 {
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ReadMerkleTreeHeader reads the merkle tree header. Fragment and middle hashes are not loaded.
|
||||
func ReadMerkleTreeHeader(data []byte) (tree *MerkleTree) {
|
||||
// Read the header. Enforce the minimum size.
|
||||
if len(data) < MerkleTreeFileHeaderSize {
|
||||
return nil
|
||||
}
|
||||
|
||||
tree = &MerkleTree{
|
||||
FileSize: binary.LittleEndian.Uint64(data[0:8]),
|
||||
FragmentSize: binary.LittleEndian.Uint64(data[8:16]),
|
||||
}
|
||||
tree.FragmentCount = fileSizeToFragmentCount(tree.FileSize, tree.FragmentSize)
|
||||
tree.RootHash = data[16 : 16+32]
|
||||
|
||||
return tree
|
||||
}
|
||||
70
vendor/github.com/PeernetOfficial/core/merkle/readme.md
generated
vendored
Normal file
70
vendor/github.com/PeernetOfficial/core/merkle/readme.md
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
# Fragment
|
||||
|
||||
This package implements file fragmentation. Files are split into smaller fragments of equal size (except the last one) to make it more convenient to share a file between multiple peers. This approach is similar to how Torrents are dividing files into chunks called "pieces".
|
||||
|
||||
All fragments are hashed and organized in a merkle tree. The `root hash` of this merkle tree is stored together with the `fragment size` on the blockchain for each file.
|
||||
|
||||
## Merkle Tree
|
||||
|
||||
A good explanation on how merkle trees work is published in the [BitTorrent BEP 30](https://www.bittorrent.org/beps/bep_0030.html). The advantage of using a merkle tree is that each fragment can be verified against the root hash by only providing its sibling and uncle hashes. Receivers can independently verify the validity of the content by using the provided sibling and uncle hashes to generate the root hash and compare it against the known root hash.
|
||||
|
||||
This means that only the sender needs to keep the hashes "in the middle" and a pre-data hash list exchange is not needed.
|
||||
|
||||
A hash in the tree is generated by concatenating the 2 hashes of its children in binary representation and hashing that aggregate. Blake3 is used as hashing algorithm.
|
||||
|
||||
## Fragment Size
|
||||
|
||||
Files that are stored should be accompanied by a separate file that stores the entire merkle tree (including all leafs and the middle hash). This allows to serve the file and the proof without any computation.
|
||||
|
||||
The total number of hashes (including the root hash) is `2*Fragments -1`.
|
||||
|
||||
Example: If a 4 GB file is split into 1 MB sized fragments, there are 4096 fragments. This means 8191 hashes, requiring at least 256 KB for the accompanying merkle tree file.
|
||||
|
||||
### Considerations
|
||||
|
||||
* Security: Larger fragment sizes increase the size of data to download before detecting an invalid fragment.
|
||||
* Deduplication: Smaller chunks may result in higher deduplication.
|
||||
* Streaming: For streaming videos a smaller size would be preferred, otherwise expensive buffering might be required. Larger fragments may result in noticable waiting times for the end user.
|
||||
* Overhead: The smaller the fragment size, the more fragments, thus more individual pieces to compute. The time when generating the merkle tree initially can be usually neglected (since it only happens once), but too small fragments may result in unnecessary computations when exchanging fragments.
|
||||
|
||||
### Current Algorithm
|
||||
|
||||
The current algorithm identifies static fragment sizes based on the file size per below table.
|
||||
|
||||
| File Size (max) | Fragment Size | Max Fragment Count | Merkle Tree File |
|
||||
| --------------- | ------------- | ------------------ | ---------------- |
|
||||
| 256 MB | 256 KB | 1024 | 64 KB |
|
||||
| 1 GB | 512 KB | 2048 | 128 KB |
|
||||
| 2 GB | 1 MB | 2048 | 128 KB |
|
||||
| 4 GB | 2 MB | 2048 | 128 KB |
|
||||
| 8 GB | 8 MB | 1024 | 64 KB |
|
||||
| 16 GB | 16 MB | 1024 | 64 KB |
|
||||
| 32 GB | 32 MB | 1024 | 64 KB |
|
||||
| 64 GB | 64 MB | 1024 | 64 KB |
|
||||
| 1 TB | 64 MB | 16384 | 1 MB |
|
||||
| 2 TB | 128 MB | 16384 | 1 MB |
|
||||
| 1 PB | 512 MB | 2097152 | 128 MB |
|
||||
| 1 PB + | 1 GB | 1048576 + | 64 MB + |
|
||||
|
||||
## Other Algorithms
|
||||
|
||||
### Video Streaming
|
||||
|
||||
According to the [Netflix documentation](https://help.netflix.com/en/node/306), SD quality requires 384 KB/s, HD 640 KB/s, and 4k 3.2 MB/s.
|
||||
|
||||
An example movie "Home Alone (1990) 1080p" (which would obviously require a license from 20th Century Studios Inc for distribution) is about 1.6 GB. This classic has an input bitrate of about 2 MB/s. The proposed fragment size of 1 MB seems appropriate for videos that size and codec.
|
||||
|
||||
### Torrent Piece Size
|
||||
|
||||
The target piece size in torrents is a well-discussed topic. The lower limit appears to be 256KB and upper limit 64MB (at least formerly).
|
||||
|
||||
* [Vuze Wiki](https://wiki.vuze.com/w/Torrent_Piece_Size) from 2005 suggests "a torrent should have around 1000-1500 pieces, to get a reasonably small torrent file and an efficient client and swarm download".
|
||||
* [TorrentFreak](https://torrentfreak.com/how-to-make-the-best-torrents-081121/) in 2008 suggests "the optimum number of pieces seems to be between 1200 and 2200".
|
||||
* [A random Reddit post](https://www.reddit.com/r/torrents/comments/dzxfz1/2019_whats_ideal_piece_size/) from 2019 suggests a max of 64 MB.
|
||||
* [This Reddit comment](https://www.reddit.com/r/torrents/comments/p8qr3s/comment/h9u1aym/) from 2021 lists information about unusually large torrent files which may be useful for considering real life edge cases.
|
||||
|
||||
### IPFS Block Size
|
||||
|
||||
In IPFS "a block refers to a single unit of data, identified by its key (hash)" according to the [IPFS documentation](https://docs.ipfs.io/how-to/work-with-blocks/#what-to-do-with-blocks).
|
||||
|
||||
In an [issue from 2016](https://github.com/ipfs/go-ipfs/issues/3104) IPFS is described to have a block size of 1 MB (and libp2p enforcing a 2 MB limit). The reason is stated for security (against DoS), deduplication, latency, bandwidth, and performance.
|
||||
27
vendor/github.com/PeernetOfficial/core/protocol/Command.go
generated
vendored
Normal file
27
vendor/github.com/PeernetOfficial/core/protocol/Command.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
File Name: Command.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
// Commands between peers
|
||||
const (
|
||||
// Peer List Management
|
||||
CommandAnnouncement = 0 // Announcement
|
||||
CommandResponse = 1 // Response
|
||||
CommandPing = 2 // Keep-alive message (no payload).
|
||||
CommandPong = 3 // Response to ping (no payload).
|
||||
CommandLocalDiscovery = 4 // Local discovery
|
||||
CommandTraverse = 5 // Help establish a connection between 2 remote peers
|
||||
|
||||
// Blockchain
|
||||
CommandGetBlock = 6 // Request blocks for specified peer.
|
||||
|
||||
// File Discovery
|
||||
CommandTransfer = 8 // File transfer.
|
||||
|
||||
// Debug
|
||||
CommandChat = 10 // Chat message [debug]
|
||||
)
|
||||
49
vendor/github.com/PeernetOfficial/core/protocol/File Transfer.go
generated
vendored
Normal file
49
vendor/github.com/PeernetOfficial/core/protocol/File Transfer.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
File Name: File Transfer.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Encoding of file transfer protocol. Each transfer starts with a header:
|
||||
Offset Size Info
|
||||
0 8 Total File Size
|
||||
8 8 Transfer Size
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// FileTransferWriteHeader starts writing the header for a file transfer.
|
||||
func FileTransferWriteHeader(writer io.Writer, fileSize, transferSize uint64) (err error) {
|
||||
// Send the header: Total File Size and Transfer Size.
|
||||
header := make([]byte, 16)
|
||||
binary.LittleEndian.PutUint64(header[0:8], fileSize)
|
||||
binary.LittleEndian.PutUint64(header[8:16], transferSize)
|
||||
if n, err := writer.Write(header); err != nil {
|
||||
return err
|
||||
} else if n != len(header) {
|
||||
return errors.New("error sending header")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FileTransferReadHeader starts reading the header for a file transfer. It will only read the header and keeps the connection open.
|
||||
func FileTransferReadHeader(reader io.Reader) (fileSize, transferSize uint64, err error) {
|
||||
// read the header
|
||||
header := make([]byte, 16)
|
||||
if n, err := reader.Read(header); err != nil {
|
||||
return 0, 0, err
|
||||
} else if n != len(header) {
|
||||
return 0, 0, errors.New("error reading header")
|
||||
}
|
||||
|
||||
fileSize = binary.LittleEndian.Uint64(header[0:8])
|
||||
transferSize = binary.LittleEndian.Uint64(header[8:16])
|
||||
|
||||
return fileSize, transferSize, nil
|
||||
}
|
||||
27
vendor/github.com/PeernetOfficial/core/protocol/Hash.go
generated
vendored
Normal file
27
vendor/github.com/PeernetOfficial/core/protocol/Hash.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
File Name: Hash.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
"lukechampine.com/blake3"
|
||||
)
|
||||
|
||||
// HashData abstracts the hash function.
|
||||
func HashData(data []byte) (hash []byte) {
|
||||
hash32 := blake3.Sum256(data)
|
||||
return hash32[:]
|
||||
}
|
||||
|
||||
// HashSize is blake3 hash digest size = 256 bits
|
||||
const HashSize = 32
|
||||
|
||||
// PublicKey2NodeID translates the Public Key into the node ID used in the Kademlia network.
|
||||
// It is also referenced in various other places including blockchain data at runtime. The node ID identifies the owner.
|
||||
func PublicKey2NodeID(publicKey *btcec.PublicKey) (nodeID []byte) {
|
||||
return HashData(publicKey.SerializeCompressed())
|
||||
}
|
||||
310
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Announcement.go
generated
vendored
Normal file
310
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Announcement.go
generated
vendored
Normal file
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
File Name: Message Encoding Announcement.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// MessageAnnouncement is the decoded announcement message.
|
||||
type MessageAnnouncement struct {
|
||||
*MessageRaw // Underlying raw message
|
||||
Protocol uint8 // Protocol version supported (low 4 bits).
|
||||
Features uint8 // Feature support
|
||||
Actions uint8 // Action bit array. See ActionX
|
||||
BlockchainHeight uint64 // Blockchain height
|
||||
BlockchainVersion uint64 // Blockchain version
|
||||
PortInternal uint16 // Internal port. Can be used to detect NATs.
|
||||
PortExternal uint16 // External port if known. 0 if not. Can be used for UPnP support.
|
||||
UserAgent string // User Agent. Format "Software/Version". Required in the initial announcement/bootstrap. UTF-8 encoded. Max length is 255 bytes.
|
||||
FindPeerKeys []KeyHash // FIND_PEER data
|
||||
FindDataKeys []KeyHash // FIND_VALUE data
|
||||
InfoStoreFiles []InfoStore // INFO_STORE data
|
||||
}
|
||||
|
||||
// KeyHash is a single blake3 key hash
|
||||
type KeyHash struct {
|
||||
Hash []byte
|
||||
}
|
||||
|
||||
// InfoStore informs about files stored
|
||||
type InfoStore struct {
|
||||
ID KeyHash // Hash of the file
|
||||
Size uint64 // Size of the file
|
||||
Type uint8 // Type of the file: 0 = File, 1 = Header file containing list of parts
|
||||
}
|
||||
|
||||
// Features are sent as bit array in the Announcement message.
|
||||
const (
|
||||
FeatureIPv4Listen = 0 // Sender listens on IPv4
|
||||
FeatureIPv6Listen = 1 // Sender listens on IPv6
|
||||
FeatureFirewall = 2 // Sender indicates a potential firewall. This informs uncontacted peers that a Traverse message might be required to establish a connection.
|
||||
)
|
||||
|
||||
// Actions between peers, sent via Announcement message. They correspond to the bit array index.
|
||||
const (
|
||||
ActionFindSelf = 0 // FIND_SELF Request closest neighbors to self
|
||||
ActionFindPeer = 1 // FIND_PEER Request closest neighbors to target peer
|
||||
ActionFindValue = 2 // FIND_VALUE Request data or closest peers
|
||||
ActionInfoStore = 3 // INFO_STORE Sender indicates storing provided data
|
||||
)
|
||||
|
||||
// Minimum length of Announcement payload header without User Agent
|
||||
const announcementPayloadHeaderSize = 24
|
||||
|
||||
// DecodeAnnouncement decodes the incoming announcement message. Returns nil if invalid.
|
||||
func DecodeAnnouncement(msg *MessageRaw) (result *MessageAnnouncement, err error) {
|
||||
result = &MessageAnnouncement{
|
||||
MessageRaw: msg,
|
||||
}
|
||||
|
||||
if len(msg.Payload) < announcementPayloadHeaderSize {
|
||||
return nil, errors.New("announcement: invalid minimum length")
|
||||
}
|
||||
|
||||
result.Protocol = msg.Payload[0] & 0x0F // Protocol version support is stored in the first 4 bits
|
||||
result.Features = msg.Payload[1] // Feature support
|
||||
result.Actions = msg.Payload[2]
|
||||
result.BlockchainHeight = binary.LittleEndian.Uint64(msg.Payload[3 : 3+8])
|
||||
result.BlockchainVersion = binary.LittleEndian.Uint64(msg.Payload[11 : 11+8])
|
||||
result.PortInternal = binary.LittleEndian.Uint16(msg.Payload[19 : 19+2])
|
||||
result.PortExternal = binary.LittleEndian.Uint16(msg.Payload[21 : 21+2])
|
||||
|
||||
userAgentLength := int(msg.Payload[23])
|
||||
if userAgentLength > 0 {
|
||||
if userAgentLength > len(msg.Payload)-announcementPayloadHeaderSize {
|
||||
return nil, errors.New("announcement: user agent overflow")
|
||||
}
|
||||
|
||||
userAgentB := msg.Payload[announcementPayloadHeaderSize : announcementPayloadHeaderSize+userAgentLength]
|
||||
if !utf8.Valid(userAgentB) {
|
||||
return nil, errors.New("announcement: user agent invalid encoding")
|
||||
}
|
||||
|
||||
result.UserAgent = string(userAgentB)
|
||||
}
|
||||
|
||||
data := msg.Payload[announcementPayloadHeaderSize+userAgentLength:]
|
||||
|
||||
// FIND_PEER
|
||||
if result.Actions&(1<<ActionFindPeer) > 0 {
|
||||
keys, read, valid := decodeKeys(data)
|
||||
if !valid {
|
||||
return nil, errors.New("announcement: FIND_PEER invalid data")
|
||||
}
|
||||
|
||||
data = data[read:]
|
||||
result.FindPeerKeys = keys
|
||||
}
|
||||
|
||||
// FIND_VALUE
|
||||
if result.Actions&(1<<ActionFindValue) > 0 {
|
||||
keys, read, valid := decodeKeys(data)
|
||||
if !valid {
|
||||
return nil, errors.New("announcement: FIND_VALUE invalid data")
|
||||
}
|
||||
|
||||
data = data[read:]
|
||||
result.FindDataKeys = keys
|
||||
}
|
||||
|
||||
// INFO_STORE
|
||||
if result.Actions&(1<<ActionInfoStore) > 0 {
|
||||
files, _, valid := decodeInfoStore(data)
|
||||
if !valid {
|
||||
return nil, errors.New("announcement: INFO_STORE invalid data")
|
||||
}
|
||||
|
||||
// commented out because never used
|
||||
//data = data[read:]
|
||||
result.InfoStoreFiles = files
|
||||
}
|
||||
|
||||
// Accept extra data in case future features append additional data
|
||||
//if len(data) > 0 {
|
||||
// return nil, errors.New("announcement: Unexpected extra data")
|
||||
//}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// decodeKeys decodes keys. Header is 2 bytes (count) followed by the actual keys (each 32 bytes blake3 hash).
|
||||
func decodeKeys(data []byte) (keys []KeyHash, read int, valid bool) {
|
||||
if len(data) < 2+HashSize { // minimum length
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
count := binary.LittleEndian.Uint16(data[0:2])
|
||||
|
||||
if read = 2 + int(count)*HashSize; len(data) < read {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
for n := 0; n < int(count); n++ {
|
||||
key := make([]byte, HashSize)
|
||||
copy(key, data[2+n*HashSize:2+n*HashSize+HashSize])
|
||||
keys = append(keys, KeyHash{Hash: key})
|
||||
}
|
||||
|
||||
return keys, read, true
|
||||
}
|
||||
|
||||
func decodeInfoStore(data []byte) (files []InfoStore, read int, valid bool) {
|
||||
if len(data) < 2+41 { // minimum length
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
count := binary.LittleEndian.Uint16(data[0:2])
|
||||
|
||||
if read = 2 + int(count)*41; len(data) < read {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
for n := 0; n < int(count); n++ {
|
||||
file := InfoStore{}
|
||||
file.ID.Hash = make([]byte, HashSize)
|
||||
copy(file.ID.Hash, data[2+n*41:2+n*41+HashSize])
|
||||
file.Size = binary.LittleEndian.Uint64(data[2+n*41+32 : 2+n*41+32+8])
|
||||
file.Type = data[2+n*41+40]
|
||||
|
||||
files = append(files, file)
|
||||
}
|
||||
|
||||
return files, read, true
|
||||
}
|
||||
|
||||
// EncodeAnnouncement encodes an announcement message. It may return multiple messages if the input does not fit into one.
|
||||
// findPeer is a list of node IDs (blake3 hash of peer ID compressed form)
|
||||
// findValue is a list of hashes
|
||||
// files is a list of files stored to inform about
|
||||
func EncodeAnnouncement(sendUA, findSelf bool, findPeer []KeyHash, findValue []KeyHash, files []InfoStore, features byte, blockchainHeight, blockchainVersion uint64, userAgent string) (packetsRaw [][]byte) {
|
||||
createPacketLoop:
|
||||
for {
|
||||
raw := make([]byte, 64*1024) // max UDP packet size
|
||||
packetSize := announcementPayloadHeaderSize
|
||||
|
||||
raw[0] = byte(ProtocolVersion) // Protocol
|
||||
raw[1] = features // Feature support
|
||||
//raw[2] = Actions // Action bit array
|
||||
|
||||
binary.LittleEndian.PutUint64(raw[3:3+8], blockchainHeight)
|
||||
binary.LittleEndian.PutUint64(raw[11:11+8], blockchainVersion)
|
||||
|
||||
// only on initial announcement the User Agent must be provided according to the protocol spec
|
||||
if sendUA {
|
||||
userAgentB := []byte(userAgent)
|
||||
if len(userAgentB) > 255 {
|
||||
userAgentB = userAgentB[:255]
|
||||
}
|
||||
|
||||
raw[23] = byte(len(userAgentB))
|
||||
copy(raw[announcementPayloadHeaderSize:announcementPayloadHeaderSize+len(userAgentB)], userAgentB)
|
||||
packetSize += len(userAgentB)
|
||||
}
|
||||
|
||||
// FIND_SELF
|
||||
if findSelf {
|
||||
raw[2] |= 1 << ActionFindSelf
|
||||
}
|
||||
|
||||
// FIND_PEER
|
||||
if len(findPeer) > 0 {
|
||||
// check if there is enough space for at least the header and 1 record
|
||||
if isPacketSizeExceed(packetSize, 2+32) {
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
raw[2] |= 1 << ActionFindPeer
|
||||
index := packetSize
|
||||
packetSize += 2
|
||||
|
||||
for n, find := range findPeer {
|
||||
// check if minimum length is available in packet
|
||||
if isPacketSizeExceed(packetSize, 32) {
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
findPeer = findPeer[n:]
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint16(raw[index:index+2], uint16(n+1))
|
||||
copy(raw[index+2+32*n:index+2+32*n+32], find.Hash)
|
||||
packetSize += 32
|
||||
}
|
||||
|
||||
findPeer = nil
|
||||
}
|
||||
|
||||
// FIND_VALUE
|
||||
if len(findValue) > 0 {
|
||||
// check if there is enough space for at least the header and 1 record
|
||||
if isPacketSizeExceed(packetSize, 2+32) {
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
raw[2] |= 1 << ActionFindValue
|
||||
index := packetSize
|
||||
packetSize += 2
|
||||
|
||||
for n, find := range findValue {
|
||||
// check if minimum length is available in packet
|
||||
if isPacketSizeExceed(packetSize, 32) {
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
findValue = findValue[n:]
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint16(raw[index:index+2], uint16(n+1))
|
||||
copy(raw[index+2+32*n:index+2+32*n+32], find.Hash)
|
||||
packetSize += 32
|
||||
}
|
||||
|
||||
findValue = nil
|
||||
}
|
||||
|
||||
// INFO_STORE
|
||||
if len(files) > 0 {
|
||||
// check if there is enough space for at least the header and 1 record
|
||||
if isPacketSizeExceed(packetSize, 2+41) {
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
raw[2] |= 1 << ActionInfoStore
|
||||
index := packetSize
|
||||
packetSize += 2
|
||||
|
||||
for n, file := range files {
|
||||
// check if minimum length is available in packet
|
||||
if isPacketSizeExceed(packetSize, 41) {
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
files = files[n:]
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint16(raw[index:index+2], uint16(n+1))
|
||||
copy(raw[index+2+41*n:index+2+41*n+32], file.ID.Hash)
|
||||
|
||||
binary.LittleEndian.PutUint64(raw[index+2+41*n+32:index+2+41*n+32+8], file.Size)
|
||||
raw[index+2+41*n+40] = file.Type
|
||||
|
||||
packetSize += 41
|
||||
}
|
||||
|
||||
files = nil
|
||||
}
|
||||
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
|
||||
if len(findPeer) == 0 && len(findValue) == 0 && len(files) == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
230
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Get Block.go
generated
vendored
Normal file
230
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Get Block.go
generated
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
File Name: Message Encoding Get Block.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Get Block message encoding:
|
||||
Offset Size Info
|
||||
0 1 Control
|
||||
1 33 Peer ID compressed form identifying which blockchain to transfer
|
||||
|
||||
Control = 0: Request Blocks
|
||||
34 8 Limit total count of blocks to transfer. The transfer will be terminated if the limit is reached.
|
||||
42 8 Limit of bytes per block to transfer max. Blocks exceeding this limit will not be transferred.
|
||||
50 16 Transfer ID. This will identify lite packets.
|
||||
66 2 Count of block ranges
|
||||
68 16 * ? List of block ranges
|
||||
|
||||
Block range:
|
||||
0 8 Block number
|
||||
8 8 Count of blocks
|
||||
|
||||
Control = 3: Active
|
||||
34 ? Embedded block data as stream.
|
||||
|
||||
For the block stream there is a header preceding each block:
|
||||
Offset Size Info
|
||||
0 1 Availability
|
||||
0 = Block range is available.
|
||||
1 = Block range not available.
|
||||
2 = Block range exceeds size limit.
|
||||
1 16 Block range
|
||||
17 8 Block size
|
||||
|
||||
The limit in block range must be 1 if a block is returned.
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
GetBlockControlRequestStart = 0 // Request start transfer of blocks
|
||||
GetBlockControlNotAvailable = 1 // Requested blockchain not available (not found)
|
||||
GetBlockControlActive = 2 // Active block transfer
|
||||
GetBlockControlTerminate = 3 // Terminate
|
||||
GetBlockControlEmpty = 4 // Requested blockchain has 0 blocks
|
||||
)
|
||||
|
||||
const (
|
||||
GetBlockStatusAvailable = 0
|
||||
GetBlockStatusNotAvailable = 1
|
||||
GetBlockStatusSizeExceed = 2
|
||||
)
|
||||
|
||||
// Min size of header for Get Block control 0 message.
|
||||
const getBlockRequestHeaderSize = 68
|
||||
|
||||
// MessageGetBlock is the decoded Get Block message.
|
||||
type MessageGetBlock struct {
|
||||
*MessageRaw // Underlying raw message.
|
||||
Control uint8 // Control. See TransferControlX.
|
||||
BlockchainPublicKey *btcec.PublicKey // Peer ID of blockchain to transfer.
|
||||
|
||||
// fields valid only for GetBlockControlRequestStart
|
||||
TransferID uuid.UUID // Transfer ID to identify lite packets.
|
||||
LimitBlockCount uint64 // Limit total count of blocks to transfer
|
||||
MaxBlockSize uint64 // Limit of bytes per block to transfer max. Blocks exceeding this limit will not be transferred.
|
||||
TargetBlocks []BlockRange // Target list of block ranges to transfer.
|
||||
|
||||
// fields valid only for GetBlockControlActive
|
||||
Data []byte // Embedded protocol data.
|
||||
}
|
||||
|
||||
// BlockRange is a single start-count range.
|
||||
type BlockRange struct {
|
||||
Offset uint64 // Block number start
|
||||
Limit uint64 // Count of blocks
|
||||
}
|
||||
|
||||
// DecodeGetBlock decodes a Get Block message
|
||||
func DecodeGetBlock(msg *MessageRaw) (result *MessageGetBlock, err error) {
|
||||
if len(msg.Payload) < 34 {
|
||||
return nil, errors.New("get block: invalid minimum length")
|
||||
}
|
||||
|
||||
result = &MessageGetBlock{
|
||||
MessageRaw: msg,
|
||||
}
|
||||
|
||||
result.Control = msg.Payload[0]
|
||||
|
||||
peerIDcompressed := msg.Payload[1:34]
|
||||
if result.BlockchainPublicKey, err = btcec.ParsePubKey(peerIDcompressed, btcec.S256()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.Control == GetBlockControlRequestStart {
|
||||
if len(msg.Payload) < getBlockRequestHeaderSize {
|
||||
return nil, errors.New("get block: invalid minimum length")
|
||||
}
|
||||
|
||||
result.LimitBlockCount = binary.LittleEndian.Uint64(msg.Payload[34 : 34+8])
|
||||
result.MaxBlockSize = binary.LittleEndian.Uint64(msg.Payload[42 : 42+8])
|
||||
copy(result.TransferID[:], msg.Payload[50:50+16])
|
||||
|
||||
countBlockRanges := int(binary.LittleEndian.Uint16(msg.Payload[66 : 66+2]))
|
||||
if countBlockRanges == 0 {
|
||||
return nil, errors.New("get block: empty block range")
|
||||
} else if len(msg.Payload) < getBlockRequestHeaderSize+16*countBlockRanges {
|
||||
return nil, errors.New("get block: cound block ranges exceeds length")
|
||||
}
|
||||
|
||||
index := getBlockRequestHeaderSize
|
||||
|
||||
for n := 0; n < countBlockRanges; n++ {
|
||||
var target BlockRange
|
||||
target.Offset = binary.LittleEndian.Uint64(msg.Payload[index : index+8])
|
||||
target.Limit = binary.LittleEndian.Uint64(msg.Payload[index+8 : index+16])
|
||||
result.TargetBlocks = append(result.TargetBlocks, target)
|
||||
|
||||
index += 16
|
||||
}
|
||||
} else if result.Control == GetBlockControlActive {
|
||||
result.Data = msg.Payload[34:]
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// EncodeGetBlock encodes a Get Block message. The embedded packet size must be smaller than TransferMaxEmbedSize.
|
||||
func EncodeGetBlock(senderPrivateKey *btcec.PrivateKey, data []byte, control uint8, blockchainPublicKey *btcec.PublicKey, limitBlockCount, maxBlockSize uint64, targetBlocks []BlockRange, transferID uuid.UUID) (packetRaw []byte, err error) {
|
||||
if control == GetBlockControlRequestStart && len(data) != 0 {
|
||||
return nil, errors.New("get block encode: payload not allowed in start")
|
||||
} else if isPacketSizeExceed(transferPayloadHeaderSize, len(data)) {
|
||||
return nil, errors.New("get block encode: embedded packet too big")
|
||||
} else if control == GetBlockControlRequestStart && isPacketSizeExceed(getBlockRequestHeaderSize, len(targetBlocks)*16) {
|
||||
return nil, errors.New("get block encode: too many target block ranges")
|
||||
}
|
||||
|
||||
packetSize := transferPayloadHeaderSize
|
||||
if control == GetBlockControlRequestStart {
|
||||
packetSize = getBlockRequestHeaderSize + len(targetBlocks)*16
|
||||
} else if control == GetBlockControlActive {
|
||||
packetSize += len(data)
|
||||
}
|
||||
|
||||
raw := make([]byte, packetSize)
|
||||
|
||||
raw[0] = control
|
||||
targetPeerID := blockchainPublicKey.SerializeCompressed()
|
||||
copy(raw[1:34], targetPeerID)
|
||||
|
||||
if control == GetBlockControlRequestStart {
|
||||
binary.LittleEndian.PutUint64(raw[34:34+8], limitBlockCount)
|
||||
binary.LittleEndian.PutUint64(raw[42:42+8], maxBlockSize)
|
||||
copy(raw[50:50+16], transferID[:])
|
||||
binary.LittleEndian.PutUint16(raw[66:66+2], uint16(len(targetBlocks)))
|
||||
|
||||
index := getBlockRequestHeaderSize
|
||||
for _, target := range targetBlocks {
|
||||
binary.LittleEndian.PutUint64(raw[index:index+8], target.Offset)
|
||||
binary.LittleEndian.PutUint64(raw[index+8:index+16], target.Limit)
|
||||
|
||||
index += 16
|
||||
}
|
||||
} else if control == GetBlockControlActive {
|
||||
copy(raw[34:34+len(data)], data)
|
||||
}
|
||||
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
// IsLast checks if the incoming message is the last one in this transfer.
|
||||
func (msg *MessageGetBlock) IsLast() bool {
|
||||
return msg.Control == GetBlockControlTerminate || msg.Control == GetBlockControlNotAvailable || msg.Control == GetBlockControlEmpty
|
||||
}
|
||||
|
||||
// BlockTransferWriteHeader starts writing the header for a block transfer.
|
||||
func BlockTransferWriteHeader(writer io.Writer, availability uint8, targetBlock BlockRange, blockSize uint64) (err error) {
|
||||
header := make([]byte, 25)
|
||||
header[0] = availability
|
||||
binary.LittleEndian.PutUint64(header[1:9], targetBlock.Offset)
|
||||
binary.LittleEndian.PutUint64(header[9:17], targetBlock.Limit)
|
||||
binary.LittleEndian.PutUint64(header[17:25], blockSize)
|
||||
|
||||
_, err = writer.Write(header)
|
||||
return err
|
||||
}
|
||||
|
||||
// BlockTransferReadBlock reads the header and the block from the reader
|
||||
func BlockTransferReadBlock(reader io.Reader, maxBlockSize uint64) (data []byte, targetBlock BlockRange, blockSize uint64, availability uint8, err error) {
|
||||
header := make([]byte, 25)
|
||||
|
||||
if _, err := io.ReadAtLeast(reader, header, len(header)); err != nil {
|
||||
return nil, targetBlock, 0, 0, err
|
||||
}
|
||||
|
||||
availability = header[0]
|
||||
targetBlock.Offset = binary.LittleEndian.Uint64(header[1:9])
|
||||
targetBlock.Limit = binary.LittleEndian.Uint64(header[9:17])
|
||||
blockSize = binary.LittleEndian.Uint64(header[17:25])
|
||||
|
||||
if targetBlock.Limit == 0 {
|
||||
return nil, targetBlock, blockSize, availability, errors.New("empty target block limit")
|
||||
} else if availability != GetBlockStatusAvailable { // return if status indicates the block is not available
|
||||
return nil, targetBlock, blockSize, availability, nil
|
||||
}
|
||||
|
||||
if blockSize > maxBlockSize {
|
||||
return nil, targetBlock, blockSize, availability, errors.New("remote block size exceeds limit")
|
||||
} else if targetBlock.Limit != 1 {
|
||||
return nil, targetBlock, blockSize, availability, errors.New("invalid target block limit")
|
||||
}
|
||||
|
||||
// read the block
|
||||
block := make([]byte, blockSize)
|
||||
|
||||
if _, err := io.ReadAtLeast(reader, block, len(block)); err != nil {
|
||||
return nil, targetBlock, blockSize, availability, err
|
||||
}
|
||||
|
||||
return block, targetBlock, blockSize, availability, nil
|
||||
}
|
||||
445
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Response.go
generated
vendored
Normal file
445
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Response.go
generated
vendored
Normal file
@@ -0,0 +1,445 @@
|
||||
/*
|
||||
File Name: Message Encoding Response.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
)
|
||||
|
||||
// MessageResponse is the decoded response message.
|
||||
type MessageResponse struct {
|
||||
*MessageRaw // Underlying raw message
|
||||
Protocol uint8 // Protocol version supported (low 4 bits).
|
||||
Features uint8 // Feature support (high 4 bits). Future use.
|
||||
Actions uint8 // Action bit array. See ActionX
|
||||
BlockchainHeight uint64 // Blockchain height
|
||||
BlockchainVersion uint64 // Blockchain version
|
||||
PortInternal uint16 // Internal port. Can be used to detect NATs.
|
||||
PortExternal uint16 // External port if known. 0 if not. Can be used for UPnP support.
|
||||
UserAgent string // User Agent. Format "Software/Version". Required in the initial announcement/bootstrap. UTF-8 encoded. Max length is 255 bytes.
|
||||
Hash2Peers []Hash2Peer // List of peers that know the requested hashes or at least are close to it
|
||||
FilesEmbed []EmbeddedFileData // Files that were embedded in the response
|
||||
HashesNotFound [][]byte // Hashes that were reported back as not found
|
||||
}
|
||||
|
||||
// PeerRecord informs about a peer
|
||||
type PeerRecord struct {
|
||||
PublicKey *btcec.PublicKey // Public Key
|
||||
NodeID []byte // Kademlia Node ID
|
||||
IPv4 net.IP // IPv4 address. 0 if not set.
|
||||
IPv4Port uint16 // Port (actual one used for connection)
|
||||
IPv4PortReportedInternal uint16 // Internal port as reported by that peer. This can be used to identify whether the peer is potentially behind a NAT.
|
||||
IPv4PortReportedExternal uint16 // External port as reported by that peer. This is used in case of port forwarding (manual or automated).
|
||||
IPv6 net.IP // IPv6 address. 0 if not set.
|
||||
IPv6Port uint16 // Port (actual one used for connection)
|
||||
IPv6PortReportedInternal uint16 // Internal port as reported by that peer. This can be used to identify whether the peer is potentially behind a NAT.
|
||||
IPv6PortReportedExternal uint16 // External port as reported by that peer. This is used in case of port forwarding (manual or automated).
|
||||
LastContact uint32 // Last contact in seconds
|
||||
LastContactT time.Time // Last contact time translated from seconds
|
||||
Features uint8 // Feature support. Same as in Announcement/Response message.
|
||||
}
|
||||
|
||||
// Hash2Peer links a hash to peers who are known to store the data and to peers who are considered close to the hash
|
||||
type Hash2Peer struct {
|
||||
ID KeyHash // Hash that was queried
|
||||
Closest []PeerRecord // Closest peers
|
||||
Storing []PeerRecord // Peers known to store the data identified by the hash
|
||||
IsLast bool // Whether it is the last records returned for the requested hash and no more results will follow
|
||||
}
|
||||
|
||||
// EmbeddedFileData contains embedded data sent within a response
|
||||
type EmbeddedFileData struct {
|
||||
ID KeyHash // Hash of the file
|
||||
Data []byte // Data
|
||||
}
|
||||
|
||||
// Actions in Response message
|
||||
const (
|
||||
ActionSequenceLast = 0 // SEQUENCE_LAST Last response to the announcement in the sequence
|
||||
)
|
||||
|
||||
// DecodeResponse decodes the incoming response message. Returns nil if invalid.
|
||||
func DecodeResponse(msg *MessageRaw) (result *MessageResponse, err error) {
|
||||
result = &MessageResponse{
|
||||
MessageRaw: msg,
|
||||
}
|
||||
|
||||
if len(msg.Payload) < announcementPayloadHeaderSize+6 {
|
||||
return nil, errors.New("response: invalid minimum length")
|
||||
}
|
||||
|
||||
result.Protocol = msg.Payload[0] & 0x0F // Protocol version support is stored in the first 4 bits
|
||||
result.Features = msg.Payload[1] // Feature support
|
||||
result.Actions = msg.Payload[2]
|
||||
result.BlockchainHeight = binary.LittleEndian.Uint64(msg.Payload[3 : 3+8])
|
||||
result.BlockchainVersion = binary.LittleEndian.Uint64(msg.Payload[11 : 11+8])
|
||||
result.PortInternal = binary.LittleEndian.Uint16(msg.Payload[19 : 19+2])
|
||||
result.PortExternal = binary.LittleEndian.Uint16(msg.Payload[21 : 21+2])
|
||||
|
||||
userAgentLength := int(msg.Payload[23])
|
||||
read := announcementPayloadHeaderSize
|
||||
|
||||
if userAgentLength > 0 {
|
||||
if userAgentLength > len(msg.Payload)-announcementPayloadHeaderSize {
|
||||
return nil, errors.New("response: user agent overflow")
|
||||
}
|
||||
|
||||
userAgentB := msg.Payload[announcementPayloadHeaderSize : announcementPayloadHeaderSize+userAgentLength]
|
||||
if !utf8.Valid(userAgentB) {
|
||||
return nil, errors.New("response: user agent invalid encoding")
|
||||
}
|
||||
|
||||
result.UserAgent = string(userAgentB)
|
||||
read += userAgentLength
|
||||
}
|
||||
|
||||
countPeerResponses := binary.LittleEndian.Uint16(msg.Payload[read+0 : read+0+2])
|
||||
countEmbeddedFiles := binary.LittleEndian.Uint16(msg.Payload[read+2 : read+2+2])
|
||||
countHashesNotFound := binary.LittleEndian.Uint16(msg.Payload[read+4 : read+4+2])
|
||||
read += 6
|
||||
|
||||
if countPeerResponses == 0 && countEmbeddedFiles == 0 && countHashesNotFound == 0 {
|
||||
// Empty responses are allowed. They can be useful as quasi-pings to get the latest blockchain info of the peer.
|
||||
return
|
||||
}
|
||||
|
||||
data := msg.Payload[read:]
|
||||
|
||||
// Peer response data
|
||||
if countPeerResponses > 0 {
|
||||
hash2Peers, read, valid := decodePeerRecord(data, int(countPeerResponses))
|
||||
if !valid {
|
||||
return nil, errors.New("response: peer info invalid data")
|
||||
}
|
||||
data = data[read:]
|
||||
|
||||
result.Hash2Peers = append(result.Hash2Peers, hash2Peers...)
|
||||
}
|
||||
|
||||
// Embedded files
|
||||
if countEmbeddedFiles > 0 {
|
||||
filesEmbed, read, valid := decodeEmbeddedFile(data, int(countEmbeddedFiles))
|
||||
if !valid {
|
||||
return nil, errors.New("response: embedded file invalid data")
|
||||
}
|
||||
data = data[read:]
|
||||
|
||||
result.FilesEmbed = append(result.FilesEmbed, filesEmbed...)
|
||||
}
|
||||
|
||||
// Hashes not found
|
||||
if countHashesNotFound > 0 {
|
||||
if len(data) < int(countHashesNotFound)*32 {
|
||||
return nil, errors.New("response: hash list invalid data")
|
||||
}
|
||||
|
||||
for n := 0; n < int(countHashesNotFound); n++ {
|
||||
hash := make([]byte, HashSize)
|
||||
copy(hash, data[n*32:n*32+32])
|
||||
|
||||
result.HashesNotFound = append(result.HashesNotFound, hash)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Length of peer record in bytes
|
||||
const peerRecordSize = 70
|
||||
|
||||
// decodePeerRecord decodes the response data for FIND_SELF, FIND_PEER and FIND_VALUE messages
|
||||
func decodePeerRecord(data []byte, count int) (hash2Peers []Hash2Peer, read int, valid bool) {
|
||||
index := 0
|
||||
|
||||
for n := 0; n < count; n++ {
|
||||
if read += 34; len(data) < read {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
hash := make([]byte, HashSize)
|
||||
copy(hash, data[index:index+32])
|
||||
countField := binary.LittleEndian.Uint16(data[index+32:index+32+2]) & 0x7FFF
|
||||
isLast := binary.LittleEndian.Uint16(data[index+32:index+32+2])&0x8000 > 0
|
||||
index += 34
|
||||
|
||||
hash2Peer := Hash2Peer{ID: KeyHash{hash}, IsLast: isLast}
|
||||
|
||||
// Response contains peer records
|
||||
for m := 0; m < int(countField); m++ {
|
||||
if read += peerRecordSize; len(data) < read {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
peer := PeerRecord{}
|
||||
|
||||
peerIDcompressed := make([]byte, 33)
|
||||
copy(peerIDcompressed[:], data[index:index+33])
|
||||
|
||||
// IPv4
|
||||
ipv4B := make([]byte, 4)
|
||||
copy(ipv4B[:], data[index+33:index+33+4])
|
||||
|
||||
peer.IPv4 = ipv4B
|
||||
peer.IPv4Port = binary.LittleEndian.Uint16(data[index+37 : index+37+2])
|
||||
peer.IPv4PortReportedInternal = binary.LittleEndian.Uint16(data[index+39 : index+39+2])
|
||||
peer.IPv4PortReportedExternal = binary.LittleEndian.Uint16(data[index+41 : index+41+2])
|
||||
|
||||
// IPv6
|
||||
ipv6B := make([]byte, 16)
|
||||
copy(ipv6B[:], data[index+43:index+43+16])
|
||||
|
||||
peer.IPv6 = ipv6B
|
||||
peer.IPv6Port = binary.LittleEndian.Uint16(data[index+59 : index+59+2])
|
||||
peer.IPv6PortReportedInternal = binary.LittleEndian.Uint16(data[index+61 : index+61+2])
|
||||
peer.IPv6PortReportedExternal = binary.LittleEndian.Uint16(data[index+63 : index+63+2])
|
||||
|
||||
if peer.IPv6.To4() != nil { // IPv6 address mismatch
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
peer.LastContact = binary.LittleEndian.Uint32(data[index+65 : index+65+4])
|
||||
peer.LastContactT = time.Now().Add(-time.Second * time.Duration(peer.LastContact))
|
||||
peer.Features = data[index+69] & 0x7F
|
||||
reason := data[index+69] >> 7
|
||||
|
||||
var err error
|
||||
if peer.PublicKey, err = btcec.ParsePubKey(peerIDcompressed, btcec.S256()); err != nil {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
peer.NodeID = PublicKey2NodeID(peer.PublicKey)
|
||||
|
||||
if reason == 0 { // Peer was returned because it is close to the requested hash
|
||||
hash2Peer.Closest = append(hash2Peer.Closest, peer)
|
||||
} else if reason == 1 { // Peer stores the data
|
||||
hash2Peer.Storing = append(hash2Peer.Storing, peer)
|
||||
}
|
||||
|
||||
index += peerRecordSize
|
||||
}
|
||||
|
||||
hash2Peers = append(hash2Peers, hash2Peer)
|
||||
}
|
||||
|
||||
return hash2Peers, read, true
|
||||
}
|
||||
|
||||
// decodeEmbeddedFile decodes the embedded file response data for FIND_VALUE
|
||||
func decodeEmbeddedFile(data []byte, count int) (filesEmbed []EmbeddedFileData, read int, valid bool) {
|
||||
index := 0
|
||||
|
||||
for n := 0; n < count; n++ {
|
||||
if read += 34; len(data) < read {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
hash := make([]byte, HashSize)
|
||||
copy(hash, data[index:index+32])
|
||||
sizeField := int(binary.LittleEndian.Uint16(data[index+32 : index+32+2]))
|
||||
index += 34
|
||||
|
||||
if read += sizeField; len(data) < read {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
fileData := make([]byte, sizeField)
|
||||
copy(fileData[:], data[index:index+sizeField])
|
||||
|
||||
index += sizeField
|
||||
|
||||
// validate the hash
|
||||
if !bytes.Equal(hash, HashData(fileData)) {
|
||||
return nil, read, false
|
||||
}
|
||||
|
||||
filesEmbed = append(filesEmbed, EmbeddedFileData{ID: KeyHash{Hash: hash}, Data: fileData})
|
||||
}
|
||||
|
||||
return filesEmbed, read, true
|
||||
}
|
||||
|
||||
// EmbeddedFileSizeMax is the maximum size of embedded files in response messages. Any file exceeding that must be shared via regular file transfer.
|
||||
const EmbeddedFileSizeMax = udpMaxPacketSize - PacketLengthMin - announcementPayloadHeaderSize - 2 - 35
|
||||
|
||||
// EncodeResponse encodes a response message
|
||||
// hash2Peers will be modified.
|
||||
func EncodeResponse(sendUA bool, hash2Peers []Hash2Peer, filesEmbed []EmbeddedFileData, hashesNotFound [][]byte, features byte, blockchainHeight, blockchainVersion uint64, userAgent string) (packetsRaw [][]byte, err error) {
|
||||
for n := range filesEmbed {
|
||||
if len(filesEmbed[n].Data) > EmbeddedFileSizeMax {
|
||||
return nil, errors.New("embedded file too big")
|
||||
}
|
||||
}
|
||||
|
||||
createPacketLoop:
|
||||
for {
|
||||
raw := make([]byte, 64*1024) // max UDP packet size
|
||||
packetSize := announcementPayloadHeaderSize
|
||||
|
||||
raw[0] = byte(ProtocolVersion) // Protocol
|
||||
raw[1] = features // Feature support
|
||||
//raw[2] = Actions // Action bit array
|
||||
|
||||
binary.LittleEndian.PutUint64(raw[3:3+8], blockchainHeight)
|
||||
binary.LittleEndian.PutUint64(raw[11:11+8], blockchainVersion)
|
||||
|
||||
// only on initial response the User Agent must be provided according to the protocol spec
|
||||
if sendUA {
|
||||
userAgentB := []byte(userAgent)
|
||||
if len(userAgentB) > 255 {
|
||||
userAgentB = userAgentB[:255]
|
||||
}
|
||||
|
||||
raw[23] = byte(len(userAgentB))
|
||||
copy(raw[announcementPayloadHeaderSize:announcementPayloadHeaderSize+len(userAgentB)], userAgentB)
|
||||
packetSize += len(userAgentB)
|
||||
}
|
||||
|
||||
// 3 count field at raw[index]: count of peer responses, embedded files, and hashes not found
|
||||
countIndex := packetSize
|
||||
packetSize += 6
|
||||
|
||||
// Encode the peer response data for FIND_SELF, FIND_PEER and FIND_VALUE requests.
|
||||
if len(hash2Peers) > 0 {
|
||||
for n, hash2Peer := range hash2Peers {
|
||||
if isPacketSizeExceed(packetSize, 34+peerRecordSize) { // check if minimum length is available in packet
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
hash2Peers = hash2Peers[n:]
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
index := packetSize
|
||||
copy(raw[index:index+32], hash2Peer.ID.Hash)
|
||||
count2Index := index + 32
|
||||
|
||||
packetSize += 34
|
||||
count2 := uint16(0)
|
||||
|
||||
for m := range hash2Peer.Storing {
|
||||
if isPacketSizeExceed(packetSize, peerRecordSize) { // check if minimum length is available in packet
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
hash2Peers = hash2Peers[n:]
|
||||
hash2Peer.Storing = hash2Peer.Storing[m:]
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
index := packetSize
|
||||
encodePeerRecord(raw[index:index+peerRecordSize], &hash2Peer.Storing[m], 1)
|
||||
|
||||
packetSize += peerRecordSize
|
||||
binary.LittleEndian.PutUint16(raw[count2Index+0:count2Index+2], uint16(m+1))
|
||||
count2++
|
||||
}
|
||||
|
||||
hash2Peer.Storing = nil
|
||||
|
||||
for m := range hash2Peer.Closest {
|
||||
if isPacketSizeExceed(packetSize, peerRecordSize) { // check if minimum length is available in packet
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
hash2Peers = hash2Peers[n:]
|
||||
hash2Peer.Closest = hash2Peer.Closest[m:]
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
index := packetSize
|
||||
encodePeerRecord(raw[index:index+peerRecordSize], &hash2Peer.Closest[m], 0)
|
||||
|
||||
packetSize += peerRecordSize
|
||||
count2++
|
||||
binary.LittleEndian.PutUint16(raw[count2Index+0:count2Index+2], count2)
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint16(raw[count2Index+0:count2Index+2], count2|0x8000) // signal the last result for the key with bit 15
|
||||
binary.LittleEndian.PutUint16(raw[countIndex+0:countIndex+0+2], uint16(n+1)) // count of peer responses
|
||||
}
|
||||
|
||||
hash2Peers = nil
|
||||
}
|
||||
|
||||
// FIND_VALUE response embedded data
|
||||
if len(filesEmbed) > 0 {
|
||||
if isPacketSizeExceed(packetSize, 34+len(filesEmbed[0].Data)) { // check if there is enough space for at least the header and 1 record
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
for n, file := range filesEmbed {
|
||||
if isPacketSizeExceed(packetSize, 34+len(file.Data)) { // check if minimum length is available in packet
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
filesEmbed = filesEmbed[n:]
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
index := packetSize
|
||||
copy(raw[index:index+32], file.ID.Hash)
|
||||
binary.LittleEndian.PutUint16(raw[index+32:index+32+2], uint16(len(file.Data)))
|
||||
copy(raw[index+34:index+34+len(file.Data)], file.Data)
|
||||
|
||||
binary.LittleEndian.PutUint16(raw[countIndex+2:countIndex+2+2], uint16(n+1)) // count of embedded files
|
||||
packetSize += 34 + len(file.Data)
|
||||
}
|
||||
|
||||
filesEmbed = nil
|
||||
}
|
||||
|
||||
// Hashes not found
|
||||
if len(hashesNotFound) > 0 {
|
||||
index := packetSize
|
||||
|
||||
for n, hash := range hashesNotFound {
|
||||
if isPacketSizeExceed(packetSize, 32) { // check if there is enough space for at least the header and 1 record
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
copy(raw[index+n*32:index+n*32+32], hash)
|
||||
|
||||
binary.LittleEndian.PutUint16(raw[countIndex+4:countIndex+4+2], uint16(n+1)) // count of hashes not found
|
||||
packetSize += 32
|
||||
}
|
||||
|
||||
hashesNotFound = nil
|
||||
}
|
||||
|
||||
raw[2] |= 1 << ActionSequenceLast // Indicate that no more responses will be sent in this sequence
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
|
||||
if len(hash2Peers) == 0 && len(filesEmbed) == 0 && len(hashesNotFound) == 0 { // this should always be the case here
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// encodePeerRecord encodes a single peer record and stores it into raw
|
||||
func encodePeerRecord(raw []byte, peer *PeerRecord, reason uint8) {
|
||||
copy(raw[0:0+33], peer.PublicKey.SerializeCompressed())
|
||||
binary.LittleEndian.PutUint32(raw[65:65+4], peer.LastContact)
|
||||
raw[69] = peer.Features | reason<<7
|
||||
|
||||
// IPv4
|
||||
copy(raw[33:33+4], peer.IPv4.To4())
|
||||
binary.LittleEndian.PutUint16(raw[37:37+2], peer.IPv4Port)
|
||||
binary.LittleEndian.PutUint16(raw[39:39+2], peer.IPv4PortReportedInternal)
|
||||
binary.LittleEndian.PutUint16(raw[41:41+2], peer.IPv4PortReportedExternal)
|
||||
|
||||
// IPv6
|
||||
copy(raw[43:43+16], peer.IPv6.To16())
|
||||
binary.LittleEndian.PutUint16(raw[59:59+2], peer.IPv6Port)
|
||||
binary.LittleEndian.PutUint16(raw[61:61+2], peer.IPv6PortReportedInternal)
|
||||
binary.LittleEndian.PutUint16(raw[63:63+2], peer.IPv6PortReportedExternal)
|
||||
}
|
||||
|
||||
// IsLast checks if the incoming message is the last expected response in this sequence.
|
||||
func (msg *MessageResponse) IsLast() bool {
|
||||
return msg.Actions&(1<<ActionSequenceLast) > 0
|
||||
}
|
||||
136
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Transfer.go
generated
vendored
Normal file
136
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Transfer.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
File Name: Message Encoding Transfer.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Transfer message encoding:
|
||||
Offset Size Info
|
||||
0 1 Control
|
||||
1 1 Transfer Protocol
|
||||
2 32 File Hash
|
||||
|
||||
Control = 0: Request Start
|
||||
34 8 Offset to start reading in the file
|
||||
42 8 Limit of bytes to read at the offset
|
||||
50 16 Transfer ID. This will identify lite packets.
|
||||
|
||||
Offset + limit must not exceed the file size. Actual data transfer should be sent via lite packets.
|
||||
The regular Peernet packets would be too CPU expensive and slow due to public key signing.
|
||||
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// MessageTransfer is the decoded transfer message.
|
||||
// It is sent to initiate a file transfer, and to send data as part of a file transfer. The actual file data is encapsulated via UDT.
|
||||
type MessageTransfer struct {
|
||||
*MessageRaw // Underlying raw message.
|
||||
Control uint8 // Control. See TransferControlX.
|
||||
TransferProtocol uint8 // Embedded transfer protocol: 0 = UDT
|
||||
Hash []byte // Hash of the file to transfer.
|
||||
Offset uint64 // Offset to start reading at. Only TransferControlRequestStart.
|
||||
Limit uint64 // Limit (count of bytes) to read starting at the offset. Only TransferControlRequestStart.
|
||||
TransferID uuid.UUID // Transfer ID to identify lite packets.
|
||||
Data []byte // Embedded protocol data. Only TransferControlActive.
|
||||
}
|
||||
|
||||
const (
|
||||
TransferControlRequestStart = 0 // Request start transfer of file. Data at byte 34 is offset and limit to read, each 8 bytes. Limit may be 0 to indicate entire file.
|
||||
TransferControlNotAvailable = 1 // Requested file not available
|
||||
TransferControlActive = 2 // Active file transfer
|
||||
TransferControlTerminate = 3 // Terminate
|
||||
)
|
||||
|
||||
const (
|
||||
TransferProtocolUDT = 0 // UDT via lite packets. No encryption.
|
||||
)
|
||||
|
||||
const transferPayloadHeaderSize = 34
|
||||
|
||||
// DecodeTransfer decodes a transfer message
|
||||
func DecodeTransfer(msg *MessageRaw) (result *MessageTransfer, err error) {
|
||||
if len(msg.Payload) < transferPayloadHeaderSize {
|
||||
return nil, errors.New("transfer: invalid minimum length")
|
||||
}
|
||||
|
||||
result = &MessageTransfer{
|
||||
MessageRaw: msg,
|
||||
Hash: make([]byte, HashSize),
|
||||
}
|
||||
|
||||
result.Control = msg.Payload[0]
|
||||
result.TransferProtocol = msg.Payload[1]
|
||||
copy(result.Hash, msg.Payload[2:2+HashSize])
|
||||
|
||||
switch result.Control {
|
||||
case TransferControlRequestStart:
|
||||
// Offset and Limit must be provided after the header.
|
||||
if len(msg.Payload) < transferPayloadHeaderSize+16 {
|
||||
return nil, errors.New("transfer: invalid minimum length")
|
||||
}
|
||||
|
||||
result.Offset = binary.LittleEndian.Uint64(msg.Payload[34 : 34+8])
|
||||
result.Limit = binary.LittleEndian.Uint64(msg.Payload[42 : 42+8])
|
||||
copy(result.TransferID[:], msg.Payload[50:50+16])
|
||||
|
||||
case TransferControlActive:
|
||||
// Data should be transferred via lite packets for performance reasons, but it is allowed to be encapsulated in Peernet packets.
|
||||
result.Data = msg.Payload[transferPayloadHeaderSize:]
|
||||
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// TransferMaxEmbedSize is a recommended default upper size of embedded data inside the Transfer message, to be used as MaxPacketSize limit in the embedded protocol.
|
||||
// This value is chosen as the lowest denominator of different environments (IPv4, IPv6, Ethernet, Internet) for safe transfer, not for highest performance.
|
||||
// The caller may send bigger payloads but may risk that data packets are simply dropped and never arrive. A MTU negotiation or detection could pimp that.
|
||||
const TransferMaxEmbedSize = internetSafeMTU - PacketLengthMin - transferPayloadHeaderSize
|
||||
|
||||
// Same as TransferMaxEmbedSize but for encoding via lite packets.
|
||||
const TransferMaxEmbedSizeLite = internetSafeMTU - PacketLiteSizeMin
|
||||
|
||||
// EncodeTransfer encodes a transfer message. The embedded packet size must be smaller than TransferMaxEmbedSize.
|
||||
func EncodeTransfer(senderPrivateKey *btcec.PrivateKey, data []byte, control, transferProtocol uint8, hash []byte, offset, limit uint64, transferID uuid.UUID) (packetRaw []byte, err error) {
|
||||
if control == TransferControlRequestStart && len(data) != 0 {
|
||||
return nil, errors.New("transfer encode: payload not allowed in start")
|
||||
} else if isPacketSizeExceed(transferPayloadHeaderSize, len(data)) {
|
||||
return nil, errors.New("transfer encode: embedded packet too big")
|
||||
}
|
||||
|
||||
packetSize := transferPayloadHeaderSize
|
||||
if control == TransferControlRequestStart {
|
||||
packetSize += 32
|
||||
} else if control == TransferControlActive {
|
||||
packetSize += len(data)
|
||||
}
|
||||
|
||||
raw := make([]byte, packetSize)
|
||||
|
||||
raw[0] = control
|
||||
raw[1] = transferProtocol
|
||||
copy(raw[2:2+HashSize], hash)
|
||||
|
||||
if control == TransferControlRequestStart {
|
||||
binary.LittleEndian.PutUint64(raw[34:34+8], offset)
|
||||
binary.LittleEndian.PutUint64(raw[42:42+8], limit)
|
||||
copy(raw[50:50+16], transferID[:])
|
||||
} else if control == TransferControlActive {
|
||||
copy(raw[34:34+len(data)], data)
|
||||
}
|
||||
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
// IsLast checks if the incoming message is the last one in this transfer.
|
||||
func (msg *MessageTransfer) IsLast() bool {
|
||||
return msg.Control == TransferControlTerminate || msg.Control == TransferControlNotAvailable
|
||||
}
|
||||
165
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Traverse.go
generated
vendored
Normal file
165
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Traverse.go
generated
vendored
Normal file
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
File Name: Message Encoding Traverse.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
)
|
||||
|
||||
// MessageTraverse is the decoded traverse message.
|
||||
// It is sent by an original sender to a relay, to a final receiver (targert peer).
|
||||
type MessageTraverse struct {
|
||||
*MessageRaw // Underlying raw message.
|
||||
TargetPeer *btcec.PublicKey // End receiver peer ID.
|
||||
AuthorizedRelayPeer *btcec.PublicKey // Peer ID that is authorized to relay this message to the end receiver.
|
||||
Expires time.Time // Expiration time when this forwarded message becomes invalid.
|
||||
EmbeddedPacketRaw []byte // Embedded packet.
|
||||
SignerPublicKey *btcec.PublicKey // Public key that signed this message, ECDSA (secp256k1) 257-bit
|
||||
IPv4 net.IP // IPv4 address of the original sender. Set by authorized relay. 0 if not set.
|
||||
PortIPv4 uint16 // Port (actual one used for connection) of the original sender. Set by authorized relay.
|
||||
PortIPv4ReportedExternal uint16 // External port as reported by the original sender. This is used in case of port forwarding (manual or automated).
|
||||
IPv6 net.IP // IPv6 address of the original sender. Set by authorized relay. 0 if not set.
|
||||
PortIPv6 uint16 // Port (actual one used for connection) of the original sender. Set by authorized relay.
|
||||
PortIPv6ReportedExternal uint16 // External port as reported by the original sender. This is used in case of port forwarding (manual or automated).
|
||||
}
|
||||
|
||||
const traversePayloadHeaderSize = 76 + 65 + 28
|
||||
|
||||
// DecodeTraverse decodes a traverse message.
|
||||
// It does not verify if the receiver is authorized to read or forward this message.
|
||||
// It validates the signature, but does not validate the signer.
|
||||
func DecodeTraverse(msg *MessageRaw) (result *MessageTraverse, err error) {
|
||||
result = &MessageTraverse{
|
||||
MessageRaw: msg,
|
||||
}
|
||||
|
||||
if len(msg.Payload) < traversePayloadHeaderSize {
|
||||
return nil, errors.New("traverse: invalid minimum length")
|
||||
}
|
||||
|
||||
targetPeerIDcompressed := msg.Payload[0:33]
|
||||
authorizedRelayPeerIDcompressed := msg.Payload[33:66]
|
||||
|
||||
if result.TargetPeer, err = btcec.ParsePubKey(targetPeerIDcompressed, btcec.S256()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result.AuthorizedRelayPeer, err = btcec.ParsePubKey(authorizedRelayPeerIDcompressed, btcec.S256()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// receiver and target must not be the same
|
||||
if result.TargetPeer.IsEqual(result.AuthorizedRelayPeer) {
|
||||
return nil, errors.New("traverse: target and relay invalid")
|
||||
}
|
||||
|
||||
expires64 := binary.LittleEndian.Uint64(msg.Payload[66 : 66+8])
|
||||
result.Expires = time.Unix(int64(expires64), 0)
|
||||
|
||||
sizePacketEmbed := binary.LittleEndian.Uint16(msg.Payload[74 : 74+2])
|
||||
if int(sizePacketEmbed) != len(msg.Payload)-traversePayloadHeaderSize {
|
||||
return nil, errors.New("traverse: size embedded packet mismatch")
|
||||
}
|
||||
|
||||
result.EmbeddedPacketRaw = msg.Payload[76 : 76+sizePacketEmbed]
|
||||
|
||||
signature := msg.Payload[76+sizePacketEmbed : 76+sizePacketEmbed+65]
|
||||
|
||||
result.SignerPublicKey, _, err = btcec.RecoverCompact(btcec.S256(), signature, HashData(msg.Payload[:76+sizePacketEmbed]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// IPv4
|
||||
ipv4B := make([]byte, 4)
|
||||
copy(ipv4B[:], msg.Payload[76+sizePacketEmbed+65:76+sizePacketEmbed+65+4])
|
||||
|
||||
result.IPv4 = ipv4B
|
||||
result.PortIPv4 = binary.LittleEndian.Uint16(msg.Payload[76+sizePacketEmbed+65+4 : 76+sizePacketEmbed+65+4+2])
|
||||
result.PortIPv4ReportedExternal = binary.LittleEndian.Uint16(msg.Payload[76+sizePacketEmbed+65+6 : 76+sizePacketEmbed+65+6+2])
|
||||
|
||||
// IPv6
|
||||
ipv6B := make([]byte, 16)
|
||||
copy(ipv6B[:], msg.Payload[76+sizePacketEmbed+65+8:76+sizePacketEmbed+65+8+16])
|
||||
|
||||
result.IPv6 = ipv6B
|
||||
result.PortIPv6 = binary.LittleEndian.Uint16(msg.Payload[76+sizePacketEmbed+65+24 : 76+sizePacketEmbed+65+24+2])
|
||||
result.PortIPv6ReportedExternal = binary.LittleEndian.Uint16(msg.Payload[76+sizePacketEmbed+65+26 : 76+sizePacketEmbed+65+26+2])
|
||||
|
||||
// TODO: Validate IPv4 and IPv6. Only external ones allowed.
|
||||
if result.IPv6.To4() != nil {
|
||||
return nil, errors.New("traverse: ipv6 address mismatch")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// EncodeTraverse encodes a traverse message
|
||||
func EncodeTraverse(senderPrivateKey *btcec.PrivateKey, embeddedPacketRaw []byte, receiverEnd *btcec.PublicKey, relayPeer *btcec.PublicKey) (packetRaw []byte, err error) {
|
||||
sizePacketEmbed := len(embeddedPacketRaw)
|
||||
if isPacketSizeExceed(traversePayloadHeaderSize, sizePacketEmbed) {
|
||||
return nil, errors.New("traverse encode: embedded packet too big")
|
||||
}
|
||||
|
||||
raw := make([]byte, traversePayloadHeaderSize+sizePacketEmbed)
|
||||
|
||||
targetPeerID := receiverEnd.SerializeCompressed()
|
||||
copy(raw[0:33], targetPeerID)
|
||||
authorizedRelayPeerID := relayPeer.SerializeCompressed()
|
||||
copy(raw[33:66], authorizedRelayPeerID)
|
||||
|
||||
expires64 := time.Now().Add(time.Hour).UTC().Unix()
|
||||
binary.LittleEndian.PutUint64(raw[66:66+8], uint64(expires64))
|
||||
|
||||
binary.LittleEndian.PutUint16(raw[74:74+2], uint16(sizePacketEmbed))
|
||||
copy(raw[76:76+sizePacketEmbed], embeddedPacketRaw)
|
||||
|
||||
// add signature
|
||||
signature, err := btcec.SignCompact(btcec.S256(), senderPrivateKey, HashData(raw[:76+sizePacketEmbed]), true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(raw[76+sizePacketEmbed:76+sizePacketEmbed+65], signature)
|
||||
|
||||
// IP and ports are to be filled by authorized relay peer
|
||||
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
// EncodeTraverseSetAddress sets the IP and Port in a traverse message that shall be forwarded to another peer
|
||||
func EncodeTraverseSetAddress(raw []byte, IPv4 net.IP, PortIPv4, PortIPv4ReportedExternal uint16, IPv6 net.IP, PortIPv6, PortIPv6ReportedExternal uint16) (err error) {
|
||||
if isPacketSizeExceed(len(raw), 0) {
|
||||
return errors.New("traverse encode 2: embedded packet too big")
|
||||
} else if len(raw) < traversePayloadHeaderSize {
|
||||
return errors.New("traverse encode 2: invalid packet")
|
||||
}
|
||||
|
||||
sizePacketEmbed := binary.LittleEndian.Uint16(raw[74 : 74+2])
|
||||
if int(sizePacketEmbed) != len(raw)-traversePayloadHeaderSize {
|
||||
return errors.New("traverse encode 2: size embedded packet mismatch")
|
||||
}
|
||||
|
||||
// IPv4
|
||||
if IPv4 != nil && len(IPv4) == net.IPv4len {
|
||||
copy(raw[76+sizePacketEmbed+65:76+sizePacketEmbed+65+4], IPv4.To4())
|
||||
binary.LittleEndian.PutUint16(raw[76+sizePacketEmbed+65+4:76+sizePacketEmbed+65+4+2], PortIPv4)
|
||||
binary.LittleEndian.PutUint16(raw[76+sizePacketEmbed+65+6:76+sizePacketEmbed+65+6+2], PortIPv4ReportedExternal)
|
||||
}
|
||||
|
||||
// IPv6
|
||||
if IPv6 != nil && len(IPv6) == net.IPv6len {
|
||||
copy(raw[76+sizePacketEmbed+65+8:76+sizePacketEmbed+65+8+16], IPv6.To16())
|
||||
binary.LittleEndian.PutUint16(raw[76+sizePacketEmbed+65+24:76+sizePacketEmbed+65+24+2], PortIPv6)
|
||||
binary.LittleEndian.PutUint16(raw[76+sizePacketEmbed+65+26:76+sizePacketEmbed+65+26+2], PortIPv6ReportedExternal)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
40
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding.go
generated
vendored
Normal file
40
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
File Name: Message Encoding.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Intermediary between low-level packets and high-level interpretation.
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
)
|
||||
|
||||
// ProtocolVersion is the current protocol version
|
||||
const ProtocolVersion = 0
|
||||
|
||||
// MessageRaw is a high-level message between peers that has not been decoded
|
||||
type MessageRaw struct {
|
||||
PacketRaw
|
||||
SenderPublicKey *btcec.PublicKey // Sender Public Key, ECDSA (secp256k1) 257-bit
|
||||
SequenceInfo *SequenceExpiry // Sequence
|
||||
}
|
||||
|
||||
// The maximum packet size is = 65535 - 8 UDP byte header - 40 byte IPv6 header (IPv4 header is only 20 bytes).
|
||||
// However, due to the MTU soft limit and fragmentation, packets should be as small as possible.
|
||||
const udpMaxPacketSize = 65535 - 8 - 40
|
||||
|
||||
// internetSafeMTU is a value relatively safe to use for transmitting over the internet
|
||||
// Theory: The value is different for IPv4 (min 576 bytes, Ethernet 1500 bytes) and IPv6 (min 1280 bytes). 8 byte UDP header must be subtracted, as well as the IP header (20 bytes for IPv4, 40 for IPv6).
|
||||
// One simple test during development showed that 1500 - 8 - 40 - 8 worked for file transfer over IPv6 in Prague.
|
||||
// For IPv6 the internet recommends the minimal possible value: 1280 bytes.
|
||||
// This will be good enough for now. MTU negotiation that deviates from this value can be implemented separately (for example as part of file transfer).
|
||||
// Since packets may be sent at anytime via IPv4/IPv6 connections (even concurrently on multiple), there is a single MTU value here.
|
||||
const internetSafeMTU = 1280 - 8 - 40
|
||||
|
||||
// isPacketSizeExceed checks if the max packet size would be exceeded with the payload
|
||||
func isPacketSizeExceed(currentSize int, testSize int) bool {
|
||||
return currentSize+testSize > udpMaxPacketSize-PacketLengthMin
|
||||
}
|
||||
157
vendor/github.com/PeernetOfficial/core/protocol/Packet Encoding.go
generated
vendored
Normal file
157
vendor/github.com/PeernetOfficial/core/protocol/Packet Encoding.go
generated
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
File Name: Packet Encoding.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Basic packet structure of ALL packets:
|
||||
Offset Size Info
|
||||
0 4 Nonce
|
||||
4 1 Protocol version = 0
|
||||
5 1 Command
|
||||
6 4 Sequence
|
||||
10 2 Size of payload data
|
||||
12 ? Payload
|
||||
? Randomized garbage
|
||||
? 65 Signature, ECDSA secp256k1 512-bit + 1 header byte
|
||||
|
||||
The peer ID of the sender, which is a ECDSA (secp256k1) 257-bit public key, can be extracted from the ECDSA signature.
|
||||
The signature is applied on the entire packet, which guarantees that the signature becomes invalid should someone try to forge the receiver (i.e. forward the packet).
|
||||
Because the signature could be a possible fingerpint, it is encrypted itself.
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/rand"
|
||||
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
"golang.org/x/crypto/salsa20"
|
||||
)
|
||||
|
||||
// PacketRaw is a decrypted P2P message
|
||||
type PacketRaw struct {
|
||||
Protocol uint8 // Protocol version = 0
|
||||
Command uint8 // 0 = Announcement
|
||||
Sequence uint32 // Sequence number
|
||||
Payload []byte // Payload
|
||||
}
|
||||
|
||||
// The minimum packet size is 12 bytes (minimum header size) + 65 bytes (signature)
|
||||
const PacketLengthMin = 12 + signatureSize
|
||||
const signatureSize = 65
|
||||
const maxRandomGarbage = 20
|
||||
|
||||
// PacketDecrypt decrypts the packet, verifies its signature and returns a high-level version of the packet.
|
||||
func PacketDecrypt(raw []byte, receiverPublicKey *btcec.PublicKey) (packet *PacketRaw, senderPublicKey *btcec.PublicKey, err error) {
|
||||
// Packet is assumed to be already checked for minimum length.
|
||||
|
||||
// Prepare Salsa20 nonce and key. Nonce = 2x first 4 bytes. For size reasons, only 4 bytes (instead of 8 bytes) is supplied in the packet.
|
||||
// This could be a risk, but considering we only use the PUBLIC key as decryption key, it is negligible.
|
||||
nonce := make([]byte, 8)
|
||||
copy(nonce[0:4], raw[0:4])
|
||||
copy(nonce[4:8], raw[0:4])
|
||||
|
||||
// Verify the signature and extract the public key from it.
|
||||
var signature [signatureSize]byte
|
||||
copy(signature[:], raw[len(raw)-signatureSize:])
|
||||
keySalsa := publicKeyToSalsa20Key(receiverPublicKey)
|
||||
salsa20.XORKeyStream(signature[:], signature[:], nonce, keySalsa)
|
||||
|
||||
senderPublicKey, _, err = btcec.RecoverCompact(btcec.S256(), signature[:], HashData(raw[:len(raw)-signatureSize]))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Decrypt the packet using Salsa20.
|
||||
bufferDecrypted := make([]byte, len(raw)-signatureSize-4) // full length -signature -nonce
|
||||
salsa20.XORKeyStream(bufferDecrypted[:], raw[4:len(raw)-signatureSize], nonce, keySalsa)
|
||||
|
||||
// copy all fields
|
||||
packet = &PacketRaw{Protocol: bufferDecrypted[0], Command: bufferDecrypted[1]}
|
||||
packet.Sequence = binary.LittleEndian.Uint32(bufferDecrypted[2:6])
|
||||
|
||||
sizePayload := binary.LittleEndian.Uint16(bufferDecrypted[6:8])
|
||||
if int(sizePayload) > len(bufferDecrypted)-8 { // invalid length?
|
||||
return nil, nil, errors.New("invalid length field")
|
||||
}
|
||||
if sizePayload > 0 {
|
||||
packet.Payload = make([]byte, int(sizePayload))
|
||||
copy(packet.Payload, bufferDecrypted[8:8+int(sizePayload)])
|
||||
}
|
||||
|
||||
return packet, senderPublicKey, nil
|
||||
}
|
||||
|
||||
// PacketEncrypt encrypts a packet using the provided senders private key and receivers compressed public key.
|
||||
func PacketEncrypt(senderPrivateKey *btcec.PrivateKey, receiverPublicKey *btcec.PublicKey, packet *PacketRaw) (raw []byte, err error) {
|
||||
garbage := packetGarbage(PacketLengthMin + len(packet.Payload))
|
||||
raw = make([]byte, PacketLengthMin+len(packet.Payload)+len(garbage))
|
||||
|
||||
nonceC := rand.Uint32()
|
||||
nonce := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint32(nonce[0:4], nonceC)
|
||||
binary.LittleEndian.PutUint32(nonce[4:8], nonceC)
|
||||
copy(raw[0:4], nonce[0:4])
|
||||
|
||||
raw[4] = packet.Protocol
|
||||
raw[5] = packet.Command
|
||||
|
||||
binary.LittleEndian.PutUint32(raw[6:10], uint32(packet.Sequence))
|
||||
binary.LittleEndian.PutUint16(raw[10:12], uint16(len(packet.Payload)))
|
||||
copy(raw[12:], packet.Payload)
|
||||
copy(raw[12+len(packet.Payload):12+len(packet.Payload)+len(garbage)], garbage)
|
||||
|
||||
// encrypt it using Salsa20
|
||||
keySalsa := publicKeyToSalsa20Key(receiverPublicKey)
|
||||
salsa20.XORKeyStream(raw[4:12+len(packet.Payload)+len(garbage)], raw[4:12+len(packet.Payload)+len(garbage)], nonce, keySalsa)
|
||||
|
||||
// add signature
|
||||
signature, err := btcec.SignCompact(btcec.S256(), senderPrivateKey, HashData(raw[:len(raw)-signatureSize]), true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
salsa20.XORKeyStream(signature[:], signature[:], nonce, keySalsa)
|
||||
copy(raw[len(raw)-signatureSize:], signature)
|
||||
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
func packetGarbage(packetLength int) (random []byte) {
|
||||
// Align maximum length at 508 bytes (UDP minimum no fragmentation) and at a relatively safe MTU.
|
||||
maxLength := maxRandomGarbage
|
||||
switch {
|
||||
case packetLength == 508, packetLength == internetSafeMTU:
|
||||
return nil
|
||||
case packetLength < 508 && (508-packetLength) < maxRandomGarbage:
|
||||
maxLength = 508 - packetLength
|
||||
case packetLength < internetSafeMTU && (internetSafeMTU-packetLength) < maxRandomGarbage:
|
||||
maxLength = internetSafeMTU - packetLength
|
||||
}
|
||||
|
||||
b := make([]byte, rand.Intn(maxLength))
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return nil
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func publicKeyToSalsa20Key(publicKey *btcec.PublicKey) (key *[32]byte) {
|
||||
// bit 0 from PublicKey.Y is ignored here, but is negligible for this purpose
|
||||
key = new([32]byte)
|
||||
copy(key[:], publicKey.SerializeCompressed()[1:])
|
||||
return key
|
||||
}
|
||||
|
||||
// SetSelfReportedPorts sets the fields Internal Port and External Port according to the connection details.
|
||||
// This is important for the remote peer to make smart decisions whether this peer is behind a NAT/firewall and supports port forwarding/UPnP.
|
||||
func (packet *PacketRaw) SetSelfReportedPorts(portI, portE uint16) {
|
||||
if packet.Command != CommandAnnouncement && packet.Command != CommandResponse { // only for Announcement and Response messages
|
||||
return
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint16(packet.Payload[19:19+2], portI)
|
||||
binary.LittleEndian.PutUint16(packet.Payload[21:21+2], portE)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user