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 }