mirror of
https://github.com/PeernetOfficial/core.git
synced 2026-07-17 02:47:51 +01:00
Sequences add pointer to high-level data. Proper mapping of sequences to information requests.
This commit is contained in:
20
Bootstrap.go
20
Bootstrap.go
@@ -204,13 +204,25 @@ func contactArbitraryPeer(publicKey *btcec.PublicKey, addresses []*net.UDPAddr)
|
||||
}
|
||||
|
||||
for _, address := range addresses {
|
||||
sendAllNetworks(publicKey, &PacketRaw{Command: CommandAnnouncement, Payload: packets[0].raw}, address)
|
||||
sendAllNetworks(publicKey, &PacketRaw{Command: CommandAnnouncement, Payload: packets[0].raw}, address, &bootstrapFindSelf{})
|
||||
}
|
||||
}
|
||||
|
||||
// cmdResponseFindSelf processes FIND_SELF responses
|
||||
func (peer *PeerInfo) cmdResponseFindSelf(msg *MessageResponse, Closest []PeerRecord) {
|
||||
for _, closePeer := range Closest {
|
||||
// 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 *MessageResponse, closest []PeerRecord) {
|
||||
if len(closest) > bootstrapAcceptContacts {
|
||||
closest = closest[:bootstrapAcceptContacts]
|
||||
}
|
||||
|
||||
for _, closePeer := range closest {
|
||||
// Initiate contact. Once a response comes back, the peer is actually added to the list.
|
||||
contactArbitraryPeer(closePeer.PublicKey, []*net.UDPAddr{{IP: closePeer.IP, Port: int(closePeer.Port)}})
|
||||
}
|
||||
|
||||
70
Commands.go
70
Commands.go
@@ -123,52 +123,64 @@ func (peer *PeerInfo) cmdResponse(msg *MessageResponse) {
|
||||
fmt.Printf("Incoming initial response from %s\n", msg.connection.Address.String())
|
||||
}
|
||||
|
||||
// Each result is checked against the list of information requests. Only 1 response by peer is permitted per query currently.
|
||||
// Response packets could be duplicated, which could happen due to auto broadcast when the remote peer responds and has multiple connections to self but none was active.
|
||||
|
||||
for _, hash := range msg.HashesNotFound {
|
||||
info := nodesDHT.IRLookup(peer.NodeID, hash)
|
||||
if info == nil {
|
||||
continue
|
||||
// The sequence data is used to correlate this response with the announcement.
|
||||
if msg.sequence == nil || msg.sequence.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 {
|
||||
log.Printf("cmdResponse unsolicited response data received from %s\n", msg.connection.Address.String())
|
||||
}
|
||||
info.ActiveNodesSub(1)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
for _, hash2Peer := range msg.Hash2Peers {
|
||||
info := nodesDHT.IRLookup(peer.NodeID, hash2Peer.ID.Hash)
|
||||
if info == nil {
|
||||
// Response to FIND_SELF?
|
||||
if bytes.Equal(hash2Peer.ID.Hash, nodeID) && len(hash2Peer.Closest) > 0 {
|
||||
peer.cmdResponseFindSelf(msg, hash2Peer.Closest)
|
||||
// bootstrap FIND_SELF?
|
||||
if _, ok := msg.sequence.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, nodeID) || len(hash2Peer.Closest) == 0 {
|
||||
log.Printf("Incoming response to bootstrap FIND_SELF contains invalid data from %s\n", msg.connection.Address.String())
|
||||
return
|
||||
}
|
||||
continue
|
||||
|
||||
peer.cmdResponseBootstrapFindSelf(msg, hash2Peer.Closest)
|
||||
}
|
||||
|
||||
info.ResultChan <- &dht.NodeMessage{SenderID: peer.NodeID, Closest: records2Nodes(hash2Peer.Closest, msg.connection.Network), Storing: records2Nodes(hash2Peer.Storing, msg.connection.Network)}
|
||||
|
||||
// Future: a terminate field inform wether the remote peer is done sending. For now terminate after 1 packet.
|
||||
info.ActiveNodesSub(1)
|
||||
return
|
||||
}
|
||||
|
||||
for _, file := range msg.FilesEmbed {
|
||||
info := nodesDHT.IRLookup(peer.NodeID, file.ID.Hash)
|
||||
if info == nil {
|
||||
continue
|
||||
// Response to an information request?
|
||||
if _, ok := msg.sequence.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.sequence.data.(*dht.InformationRequest)
|
||||
|
||||
if len(msg.HashesNotFound) > 0 {
|
||||
info.Done()
|
||||
}
|
||||
|
||||
info.ResultChan <- &dht.NodeMessage{SenderID: peer.NodeID, Data: file.Data}
|
||||
for _, hash2Peer := range msg.Hash2Peers {
|
||||
info.ResultChan <- &dht.NodeMessage{SenderID: peer.NodeID, Closest: records2Nodes(hash2Peer.Closest, msg.connection.Network), Storing: records2Nodes(hash2Peer.Storing, msg.connection.Network)}
|
||||
|
||||
info.ActiveNodesSub(1)
|
||||
info.Terminate() // file was found, terminate the request.
|
||||
if hash2Peer.IsLast {
|
||||
info.Done()
|
||||
}
|
||||
}
|
||||
|
||||
for _, file := range msg.FilesEmbed {
|
||||
info.ResultChan <- &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 *MessageRaw) {
|
||||
if peer == nil {
|
||||
// Unexpected incoming ping, reply with announce message
|
||||
// Unexpected incoming ping, reply with announcement message. For security reasons the remote peer is not asked for FIND_SELF.
|
||||
peer, _ = PeerlistAdd(msg.SenderPublicKey, msg.connection)
|
||||
peer.sendAnnouncement(true, true, nil, nil, nil)
|
||||
peer.sendAnnouncement(true, false, nil, nil, nil, nil)
|
||||
}
|
||||
peer.send(&PacketRaw{Command: CommandPong, Sequence: msg.Sequence})
|
||||
//fmt.Printf("Incoming ping from %s on %s\n", msg.connection.Address.String(), msg.connection.Address.String())
|
||||
@@ -201,7 +213,7 @@ func (peer *PeerInfo) cmdLocalDiscovery(msg *MessageAnnouncement) {
|
||||
// fmt.Printf("Incoming secondary local discovery from %s\n", msg.connection.Address.String())
|
||||
}
|
||||
|
||||
peer.sendAnnouncement(true, true, nil, nil, nil)
|
||||
peer.sendAnnouncement(true, true, nil, nil, nil, &bootstrapFindSelf{})
|
||||
}
|
||||
|
||||
// pingTime is the time in seconds to send out ping messages
|
||||
|
||||
@@ -303,7 +303,7 @@ func (peer *PeerInfo) sendConnection(packet *PacketRaw, connection *Connection)
|
||||
}
|
||||
|
||||
// sendAllNetworks sends a raw packet via all networks. It assigns a new sequence for each sent packet.
|
||||
func sendAllNetworks(receiverPublicKey *btcec.PublicKey, packet *PacketRaw, remote *net.UDPAddr) (err error) {
|
||||
func sendAllNetworks(receiverPublicKey *btcec.PublicKey, packet *PacketRaw, remote *net.UDPAddr, sequenceData interface{}) (err error) {
|
||||
var raw []byte
|
||||
packet.Protocol = ProtocolVersion
|
||||
|
||||
@@ -319,7 +319,7 @@ func sendAllNetworks(receiverPublicKey *btcec.PublicKey, packet *PacketRaw, remo
|
||||
continue
|
||||
}
|
||||
|
||||
packet.Sequence = msgArbitrarySequence(receiverPublicKey)
|
||||
packet.Sequence = msgArbitrarySequence(receiverPublicKey, sequenceData).sequence
|
||||
if raw, err = PacketEncrypt(peerPrivateKey, receiverPublicKey, packet); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -336,7 +336,7 @@ func sendAllNetworks(receiverPublicKey *btcec.PublicKey, packet *PacketRaw, remo
|
||||
continue
|
||||
}
|
||||
|
||||
packet.Sequence = msgArbitrarySequence(receiverPublicKey)
|
||||
packet.Sequence = msgArbitrarySequence(receiverPublicKey, sequenceData).sequence
|
||||
if raw, err = PacketEncrypt(peerPrivateKey, receiverPublicKey, packet); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
18
Kademlia.go
18
Kademlia.go
@@ -49,9 +49,9 @@ func initKademlia() {
|
||||
func (peer *PeerInfo) sendAnnouncementFindNode(request *dht.InformationRequest) {
|
||||
// If the key is self, send it as FIND_SELF
|
||||
if bytes.Equal(request.Key, nodeID) {
|
||||
peer.sendAnnouncement(false, true, nil, nil, nil)
|
||||
peer.sendAnnouncement(false, true, nil, nil, nil, request)
|
||||
} else {
|
||||
peer.sendAnnouncement(false, false, []KeyHash{{Hash: request.Key}}, nil, nil)
|
||||
peer.sendAnnouncement(false, false, []KeyHash{{Hash: request.Key}}, nil, nil, request)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,11 +63,11 @@ func (peer *PeerInfo) sendAnnouncementFindValue(request *dht.InformationRequest)
|
||||
|
||||
findValue = append(findValue, KeyHash{Hash: request.Key})
|
||||
|
||||
peer.sendAnnouncement(false, findSelf, findPeer, findValue, nil)
|
||||
peer.sendAnnouncement(false, findSelf, findPeer, findValue, nil, request)
|
||||
}
|
||||
|
||||
func (peer *PeerInfo) sendAnnouncementStore(fileHash []byte, fileSize uint64) {
|
||||
peer.sendAnnouncement(false, false, nil, nil, []InfoStore{{ID: KeyHash{Hash: fileHash}, Size: fileSize, Type: 0}})
|
||||
peer.sendAnnouncement(false, false, nil, nil, []InfoStore{{ID: KeyHash{Hash: fileHash}, Size: fileSize, Type: 0}}, nil)
|
||||
}
|
||||
|
||||
// ---- CORE DATA FUNCTIONS ----
|
||||
@@ -78,9 +78,9 @@ func Data2Hash(data []byte) (hash []byte) {
|
||||
}
|
||||
|
||||
// GetData returns the requested data. It checks first the local store and then tries via DHT.
|
||||
func GetData(hash []byte) (data []byte, found bool) {
|
||||
func GetData(hash []byte) (data []byte, senderNodeID []byte, found bool) {
|
||||
if data, found = GetDataLocal(hash); found {
|
||||
return data, found
|
||||
return data, nodeID, found
|
||||
}
|
||||
|
||||
return GetDataDHT(hash)
|
||||
@@ -92,9 +92,9 @@ func GetDataLocal(hash []byte) (data []byte, found bool) {
|
||||
}
|
||||
|
||||
// GetDataDHT requests data via DHT
|
||||
func GetDataDHT(hash []byte) (data []byte, found bool) {
|
||||
data, found, _ = nodesDHT.Get(hash)
|
||||
return data, found
|
||||
func GetDataDHT(hash []byte) (data []byte, senderNodeID []byte, found bool) {
|
||||
data, senderNodeID, found, _ = nodesDHT.Get(hash)
|
||||
return data, senderNodeID, found
|
||||
}
|
||||
|
||||
// StoreDataLocal stores data into the local warehouse.
|
||||
|
||||
@@ -67,6 +67,7 @@ type MessageRaw struct {
|
||||
PacketRaw
|
||||
SenderPublicKey *btcec.PublicKey // Sender Public Key, ECDSA (secp256k1) 257-bit
|
||||
connection *Connection // Connection that received the packet
|
||||
sequence *sequenceExpiry // Sequence
|
||||
}
|
||||
|
||||
// MessageAnnouncement is the decoded announcement message.
|
||||
@@ -455,10 +456,10 @@ func selfFeatureSupport() (feature byte) {
|
||||
|
||||
// announcementPacket contains information about a single announcement message
|
||||
type announcementPacket struct {
|
||||
raw []byte // The raw packet
|
||||
hashes [][]byte // List of hashes that are being searched for
|
||||
sequence uint32 // Sequence number once sent
|
||||
err error // Sending error, if any
|
||||
raw []byte // The raw packet
|
||||
hashes [][]byte // List of hashes that are being searched for
|
||||
sequence *sequenceExpiry // Sequence
|
||||
err error // Sending error, if any
|
||||
}
|
||||
|
||||
// msgEncodeAnnouncement encodes an announcement message. It may return multiple messages if the input does not fit into one.
|
||||
@@ -761,7 +762,7 @@ createPacketLoop:
|
||||
|
||||
// pingConnection sends a ping to the target peer via the specified connection
|
||||
func (peer *PeerInfo) pingConnection(connection *Connection) {
|
||||
err := peer.sendConnection(&PacketRaw{Command: CommandPing, Sequence: peer.msgNewSequence()}, connection)
|
||||
err := peer.sendConnection(&PacketRaw{Command: CommandPing, Sequence: peer.msgNewSequence(nil).sequence}, connection)
|
||||
connection.LastPingOut = time.Now()
|
||||
|
||||
if (connection.Status == ConnectionActive || connection.Status == ConnectionRedundant) && IsNetworkErrorFatal(err) {
|
||||
@@ -774,13 +775,13 @@ func (peer *PeerInfo) Chat(text string) {
|
||||
peer.send(&PacketRaw{Command: CommandChat, Payload: []byte(text)})
|
||||
}
|
||||
|
||||
// sendAnnouncement sends the announcement message
|
||||
func (peer *PeerInfo) sendAnnouncement(sendUA, findSelf bool, findPeer []KeyHash, findValue []KeyHash, files []InfoStore) (packets []*announcementPacket) {
|
||||
// sendAnnouncement sends the announcement message. It acquires a new sequence for each message.
|
||||
func (peer *PeerInfo) sendAnnouncement(sendUA, findSelf bool, findPeer []KeyHash, findValue []KeyHash, files []InfoStore, sequenceData interface{}) (packets []*announcementPacket) {
|
||||
packets = msgEncodeAnnouncement(sendUA, findSelf, findPeer, findValue, files)
|
||||
|
||||
for _, packet := range packets {
|
||||
packet.sequence = peer.msgNewSequence()
|
||||
packet.err = peer.send(&PacketRaw{Command: CommandAnnouncement, Payload: packet.raw, Sequence: packet.sequence})
|
||||
packet.sequence = peer.msgNewSequence(sequenceData)
|
||||
packet.err = peer.send(&PacketRaw{Command: CommandAnnouncement, Payload: packet.raw, Sequence: packet.sequence.sequence})
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
@@ -30,9 +30,11 @@ var sequences map[string]*sequenceExpiry
|
||||
var sequencesMutex sync.Mutex
|
||||
|
||||
type sequenceExpiry struct {
|
||||
created time.Time // When the sequence was created.
|
||||
expires time.Time // When the sequence expires. This can be extended on the fly!
|
||||
counter int // How many replies used the sequence. Multiple Response messages may be returned for a single Announcement one.
|
||||
sequence uint32 // Sequence number
|
||||
created time.Time // When the sequence was created.
|
||||
expires time.Time // When the sequence expires. This can be extended on the fly!
|
||||
counter int // How many replies used the sequence. Multiple Response messages may be returned for a single Announcement one.
|
||||
data interface{} // Optional high-level data associated with the sequence
|
||||
}
|
||||
|
||||
func initMessageSequence() {
|
||||
@@ -57,40 +59,42 @@ func initMessageSequence() {
|
||||
|
||||
// msgNewSequence returns a new sequence and registers is
|
||||
// Use only for Announcement and Ping messages.
|
||||
func (peer *PeerInfo) msgNewSequence() (sequence uint32) {
|
||||
sequence = atomic.AddUint32(&peer.messageSequence, 1)
|
||||
|
||||
key := string(peer.PublicKey.SerializeCompressed()) + strconv.FormatUint(uint64(sequence), 10)
|
||||
func (peer *PeerInfo) msgNewSequence(data interface{}) (info *sequenceExpiry) {
|
||||
info = &sequenceExpiry{
|
||||
sequence: atomic.AddUint32(&peer.messageSequence, 1),
|
||||
created: time.Now(),
|
||||
expires: time.Now().Add(time.Duration(ReplyTimeout) * time.Second),
|
||||
data: data,
|
||||
}
|
||||
|
||||
// Add the sequence to the list. Sequences are unique enough that collisions are unlikely and negligible.
|
||||
key := string(peer.PublicKey.SerializeCompressed()) + strconv.FormatUint(uint64(info.sequence), 10)
|
||||
sequencesMutex.Lock()
|
||||
sequences[key] = &sequenceExpiry{
|
||||
created: time.Now(),
|
||||
expires: time.Now().Add(time.Duration(ReplyTimeout) * time.Second),
|
||||
}
|
||||
sequences[key] = info
|
||||
sequencesMutex.Unlock()
|
||||
|
||||
return sequence
|
||||
return
|
||||
}
|
||||
|
||||
// msgArbitrarySequence returns an arbitrary sequence to be used for uncontacted peers
|
||||
func msgArbitrarySequence(publicKey *btcec.PublicKey) (sequence uint32) {
|
||||
sequence = rand.Uint32()
|
||||
|
||||
key := string(publicKey.SerializeCompressed()) + strconv.FormatUint(uint64(sequence), 10)
|
||||
func msgArbitrarySequence(publicKey *btcec.PublicKey, data interface{}) (info *sequenceExpiry) {
|
||||
info = &sequenceExpiry{
|
||||
sequence: rand.Uint32(),
|
||||
created: time.Now(),
|
||||
expires: time.Now().Add(time.Duration(ReplyTimeout) * time.Second),
|
||||
data: data,
|
||||
}
|
||||
|
||||
// Add the sequence to the list. Sequences are unique enough that collisions are unlikely and negligible.
|
||||
key := string(publicKey.SerializeCompressed()) + strconv.FormatUint(uint64(info.sequence), 10)
|
||||
sequencesMutex.Lock()
|
||||
sequences[key] = &sequenceExpiry{
|
||||
created: time.Now(),
|
||||
expires: time.Now().Add(time.Duration(ReplyTimeout) * time.Second),
|
||||
}
|
||||
sequences[key] = info
|
||||
sequencesMutex.Unlock()
|
||||
|
||||
return sequence
|
||||
return
|
||||
}
|
||||
|
||||
// msgValidateSequence validates the sequence number of an incoming message
|
||||
// msgValidateSequence validates the sequence number of an incoming message. It will set raw.sequence if valid.
|
||||
func msgValidateSequence(raw *MessageRaw, invalidate bool) (valid bool, rtt time.Duration) {
|
||||
// Only Response and Pong
|
||||
if raw.Command != CommandResponse && raw.Command != CommandPong {
|
||||
@@ -124,6 +128,8 @@ func msgValidateSequence(raw *MessageRaw, invalidate bool) (valid bool, rtt time
|
||||
sequence.expires = time.Now().Add(time.Duration(ReplyTimeout) * time.Second / 2)
|
||||
}
|
||||
|
||||
raw.sequence = sequence
|
||||
|
||||
return sequence.expires.After(time.Now()), rtt
|
||||
}
|
||||
|
||||
|
||||
8
go.mod
8
go.mod
@@ -3,12 +3,12 @@ module github.com/PeernetOfficial/core
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/PeernetOfficial/core/dht v0.0.0-20210410140655-6a9d16717779
|
||||
github.com/PeernetOfficial/core/dht v0.0.0-20210421010630-e432696ad116
|
||||
github.com/btcsuite/btcd v0.21.0-beta.0.20210401013323-36a96f6a0025
|
||||
github.com/pkg/errors v0.9.1
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
|
||||
golang.org/x/net v0.0.0-20210414194228-064579744ee0
|
||||
golang.org/x/sys v0.0.0-20210415045647-66c3f260301c
|
||||
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc
|
||||
golang.org/x/net v0.0.0-20210420210106-798c2154c571
|
||||
golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
lukechampine.com/blake3 v1.1.5
|
||||
)
|
||||
|
||||
18
go.sum
18
go.sum
@@ -1,5 +1,5 @@
|
||||
github.com/PeernetOfficial/core/dht v0.0.0-20210410140655-6a9d16717779 h1:P21VQkgh7qrNuXG/tiXC56BNv2cjGI6z/CYpfrCAVCk=
|
||||
github.com/PeernetOfficial/core/dht v0.0.0-20210410140655-6a9d16717779/go.mod h1:sb2H21VIVTohCXVrDFo/Skl2tN8Yzba+MXSS6NgCYXw=
|
||||
github.com/PeernetOfficial/core/dht v0.0.0-20210421010630-e432696ad116 h1:1dqRhS20S6dRPdLL5dqp4TjTWm3vNvSm/EtRpPfxU6U=
|
||||
github.com/PeernetOfficial/core/dht v0.0.0-20210421010630-e432696ad116/go.mod h1:sb2H21VIVTohCXVrDFo/Skl2tN8Yzba+MXSS6NgCYXw=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btcd v0.21.0-beta.0.20210401013323-36a96f6a0025 h1:aoVqvZk4mLyF3WZbqEVPq+vXnwL2wekZg4P4mjYJNLs=
|
||||
@@ -38,22 +38,22 @@ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc h1:+q90ECDSAQirdykUN6sPEiBXBsp8Csjcca8Oy7bgLTA=
|
||||
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210414194228-064579744ee0 h1:iqW3Mjl/6IP9cHJC/wdiIu3lyBDMUfDElRMyFlqbtiQ=
|
||||
golang.org/x/net v0.0.0-20210414194228-064579744ee0/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
golang.org/x/net v0.0.0-20210420210106-798c2154c571 h1:Q6Bg8xzKzpFPU4Oi1sBnBTHBwlMsLeEXpu4hYBY8rAg=
|
||||
golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210415045647-66c3f260301c h1:6L+uOeS3OQt/f4eFHXZcTxeZrGCuz+CLElgEBjbcTA4=
|
||||
golang.org/x/sys v0.0.0-20210415045647-66c3f260301c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988 h1:EjgCl+fVlIaPJSori0ikSz3uV0DOHKWOJFpv1sAAhBM=
|
||||
golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
|
||||
Reference in New Issue
Block a user