feat: Waku v2 bridge

Issue #12610
This commit is contained in:
Michal Iskierko
2023-11-12 13:29:38 +01:00
parent 56e7bd01ca
commit 6d31343205
6716 changed files with 1982502 additions and 5891 deletions

115
vendor/github.com/status-im/status-go/server/certs.go generated vendored Normal file
View File

@@ -0,0 +1,115 @@
package server
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"time"
"github.com/ethereum/go-ethereum/log"
)
var globalMediaCertificate *tls.Certificate = nil
var globalMediaPem string
func makeRandomSerialNumber() (*big.Int, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
return rand.Int(rand.Reader, serialNumberLimit)
}
func GenerateX509Cert(sn *big.Int, from, to time.Time, IPAddresses []net.IP, DNSNames []string) *x509.Certificate {
return &x509.Certificate{
SerialNumber: sn,
Subject: pkix.Name{Organization: []string{"Self-signed cert"}},
NotBefore: from,
NotAfter: to,
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true,
IPAddresses: IPAddresses,
DNSNames: DNSNames,
}
}
func GenerateX509PEMs(cert *x509.Certificate, key *ecdsa.PrivateKey) (certPem, keyPem []byte, err error) {
derBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, &key.PublicKey, key)
if err != nil {
return
}
certPem = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
privBytes, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return
}
keyPem = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
return
}
func GenerateTLSCert(notBefore, notAfter time.Time, IPAddresses []net.IP, DNSNames []string) (*tls.Certificate, []byte, error) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, err
}
sn, err := makeRandomSerialNumber()
if err != nil {
return nil, nil, err
}
cert := GenerateX509Cert(sn, notBefore, notAfter, IPAddresses, DNSNames)
certPem, keyPem, err := GenerateX509PEMs(cert, priv)
if err != nil {
return nil, nil, err
}
finalCert, err := tls.X509KeyPair(certPem, keyPem)
return &finalCert, certPem, err
}
func generateMediaTLSCert() error {
if globalMediaCertificate != nil {
return nil
}
now := time.Now()
notBefore := now.Add(-365 * 24 * time.Hour * 100)
notAfter := now.Add(365 * 24 * time.Hour * 100)
log.Debug("generate media cert", "system time", time.Now().String(), "cert notBefore", notBefore.String(), "cert notAfter", notAfter.String())
finalCert, certPem, err := GenerateTLSCert(notBefore, notAfter, []net.IP{}, []string{Localhost})
if err != nil {
return err
}
globalMediaCertificate = finalCert
globalMediaPem = string(certPem)
return nil
}
func PublicMediaTLSCert() (string, error) {
err := generateMediaTLSCert()
if err != nil {
return "", err
}
return globalMediaPem, nil
}
// ToECDSA takes a []byte of D and uses it to create an ecdsa.PublicKey on the elliptic.P256 curve
// this function is basically a P256 curve version of eth-node/crypto.ToECDSA without all the nice validation
func ToECDSA(d []byte) *ecdsa.PrivateKey {
k := new(ecdsa.PrivateKey)
k.D = new(big.Int).SetBytes(d)
k.PublicKey.Curve = elliptic.P256()
k.PublicKey.X, k.PublicKey.Y = k.PublicKey.Curve.ScalarBaseMult(d)
return k
}

34
vendor/github.com/status-im/status-go/server/device.go generated vendored Normal file
View File

@@ -0,0 +1,34 @@
package server
import (
"os"
"strings"
)
var (
local = ".local"
)
func RemoveSuffix(input, suffix string) string {
il := len(input)
sl := len(suffix)
if il > sl {
if input[il-sl:] == suffix {
return input[:il-sl]
}
}
return input
}
func parseHostname(hostname string) string {
hostname = RemoveSuffix(hostname, local)
return strings.ReplaceAll(hostname, "-", " ")
}
func GetDeviceName() (string, error) {
name, err := os.Hostname()
if err != nil {
return "", err
}
return parseHostname(name), nil
}

