feat(notify): 重构通信内核并补齐 stream/bulk/record/transfer 能力
- 引入 LogicalConn/TransportConn 分层,ClientConn 保留兼容适配层 - 新增 Stream、Bulk、RecordStream 三条数据面能力及对应控制路径 - 完成 transfer/file 传输内核与状态快照、诊断能力 - 补齐 reconnect、inbound dispatcher、modern psk 等基础模块 - 增加大规模回归、并发与基准测试覆盖 - 更新依赖库
This commit is contained in:
+429
-6
@@ -2,7 +2,10 @@ package starnotify
|
||||
|
||||
import (
|
||||
"b612.me/notify"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -20,9 +23,23 @@ func init() {
|
||||
|
||||
func NewClient(key string) notify.Client {
|
||||
client := notify.NewClient()
|
||||
cmu.Lock()
|
||||
starClient[key] = client
|
||||
cmu.Unlock()
|
||||
storeClient(key, client)
|
||||
return client
|
||||
}
|
||||
|
||||
func NewModernPSKClient(key string, sharedSecret []byte, opts *notify.ModernPSKOptions) (notify.Client, error) {
|
||||
client := notify.NewClient()
|
||||
if err := notify.UseModernPSKClient(client, sharedSecret, opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
storeClient(key, client)
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func NewLegacySecurityClient(key string) notify.Client {
|
||||
client := notify.NewClient()
|
||||
notify.UseLegacySecurityClient(client)
|
||||
storeClient(key, client)
|
||||
return client
|
||||
}
|
||||
|
||||
@@ -45,9 +62,23 @@ func DeleteClient(key string) (err error) {
|
||||
|
||||
func NewServer(key string) notify.Server {
|
||||
server := notify.NewServer()
|
||||
smu.Lock()
|
||||
starServer[key] = server
|
||||
smu.Unlock()
|
||||
storeServer(key, server)
|
||||
return server
|
||||
}
|
||||
|
||||
func NewModernPSKServer(key string, sharedSecret []byte, opts *notify.ModernPSKOptions) (notify.Server, error) {
|
||||
server := notify.NewServer()
|
||||
if err := notify.UseModernPSKServer(server, sharedSecret, opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
storeServer(key, server)
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func NewLegacySecurityServer(key string) notify.Server {
|
||||
server := notify.NewServer()
|
||||
notify.UseLegacySecurityServer(server)
|
||||
storeServer(key, server)
|
||||
return server
|
||||
}
|
||||
|
||||
@@ -107,3 +138,395 @@ func Client(key string) (notify.Client, error) {
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func OpenClientStreamFromReader(ctx context.Context, key string, src io.Reader, opt notify.StreamOpenCopyOptions) (notify.Stream, int64, error) {
|
||||
client, err := Client(key)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return notify.OpenClientStreamFromReader(ctx, client, src, opt)
|
||||
}
|
||||
|
||||
func OpenServerLogicalStreamFromReader(ctx context.Context, key string, logical *notify.LogicalConn, src io.Reader, opt notify.StreamOpenCopyOptions) (notify.Stream, int64, error) {
|
||||
server, err := Server(key)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return notify.OpenServerLogicalStreamFromReader(ctx, server, logical, src, opt)
|
||||
}
|
||||
|
||||
func OpenServerTransportStreamFromReader(ctx context.Context, key string, transport *notify.TransportConn, src io.Reader, opt notify.StreamOpenCopyOptions) (notify.Stream, int64, error) {
|
||||
server, err := Server(key)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return notify.OpenServerTransportStreamFromReader(ctx, server, transport, src, opt)
|
||||
}
|
||||
|
||||
func CopyStreamToWriter(ctx context.Context, stream notify.Stream, dst io.Writer, opt notify.StreamCopyOptions) (int64, error) {
|
||||
return notify.CopyStreamToWriter(ctx, stream, dst, opt)
|
||||
}
|
||||
|
||||
func NewTransferSourceFromReader(src io.Reader, size int64) (notify.TransferReaderAt, error) {
|
||||
return notify.NewTransferSourceFromReader(src, size)
|
||||
}
|
||||
|
||||
func NewTransferSinkFromWriter(dst io.Writer) (notify.TransferWriterAt, error) {
|
||||
return notify.NewTransferSinkFromWriter(dst)
|
||||
}
|
||||
|
||||
func NewTransferReaderFromSource(source notify.TransferReaderAt, offset int64) (io.Reader, error) {
|
||||
return notify.NewTransferReaderFromSource(source, offset)
|
||||
}
|
||||
|
||||
func NewTransferWriterFromSink(sink notify.TransferWriterAt, offset int64) (io.Writer, error) {
|
||||
return notify.NewTransferWriterFromSink(sink, offset)
|
||||
}
|
||||
|
||||
func UseModernPSKClient(key string, sharedSecret []byte, opts *notify.ModernPSKOptions) error {
|
||||
client, err := Client(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return notify.UseModernPSKClient(client, sharedSecret, opts)
|
||||
}
|
||||
|
||||
func UseModernPSKServer(key string, sharedSecret []byte, opts *notify.ModernPSKOptions) error {
|
||||
server, err := Server(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return notify.UseModernPSKServer(server, sharedSecret, opts)
|
||||
}
|
||||
|
||||
func UseLegacySecurityClient(key string) error {
|
||||
client, err := Client(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
notify.UseLegacySecurityClient(client)
|
||||
return nil
|
||||
}
|
||||
|
||||
func UseLegacySecurityServer(key string) error {
|
||||
server, err := Server(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
notify.UseLegacySecurityServer(server)
|
||||
return nil
|
||||
}
|
||||
|
||||
func UseSignalReliabilityClient(key string, opts *notify.SignalReliabilityOptions) error {
|
||||
client, err := Client(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return notify.UseSignalReliabilityClient(client, opts)
|
||||
}
|
||||
|
||||
func UseSignalReliabilityServer(key string, opts *notify.SignalReliabilityOptions) error {
|
||||
server, err := Server(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return notify.UseSignalReliabilityServer(server, opts)
|
||||
}
|
||||
|
||||
func ConnectClientWithRetry(key string, network string, addr string, opts *notify.ConnectRetryOptions) error {
|
||||
return ConnectClientWithRetryCtx(context.Background(), key, network, addr, opts)
|
||||
}
|
||||
|
||||
func ConnectClientWithRetryCtx(ctx context.Context, key string, network string, addr string, opts *notify.ConnectRetryOptions) error {
|
||||
client, err := Client(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return notify.ConnectClientWithRetry(ctx, client, network, addr, opts)
|
||||
}
|
||||
|
||||
func ConnectClientFactoryWithRetry(key string, dialFn func(context.Context) (net.Conn, error), opts *notify.ConnectRetryOptions) error {
|
||||
return ConnectClientFactoryWithRetryCtx(context.Background(), key, dialFn, opts)
|
||||
}
|
||||
|
||||
func ConnectClientFactoryWithRetryCtx(ctx context.Context, key string, dialFn func(context.Context) (net.Conn, error), opts *notify.ConnectRetryOptions) error {
|
||||
client, err := Client(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return notify.ConnectClientFactoryWithRetry(ctx, client, dialFn, opts)
|
||||
}
|
||||
|
||||
func ListenServerWithRetry(key string, network string, addr string, opts *notify.ConnectRetryOptions) error {
|
||||
return ListenServerWithRetryCtx(context.Background(), key, network, addr, opts)
|
||||
}
|
||||
|
||||
func ListenServerWithRetryCtx(ctx context.Context, key string, network string, addr string, opts *notify.ConnectRetryOptions) error {
|
||||
server, err := Server(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return notify.ListenServerWithRetry(ctx, server, network, addr, opts)
|
||||
}
|
||||
|
||||
func GetSignalReliabilityStatsClient(key string) (notify.SignalReliabilityStats, error) {
|
||||
client, err := Client(key)
|
||||
if err != nil {
|
||||
return notify.SignalReliabilityStats{}, err
|
||||
}
|
||||
return notify.GetSignalReliabilityStatsClient(client)
|
||||
}
|
||||
|
||||
func GetSignalReliabilityStatsServer(key string) (notify.SignalReliabilityStats, error) {
|
||||
server, err := Server(key)
|
||||
if err != nil {
|
||||
return notify.SignalReliabilityStats{}, err
|
||||
}
|
||||
return notify.GetSignalReliabilityStatsServer(server)
|
||||
}
|
||||
|
||||
func GetClientRuntimeSnapshot(key string) (notify.ClientRuntimeSnapshot, error) {
|
||||
client, err := Client(key)
|
||||
if err != nil {
|
||||
return notify.ClientRuntimeSnapshot{}, err
|
||||
}
|
||||
return notify.GetClientRuntimeSnapshot(client)
|
||||
}
|
||||
|
||||
func GetServerRuntimeSnapshot(key string) (notify.ServerRuntimeSnapshot, error) {
|
||||
server, err := Server(key)
|
||||
if err != nil {
|
||||
return notify.ServerRuntimeSnapshot{}, err
|
||||
}
|
||||
return notify.GetServerRuntimeSnapshot(server)
|
||||
}
|
||||
|
||||
func GetServerClientRuntimeSnapshot(serverKey string, clientID string) (notify.ClientConnRuntimeSnapshot, error) {
|
||||
return GetServerLogicalRuntimeSnapshot(serverKey, clientID)
|
||||
}
|
||||
|
||||
func GetServerLogicalRuntimeSnapshot(serverKey string, clientID string) (notify.ClientConnRuntimeSnapshot, error) {
|
||||
server, err := Server(serverKey)
|
||||
if err != nil {
|
||||
return notify.ClientConnRuntimeSnapshot{}, err
|
||||
}
|
||||
logical := server.GetLogicalConn(clientID)
|
||||
if logical == nil {
|
||||
return notify.ClientConnRuntimeSnapshot{}, errors.New("Not Exists Yet")
|
||||
}
|
||||
return notify.GetLogicalConnRuntimeSnapshot(logical)
|
||||
}
|
||||
|
||||
func GetServerLogicalConn(serverKey string, clientID string) (*notify.LogicalConn, error) {
|
||||
server, err := Server(serverKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := server.GetLogicalConn(clientID)
|
||||
if client == nil {
|
||||
return nil, errors.New("Not Exists Yet")
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func GetServerCurrentTransportConn(serverKey string, clientID string) (*notify.TransportConn, bool, error) {
|
||||
server, err := Server(serverKey)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
transport := server.GetCurrentTransportConn(clientID)
|
||||
if transport == nil {
|
||||
if server.GetLogicalConn(clientID) == nil {
|
||||
return nil, false, errors.New("Not Exists Yet")
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
return transport, true, nil
|
||||
}
|
||||
|
||||
func GetServerClientTransportRuntimeSnapshot(serverKey string, clientID string) (notify.TransportConnRuntimeSnapshot, bool, error) {
|
||||
return GetServerTransportRuntimeSnapshot(serverKey, clientID)
|
||||
}
|
||||
|
||||
func GetServerTransportRuntimeSnapshot(serverKey string, clientID string) (notify.TransportConnRuntimeSnapshot, bool, error) {
|
||||
server, err := Server(serverKey)
|
||||
if err != nil {
|
||||
return notify.TransportConnRuntimeSnapshot{}, false, err
|
||||
}
|
||||
transport := server.GetCurrentTransportConn(clientID)
|
||||
if transport == nil {
|
||||
if server.GetLogicalConn(clientID) == nil {
|
||||
return notify.TransportConnRuntimeSnapshot{}, false, errors.New("Not Exists Yet")
|
||||
}
|
||||
return notify.TransportConnRuntimeSnapshot{}, false, nil
|
||||
}
|
||||
snapshot, err := notify.GetTransportConnRuntimeSnapshot(transport)
|
||||
if err != nil {
|
||||
return notify.TransportConnRuntimeSnapshot{}, false, err
|
||||
}
|
||||
return snapshot, true, nil
|
||||
}
|
||||
|
||||
func GetServerDetachedClientRuntimeSnapshots(serverKey string) ([]notify.ClientConnRuntimeSnapshot, error) {
|
||||
server, err := Server(serverKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return notify.GetServerDetachedClientRuntimeSnapshots(server)
|
||||
}
|
||||
|
||||
func GetClientTransferSnapshots(key string) ([]notify.TransferSnapshot, error) {
|
||||
client, err := Client(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return notify.GetClientTransferSnapshots(client)
|
||||
}
|
||||
|
||||
func GetServerTransferSnapshots(key string) ([]notify.TransferSnapshot, error) {
|
||||
server, err := Server(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return notify.GetServerTransferSnapshots(server)
|
||||
}
|
||||
|
||||
func GetClientTransferSnapshotByID(key string, transferID string) (notify.TransferSnapshot, bool, error) {
|
||||
client, err := Client(key)
|
||||
if err != nil {
|
||||
return notify.TransferSnapshot{}, false, err
|
||||
}
|
||||
return notify.GetClientTransferSnapshotByID(client, transferID)
|
||||
}
|
||||
|
||||
func GetClientTransferSnapshotByIDScope(key string, transferID string, scope string) (notify.TransferSnapshot, bool, error) {
|
||||
client, err := Client(key)
|
||||
if err != nil {
|
||||
return notify.TransferSnapshot{}, false, err
|
||||
}
|
||||
return notify.GetClientTransferSnapshotByIDScope(client, transferID, scope)
|
||||
}
|
||||
|
||||
func GetClientTransferSnapshotByIDQuery(key string, transferID string, query notify.TransferSnapshotQuery) (notify.TransferSnapshot, bool, error) {
|
||||
client, err := Client(key)
|
||||
if err != nil {
|
||||
return notify.TransferSnapshot{}, false, err
|
||||
}
|
||||
return notify.GetClientTransferSnapshotByIDQuery(client, transferID, query)
|
||||
}
|
||||
|
||||
func GetServerTransferSnapshotByID(key string, transferID string) (notify.TransferSnapshot, bool, error) {
|
||||
server, err := Server(key)
|
||||
if err != nil {
|
||||
return notify.TransferSnapshot{}, false, err
|
||||
}
|
||||
return notify.GetServerTransferSnapshotByID(server, transferID)
|
||||
}
|
||||
|
||||
func GetServerTransferSnapshotByIDScope(key string, transferID string, scope string) (notify.TransferSnapshot, bool, error) {
|
||||
server, err := Server(key)
|
||||
if err != nil {
|
||||
return notify.TransferSnapshot{}, false, err
|
||||
}
|
||||
return notify.GetServerTransferSnapshotByIDScope(server, transferID, scope)
|
||||
}
|
||||
|
||||
func GetServerTransferSnapshotByIDQuery(key string, transferID string, query notify.TransferSnapshotQuery) (notify.TransferSnapshot, bool, error) {
|
||||
server, err := Server(key)
|
||||
if err != nil {
|
||||
return notify.TransferSnapshot{}, false, err
|
||||
}
|
||||
return notify.GetServerTransferSnapshotByIDQuery(server, transferID, query)
|
||||
}
|
||||
|
||||
func GetClientFileTransferActiveSummaries(key string) (notify.FileTransferSummaryGroup, error) {
|
||||
client, err := Client(key)
|
||||
if err != nil {
|
||||
return notify.FileTransferSummaryGroup{}, err
|
||||
}
|
||||
return notify.GetClientFileTransferActiveSummaries(client)
|
||||
}
|
||||
|
||||
func GetServerFileTransferActiveSummaries(key string) (notify.FileTransferSummaryGroup, error) {
|
||||
server, err := Server(key)
|
||||
if err != nil {
|
||||
return notify.FileTransferSummaryGroup{}, err
|
||||
}
|
||||
return notify.GetServerFileTransferActiveSummaries(server)
|
||||
}
|
||||
|
||||
func GetClientFileTransferCompletedSummaries(key string) (notify.FileTransferSummaryGroup, error) {
|
||||
client, err := Client(key)
|
||||
if err != nil {
|
||||
return notify.FileTransferSummaryGroup{}, err
|
||||
}
|
||||
return notify.GetClientFileTransferCompletedSummaries(client)
|
||||
}
|
||||
|
||||
func GetServerFileTransferCompletedSummaries(key string) (notify.FileTransferSummaryGroup, error) {
|
||||
server, err := Server(key)
|
||||
if err != nil {
|
||||
return notify.FileTransferSummaryGroup{}, err
|
||||
}
|
||||
return notify.GetServerFileTransferCompletedSummaries(server)
|
||||
}
|
||||
|
||||
func GetClientFileTransferFailedSummaries(key string) (notify.FileTransferSummaryGroup, error) {
|
||||
client, err := Client(key)
|
||||
if err != nil {
|
||||
return notify.FileTransferSummaryGroup{}, err
|
||||
}
|
||||
return notify.GetClientFileTransferFailedSummaries(client)
|
||||
}
|
||||
|
||||
func GetServerFileTransferFailedSummaries(key string) (notify.FileTransferSummaryGroup, error) {
|
||||
server, err := Server(key)
|
||||
if err != nil {
|
||||
return notify.FileTransferSummaryGroup{}, err
|
||||
}
|
||||
return notify.GetServerFileTransferFailedSummaries(server)
|
||||
}
|
||||
|
||||
func GetClientFileTransferLatestByFileID(key string, fileID string) (notify.FileTransferSummaryGroup, error) {
|
||||
client, err := Client(key)
|
||||
if err != nil {
|
||||
return notify.FileTransferSummaryGroup{}, err
|
||||
}
|
||||
return notify.GetClientFileTransferLatestByFileID(client, fileID)
|
||||
}
|
||||
|
||||
func GetServerFileTransferLatestByFileID(key string, fileID string) (notify.FileTransferSummaryGroup, error) {
|
||||
server, err := Server(key)
|
||||
if err != nil {
|
||||
return notify.FileTransferSummaryGroup{}, err
|
||||
}
|
||||
return notify.GetServerFileTransferLatestByFileID(server, fileID)
|
||||
}
|
||||
|
||||
func GetClientFileTransferLatestByFileIDQuery(key string, fileID string, query notify.FileTransferSummaryQuery) (notify.FileTransferSummaryGroup, error) {
|
||||
client, err := Client(key)
|
||||
if err != nil {
|
||||
return notify.FileTransferSummaryGroup{}, err
|
||||
}
|
||||
return notify.GetClientFileTransferLatestByFileIDQuery(client, fileID, query)
|
||||
}
|
||||
|
||||
func GetServerFileTransferLatestByFileIDQuery(key string, fileID string, query notify.FileTransferSummaryQuery) (notify.FileTransferSummaryGroup, error) {
|
||||
server, err := Server(key)
|
||||
if err != nil {
|
||||
return notify.FileTransferSummaryGroup{}, err
|
||||
}
|
||||
return notify.GetServerFileTransferLatestByFileIDQuery(server, fileID, query)
|
||||
}
|
||||
|
||||
func storeClient(key string, client notify.Client) {
|
||||
cmu.Lock()
|
||||
starClient[key] = client
|
||||
cmu.Unlock()
|
||||
}
|
||||
|
||||
func storeServer(key string, server notify.Server) {
|
||||
smu.Lock()
|
||||
starServer[key] = server
|
||||
smu.Unlock()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
package starnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"b612.me/notify"
|
||||
)
|
||||
|
||||
func testModernPSKOptions() *notify.ModernPSKOptions {
|
||||
return ¬ify.ModernPSKOptions{
|
||||
Salt: []byte("starnotify-modern-psk-test-salt"),
|
||||
AAD: []byte("starnotify-modern-psk-test-aad"),
|
||||
Argon2Params: notify.DefaultModernPSKOptions().Argon2Params,
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewModernPSKClientStoresConfiguredClient(t *testing.T) {
|
||||
const key = "modern-client"
|
||||
_ = DeleteClient(key)
|
||||
defer DeleteClient(key)
|
||||
|
||||
client, err := NewModernPSKClient(key, []byte("shared-secret"), testModernPSKOptions())
|
||||
if err != nil {
|
||||
t.Fatalf("NewModernPSKClient failed: %v", err)
|
||||
}
|
||||
if got := C(key); got != client {
|
||||
t.Fatal("stored client does not match returned client")
|
||||
}
|
||||
if !client.SkipExchangeKey() {
|
||||
t.Fatal("modern PSK client should skip legacy key exchange")
|
||||
}
|
||||
if len(client.GetSecretKey()) == 0 {
|
||||
t.Fatal("modern PSK client should derive a transport key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewModernPSKServerStoresConfiguredServer(t *testing.T) {
|
||||
const key = "modern-server"
|
||||
_ = DeleteServer(key)
|
||||
defer DeleteServer(key)
|
||||
|
||||
server, err := NewModernPSKServer(key, []byte("shared-secret"), testModernPSKOptions())
|
||||
if err != nil {
|
||||
t.Fatalf("NewModernPSKServer failed: %v", err)
|
||||
}
|
||||
if got := S(key); got != server {
|
||||
t.Fatal("stored server does not match returned server")
|
||||
}
|
||||
if len(server.GetSecretKey()) == 0 {
|
||||
t.Fatal("modern PSK server should derive a transport key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewLegacySecurityClientStoresConfiguredClient(t *testing.T) {
|
||||
const key = "legacy-client"
|
||||
_ = DeleteClient(key)
|
||||
defer DeleteClient(key)
|
||||
|
||||
client := NewLegacySecurityClient(key)
|
||||
if got := C(key); got != client {
|
||||
t.Fatal("stored client does not match returned client")
|
||||
}
|
||||
if client.SkipExchangeKey() {
|
||||
t.Fatal("legacy client should keep legacy key exchange enabled")
|
||||
}
|
||||
if len(client.GetSecretKey()) == 0 {
|
||||
t.Fatal("legacy client should restore a transport key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewLegacySecurityServerStoresConfiguredServer(t *testing.T) {
|
||||
const key = "legacy-server"
|
||||
_ = DeleteServer(key)
|
||||
defer DeleteServer(key)
|
||||
|
||||
server := NewLegacySecurityServer(key)
|
||||
if got := S(key); got != server {
|
||||
t.Fatal("stored server does not match returned server")
|
||||
}
|
||||
if len(server.GetSecretKey()) == 0 {
|
||||
t.Fatal("legacy server should restore a transport key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUseModernPSKClientConfiguresExistingClient(t *testing.T) {
|
||||
const key = "existing-client"
|
||||
_ = DeleteClient(key)
|
||||
defer DeleteClient(key)
|
||||
|
||||
client := NewClient(key)
|
||||
if err := UseModernPSKClient(key, []byte("shared-secret"), testModernPSKOptions()); err != nil {
|
||||
t.Fatalf("UseModernPSKClient failed: %v", err)
|
||||
}
|
||||
if !client.SkipExchangeKey() {
|
||||
t.Fatal("existing client should skip legacy key exchange after UseModernPSKClient")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUseLegacySecurityClientConfiguresExistingClient(t *testing.T) {
|
||||
const key = "existing-legacy-client"
|
||||
_ = DeleteClient(key)
|
||||
defer DeleteClient(key)
|
||||
|
||||
client := NewClient(key)
|
||||
if err := UseLegacySecurityClient(key); err != nil {
|
||||
t.Fatalf("UseLegacySecurityClient failed: %v", err)
|
||||
}
|
||||
if client.SkipExchangeKey() {
|
||||
t.Fatal("existing client should re-enable legacy exchange after UseLegacySecurityClient")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarnotifyNewClientUsesModernDefault(t *testing.T) {
|
||||
const key = "default-modern-client"
|
||||
_ = DeleteClient(key)
|
||||
defer DeleteClient(key)
|
||||
|
||||
client := NewClient(key)
|
||||
err := client.Connect("tcp", "127.0.0.1:1")
|
||||
if err == nil {
|
||||
t.Fatal("default client should require security configuration before Connect")
|
||||
}
|
||||
if !errors.Is(err, notify.NewClient().Connect("tcp", "127.0.0.1:1")) {
|
||||
t.Fatalf("default client error = %v, want notify modern-default behavior", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUseModernPSKServerMissingKey(t *testing.T) {
|
||||
if err := UseModernPSKServer("missing-server", []byte("shared-secret"), testModernPSKOptions()); err == nil {
|
||||
t.Fatal("UseModernPSKServer should fail for missing key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUseLegacySecurityServerMissingKey(t *testing.T) {
|
||||
if err := UseLegacySecurityServer("missing-server"); err == nil {
|
||||
t.Fatal("UseLegacySecurityServer should fail for missing key")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package starnotify
|
||||
|
||||
import "b612.me/notify"
|
||||
|
||||
func GetClientDiagnosticsSnapshot(key string) (notify.ClientDiagnosticsSnapshot, error) {
|
||||
client, err := Client(key)
|
||||
if err != nil {
|
||||
return notify.ClientDiagnosticsSnapshot{}, err
|
||||
}
|
||||
return notify.GetClientDiagnosticsSnapshot(client)
|
||||
}
|
||||
|
||||
func GetServerDiagnosticsSnapshot(key string) (notify.ServerDiagnosticsSnapshot, error) {
|
||||
server, err := Server(key)
|
||||
if err != nil {
|
||||
return notify.ServerDiagnosticsSnapshot{}, err
|
||||
}
|
||||
return notify.GetServerDiagnosticsSnapshot(server)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package starnotify
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGetDiagnosticsSnapshotByKeyDefaults(t *testing.T) {
|
||||
const clientKey = "diagnostics-client"
|
||||
const serverKey = "diagnostics-server"
|
||||
_ = DeleteClient(clientKey)
|
||||
_ = DeleteServer(serverKey)
|
||||
defer DeleteClient(clientKey)
|
||||
defer DeleteServer(serverKey)
|
||||
|
||||
NewClient(clientKey)
|
||||
NewServer(serverKey)
|
||||
|
||||
clientSnapshot, err := GetClientDiagnosticsSnapshot(clientKey)
|
||||
if err != nil {
|
||||
t.Fatalf("GetClientDiagnosticsSnapshot failed: %v", err)
|
||||
}
|
||||
if got, want := clientSnapshot.Runtime.OwnerState, "idle"; got != want {
|
||||
t.Fatalf("client Runtime.OwnerState = %q, want %q", got, want)
|
||||
}
|
||||
if clientSnapshot.Summary.LogicalCount != 0 || clientSnapshot.Summary.StreamCount != 0 || clientSnapshot.Summary.BulkCount != 0 || clientSnapshot.Summary.TransferCount != 0 {
|
||||
t.Fatalf("unexpected default client summary: %+v", clientSnapshot.Summary)
|
||||
}
|
||||
|
||||
serverSnapshot, err := GetServerDiagnosticsSnapshot(serverKey)
|
||||
if err != nil {
|
||||
t.Fatalf("GetServerDiagnosticsSnapshot failed: %v", err)
|
||||
}
|
||||
if got, want := serverSnapshot.Runtime.OwnerState, "idle"; got != want {
|
||||
t.Fatalf("server Runtime.OwnerState = %q, want %q", got, want)
|
||||
}
|
||||
if len(serverSnapshot.Logicals) != 0 || len(serverSnapshot.CurrentTransports) != 0 {
|
||||
t.Fatalf("unexpected default server diagnostics: %+v", serverSnapshot)
|
||||
}
|
||||
if serverSnapshot.Summary.LogicalCount != 0 || serverSnapshot.Summary.StreamCount != 0 || serverSnapshot.Summary.BulkCount != 0 || serverSnapshot.Summary.TransferCount != 0 {
|
||||
t.Fatalf("unexpected default server summary: %+v", serverSnapshot.Summary)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package starnotify
|
||||
|
||||
import "testing"
|
||||
|
||||
import "b612.me/notify"
|
||||
|
||||
func TestGetFileTransferSummariesByKeyDefaults(t *testing.T) {
|
||||
const clientKey = "file-transfer-public-client"
|
||||
const serverKey = "file-transfer-public-server"
|
||||
_ = DeleteClient(clientKey)
|
||||
_ = DeleteServer(serverKey)
|
||||
defer DeleteClient(clientKey)
|
||||
defer DeleteServer(serverKey)
|
||||
|
||||
NewClient(clientKey)
|
||||
NewServer(serverKey)
|
||||
|
||||
clientActive, err := GetClientFileTransferActiveSummaries(clientKey)
|
||||
if err != nil {
|
||||
t.Fatalf("GetClientFileTransferActiveSummaries failed: %v", err)
|
||||
}
|
||||
if len(clientActive.Send) != 0 || len(clientActive.Receive) != 0 {
|
||||
t.Fatalf("client active summary should be empty: %+v", clientActive)
|
||||
}
|
||||
|
||||
serverCompleted, err := GetServerFileTransferCompletedSummaries(serverKey)
|
||||
if err != nil {
|
||||
t.Fatalf("GetServerFileTransferCompletedSummaries failed: %v", err)
|
||||
}
|
||||
if len(serverCompleted.Send) != 0 || len(serverCompleted.Receive) != 0 {
|
||||
t.Fatalf("server completed summary should be empty: %+v", serverCompleted)
|
||||
}
|
||||
|
||||
clientLatest, err := GetClientFileTransferLatestByFileID(clientKey, "missing")
|
||||
if err != nil {
|
||||
t.Fatalf("GetClientFileTransferLatestByFileID failed: %v", err)
|
||||
}
|
||||
if len(clientLatest.Send) != 0 || len(clientLatest.Receive) != 0 {
|
||||
t.Fatalf("client latest summary should be empty: %+v", clientLatest)
|
||||
}
|
||||
|
||||
serverLatestQuery, err := GetServerFileTransferLatestByFileIDQuery(serverKey, "missing", notify.FileTransferSummaryQuery{
|
||||
Scope: "scope-a",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("GetServerFileTransferLatestByFileIDQuery failed: %v", err)
|
||||
}
|
||||
if len(serverLatestQuery.Send) != 0 || len(serverLatestQuery.Receive) != 0 {
|
||||
t.Fatalf("server latest query summary should be empty: %+v", serverLatestQuery)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,562 @@
|
||||
package starnotify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"b612.me/notify"
|
||||
)
|
||||
|
||||
var errSingleConnListenerClosed = errors.New("single conn listener closed")
|
||||
|
||||
type singleConnListener struct {
|
||||
conn net.Conn
|
||||
used bool
|
||||
mu sync.Mutex
|
||||
closed chan struct{}
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func newSingleConnListener(conn net.Conn) *singleConnListener {
|
||||
return &singleConnListener{
|
||||
conn: conn,
|
||||
closed: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *singleConnListener) Accept() (net.Conn, error) {
|
||||
l.mu.Lock()
|
||||
if !l.used && l.conn != nil {
|
||||
conn := l.conn
|
||||
l.used = true
|
||||
l.mu.Unlock()
|
||||
return conn, nil
|
||||
}
|
||||
l.mu.Unlock()
|
||||
<-l.closed
|
||||
return nil, errSingleConnListenerClosed
|
||||
}
|
||||
|
||||
func (l *singleConnListener) Close() error {
|
||||
l.once.Do(func() {
|
||||
close(l.closed)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *singleConnListener) Addr() net.Addr {
|
||||
return singleConnAddr("starnotify-single-listener")
|
||||
}
|
||||
|
||||
type singleConnAddr string
|
||||
|
||||
func (a singleConnAddr) Network() string { return "single-conn" }
|
||||
func (a singleConnAddr) String() string { return string(a) }
|
||||
|
||||
func TestGetRuntimeSnapshotByKeyDefaults(t *testing.T) {
|
||||
const clientKey = "runtime-snapshot-client"
|
||||
const serverKey = "runtime-snapshot-server"
|
||||
_ = DeleteClient(clientKey)
|
||||
_ = DeleteServer(serverKey)
|
||||
defer DeleteClient(clientKey)
|
||||
defer DeleteServer(serverKey)
|
||||
|
||||
NewClient(clientKey)
|
||||
NewServer(serverKey)
|
||||
|
||||
clientSnapshot, err := GetClientRuntimeSnapshot(clientKey)
|
||||
if err != nil {
|
||||
t.Fatalf("GetClientRuntimeSnapshot failed: %v", err)
|
||||
}
|
||||
if got, want := clientSnapshot.OwnerState, "idle"; got != want {
|
||||
t.Fatalf("client OwnerState mismatch: got %q want %q", got, want)
|
||||
}
|
||||
if clientSnapshot.Alive {
|
||||
t.Fatalf("client Alive mismatch: got %v want false", clientSnapshot.Alive)
|
||||
}
|
||||
if !clientSnapshot.HasRuntimeStopCtx {
|
||||
t.Fatalf("client HasRuntimeStopCtx mismatch: got %v want true", clientSnapshot.HasRuntimeStopCtx)
|
||||
}
|
||||
if clientSnapshot.Retry != (notify.ConnectionRetrySnapshot{}) {
|
||||
t.Fatalf("client Retry snapshot mismatch: %+v", clientSnapshot.Retry)
|
||||
}
|
||||
|
||||
serverSnapshot, err := GetServerRuntimeSnapshot(serverKey)
|
||||
if err != nil {
|
||||
t.Fatalf("GetServerRuntimeSnapshot failed: %v", err)
|
||||
}
|
||||
if got, want := serverSnapshot.OwnerState, "idle"; got != want {
|
||||
t.Fatalf("server OwnerState mismatch: got %q want %q", got, want)
|
||||
}
|
||||
if serverSnapshot.Alive {
|
||||
t.Fatalf("server Alive mismatch: got %v want false", serverSnapshot.Alive)
|
||||
}
|
||||
if !serverSnapshot.HasRuntimeStopCtx {
|
||||
t.Fatalf("server HasRuntimeStopCtx mismatch: got %v want true", serverSnapshot.HasRuntimeStopCtx)
|
||||
}
|
||||
if serverSnapshot.Retry != (notify.ConnectionRetrySnapshot{}) {
|
||||
t.Fatalf("server Retry snapshot mismatch: %+v", serverSnapshot.Retry)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRuntimeSnapshotMissingKey(t *testing.T) {
|
||||
if _, err := GetClientRuntimeSnapshot("missing-client"); err == nil {
|
||||
t.Fatal("GetClientRuntimeSnapshot should fail for missing key")
|
||||
}
|
||||
if _, err := GetServerRuntimeSnapshot("missing-server"); err == nil {
|
||||
t.Fatal("GetServerRuntimeSnapshot should fail for missing key")
|
||||
}
|
||||
if _, err := GetServerClientRuntimeSnapshot("missing-server", "peer"); err == nil {
|
||||
t.Fatal("GetServerClientRuntimeSnapshot should fail for missing server key")
|
||||
}
|
||||
if _, err := GetServerLogicalConn("missing-server", "peer"); err == nil {
|
||||
t.Fatal("GetServerLogicalConn should fail for missing server key")
|
||||
}
|
||||
if _, ok, err := GetServerCurrentTransportConn("missing-server", "peer"); err == nil || ok {
|
||||
t.Fatal("GetServerCurrentTransportConn should fail for missing server key")
|
||||
}
|
||||
if _, ok, err := GetServerClientTransportRuntimeSnapshot("missing-server", "peer"); err == nil || ok {
|
||||
t.Fatal("GetServerClientTransportRuntimeSnapshot should fail for missing server key")
|
||||
}
|
||||
if _, err := GetServerDetachedClientRuntimeSnapshots("missing-server"); err == nil {
|
||||
t.Fatal("GetServerDetachedClientRuntimeSnapshots should fail for missing server key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRuntimeSnapshotExposesRetryState(t *testing.T) {
|
||||
const clientKey = "runtime-retry-client"
|
||||
const serverKey = "runtime-retry-server"
|
||||
_ = DeleteClient(clientKey)
|
||||
_ = DeleteServer(serverKey)
|
||||
defer DeleteClient(clientKey)
|
||||
defer DeleteServer(serverKey)
|
||||
|
||||
NewLegacySecurityClient(clientKey)
|
||||
NewServer(serverKey)
|
||||
|
||||
clientRetryErr := errors.New("dial failed")
|
||||
err := ConnectClientFactoryWithRetry(clientKey, func(context.Context) (net.Conn, error) {
|
||||
return nil, clientRetryErr
|
||||
}, ¬ify.ConnectRetryOptions{
|
||||
MaxAttempts: 2,
|
||||
BaseDelay: time.Millisecond,
|
||||
MaxDelay: time.Millisecond,
|
||||
})
|
||||
if !errors.Is(err, clientRetryErr) {
|
||||
t.Fatalf("ConnectClientFactoryWithRetry error = %v, want %v", err, clientRetryErr)
|
||||
}
|
||||
|
||||
clientSnapshot, err := GetClientRuntimeSnapshot(clientKey)
|
||||
if err != nil {
|
||||
t.Fatalf("GetClientRuntimeSnapshot failed: %v", err)
|
||||
}
|
||||
if got, want := clientSnapshot.Retry.RetryEventTotal, uint64(1); got != want {
|
||||
t.Fatalf("client RetryEventTotal mismatch: got %d want %d", got, want)
|
||||
}
|
||||
if got, want := clientSnapshot.Retry.LastRetryAttempt, 1; got != want {
|
||||
t.Fatalf("client LastRetryAttempt mismatch: got %d want %d", got, want)
|
||||
}
|
||||
if got, want := clientSnapshot.Retry.LastRetryError, clientRetryErr.Error(); got != want {
|
||||
t.Fatalf("client LastRetryError mismatch: got %q want %q", got, want)
|
||||
}
|
||||
if got, want := clientSnapshot.Retry.LastResultError, clientRetryErr.Error(); got != want {
|
||||
t.Fatalf("client LastResultError mismatch: got %q want %q", got, want)
|
||||
}
|
||||
if clientSnapshot.Retry.LastRetryAt.IsZero() {
|
||||
t.Fatal("client LastRetryAt should be recorded")
|
||||
}
|
||||
if clientSnapshot.Retry.LastResultAt.IsZero() {
|
||||
t.Fatal("client LastResultAt should be recorded")
|
||||
}
|
||||
|
||||
serverErr := ListenServerWithRetry(serverKey, "tcp", "127.0.0.1:0", ¬ify.ConnectRetryOptions{
|
||||
MaxAttempts: 2,
|
||||
BaseDelay: time.Millisecond,
|
||||
MaxDelay: time.Millisecond,
|
||||
})
|
||||
if serverErr == nil {
|
||||
t.Fatal("ListenServerWithRetry should fail without security configuration")
|
||||
}
|
||||
|
||||
serverSnapshot, err := GetServerRuntimeSnapshot(serverKey)
|
||||
if err != nil {
|
||||
t.Fatalf("GetServerRuntimeSnapshot failed: %v", err)
|
||||
}
|
||||
if got, want := serverSnapshot.Retry.RetryEventTotal, uint64(1); got != want {
|
||||
t.Fatalf("server RetryEventTotal mismatch: got %d want %d", got, want)
|
||||
}
|
||||
if got, want := serverSnapshot.Retry.LastRetryAttempt, 1; got != want {
|
||||
t.Fatalf("server LastRetryAttempt mismatch: got %d want %d", got, want)
|
||||
}
|
||||
if got, want := serverSnapshot.Retry.LastRetryError, serverErr.Error(); got != want {
|
||||
t.Fatalf("server LastRetryError mismatch: got %q want %q", got, want)
|
||||
}
|
||||
if got, want := serverSnapshot.Retry.LastResultError, serverErr.Error(); got != want {
|
||||
t.Fatalf("server LastResultError mismatch: got %q want %q", got, want)
|
||||
}
|
||||
if serverSnapshot.Retry.LastRetryAt.IsZero() {
|
||||
t.Fatal("server LastRetryAt should be recorded")
|
||||
}
|
||||
if serverSnapshot.Retry.LastResultAt.IsZero() {
|
||||
t.Fatal("server LastResultAt should be recorded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetServerClientRuntimeSnapshotByKey(t *testing.T) {
|
||||
const serverKey = "runtime-peer-server"
|
||||
_ = DeleteServer(serverKey)
|
||||
defer DeleteServer(serverKey)
|
||||
|
||||
server := NewServer(serverKey)
|
||||
client := notify.NewClient()
|
||||
secret := []byte("0123456789abcdef0123456789abcdef")
|
||||
server.SetSecretKey(secret)
|
||||
client.SetSecretKey(secret)
|
||||
|
||||
serverConn, clientConn := net.Pipe()
|
||||
defer clientConn.Close()
|
||||
listener := newSingleConnListener(serverConn)
|
||||
defer listener.Close()
|
||||
|
||||
if err := server.ListenByListener(listener); err != nil {
|
||||
t.Fatalf("ListenByListener failed: %v", err)
|
||||
}
|
||||
defer server.Stop()
|
||||
|
||||
if err := client.ConnectByConn(clientConn); err != nil {
|
||||
t.Fatalf("ConnectByConn failed: %v", err)
|
||||
}
|
||||
defer client.Stop()
|
||||
|
||||
srv, err := Server(serverKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Server lookup failed: %v", err)
|
||||
}
|
||||
|
||||
var clientID string
|
||||
deadline := time.Now().Add(time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
list := srv.GetLogicalConnList()
|
||||
if len(list) == 1 && list[0] != nil {
|
||||
clientID = list[0].ClientID
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if clientID == "" {
|
||||
t.Fatal("server did not expose accepted client in time")
|
||||
}
|
||||
|
||||
snapshot, err := GetServerClientRuntimeSnapshot(serverKey, clientID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetServerClientRuntimeSnapshot failed: %v", err)
|
||||
}
|
||||
if got, want := snapshot.ClientID, clientID; got != want {
|
||||
t.Fatalf("ClientID mismatch: got %q want %q", got, want)
|
||||
}
|
||||
if !snapshot.Alive {
|
||||
t.Fatalf("Alive mismatch: got %v want true", snapshot.Alive)
|
||||
}
|
||||
if !snapshot.IdentityBound {
|
||||
t.Fatal("IdentityBound mismatch: got false want true")
|
||||
}
|
||||
if !snapshot.TransportAttached {
|
||||
t.Fatalf("TransportAttached mismatch: got %v want true", snapshot.TransportAttached)
|
||||
}
|
||||
|
||||
logicalSnapshot, err := GetServerLogicalRuntimeSnapshot(serverKey, clientID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetServerLogicalRuntimeSnapshot failed: %v", err)
|
||||
}
|
||||
if logicalSnapshot.ClientID != snapshot.ClientID || logicalSnapshot.TransportGeneration != snapshot.TransportGeneration {
|
||||
t.Fatalf("logical runtime snapshot mismatch: got %+v want %+v", logicalSnapshot, snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetServerClientTransportRuntimeSnapshotByKey(t *testing.T) {
|
||||
const serverKey = "runtime-transport-server"
|
||||
_ = DeleteServer(serverKey)
|
||||
defer DeleteServer(serverKey)
|
||||
|
||||
server := NewServer(serverKey)
|
||||
client := notify.NewClient()
|
||||
secret := []byte("0123456789abcdef0123456789abcdef")
|
||||
server.SetSecretKey(secret)
|
||||
client.SetSecretKey(secret)
|
||||
|
||||
serverConn, clientConn := net.Pipe()
|
||||
defer clientConn.Close()
|
||||
listener := newSingleConnListener(serverConn)
|
||||
defer listener.Close()
|
||||
|
||||
if err := server.ListenByListener(listener); err != nil {
|
||||
t.Fatalf("ListenByListener failed: %v", err)
|
||||
}
|
||||
defer server.Stop()
|
||||
|
||||
if err := client.ConnectByConn(clientConn); err != nil {
|
||||
t.Fatalf("ConnectByConn failed: %v", err)
|
||||
}
|
||||
defer client.Stop()
|
||||
|
||||
srv, err := Server(serverKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Server lookup failed: %v", err)
|
||||
}
|
||||
|
||||
var clientID string
|
||||
deadline := time.Now().Add(time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
list := srv.GetLogicalConnList()
|
||||
if len(list) == 1 && list[0] != nil {
|
||||
clientID = list[0].ClientID
|
||||
if clientID != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if clientID == "" {
|
||||
t.Fatal("server did not expose accepted client in time")
|
||||
}
|
||||
|
||||
snapshot, ok, err := GetServerClientTransportRuntimeSnapshot(serverKey, clientID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetServerClientTransportRuntimeSnapshot failed: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("GetServerClientTransportRuntimeSnapshot should report current transport")
|
||||
}
|
||||
if got, want := snapshot.ClientID, clientID; got != want {
|
||||
t.Fatalf("ClientID mismatch: got %q want %q", got, want)
|
||||
}
|
||||
if !snapshot.Attached {
|
||||
t.Fatal("Attached mismatch: got false want true")
|
||||
}
|
||||
if !snapshot.HasRuntimeConn {
|
||||
t.Fatal("HasRuntimeConn mismatch: got false want true")
|
||||
}
|
||||
if !snapshot.Current {
|
||||
t.Fatal("Current mismatch: got false want true")
|
||||
}
|
||||
|
||||
transportSnapshot, ok, err := GetServerTransportRuntimeSnapshot(serverKey, clientID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetServerTransportRuntimeSnapshot failed: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("GetServerTransportRuntimeSnapshot should report current transport")
|
||||
}
|
||||
if transportSnapshot.ClientID != snapshot.ClientID || transportSnapshot.TransportGeneration != snapshot.TransportGeneration {
|
||||
t.Fatalf("transport runtime snapshot mismatch: got %+v want %+v", transportSnapshot, snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetServerLogicalAndTransportConnByKey(t *testing.T) {
|
||||
const serverKey = "runtime-conn-object-server"
|
||||
_ = DeleteServer(serverKey)
|
||||
defer DeleteServer(serverKey)
|
||||
|
||||
server := NewServer(serverKey)
|
||||
client := notify.NewClient()
|
||||
secret := []byte("0123456789abcdef0123456789abcdef")
|
||||
server.SetSecretKey(secret)
|
||||
client.SetSecretKey(secret)
|
||||
|
||||
serverConn, clientConn := net.Pipe()
|
||||
defer clientConn.Close()
|
||||
listener := newSingleConnListener(serverConn)
|
||||
defer listener.Close()
|
||||
|
||||
if err := server.ListenByListener(listener); err != nil {
|
||||
t.Fatalf("ListenByListener failed: %v", err)
|
||||
}
|
||||
defer server.Stop()
|
||||
|
||||
if err := client.ConnectByConn(clientConn); err != nil {
|
||||
t.Fatalf("ConnectByConn failed: %v", err)
|
||||
}
|
||||
defer client.Stop()
|
||||
|
||||
srv, err := Server(serverKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Server lookup failed: %v", err)
|
||||
}
|
||||
|
||||
var clientID string
|
||||
deadline := time.Now().Add(time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
list := srv.GetLogicalConnList()
|
||||
if len(list) == 1 && list[0] != nil && list[0].ClientID != "" {
|
||||
clientID = list[0].ClientID
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if clientID == "" {
|
||||
t.Fatal("server did not expose accepted logical conn in time")
|
||||
}
|
||||
|
||||
logical, err := GetServerLogicalConn(serverKey, clientID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetServerLogicalConn failed: %v", err)
|
||||
}
|
||||
if logical == nil || logical.ClientID != clientID {
|
||||
t.Fatalf("logical conn mismatch: %+v", logical)
|
||||
}
|
||||
|
||||
transport, ok, err := GetServerCurrentTransportConn(serverKey, clientID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetServerCurrentTransportConn failed: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("GetServerCurrentTransportConn should report current transport")
|
||||
}
|
||||
if transport == nil || transport.ClientID() != clientID || !transport.IsCurrent() {
|
||||
t.Fatalf("transport conn mismatch: %+v", transport)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetServerDetachedClientRuntimeSnapshotsByKey(t *testing.T) {
|
||||
const serverKey = "runtime-detached-server"
|
||||
const clientKey = "runtime-detached-client"
|
||||
_ = DeleteClient(clientKey)
|
||||
_ = DeleteServer(serverKey)
|
||||
defer DeleteClient(clientKey)
|
||||
defer DeleteServer(serverKey)
|
||||
|
||||
server, err := NewModernPSKServer(serverKey, []byte("shared-secret"), testModernPSKOptions())
|
||||
if err != nil {
|
||||
t.Fatalf("NewModernPSKServer failed: %v", err)
|
||||
}
|
||||
server.SetDetachedClientKeepSec(30)
|
||||
|
||||
client, err := NewModernPSKClient(clientKey, []byte("shared-secret"), testModernPSKOptions())
|
||||
if err != nil {
|
||||
t.Fatalf("NewModernPSKClient failed: %v", err)
|
||||
}
|
||||
|
||||
serverConn, clientConn := net.Pipe()
|
||||
listener := newSingleConnListener(serverConn)
|
||||
defer listener.Close()
|
||||
defer clientConn.Close()
|
||||
|
||||
if err := server.ListenByListener(listener); err != nil {
|
||||
t.Fatalf("ListenByListener failed: %v", err)
|
||||
}
|
||||
defer server.Stop()
|
||||
|
||||
if err := client.ConnectByConn(clientConn); err != nil {
|
||||
t.Fatalf("ConnectByConn failed: %v", err)
|
||||
}
|
||||
defer client.Stop()
|
||||
|
||||
srv, err := Server(serverKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Server lookup failed: %v", err)
|
||||
}
|
||||
|
||||
deadline := time.Now().Add(time.Second)
|
||||
var boundClientID string
|
||||
for time.Now().Before(deadline) {
|
||||
list := srv.GetClientLists()
|
||||
if len(list) == 1 && list[0] != nil {
|
||||
snapshot, snapErr := notify.GetClientConnRuntimeSnapshot(list[0])
|
||||
if snapErr == nil && snapshot.IdentityBound {
|
||||
boundClientID = snapshot.ClientID
|
||||
break
|
||||
}
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if boundClientID == "" {
|
||||
t.Fatal("server did not bind accepted client identity in time")
|
||||
}
|
||||
|
||||
if err := clientConn.Close(); err != nil {
|
||||
t.Fatalf("close client conn failed: %v", err)
|
||||
}
|
||||
|
||||
var snapshots []notify.ClientConnRuntimeSnapshot
|
||||
deadline = time.Now().Add(time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
snapshots, err = GetServerDetachedClientRuntimeSnapshots(serverKey)
|
||||
if err != nil {
|
||||
t.Fatalf("GetServerDetachedClientRuntimeSnapshots failed: %v", err)
|
||||
}
|
||||
if len(snapshots) == 1 {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if len(snapshots) != 1 {
|
||||
t.Fatalf("detached snapshot count mismatch: got %d want 1", len(snapshots))
|
||||
}
|
||||
snapshot := snapshots[0]
|
||||
if got, want := snapshot.ClientID, boundClientID; got != want {
|
||||
t.Fatalf("detached snapshot ClientID mismatch: got %q want %q", got, want)
|
||||
}
|
||||
if snapshot.TransportAttached {
|
||||
t.Fatalf("detached snapshot TransportAttached mismatch: got %v want false", snapshot.TransportAttached)
|
||||
}
|
||||
if !snapshot.IdentityBound {
|
||||
t.Fatal("detached snapshot should remain identity bound")
|
||||
}
|
||||
if got, want := snapshot.DetachedClientKeepSec, int64(30); got != want {
|
||||
t.Fatalf("detached snapshot keep seconds mismatch: got %d want %d", got, want)
|
||||
}
|
||||
if snapshot.TransportDetachedAt.IsZero() {
|
||||
t.Fatal("detached snapshot should expose detach time")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTransferSnapshotsByKeyDefaults(t *testing.T) {
|
||||
const clientKey = "transfer-snapshot-client"
|
||||
const serverKey = "transfer-snapshot-server"
|
||||
_ = DeleteClient(clientKey)
|
||||
_ = DeleteServer(serverKey)
|
||||
defer DeleteClient(clientKey)
|
||||
defer DeleteServer(serverKey)
|
||||
|
||||
NewClient(clientKey)
|
||||
NewServer(serverKey)
|
||||
|
||||
clientSnapshots, err := GetClientTransferSnapshots(clientKey)
|
||||
if err != nil {
|
||||
t.Fatalf("GetClientTransferSnapshots failed: %v", err)
|
||||
}
|
||||
if len(clientSnapshots) != 0 {
|
||||
t.Fatalf("client transfer snapshots count = %d, want 0", len(clientSnapshots))
|
||||
}
|
||||
|
||||
serverSnapshots, err := GetServerTransferSnapshots(serverKey)
|
||||
if err != nil {
|
||||
t.Fatalf("GetServerTransferSnapshots failed: %v", err)
|
||||
}
|
||||
if len(serverSnapshots) != 0 {
|
||||
t.Fatalf("server transfer snapshots count = %d, want 0", len(serverSnapshots))
|
||||
}
|
||||
|
||||
if _, ok, err := GetClientTransferSnapshotByID(clientKey, "missing"); err != nil || ok {
|
||||
t.Fatalf("GetClientTransferSnapshotByID = (%v, %v), want (nil, false)", err, ok)
|
||||
}
|
||||
if _, ok, err := GetClientTransferSnapshotByIDScope(clientKey, "missing", "scope-a"); err != nil || ok {
|
||||
t.Fatalf("GetClientTransferSnapshotByIDScope = (%v, %v), want (nil, false)", err, ok)
|
||||
}
|
||||
if _, ok, err := GetClientTransferSnapshotByIDQuery(clientKey, "missing", notify.TransferSnapshotQuery{Scope: "scope-a"}); err != nil || ok {
|
||||
t.Fatalf("GetClientTransferSnapshotByIDQuery = (%v, %v), want (nil, false)", err, ok)
|
||||
}
|
||||
if _, ok, err := GetServerTransferSnapshotByID(serverKey, "missing"); err != nil || ok {
|
||||
t.Fatalf("GetServerTransferSnapshotByID = (%v, %v), want (nil, false)", err, ok)
|
||||
}
|
||||
if _, ok, err := GetServerTransferSnapshotByIDScope(serverKey, "missing", "scope-a"); err != nil || ok {
|
||||
t.Fatalf("GetServerTransferSnapshotByIDScope = (%v, %v), want (nil, false)", err, ok)
|
||||
}
|
||||
if _, ok, err := GetServerTransferSnapshotByIDQuery(serverKey, "missing", notify.TransferSnapshotQuery{Scope: "scope-a"}); err != nil || ok {
|
||||
t.Fatalf("GetServerTransferSnapshotByIDQuery = (%v, %v), want (nil, false)", err, ok)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package starnotify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"b612.me/notify"
|
||||
)
|
||||
|
||||
func TestUseSignalReliabilityClient(t *testing.T) {
|
||||
const key = "signal-reliable-client"
|
||||
_ = DeleteClient(key)
|
||||
defer DeleteClient(key)
|
||||
|
||||
NewClient(key)
|
||||
err := UseSignalReliabilityClient(key, ¬ify.SignalReliabilityOptions{
|
||||
AckTimeout: 50 * time.Millisecond,
|
||||
SendRetry: 4,
|
||||
ReceiveCacheLimit: 8,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("UseSignalReliabilityClient failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUseSignalReliabilityServer(t *testing.T) {
|
||||
const key = "signal-reliable-server"
|
||||
_ = DeleteServer(key)
|
||||
defer DeleteServer(key)
|
||||
|
||||
NewServer(key)
|
||||
opts := notify.DefaultSignalReliabilityOptions()
|
||||
err := UseSignalReliabilityServer(key, &opts)
|
||||
if err != nil {
|
||||
t.Fatalf("UseSignalReliabilityServer failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUseSignalReliabilityMissingKey(t *testing.T) {
|
||||
if err := UseSignalReliabilityClient("missing-client", nil); err == nil {
|
||||
t.Fatal("UseSignalReliabilityClient should fail for missing client key")
|
||||
}
|
||||
if err := UseSignalReliabilityServer("missing-server", nil); err == nil {
|
||||
t.Fatal("UseSignalReliabilityServer should fail for missing server key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSignalReliabilityStatsByKey(t *testing.T) {
|
||||
const clientKey = "signal-reliable-stats-client"
|
||||
const serverKey = "signal-reliable-stats-server"
|
||||
_ = DeleteClient(clientKey)
|
||||
_ = DeleteServer(serverKey)
|
||||
defer DeleteClient(clientKey)
|
||||
defer DeleteServer(serverKey)
|
||||
|
||||
NewClient(clientKey)
|
||||
NewServer(serverKey)
|
||||
|
||||
clientStats, err := GetSignalReliabilityStatsClient(clientKey)
|
||||
if err != nil {
|
||||
t.Fatalf("GetSignalReliabilityStatsClient failed: %v", err)
|
||||
}
|
||||
if clientStats != (notify.SignalReliabilityStats{}) {
|
||||
t.Fatalf("client stats mismatch: %+v", clientStats)
|
||||
}
|
||||
|
||||
serverStats, err := GetSignalReliabilityStatsServer(serverKey)
|
||||
if err != nil {
|
||||
t.Fatalf("GetSignalReliabilityStatsServer failed: %v", err)
|
||||
}
|
||||
if serverStats != (notify.SignalReliabilityStats{}) {
|
||||
t.Fatalf("server stats mismatch: %+v", serverStats)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSignalReliabilityStatsMissingKey(t *testing.T) {
|
||||
if _, err := GetSignalReliabilityStatsClient("missing-client"); err == nil {
|
||||
t.Fatal("GetSignalReliabilityStatsClient should fail for missing key")
|
||||
}
|
||||
if _, err := GetSignalReliabilityStatsServer("missing-server"); err == nil {
|
||||
t.Fatal("GetSignalReliabilityStatsServer should fail for missing key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectRetryWrappersContextCanceled(t *testing.T) {
|
||||
const clientKey = "signal-reliable-retry-client"
|
||||
const serverKey = "signal-reliable-retry-server"
|
||||
_ = DeleteClient(clientKey)
|
||||
_ = DeleteServer(serverKey)
|
||||
defer DeleteClient(clientKey)
|
||||
defer DeleteServer(serverKey)
|
||||
|
||||
NewClient(clientKey)
|
||||
NewServer(serverKey)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
connectErr := ConnectClientWithRetryCtx(ctx, clientKey, "tcp", "127.0.0.1:1", ¬ify.ConnectRetryOptions{
|
||||
MaxAttempts: 3,
|
||||
BaseDelay: time.Millisecond,
|
||||
MaxDelay: time.Millisecond,
|
||||
})
|
||||
if !errors.Is(connectErr, context.Canceled) {
|
||||
t.Fatalf("ConnectClientWithRetryCtx error = %v, want %v", connectErr, context.Canceled)
|
||||
}
|
||||
|
||||
listenErr := ListenServerWithRetryCtx(ctx, serverKey, "tcp", "127.0.0.1:1", ¬ify.ConnectRetryOptions{
|
||||
MaxAttempts: 3,
|
||||
BaseDelay: time.Millisecond,
|
||||
MaxDelay: time.Millisecond,
|
||||
})
|
||||
if !errors.Is(listenErr, context.Canceled) {
|
||||
t.Fatalf("ListenServerWithRetryCtx error = %v, want %v", listenErr, context.Canceled)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user