FinalCommander/content/server.go

175 lines
4.2 KiB
Go

package content
import (
"crypto/tls"
"encoding/base64"
"fmt"
"git.gammaspectra.live/S.O.N.G/FinalCommander/utilities"
"git.gammaspectra.live/S.O.N.G/MakyuuIchaival"
"git.gammaspectra.live/S.O.N.G/MakyuuIchaival/contentmessage"
"github.com/cloudflare/circl/sign/ed25519"
"github.com/multiformats/go-multihash"
"net/http"
"strconv"
"strings"
"sync"
"time"
)
type ServerProtocol int
const (
ProtocolOrbitalBeatV1 ServerProtocol = iota
)
type Server struct {
Index int
Address string
Protocol ServerProtocol
key []byte
Weight uint
LastCheckResult bool
lastCheckMutex sync.RWMutex
}
func NewContentServerFromArgument(arg string, index int, defaultKey []byte) (*Server, error) {
//Format Address:PORT/WEIGHT[/publicKey],
protos := strings.Split(arg, "=")
serverProtocol := ProtocolOrbitalBeatV1
serverKey := defaultKey
if len(protos) > 1 {
switch protos[0] {
case "orbt":
serverProtocol = ProtocolOrbitalBeatV1
default:
return nil, fmt.Errorf("invalid server Protocol %s", arg)
}
}
p := strings.Split(protos[len(protos)-1], "/")
if len(p) < 2 {
return nil, fmt.Errorf("invalid weighted server %s", arg)
}
weight, err := strconv.ParseUint(p[1], 10, 32)
if err != nil {
return nil, err
}
cs := &Server{
Index: index,
Address: p[0],
Protocol: serverProtocol,
key: serverKey,
Weight: uint(weight),
LastCheckResult: false,
}
return cs, nil
}
func (s *Server) GetContentURL(content *Entry, skip []int) string {
switch s.Protocol {
case ProtocolOrbitalBeatV1:
message := contentmessage.NewContentMessageV1(content.Multihash(), ed25519.PrivateKey(s.key))
skip = append(skip, s.Index)
return s.getBaseURL(MakyuuIchaival.Bech32Encoding.EncodeToString(message.Encode()), MakyuuIchaival.Bech32Encoding.EncodeToString(utilities.EncodeIntegerList(skip)))
default:
return ""
}
}
func (s *Server) GetHashURL(mh multihash.Multihash, skip []int) string {
switch s.Protocol {
case ProtocolOrbitalBeatV1:
message := contentmessage.NewContentMessageV1(mh, ed25519.PrivateKey(s.key))
skip = append(skip, s.Index)
return s.getBaseURL(MakyuuIchaival.Bech32Encoding.EncodeToString(message.Encode()), MakyuuIchaival.Bech32Encoding.EncodeToString(utilities.EncodeIntegerList(skip)))
default:
return ""
}
}
func (s *Server) getBaseURL(args ...string) string {
return fmt.Sprintf("https://%s/%s", s.Address, strings.Join(args, "/"))
}
func (s *Server) GetCheckResult() bool {
s.lastCheckMutex.RLock()
defer s.lastCheckMutex.RUnlock()
return s.LastCheckResult
}
func (s *Server) setCheckResult(result bool) {
s.lastCheckMutex.Lock()
defer s.lastCheckMutex.Unlock()
s.LastCheckResult = result
}
func (s *Server) Check() {
customTransport := http.DefaultTransport.(*http.Transport).Clone()
customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
client := &http.Client{
Transport: customTransport,
Timeout: 5 * time.Second,
}
response, err := client.Head(s.getBaseURL())
if err != nil {
s.setCheckResult(false)
return
}
defer response.Body.Close()
if response.StatusCode != http.StatusBadRequest {
s.setCheckResult(false)
return
}
s.setCheckResult(true)
}
func (s *Server) CheckEntryKey(key *HashIdentifier) (*HashIdentifier, error) {
customTransport := http.DefaultTransport.(*http.Transport).Clone()
customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
client := &http.Client{
Transport: customTransport,
Timeout: 5 * time.Second,
}
response, err := client.Head(s.GetHashURL(key.Hash(), []int{}))
if err != nil {
return nil, err
}
defer response.Body.Close()
if response.StatusCode == http.StatusNotFound {
return nil, nil
}
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code %d", response.StatusCode)
}
v := strings.Split(response.Header.Get("Digest"), "=")
if len(v) >= 2 && v[0] == "sha-256" {
sha, err := base64.StdEncoding.DecodeString(strings.Join(v[1:], "="))
if err != nil {
return nil, nil
}
mh, err := multihash.Encode(sha, multihash.SHA2_256)
if err != nil {
return nil, nil
}
return NewHashIdentifierFromMultihash(mh), nil
}
return nil, nil
}