1154
vendor/github.com/status-im/status-go/server/handlers.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,209 @@
package server
import (
"database/sql"
"encoding/json"
"fmt"
"net/http"
"github.com/golang/protobuf/proto"
"go.uber.org/zap"
"github.com/status-im/status-go/images"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/protobuf"
)
func getThumbnailPayload(db *sql.DB, logger *zap.Logger, msgID string, thumbnailURL string) ([]byte, error) {
var payload []byte
var result []byte
err := db.QueryRow(`SELECT unfurled_links FROM user_messages WHERE id = ?`, msgID).Scan(&result)
if err != nil {
return payload, fmt.Errorf("could not find message with message-id '%s': %w", msgID, err)
}
var links []*protobuf.UnfurledLink
err = json.Unmarshal(result, &links)
if err != nil {
return payload, fmt.Errorf("failed to unmarshal protobuf.UrlPreview: %w", err)
}
for _, p := range links {
if p.Url == thumbnailURL {
payload = p.ThumbnailPayload
break
}
}
return payload, nil
}
func handleLinkPreviewThumbnail(db *sql.DB, logger *zap.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
params := r.URL.Query()
parsed := ParseImageParams(logger, params)
if parsed.MessageID == "" {
http.Error(w, "missing query parameter 'message-id'", http.StatusBadRequest)
return
}
if parsed.URL == "" {
http.Error(w, "missing query parameter 'url'", http.StatusBadRequest)
return
}
thumbnail, err := getThumbnailPayload(db, logger, parsed.MessageID, parsed.URL)
if err != nil {
logger.Error("failed to get thumbnail", zap.String("msgID", parsed.MessageID))
http.Error(w, "failed to get thumbnail", http.StatusInternalServerError)
return
}
mimeType, err := images.GetMimeType(thumbnail)
if err != nil {
http.Error(w, "mime type not supported", http.StatusNotImplemented)
return
}
w.Header().Set("Content-Type", "image/"+mimeType)
w.Header().Set("Cache-Control", "no-store")
_, err = w.Write(thumbnail)
if err != nil {
logger.Error("failed to write response", zap.Error(err))
}
}
}
func getStatusLinkPreviewImage(p *protobuf.UnfurledStatusLink, imageID common.MediaServerImageID) ([]byte, error) {
switch imageID {
case common.MediaServerContactIcon:
contact := p.GetContact()
if contact == nil {
return nil, fmt.Errorf("this is not a contact link")
}
if contact.Icon == nil {
return nil, fmt.Errorf("contact icon is empty")
}
return contact.Icon.Payload, nil
case common.MediaServerCommunityIcon:
community := p.GetCommunity()
if community == nil {
return nil, fmt.Errorf("this is not a community link")
}
if community.Icon == nil {
return nil, fmt.Errorf("community icon is empty")
}
return community.Icon.Payload, nil
case common.MediaServerCommunityBanner:
community := p.GetCommunity()
if community == nil {
return nil, fmt.Errorf("this is not a community link")
}
if community.Banner == nil {
return nil, fmt.Errorf("community banner is empty")
}
return community.Banner.Payload, nil
case common.MediaServerChannelCommunityIcon:
channel := p.GetChannel()
if channel == nil {
return nil, fmt.Errorf("this is not a community channel link")
}
if channel.Community == nil {
return nil, fmt.Errorf("channel community is empty")
}
if channel.Community.Icon == nil {
return nil, fmt.Errorf("channel community icon is empty")
}
return channel.Community.Icon.Payload, nil
case common.MediaServerChannelCommunityBanner:
channel := p.GetChannel()
if channel == nil {
return nil, fmt.Errorf("this is not a community channel link")
}
if channel.Community == nil {
return nil, fmt.Errorf("channel community is empty")
}
if channel.Community.Banner == nil {
return nil, fmt.Errorf("channel community banner is empty")
}
return channel.Community.Banner.Payload, nil
}
return nil, fmt.Errorf("value not supported")
}
func getStatusLinkPreviewThumbnail(db *sql.DB, messageID string, URL string, imageID common.MediaServerImageID) ([]byte, int, error) {
var messageLinks []byte
err := db.QueryRow(`SELECT unfurled_status_links FROM user_messages WHERE id = ?`, messageID).Scan(&messageLinks)
if err != nil {
return nil, http.StatusBadRequest, fmt.Errorf("could not find message with message-id '%s': %w", messageID, err)
}
var links protobuf.UnfurledStatusLinks
err = proto.Unmarshal(messageLinks, &links)
if err != nil {
return nil, http.StatusInternalServerError, fmt.Errorf("failed to unmarshal protobuf.UrlPreview: %w", err)
}
for _, p := range links.UnfurledStatusLinks {
if p.Url == URL {
thumbnailPayload, err := getStatusLinkPreviewImage(p, imageID)
if err != nil {
return nil, http.StatusBadRequest, fmt.Errorf("invalid query parameter 'image-id' value: %w", err)
}
return thumbnailPayload, http.StatusOK, nil
}
}
return nil, http.StatusBadRequest, fmt.Errorf("no link preview found for given url")
}
func handleStatusLinkPreviewThumbnail(db *sql.DB, logger *zap.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
params := r.URL.Query()
parsed := ParseImageParams(logger, params)
if parsed.MessageID == "" {
http.Error(w, "missing query parameter 'message-id'", http.StatusBadRequest)
return
}
if parsed.URL == "" {
http.Error(w, "missing query parameter 'url'", http.StatusBadRequest)
return
}
if parsed.ImageID == "" {
http.Error(w, "missing query parameter 'image-id'", http.StatusBadRequest)
return
}
thumbnail, httpsStatusCode, err := getStatusLinkPreviewThumbnail(db, parsed.MessageID, parsed.URL, common.MediaServerImageID(parsed.ImageID))
if err != nil {
http.Error(w, err.Error(), httpsStatusCode)
return
}
mimeType, err := images.GetMimeType(thumbnail)
if err != nil {
http.Error(w, "mime type not supported", http.StatusNotImplemented)
return
}
w.Header().Set("Content-Type", "image/"+mimeType)
w.Header().Set("Cache-Control", "no-store")
_, err = w.Write(thumbnail)
if err != nil {
logger.Error("failed to write response", zap.Error(err))
}
}
}

203
vendor/github.com/status-im/status-go/server/ips.go generated vendored Normal file
View File

