105 lines
3.6 KiB
Go
105 lines
3.6 KiB
Go
package connector
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"galaxy/model/client"
|
|
"galaxy/model/report"
|
|
)
|
|
|
|
// Connector is a main interface to provide connectivity with app's server.
|
|
type Connector interface {
|
|
UIConnector
|
|
|
|
// CheckConnection is called asynchronously every 5 seconds and tests is connection available with a specific backend server endpoint.
|
|
// There is guaranteed jittered backoff with caps 5s -> 15s -> 30s -> 60s when no connection is available.
|
|
CheckConnection() bool
|
|
|
|
// CheckVersion is called asynchronously every 30 minutes and receives from backend server information about currently available app versions.
|
|
CheckVersion() ([]VersionInfo, error)
|
|
|
|
// DownloadVersion asynchronously retrieves from a specific string URL a binary artifact from backend server.
|
|
DownloadVersion(string) ([]byte, error)
|
|
}
|
|
|
|
// UIConnector contains only funcs are needed for the client app to be functional.
|
|
type UIConnector interface {
|
|
// FetchReport asynchronously requests from backend server a [report.Report] for a given [model.GameID] and turn number.
|
|
// Passed callback func will will accept non-nil error in case of I/O or decoding errors occuried,
|
|
// otherwise callback func accepts loaded [report.Report].
|
|
FetchReport(client.GameID, uint, func(report.Report, error))
|
|
}
|
|
|
|
type VersionInfo struct {
|
|
OS string `json:"os"` // Operating System name (unix, darwin, windows, etc.)
|
|
Version string `json:"version"` // Semver format: X.Y.Z
|
|
URL string `json:"url"` // Artifact download URL for this version
|
|
Checksum SHA256Digest `json:"sha256"` // Base64 SHA-256 checksum for artifact binary data
|
|
}
|
|
|
|
// SHA256Digest represents a SHA-256 digest in raw binary form.
|
|
//
|
|
// Internally it stores the exact 32-byte digest.
|
|
// In JSON it is encoded as a lowercase hexadecimal string of 64 characters.
|
|
type SHA256Digest [32]byte
|
|
|
|
// NewSHA256Digest calculates SHA-256 for the provided byte slice
|
|
// and returns the digest as SHA256Digest.
|
|
//
|
|
// The function does not modify the input data.
|
|
func NewSHA256Digest(data []byte) SHA256Digest {
|
|
sum := sha256.Sum256(data)
|
|
return SHA256Digest(sum)
|
|
}
|
|
|
|
// String returns the lowercase hexadecimal representation
|
|
// of the digest.
|
|
//
|
|
// The returned string always contains exactly 64 characters.
|
|
func (d SHA256Digest) String() string {
|
|
return hex.EncodeToString(d[:])
|
|
}
|
|
|
|
// MarshalJSON encodes the digest as a JSON string containing
|
|
// the lowercase hexadecimal SHA-256 value.
|
|
//
|
|
// Example JSON value:
|
|
//
|
|
// "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
|
|
func (d SHA256Digest) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(d.String())
|
|
}
|
|
|
|
// UnmarshalJSON decodes a JSON string containing a lowercase or uppercase
|
|
// hexadecimal SHA-256 value into the digest.
|
|
//
|
|
// The input must be a JSON string with exactly 64 hexadecimal characters.
|
|
func (d *SHA256Digest) UnmarshalJSON(data []byte) error {
|
|
var s string
|
|
if err := json.Unmarshal(data, &s); err != nil {
|
|
return fmt.Errorf("sha256 digest must be a JSON string: %w", err)
|
|
}
|
|
|
|
if len(s) != hex.EncodedLen(len(d)) {
|
|
return fmt.Errorf("invalid SHA-256 hex length: got %d, want %d", len(s), hex.EncodedLen(len(d)))
|
|
}
|
|
|
|
decoded, err := hex.DecodeString(s)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid SHA-256 hex value: %w", err)
|
|
}
|
|
|
|
copy(d[:], decoded)
|
|
return nil
|
|
}
|
|
|
|
// Equal returns true when both digests are identical.
|
|
//
|
|
// Since SHA256Digest is based on a fixed-size array, direct value comparison
|
|
// is efficient and idiomatic for non-constant-time equality checks.
|
|
func (d SHA256Digest) Equal(other SHA256Digest) bool {
|
|
return d == other
|
|
}
|