2018-01-29 20:37:26 +01:00
|
|
|
package peer
|
|
|
|
|
|
|
|
import (
|
2018-01-31 02:15:21 +01:00
|
|
|
"bufio"
|
2018-01-29 20:37:26 +01:00
|
|
|
"crypto/sha512"
|
|
|
|
"encoding/hex"
|
|
|
|
"encoding/json"
|
|
|
|
"io"
|
|
|
|
"net"
|
2018-01-31 02:15:21 +01:00
|
|
|
"strings"
|
|
|
|
"time"
|
2018-01-29 20:37:26 +01:00
|
|
|
|
2018-05-29 23:19:40 +02:00
|
|
|
"github.com/lbryio/lbry.go/errors"
|
2018-06-07 05:48:07 +02:00
|
|
|
"github.com/lbryio/lbry.go/stopOnce"
|
2018-01-29 20:37:26 +01:00
|
|
|
"github.com/lbryio/reflector.go/store"
|
|
|
|
|
2018-05-29 23:19:40 +02:00
|
|
|
"github.com/davecgh/go-spew/spew"
|
2018-01-29 20:37:26 +01:00
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2018-05-30 03:38:55 +02:00
|
|
|
// DefaultPort is the port the peer server listens on if not passed in.
|
|
|
|
DefaultPort = 3333
|
|
|
|
// LbrycrdAddress to be used when paying for data. Not implemented yet.
|
2018-02-07 21:21:20 +01:00
|
|
|
LbrycrdAddress = "bJxKvpD96kaJLriqVajZ7SaQTsWWyrGQct"
|
2018-01-29 20:37:26 +01:00
|
|
|
)
|
|
|
|
|
2018-05-30 03:38:55 +02:00
|
|
|
// Server is an instance of a peer server that houses the listener and store.
|
2018-01-29 20:37:26 +01:00
|
|
|
type Server struct {
|
2018-05-29 23:19:40 +02:00
|
|
|
store store.BlobStore
|
|
|
|
closed bool
|
2018-06-07 05:48:07 +02:00
|
|
|
|
|
|
|
stop *stopOnce.Stopper
|
2018-01-29 20:37:26 +01:00
|
|
|
}
|
|
|
|
|
2018-05-30 03:38:55 +02:00
|
|
|
// NewServer returns an initialized Server pointer.
|
2018-01-29 20:37:26 +01:00
|
|
|
func NewServer(store store.BlobStore) *Server {
|
|
|
|
return &Server{
|
|
|
|
store: store,
|
2018-06-07 05:48:07 +02:00
|
|
|
stop: stopOnce.New(),
|
2018-01-29 20:37:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-30 03:38:55 +02:00
|
|
|
// Shutdown gracefully shuts down the peer server.
|
2018-05-29 23:19:40 +02:00
|
|
|
func (s *Server) Shutdown() {
|
2018-06-07 05:48:07 +02:00
|
|
|
log.Debug("shutting down peer server...")
|
|
|
|
s.stop.StopAndWait()
|
2018-05-29 23:19:40 +02:00
|
|
|
}
|
|
|
|
|
2018-06-07 05:48:07 +02:00
|
|
|
// Start starts the server listener to handle connections.
|
|
|
|
func (s *Server) Start(address string) error {
|
|
|
|
|
|
|
|
log.Println("peer listening on " + address)
|
2018-01-29 20:37:26 +01:00
|
|
|
l, err := net.Listen("tcp", address)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-06-07 05:48:07 +02:00
|
|
|
go s.listenForShutdown(l)
|
|
|
|
s.stop.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer s.stop.Done()
|
|
|
|
s.listenAndServe(l)
|
|
|
|
}()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) listenForShutdown(listener net.Listener) {
|
|
|
|
<-s.stop.Ch()
|
|
|
|
s.closed = true
|
|
|
|
if err := listener.Close(); err != nil {
|
|
|
|
log.Error("error closing listener for peer server - ", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) listenAndServe(listener net.Listener) {
|
2018-01-29 20:37:26 +01:00
|
|
|
for {
|
2018-06-07 05:48:07 +02:00
|
|
|
conn, err := listener.Accept()
|
2018-01-29 20:37:26 +01:00
|
|
|
if err != nil {
|
2018-05-29 23:19:40 +02:00
|
|
|
if s.closed {
|
2018-06-07 05:48:07 +02:00
|
|
|
return
|
2018-05-29 23:19:40 +02:00
|
|
|
}
|
2018-01-29 20:37:26 +01:00
|
|
|
log.Error(err)
|
|
|
|
} else {
|
2018-06-07 05:48:07 +02:00
|
|
|
s.stop.Add(1)
|
2018-06-14 02:21:47 +02:00
|
|
|
go func() {
|
|
|
|
s.stop.Done()
|
|
|
|
s.handleConnection(conn)
|
|
|
|
}()
|
2018-01-29 20:37:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-31 02:15:21 +01:00
|
|
|
func (s *Server) handleConnection(conn net.Conn) {
|
2018-06-14 02:21:47 +02:00
|
|
|
defer func() {
|
|
|
|
if err := conn.Close(); err != nil {
|
|
|
|
log.Error("error closing client connection for peer server - ", err)
|
|
|
|
}
|
|
|
|
}()
|
2018-01-31 02:15:21 +01:00
|
|
|
timeoutDuration := 5 * time.Second
|
2018-01-29 20:37:26 +01:00
|
|
|
|
|
|
|
for {
|
2018-01-31 02:15:21 +01:00
|
|
|
var request []byte
|
|
|
|
var response []byte
|
|
|
|
var err error
|
|
|
|
|
2018-05-30 03:38:55 +02:00
|
|
|
if err := conn.SetReadDeadline(time.Now().Add(timeoutDuration)); err != nil {
|
|
|
|
log.Error("error setting read deadline for client connection - ", err)
|
|
|
|
}
|
2018-01-31 02:15:21 +01:00
|
|
|
request, err = readNextRequest(conn)
|
2018-01-29 20:37:26 +01:00
|
|
|
if err != nil {
|
|
|
|
if err != io.EOF {
|
2018-01-31 02:15:21 +01:00
|
|
|
log.Errorln(err)
|
2018-01-29 20:37:26 +01:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2018-05-30 03:38:55 +02:00
|
|
|
if err := conn.SetReadDeadline(time.Time{}); err != nil {
|
|
|
|
log.Error("error setting read deadline client connection - ", err)
|
|
|
|
}
|
2018-01-31 02:15:21 +01:00
|
|
|
|
|
|
|
if strings.Contains(string(request), `"requested_blobs"`) {
|
|
|
|
log.Debugln("received availability request")
|
|
|
|
response, err = s.handleAvailabilityRequest(request)
|
|
|
|
} else if strings.Contains(string(request), `"blob_data_payment_rate"`) {
|
|
|
|
log.Debugln("received rate negotiation request")
|
|
|
|
response, err = s.handlePaymentRateNegotiation(request)
|
|
|
|
} else if strings.Contains(string(request), `"requested_blob"`) {
|
|
|
|
log.Debugln("received blob request")
|
|
|
|
response, err = s.handleBlobRequest(request)
|
|
|
|
} else {
|
|
|
|
log.Errorln("invalid request")
|
|
|
|
spew.Dump(request)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
log.Error(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
n, err := conn.Write(response)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorln(err)
|
|
|
|
return
|
|
|
|
} else if n != len(response) {
|
|
|
|
log.Errorln(io.ErrShortWrite)
|
|
|
|
return
|
|
|
|
}
|
2018-01-29 20:37:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-31 02:15:21 +01:00
|
|
|
func (s *Server) handleAvailabilityRequest(data []byte) ([]byte, error) {
|
2018-01-29 20:37:26 +01:00
|
|
|
var request availabilityRequest
|
2018-01-31 02:15:21 +01:00
|
|
|
err := json.Unmarshal(data, &request)
|
2018-01-29 20:37:26 +01:00
|
|
|
if err != nil {
|
2018-01-31 02:15:21 +01:00
|
|
|
return []byte{}, err
|
2018-01-29 20:37:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
availableBlobs := []string{}
|
|
|
|
for _, blobHash := range request.RequestedBlobs {
|
|
|
|
exists, err := s.store.Has(blobHash)
|
|
|
|
if err != nil {
|
2018-01-31 02:15:21 +01:00
|
|
|
return []byte{}, err
|
2018-01-29 20:37:26 +01:00
|
|
|
}
|
|
|
|
if exists {
|
|
|
|
availableBlobs = append(availableBlobs, blobHash)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-07 21:21:20 +01:00
|
|
|
return json.Marshal(availabilityResponse{LbrycrdAddress: LbrycrdAddress, AvailableBlobs: availableBlobs})
|
2018-01-29 20:37:26 +01:00
|
|
|
}
|
|
|
|
|
2018-01-31 02:15:21 +01:00
|
|
|
func (s *Server) handlePaymentRateNegotiation(data []byte) ([]byte, error) {
|
2018-01-29 20:37:26 +01:00
|
|
|
var request paymentRateRequest
|
2018-01-31 02:15:21 +01:00
|
|
|
err := json.Unmarshal(data, &request)
|
2018-01-29 20:37:26 +01:00
|
|
|
if err != nil {
|
2018-01-31 02:15:21 +01:00
|
|
|
return []byte{}, err
|
2018-01-29 20:37:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
offerReply := paymentRateAccepted
|
|
|
|
if request.BlobDataPaymentRate < 0 {
|
|
|
|
offerReply = paymentRateTooLow
|
|
|
|
}
|
|
|
|
|
2018-01-31 02:15:21 +01:00
|
|
|
return json.Marshal(paymentRateResponse{BlobDataPaymentRate: offerReply})
|
2018-01-29 20:37:26 +01:00
|
|
|
}
|
|
|
|
|
2018-01-31 02:15:21 +01:00
|
|
|
func (s *Server) handleBlobRequest(data []byte) ([]byte, error) {
|
2018-01-29 20:37:26 +01:00
|
|
|
var request blobRequest
|
2018-01-31 02:15:21 +01:00
|
|
|
err := json.Unmarshal(data, &request)
|
2018-01-29 20:37:26 +01:00
|
|
|
if err != nil {
|
2018-01-31 02:15:21 +01:00
|
|
|
return []byte{}, err
|
2018-01-29 20:37:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
log.Println("Sending blob " + request.RequestedBlob[:8])
|
|
|
|
|
|
|
|
blob, err := s.store.Get(request.RequestedBlob)
|
|
|
|
if err != nil {
|
2018-01-31 02:15:21 +01:00
|
|
|
return []byte{}, err
|
2018-01-29 20:37:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
response, err := json.Marshal(blobResponse{IncomingBlob: incomingBlob{
|
2018-05-15 02:55:45 +02:00
|
|
|
BlobHash: GetBlobHash(blob),
|
2018-01-29 20:37:26 +01:00
|
|
|
Length: len(blob),
|
|
|
|
}})
|
|
|
|
if err != nil {
|
2018-01-31 02:15:21 +01:00
|
|
|
return []byte{}, err
|
2018-01-29 20:37:26 +01:00
|
|
|
}
|
|
|
|
|
2018-01-31 02:15:21 +01:00
|
|
|
return append(response, blob...), nil
|
2018-01-29 20:37:26 +01:00
|
|
|
}
|
|
|
|
|
2018-01-31 02:15:21 +01:00
|
|
|
func readNextRequest(conn net.Conn) ([]byte, error) {
|
|
|
|
request := make([]byte, 0)
|
|
|
|
eof := false
|
|
|
|
buf := bufio.NewReader(conn)
|
|
|
|
|
2018-01-29 20:37:26 +01:00
|
|
|
for {
|
2018-01-31 02:15:21 +01:00
|
|
|
chunk, err := buf.ReadBytes('}')
|
2018-01-29 20:37:26 +01:00
|
|
|
if err != nil {
|
|
|
|
if err != io.EOF {
|
2018-01-31 02:15:21 +01:00
|
|
|
log.Errorln("read error:", err)
|
|
|
|
return request, err
|
|
|
|
}
|
|
|
|
eof = true
|
|
|
|
}
|
|
|
|
|
|
|
|
//log.Debugln("got", len(chunk), "bytes.")
|
|
|
|
//spew.Dump(chunk)
|
|
|
|
|
|
|
|
if len(chunk) > 0 {
|
|
|
|
request = append(request, chunk...)
|
|
|
|
|
|
|
|
if len(request) > maxRequestSize {
|
|
|
|
return request, errRequestTooLarge
|
|
|
|
}
|
|
|
|
|
|
|
|
// yes, this is how the peer protocol knows when the request finishes
|
|
|
|
if isValidJSON(request) {
|
|
|
|
break
|
2018-01-29 20:37:26 +01:00
|
|
|
}
|
2018-01-31 02:15:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if eof {
|
2018-01-29 20:37:26 +01:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2018-01-31 02:15:21 +01:00
|
|
|
|
|
|
|
//log.Debugln("total size:", len(request))
|
|
|
|
//if len(request) > 0 {
|
|
|
|
// spew.Dump(request)
|
|
|
|
//}
|
|
|
|
|
|
|
|
if len(request) == 0 && eof {
|
|
|
|
return []byte{}, io.EOF
|
2018-01-29 20:37:26 +01:00
|
|
|
}
|
2018-01-31 02:15:21 +01:00
|
|
|
|
|
|
|
return request, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func isValidJSON(b []byte) bool {
|
|
|
|
var r json.RawMessage
|
|
|
|
return json.Unmarshal(b, &r) == nil
|
2018-01-29 20:37:26 +01:00
|
|
|
}
|
|
|
|
|
2018-05-30 03:38:55 +02:00
|
|
|
// GetBlobHash returns the sha512 hash hex encoded string of the blob byte slice.
|
2018-05-15 02:55:45 +02:00
|
|
|
func GetBlobHash(blob []byte) string {
|
2018-01-29 20:37:26 +01:00
|
|
|
hashBytes := sha512.Sum384(blob)
|
|
|
|
return hex.EncodeToString(hashBytes[:])
|
|
|
|
}
|
2018-05-29 23:19:40 +02:00
|
|
|
|
|
|
|
const (
|
|
|
|
maxRequestSize = 64 * (2 ^ 10) // 64kb
|
|
|
|
paymentRateAccepted = "RATE_ACCEPTED"
|
|
|
|
paymentRateTooLow = "RATE_TOO_LOW"
|
2018-05-30 03:38:55 +02:00
|
|
|
//ToDo: paymentRateUnset is not used but exists in the protocol.
|
|
|
|
//paymentRateUnset = "RATE_UNSET"
|
2018-05-29 23:19:40 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
var errRequestTooLarge = errors.Base("request is too large")
|
|
|
|
|
|
|
|
type availabilityRequest struct {
|
|
|
|
LbrycrdAddress bool `json:"lbrycrd_address"`
|
|
|
|
RequestedBlobs []string `json:"requested_blobs"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type availabilityResponse struct {
|
|
|
|
LbrycrdAddress string `json:"lbrycrd_address"`
|
|
|
|
AvailableBlobs []string `json:"available_blobs"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type paymentRateRequest struct {
|
|
|
|
BlobDataPaymentRate float64 `json:"blob_data_payment_rate"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type paymentRateResponse struct {
|
|
|
|
BlobDataPaymentRate string `json:"blob_data_payment_rate"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type blobRequest struct {
|
|
|
|
RequestedBlob string `json:"requested_blob"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type incomingBlob struct {
|
|
|
|
Error string `json:"error,omitempty"`
|
|
|
|
BlobHash string `json:"blob_hash"`
|
|
|
|
Length int `json:"length"`
|
|
|
|
}
|
|
|
|
type blobResponse struct {
|
|
|
|
IncomingBlob incomingBlob `json:"incoming_blob"`
|
|
|
|
}
|