@@ -0,0 +1,203 @@
package server
import (
"net"
"go.uber.org/zap"
"github.com/status-im/status-go/common"
"github.com/status-im/status-go/logutils"
)
var (
LocalHostIP = net.IP{127, 0, 0, 1}
Localhost = "Localhost"
)
func GetOutboundIP() (net.IP, error) {
conn, err := net.Dial("udp", "255.255.255.255:8080")
if err != nil {
return nil, err
}
defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP, nil
}
// addrToIPNet casts addr to IPNet.
// Returns nil if addr is not of IPNet type.
func addrToIPNet(addr net.Addr) *net.IPNet {
switch v := addr.(type) {
case *net.IPNet:
return v
default:
return nil
}
}
// filterAddressesForPairingServer filters private unicast addresses.
// ips is a 2-dimensional array, where each sub-array is a list of IP
// addresses for a single network interface.
func filterAddressesForPairingServer(ips [][]net.IP) []net.IP {
var result = map[string]net.IP{}
for _, niIps := range ips {
var ipv4, ipv6 []net.IP
for _, ip := range niIps {
// Only take private global unicast addrs
if !ip.IsGlobalUnicast() || !ip.IsPrivate() {
continue
}
if v := ip.To4(); v != nil {
ipv4 = append(ipv4, ip)
} else {
ipv6 = append(ipv6, ip)
}
}
// Prefer IPv4 over IPv6 for shorter connection string
if len(ipv4) == 0 {
for _, ip := range ipv6 {
result[ip.String()] = ip
}
} else {
for _, ip := range ipv4 {
result[ip.String()] = ip
}
}
}
var out []net.IP
for _, v := range result {
out = append(out, v)
}
return out
}
// getAndroidLocalIP uses the net dial default ip as the standard Android IP address
// patches https://github.com/status-im/status-mobile/issues/17156
// more work required for a more robust implementation, see https://github.com/wlynxg/anet
func getAndroidLocalIP() ([][]net.IP, error) {
ip, err := GetOutboundIP()
if err != nil {
return nil, err
}
return [][]net.IP{{ip}}, nil
}
// getLocalAddresses returns an array of all addresses
// of all available network interfaces.
func getLocalAddresses() ([][]net.IP, error) {
// TODO until we can resolve Android errors when calling net.Interfaces() just return the outbound local address.
// Sorry Android
if common.OperatingSystemIs(common.AndroidPlatform) {
return getAndroidLocalIP()
}
nis, err := net.Interfaces()
if err != nil {
return nil, err
}
var ips [][]net.IP
for _, ni := range nis {
var niIps []net.IP
addrs, err := ni.Addrs()
if err != nil {
logutils.ZapLogger().Warn("failed to get addresses of network interface",
zap.String("networkInterface", ni.Name),
zap.Error(err))
continue
}
for _, addr := range addrs {
var ip net.IP
if ipNet := addrToIPNet(addr); ipNet == nil {
continue
} else {
ip = ipNet.IP
}
niIps = append(niIps, ip)
}
if len(niIps) > 0 {
ips = append(ips, niIps)
}
}
return ips, nil
}
// GetLocalAddressesForPairingServer is a high-level func
// that returns a list of addresses to be used by local pairing server.
func GetLocalAddressesForPairingServer() ([]net.IP, error) {
ips, err := getLocalAddresses()
if err != nil {
return nil, err
}
return filterAddressesForPairingServer(ips), nil
}
// findReachableAddresses returns a filtered remoteIps array,
// in which each IP matches one or more of given localNets.
func findReachableAddresses(remoteIPs []net.IP, localNets []net.IPNet) []net.IP {
var result []net.IP
for _, localNet := range localNets {
for _, remoteIP := range remoteIPs {
if localNet.Contains(remoteIP) {
result = append(result, remoteIP)
}
}
}
return result
}
// getAllAvailableNetworks collects all networks
// from available network interfaces.
func getAllAvailableNetworks() ([]net.IPNet, error) {
var localNets []net.IPNet
nis, err := net.Interfaces()
if err != nil {
return nil, err
}
for _, ni := range nis {
addrs, err := ni.Addrs()
if err != nil {
logutils.ZapLogger().Warn("failed to get addresses of network interface",
zap.String("networkInterface", ni.Name),
zap.Error(err))
continue
}
for _, localAddr := range addrs {
localNets = append(localNets, *addrToIPNet(localAddr))
}
}
return localNets, nil
}
// FindReachableAddressesForPairingClient is a high-level func
// that returns a reachable server's address to be used by local pairing client.
func FindReachableAddressesForPairingClient(serverIps []net.IP) ([]net.IP, error) {
// TODO until we can resolve Android errors when calling net.Interfaces() just noop. Sorry Android
if common.OperatingSystemIs(common.AndroidPlatform) {
return serverIps, nil
}
nets, err := getAllAvailableNetworks()
if err != nil {
return nil, err
}
return findReachableAddresses(serverIps, nets), nil
}

View File

@@ -0,0 +1,93 @@
package statecontrol
// TODO refactor into the pairing package once the backend dependencies have been removed.
import (
"fmt"
"sync"
)
var (
ErrProcessStateManagerAlreadyPairing = fmt.Errorf("cannot start new LocalPairing session, already pairing")
ErrProcessStateManagerAlreadyPaired = func(sessionName string) error {
return fmt.Errorf("given connection string already successfully used '%s'", sessionName)
}
)
// ProcessStateManager represents a g
type ProcessStateManager struct {
pairing bool
pairingLock sync.Mutex
// sessions represents a map[string]bool:
// where string is a ConnectionParams string and bool is the transfer success state of that connection string
sessions sync.Map
}
// IsPairing returns if the ProcessStateManager is currently in pairing mode
func (psm *ProcessStateManager) IsPairing() bool {
psm.pairingLock.Lock()
defer psm.pairingLock.Unlock()
return psm.pairing
}
// SetPairing sets the ProcessStateManager pairing state
func (psm *ProcessStateManager) SetPairing(state bool) {
psm.pairingLock.Lock()
defer psm.pairingLock.Unlock()
psm.pairing = state
}
// RegisterSession stores a sessionName with the default false value.
// In practice a sessionName will be a ConnectionParams string provided by the server mode device.
// The boolean value represents whether the ConnectionParams string session resulted in a successful transfer.
func (psm *ProcessStateManager) RegisterSession(sessionName string) {
psm.sessions.Store(sessionName, false)
}
// CompleteSession updates a transfer session with a given transfer success state only if the session is registered.
func (psm *ProcessStateManager) CompleteSession(sessionName string) {
r, c := psm.GetSession(sessionName)
if r && !c {
psm.sessions.Store(sessionName, true)
}
}
// GetSession returns two booleans for a given sessionName.
// These represent if the sessionName has been registered and if it has resulted in a successful transfer
func (psm *ProcessStateManager) GetSession(sessionName string) (bool, bool) {
completed, registered := psm.sessions.Load(sessionName)
if !registered {
return registered, false
}
return registered, completed.(bool)
}
// StartPairing along with StopPairing are the core functions of the ProcessStateManager
// This function takes a sessionName, which in practice is a ConnectionParams string, and attempts to init pairing state management.
// The function will return an error if the ProcessStateManager is already currently pairing or if the sessionName was previously successful.
func (psm *ProcessStateManager) StartPairing(sessionName string) error {
if psm.IsPairing() {
return ErrProcessStateManagerAlreadyPairing
}
registered, completed := psm.GetSession(sessionName)
if completed {
return ErrProcessStateManagerAlreadyPaired(sessionName)
}
if !registered {
psm.RegisterSession(sessionName)
}
psm.SetPairing(true)
return nil
}
// StopPairing takes a sessionName and an error, if the error is nil the sessionName will be recorded as successful
// the pairing state of the ProcessStateManager is set to false.
func (psm *ProcessStateManager) StopPairing(sessionName string, err error) {
if err == nil {
psm.CompleteSession(sessionName)
}
psm.SetPairing(false)
}

80
vendor/github.com/status-im/status-go/server/ports.go generated vendored Normal file
View File

@@ -0,0 +1,80 @@
package server
import (
"fmt"
"time"
"go.uber.org/zap"
)
// portManager is responsible for maintaining segregated access to the port field
type portManger struct {
logger *zap.Logger
port int
afterPortChanged func(port int)
}
// newPortManager returns a newly initialised portManager
func newPortManager(logger *zap.Logger, afterPortChanged func(int)) portManger {
pm := portManger{
logger: logger.Named("portManger"),
afterPortChanged: afterPortChanged,
}
return pm
}
// SetPort sets portManger.port field to the given port value
// next triggers any given portManger.afterPortChanged function
func (p *portManger) SetPort(port int) error {
l := p.logger.Named("SetPort")
l.Debug("fired", zap.Int("port", port))
if port == 0 {
errMsg := "port can not be `0`, use ResetPort() instead"
l.Error(errMsg)
return fmt.Errorf(errMsg)
}
p.port = port
if p.afterPortChanged != nil {
l.Debug("p.afterPortChanged != nil")
p.afterPortChanged(port)
}
return nil
}
// ResetPort resets portManger.port to 0
func (p *portManger) ResetPort() {
l := p.logger.Named("ResetPort")
l.Debug("fired")
p.port = 0
}
// GetPort gets the current value of portManager.port without any concern for the state of its value
// and therefore does not wait if portManager.port is 0
func (p *portManger) GetPort() int {
l := p.logger.Named("GetPort")
l.Debug("fired")
return p.port
}
// MustGetPort only returns portManager.port if portManager.port is not 0.
func (p *portManger) MustGetPort() int {
l := p.logger.Named("MustGetPort")
l.Debug("fired")
for {
if p.port != 0 {
port := p.port
if port == 0 {
panic("port is zero, port has reset")
}
return port
}
l.Debug("port is zero")
time.Sleep(20 * time.Millisecond)
}
}

306
vendor/github.com/status-im/status-go/server/qrops.go generated vendored Normal file
View File

@@ -0,0 +1,306 @@
package server
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"image"
"net/url"
"strconv"
"github.com/yeqown/go-qrcode/v2"
"github.com/yeqown/go-qrcode/writer/standard"
"go.uber.org/zap"
"github.com/status-im/status-go/images"
"github.com/status-im/status-go/multiaccounts"
)
type WriterCloserByteBuffer struct {
*bytes.Buffer
}
func (wc WriterCloserByteBuffer) Close() error {
return nil
}
func NewWriterCloserByteBuffer() *WriterCloserByteBuffer {
return &WriterCloserByteBuffer{bytes.NewBuffer([]byte{})}
}
type QRConfig struct {
DecodedQRURL string
WithLogo bool
CorrectionLevel qrcode.EncodeOption
KeyUID string
ImageName string
Size int
Params url.Values
}
func NewQRConfig(params url.Values, logger *zap.Logger) (*QRConfig, error) {
config := &QRConfig{}
config.Params = params
err := config.setQrURL()
if err != nil {
logger.Error("[qrops-error] error in setting QRURL", zap.Error(err))
return nil, err
}
config.setAllowProfileImage()
config.setErrorCorrectionLevel()
err = config.setSize()
if err != nil {
logger.Error("[qrops-error] could not convert string to int for size param ", zap.Error(err))
return nil, err
}
if config.WithLogo {
err = config.setKeyUID()
if err != nil {
logger.Error(err.Error())
return nil, err
}
config.setImageName()
}
return config, nil
}
func (q *QRConfig) setQrURL() error {
qrURL, ok := q.Params["url"]
if !ok || len(qrURL) == 0 {
return errors.New("[qrops-error] no qr url provided")
}
decodedURL, err := base64.StdEncoding.DecodeString(qrURL[0])
if err != nil {
return err
}
q.DecodedQRURL = string(decodedURL)
return nil
}
func (q *QRConfig) setAllowProfileImage() {
allowProfileImage, ok := q.Params["allowProfileImage"]
if !ok || len(allowProfileImage) == 0 {
// we default to false when this flag was not provided
// so someone does not want to allowProfileImage on their QR Image
// fine then :)
q.WithLogo = false
}
LogoOnImage, err := strconv.ParseBool(allowProfileImage[0])
if err != nil {
// maybe for fun someone tries to send non-boolean values to this flag
// we also default to false in that case
q.WithLogo = false
}
// if we reach here its most probably true
q.WithLogo = LogoOnImage
}
func (q *QRConfig) setErrorCorrectionLevel() {
level, ok := q.Params["level"]
if !ok || len(level) == 0 {
// we default to MediumLevel of error correction when the level flag
// is not passed.
q.CorrectionLevel = qrcode.WithErrorCorrectionLevel(qrcode.ErrorCorrectionMedium)
}
levelInt, err := strconv.Atoi(level[0])
if err != nil || levelInt < 0 {
// if there is any issue with string to int conversion
// we still default to MediumLevel of error correction
q.CorrectionLevel = qrcode.WithErrorCorrectionLevel(qrcode.ErrorCorrectionMedium)
}
switch levelInt {
case 1:
q.CorrectionLevel = qrcode.WithErrorCorrectionLevel(qrcode.ErrorCorrectionLow)
case 2:
q.CorrectionLevel = qrcode.WithErrorCorrectionLevel(qrcode.ErrorCorrectionMedium)
case 3:
q.CorrectionLevel = qrcode.WithErrorCorrectionLevel(qrcode.ErrorCorrectionQuart)
case 4:
q.CorrectionLevel = qrcode.WithErrorCorrectionLevel(qrcode.ErrorCorrectionHighest)
default:
q.CorrectionLevel = qrcode.WithErrorCorrectionLevel(qrcode.ErrorCorrectionMedium)
}
}
func (q *QRConfig) setSize() error {
size, ok := q.Params["size"]
if ok {
imageToBeResized, err := strconv.Atoi(size[0])
if err != nil {
return err
}
if imageToBeResized <= 0 {
return errors.New("[qrops-error] Got an invalid size parameter, it should be greater than zero")
}
q.Size = imageToBeResized
}
return nil
}
func (q *QRConfig) setKeyUID() error {
keyUID, ok := q.Params["keyUid"]
// the keyUID was not passed, which is a requirement to get the multiaccount image,
// so we log this error
if !ok || len(keyUID) == 0 {
return errors.New("[qrops-error] A keyUID is required to put logo on image and it was not passed in the parameters")
}
q.KeyUID = keyUID[0]
return nil
}
func (q *QRConfig) setImageName() {
imageName, ok := q.Params["imageName"]
//if the imageName was not passed, we default to const images.LargeDimName
if !ok || len(imageName) == 0 {
q.ImageName = images.LargeDimName
}
q.ImageName = imageName[0]
}
func ToLogoImageFromBytes(imageBytes []byte, padding int) ([]byte, error) {
img, _, err := image.Decode(bytes.NewReader(imageBytes))
if err != nil {
return nil, fmt.Errorf("decoding image failed: %v", err)
}
circle := images.CreateCircleWithPadding(img, padding)
resultBytes, err := images.EncodePNG(circle)
if err != nil {
return nil, fmt.Errorf("encoding PNG failed: %v", err)
}
return resultBytes, nil
}
func GetLogoImage(multiaccountsDB *multiaccounts.Database, keyUID string, imageName string) ([]byte, error) {
var (
padding int
LogoBytes []byte
)
staticImageData, err := images.Asset("_assets/tests/qr/status.png")
if err != nil { // Asset was not found.
return nil, err
}
identityImageObjectFromDB, err := multiaccountsDB.GetIdentityImage(keyUID, imageName)
if err != nil {
return nil, err
}
// default padding to 10 to make the QR with profile image look as per
// the designs
padding = 10
if identityImageObjectFromDB == nil {
LogoBytes, err = ToLogoImageFromBytes(staticImageData, padding)
} else {
LogoBytes, err = ToLogoImageFromBytes(identityImageObjectFromDB.Payload, padding)
}
return LogoBytes, err
}
func GetPadding(imgBytes []byte) int {
const (
defaultPadding = 20
)
size, _, err := images.GetImageDimensions(imgBytes)
if err != nil {
return defaultPadding
}
return size / 5
}
func generateQRBytes(params url.Values, logger *zap.Logger, multiaccountsDB *multiaccounts.Database) []byte {
qrGenerationConfig, err := NewQRConfig(params, logger)
if err != nil {
logger.Error("could not generate QRConfig please rectify the errors with input parameters", zap.Error(err))
return nil
}
qrc, err := qrcode.NewWith(qrGenerationConfig.DecodedQRURL,
qrcode.WithEncodingMode(qrcode.EncModeAuto),
qrGenerationConfig.CorrectionLevel,
)
if err != nil {
logger.Error("could not generate QRCode with provided options", zap.Error(err))
return nil
}
buf := NewWriterCloserByteBuffer()
nw := standard.NewWithWriter(buf)
err = qrc.Save(nw)
if err != nil {
logger.Error("could not save image", zap.Error(err))
return nil
}
payload := buf.Bytes()
if qrGenerationConfig.WithLogo {
logo, err := GetLogoImage(multiaccountsDB, qrGenerationConfig.KeyUID, qrGenerationConfig.ImageName)
if err != nil {
logger.Error("could not get logo image from multiaccountsDB", zap.Error(err))
return nil
}
qrWidth, qrHeight, err := images.GetImageDimensions(payload)
if err != nil {
logger.Error("could not get image dimensions from payload", zap.Error(err))
return nil
}
logo, err = images.ResizeImage(logo, qrWidth/5, qrHeight/5)
if err != nil {
logger.Error("could not resize logo image ", zap.Error(err))
return nil
}
payload = images.SuperimposeLogoOnQRImage(payload, logo)
}
if qrGenerationConfig.Size > 0 {
payload, err = images.ResizeImage(payload, qrGenerationConfig.Size, qrGenerationConfig.Size)
if err != nil {
logger.Error("could not resize final logo image ", zap.Error(err))
return nil
}
}
return payload
}

174
vendor/github.com/status-im/status-go/server/server.go generated vendored Normal file
View File

@@ -0,0 +1,174 @@
package server
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"net/url"
"go.uber.org/zap"
)
type Server struct {
isRunning bool
server *http.Server
logger *zap.Logger
cert *tls.Certificate
hostname string
handlers HandlerPatternMap
portManger
*timeoutManager
}
func NewServer(cert *tls.Certificate, hostname string, afterPortChanged func(int), logger *zap.Logger) Server {
return Server{
logger: logger,
cert: cert,
hostname: hostname,
portManger: newPortManager(logger.Named("Server"), afterPortChanged),
timeoutManager: newTimeoutManager(),
}
}
func (s *Server) getHost() string {
return fmt.Sprintf("%s:%d", s.hostname, s.GetPort())
}
func (s *Server) GetHostname() string {
return s.hostname
}
func (s *Server) GetCert() *tls.Certificate {
return s.cert
}
func (s *Server) GetLogger() *zap.Logger {
return s.logger
}
func (s *Server) mustGetHost() string {
return fmt.Sprintf("%s:%d", s.hostname, s.MustGetPort())
}
func (s *Server) listenAndServe() {
cfg := &tls.Config{Certificates: []tls.Certificate{*s.cert}, ServerName: s.hostname, MinVersion: tls.VersionTLS12}
// in case of restart, we should use the same port as the first start in order not to break existing links
listener, err := tls.Listen("tcp", s.getHost(), cfg)
if err != nil {
s.logger.Error("failed to start server, retrying", zap.Error(err))
s.ResetPort()
err = s.Start()
if err != nil {
s.logger.Error("server start failed, giving up", zap.Error(err))
}
return
}
err = s.SetPort(listener.Addr().(*net.TCPAddr).Port)
if err != nil {
s.logger.Error("failed to set Server.port", zap.Error(err))
return
}
s.isRunning = true
s.StartTimeout(func() {
err := s.Stop()
if err != nil {
s.logger.Error("server termination fail", zap.Error(err))
}
})
err = s.server.Serve(listener)
if err != http.ErrServerClosed {
s.logger.Error("server failed unexpectedly, restarting", zap.Error(err))
err = s.Start()
if err != nil {
s.logger.Error("server start failed, giving up", zap.Error(err))
}
return
}
s.isRunning = false
}
func (s *Server) resetServer() {
s.StopTimeout()
s.server = new(http.Server)
s.ResetPort()
}
func (s *Server) applyHandlers() {
if s.server == nil {
s.server = new(http.Server)
}
mux := http.NewServeMux()
for p, h := range s.handlers {
mux.HandleFunc(p, h)
}
s.server.Handler = mux
}
func (s *Server) Start() error {
// Once Shutdown has been called on a server, it may not be reused;
s.resetServer()
s.applyHandlers()
go s.listenAndServe()
return nil
}
func (s *Server) Stop() error {
s.StopTimeout()
if s.server != nil {
return s.server.Shutdown(context.Background())
}
return nil
}
func (s *Server) IsRunning() bool {
return s.isRunning
}
func (s *Server) ToForeground() {
if !s.isRunning && (s.server != nil) {
err := s.Start()
if err != nil {
s.logger.Error("server start failed during foreground transition", zap.Error(err))
}
}
}
func (s *Server) ToBackground() {
if s.isRunning {
err := s.Stop()
if err != nil {
s.logger.Error("server stop failed during background transition", zap.Error(err))
}
}
}
func (s *Server) SetHandlers(handlers HandlerPatternMap) {
s.handlers = handlers
}
func (s *Server) AddHandlers(handlers HandlerPatternMap) {
if s.handlers == nil {
s.handlers = make(HandlerPatternMap)
}
for name := range handlers {
s.handlers[name] = handlers[name]
}
}
func (s *Server) MakeBaseURL() *url.URL {
return &url.URL{
Scheme: "https",
Host: s.mustGetHost(),
}
}

View File

@@ -0,0 +1,194 @@
package server
import (
"database/sql"
"net/url"
"strconv"
"github.com/status-im/status-go/ipfs"
"github.com/status-im/status-go/logutils"
"github.com/status-im/status-go/multiaccounts"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/signal"
)
type MediaServer struct {
Server
db *sql.DB
downloader *ipfs.Downloader
multiaccountsDB *multiaccounts.Database
walletDB *sql.DB
}
// NewMediaServer returns a *MediaServer
func NewMediaServer(db *sql.DB, downloader *ipfs.Downloader, multiaccountsDB *multiaccounts.Database, walletDB *sql.DB) (*MediaServer, error) {
err := generateMediaTLSCert()
if err != nil {
return nil, err
}
s := &MediaServer{
Server: NewServer(
globalMediaCertificate,
Localhost,
signal.SendMediaServerStarted,
logutils.ZapLogger().Named("MediaServer"),
),
db: db,
downloader: downloader,
multiaccountsDB: multiaccountsDB,
walletDB: walletDB,
}
s.SetHandlers(HandlerPatternMap{
accountImagesPath: handleAccountImages(s.multiaccountsDB, s.logger),
accountInitialsPath: handleAccountInitials(s.multiaccountsDB, s.logger),
audioPath: handleAudio(s.db, s.logger),
contactImagesPath: handleContactImages(s.db, s.logger),
discordAttachmentsPath: handleDiscordAttachment(s.db, s.logger),
discordAuthorsPath: handleDiscordAuthorAvatar(s.db, s.logger),
generateQRCode: handleQRCodeGeneration(s.multiaccountsDB, s.logger),
imagesPath: handleImage(s.db, s.logger),
ipfsPath: handleIPFS(s.downloader, s.logger),
LinkPreviewThumbnailPath: handleLinkPreviewThumbnail(s.db, s.logger),
StatusLinkPreviewThumbnailPath: handleStatusLinkPreviewThumbnail(s.db, s.logger),
communityTokenImagesPath: handleCommunityTokenImages(s.db, s.logger),
walletCommunityImagesPath: handleWalletCommunityImages(s.walletDB, s.logger),
walletCollectionImagesPath: handleWalletCollectionImages(s.walletDB, s.logger),
walletCollectibleImagesPath: handleWalletCollectibleImages(s.walletDB, s.logger),
})
return s, nil
}
func (s *MediaServer) MakeImageServerURL() string {
u := s.MakeBaseURL()
u.Path = basePath + "/"
return u.String()
}
func (s *MediaServer) MakeImageURL(id string) string {
u := s.MakeBaseURL()
u.Path = imagesPath
u.RawQuery = url.Values{"messageId": {id}}.Encode()
return u.String()
}
func (s *MediaServer) MakeLinkPreviewThumbnailURL(msgID string, previewURL string) string {
u := s.MakeBaseURL()
u.Path = LinkPreviewThumbnailPath
u.RawQuery = url.Values{"message-id": {msgID}, "url": {previewURL}}.Encode()
return u.String()
}
func (s *MediaServer) MakeStatusLinkPreviewThumbnailURL(msgID string, previewURL string, imageID common.MediaServerImageID) string {
u := s.MakeBaseURL()
u.Path = StatusLinkPreviewThumbnailPath
u.RawQuery = url.Values{"message-id": {msgID}, "url": {previewURL}, "image-id": {string(imageID)}}.Encode()
return u.String()
}
func (s *MediaServer) MakeDiscordAuthorAvatarURL(authorID string) string {
u := s.MakeBaseURL()
u.Path = discordAuthorsPath
u.RawQuery = url.Values{"authorId": {authorID}}.Encode()
return u.String()
}
func (s *MediaServer) MakeDiscordAttachmentURL(messageID string, id string) string {
u := s.MakeBaseURL()
u.Path = discordAttachmentsPath
u.RawQuery = url.Values{"messageId": {messageID}, "attachmentId": {id}}.Encode()
return u.String()
}
func (s *MediaServer) MakeAudioURL(id string) string {
u := s.MakeBaseURL()
u.Path = audioPath
u.RawQuery = url.Values{"messageId": {id}}.Encode()
return u.String()
}
func (s *MediaServer) MakeStickerURL(stickerHash string) string {
u := s.MakeBaseURL()
u.Path = ipfsPath
u.RawQuery = url.Values{"hash": {stickerHash}}.Encode()
return u.String()
}
func (s *MediaServer) MakeQRURL(qurul string,
allowProfileImage string,
level string,
size string,
keyUID string,
imageName string) string {
u := s.MakeBaseURL()
u.Path = generateQRCode
u.RawQuery = url.Values{"url": {qurul},
"level": {level},
"allowProfileImage": {allowProfileImage},
"size": {size},
"keyUid": {keyUID},
"imageName": {imageName}}.Encode()
return u.String()
}
func (s *MediaServer) MakeContactImageURL(publicKey string, imageType string) string {
u := s.MakeBaseURL()
u.Path = contactImagesPath
u.RawQuery = url.Values{"publicKey": {publicKey}, "imageName": {imageType}}.Encode()
return u.String()
}
func (s *MediaServer) MakeCommunityTokenImagesURL(communityID string, chainID uint64, symbol string) string {
u := s.MakeBaseURL()
u.Path = communityTokenImagesPath
u.RawQuery = url.Values{
"communityID": {communityID},
"chainID": {strconv.FormatUint(chainID, 10)},
"symbol": {symbol},
}.Encode()
return u.String()
}
func (s *MediaServer) MakeWalletCommunityImagesURL(communityID string) string {
u := s.MakeBaseURL()
u.Path = walletCommunityImagesPath
u.RawQuery = url.Values{
"communityID": {communityID},
}.Encode()
return u.String()
}
func (s *MediaServer) MakeWalletCollectionImagesURL(contractID thirdparty.ContractID) string {
u := s.MakeBaseURL()
u.Path = walletCollectionImagesPath
u.RawQuery = url.Values{
"chainID": {contractID.ChainID.String()},
"contractAddress": {contractID.Address.Hex()},
}.Encode()
return u.String()
}
func (s *MediaServer) MakeWalletCollectibleImagesURL(collectibleID thirdparty.CollectibleUniqueID) string {
u := s.MakeBaseURL()
u.Path = walletCollectibleImagesPath
u.RawQuery = url.Values{
"chainID": {collectibleID.ContractID.ChainID.String()},
"contractAddress": {collectibleID.ContractID.Address.Hex()},
"tokenID": {collectibleID.TokenID.String()},
}.Encode()
return u.String()
}

View File

@@ -0,0 +1,94 @@
package server
import (
"sync"
"time"
)
// timeoutManager represents a discrete encapsulation of timeout functionality.
// this struct expose 3 functions:
// - SetTimeout
// - StartTimeout
// - StopTimeout
type timeoutManager struct {
// timeout number of milliseconds the timeout operation will run before executing the `terminate` func()
// 0 represents an inactive timeout
timeout uint
// exitQueue handles the cancel signal channels that circumvent timeout operations and prevent the
// execution of any `terminate` func()
exitQueue *exitQueueManager
}
// newTimeoutManager returns a fully qualified and initialised timeoutManager
func newTimeoutManager() *timeoutManager {
return &timeoutManager{
exitQueue: &exitQueueManager{queue: []chan struct{}{}},
}
}
// SetTimeout sets the value of the timeoutManager.timeout
func (t *timeoutManager) SetTimeout(milliseconds uint) {
t.timeout = milliseconds
}
// StartTimeout starts a timeout operation based on the set timeoutManager.timeout value
// the given terminate func() will be executed once the timeout duration has passed
func (t *timeoutManager) StartTimeout(terminate func()) {
if t.timeout == 0 {
return
}
t.StopTimeout()
exit := make(chan struct{}, 1)
t.exitQueue.add(exit)
go t.run(terminate, exit)
}
// StopTimeout terminates a timeout operation and exits gracefully
func (t *timeoutManager) StopTimeout() {
if t.timeout == 0 {
return
}
t.exitQueue.empty()
}
// run inits the main timeout run function that awaits for the exit command to be triggered or for the
// timeout duration to elapse and trigger the parameter terminate function.
func (t *timeoutManager) run(terminate func(), exit chan struct{}) {
select {
case <-exit:
return
case <-time.After(time.Duration(t.timeout) * time.Millisecond):
terminate()
// TODO fire signal to let UI know
// https://github.com/status-im/status-go/issues/3305
return
}
}
// exitQueueManager
type exitQueueManager struct {
queue []chan struct{}
queueLock sync.Mutex
}
// add handles new exit channels adding them to the exit queue
func (e *exitQueueManager) add(exit chan struct{}) {
e.queueLock.Lock()
defer e.queueLock.Unlock()
e.queue = append(e.queue, exit)
}
// empty sends a signal to every exit channel in the queue and then resets the queue
func (e *exitQueueManager) empty() {
e.queueLock.Lock()
defer e.queueLock.Unlock()
for i := range e.queue {
e.queue[i] <- struct{}{}
}
e.queue = []chan struct{}{}
}