Files
synapse/internal/transport/transport.go
2026-06-28 03:24:26 +08:00

421 lines
11 KiB
Go

package transport
import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strings"
"sync"
"time"
"synapse/pkg/types"
)
// =============================================================================
// HTTP Transport
// =============================================================================
// HTTPTransport implements HTTP-based transport for Synapse messages
type HTTPTransport struct {
mu sync.RWMutex
// Configuration
baseURL string
timeout time.Duration
maxRetries int
retryDelay time.Duration
// HTTP Client
client *http.Client
// Security
authSecret string
// Callbacks
callbackEndpoint string
callbackHandler MessageCallbackHandler
}
// HTTPTransportConfig holds HTTP transport configuration
type HTTPTransportConfig struct {
BaseURL string
Timeout time.Duration
MaxRetries int
RetryDelay time.Duration
AuthSecret string
}
// DefaultHTTPTransportConfig returns default HTTP transport configuration
func DefaultHTTPTransportConfig() *HTTPTransportConfig {
return &HTTPTransportConfig{
Timeout: 30 * time.Second,
MaxRetries: 5,
RetryDelay: 2 * time.Second,
}
}
// NewHTTPTransport creates a new HTTP transport
func NewHTTPTransport(config *HTTPTransportConfig) *HTTPTransport {
if config == nil {
config = DefaultHTTPTransportConfig()
}
t := &HTTPTransport{
baseURL: config.BaseURL,
timeout: config.Timeout,
maxRetries: config.MaxRetries,
retryDelay: config.RetryDelay,
authSecret: config.AuthSecret,
client: &http.Client{
Timeout: config.Timeout,
},
}
return t
}
// SetCallbackHandler sets the handler for incoming callbacks
func (t *HTTPTransport) SetCallbackHandler(handler MessageCallbackHandler) {
t.callbackHandler = handler
}
// SetCallbackEndpoint sets the endpoint for receiving callbacks
func (t *HTTPTransport) SetCallbackEndpoint(endpoint string) {
t.callbackEndpoint = endpoint
}
// =============================================================================
// Message Sending
// =============================================================================
// Send sends a message via HTTP POST
func (t *HTTPTransport) Send(ctx context.Context, endpoint string, msg *types.UnifiedSynapseMessage) error {
if endpoint == "" {
return fmt.Errorf("endpoint is required")
}
// Add signature if secret is configured
if t.authSecret != "" {
t.signMessage(msg)
}
// Marshal message
body, err := json.Marshal(msg)
if err != nil {
return fmt.Errorf("failed to marshal message: %w", err)
}
// Create request
req, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewReader(body))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
// Set headers
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Synapse-Version", "1.0.0")
req.Header.Set("X-Synapse-Agent", msg.Sender.AgentID)
req.Header.Set("X-Synapse-System", msg.Sender.SystemID)
if msg.Signature != "" {
req.Header.Set("X-Synapse-Signature", msg.Signature)
}
// Send with retries
var lastErr error
for i := 0; i < t.maxRetries; i++ {
if i > 0 {
delay := t.retryDelay * time.Duration(1<<uint(i-1))
log.Printf("[Transport] Retrying request (%d/%d) after %v", i, t.maxRetries, delay)
time.Sleep(delay)
}
resp, err := t.client.Do(req)
if err != nil {
lastErr = err
log.Printf("[Transport] Request failed: %v", err)
continue
}
// Read response
respBody, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
log.Printf("[Transport] Message sent successfully: %s to %s", msg.ID, endpoint)
return nil
}
lastErr = fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(respBody))
log.Printf("[Transport] Request failed: %v", lastErr)
}
return fmt.Errorf("failed to send message after %d retries: %w", t.maxRetries, lastErr)
}
// SendWithCallback sends a message and waits for callback response
func (t *HTTPTransport) SendWithCallback(
ctx context.Context,
endpoint string,
msg *types.UnifiedSynapseMessage,
timeout time.Duration,
) (*types.UnifiedSynapseMessage, error) {
// Create channel for response
respChan := make(chan *types.UnifiedSynapseMessage, 1)
errChan := make(chan error, 1)
// Register callback handler for this message
correlationID := msg.CorrelationID
if correlationID == "" {
correlationID = msg.ID
}
// Start timeout goroutine
go func() {
select {
case <-ctx.Done():
errChan <- ctx.Err()
case <-time.After(timeout):
errChan <- fmt.Errorf("timeout waiting for response")
}
}()
// Send message
if err := t.Send(ctx, endpoint, msg); err != nil {
return nil, err
}
// Wait for response
select {
case resp := <-respChan:
return resp, nil
case err := <-errChan:
return nil, err
}
}
// =============================================================================
// Message Reception
// =============================================================================
// MessageCallbackHandler is the interface for handling incoming messages
type MessageCallbackHandler interface {
HandleMessage(msg *types.UnifiedSynapseMessage) error
}
// HandleCallback handles an incoming callback message (HTTP POST)
func (t *HTTPTransport) HandleCallback(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
// Read body
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "failed to read body", http.StatusBadRequest)
return
}
r.Body.Close()
// Parse message
var msg types.UnifiedSynapseMessage
if err := json.Unmarshal(body, &msg); err != nil {
http.Error(w, "invalid JSON", http.StatusBadRequest)
return
}
// Verify signature if configured
if t.authSecret != "" && msg.Signature != "" {
if !t.verifySignature(body, msg.Signature) {
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
}
// Handle message
if t.callbackHandler != nil {
if err := t.callbackHandler.HandleMessage(&msg); err != nil {
log.Printf("[Transport] Failed to handle message: %v", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
}
// Send success response
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"success": true,
"message": "received",
})
}
// =============================================================================
// HTTP Server
// =============================================================================
// Server is an HTTP server for Synapse transport
type Server struct {
transport *HTTPTransport
mux *http.ServeMux
server *http.Server
}
// NewServer creates a new HTTP server for Synapse transport
func NewServer(addr string, transport *HTTPTransport) *Server {
mux := http.NewServeMux()
server := &Server{
transport: transport,
mux: mux,
server: &http.Server{
Addr: addr,
Handler: mux,
},
}
// Register callback endpoint
mux.HandleFunc("/synapse/callback", transport.HandleCallback)
mux.HandleFunc("/synapse/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"ok"}`))
})
return server
}
// Start starts the HTTP server
func (s *Server) Start() error {
log.Printf("[Transport] Starting HTTP server on %s", s.server.Addr)
return s.server.ListenAndServe()
}
// Stop stops the HTTP server
func (s *Server) Stop(ctx context.Context) error {
return s.server.Shutdown(ctx)
}
// =============================================================================
// Security
// =============================================================================
func (t *HTTPTransport) signMessage(msg *types.UnifiedSynapseMessage) {
// Create canonical body (message without signature)
msgCopy := *msg
msgCopy.Signature = ""
body, _ := json.Marshal(msgCopy)
// Compute HMAC-SHA256
h := hmac.New(sha256.New, []byte(t.authSecret))
h.Write(body)
signature := hex.EncodeToString(h.Sum(nil))
msg.Signature = signature
}
func (t *HTTPTransport) verifySignature(body []byte, signature string) bool {
h := hmac.New(sha256.New, []byte(t.authSecret))
h.Write(body)
expected := hex.EncodeToString(h.Sum(nil))
return hmac.Equal([]byte(strings.ToLower(signature)), []byte(strings.ToLower(expected)))
}
// =============================================================================
// Callback Sender (implements p2p.CallbackSender)
// =============================================================================
// HTTPCallbackSender implements p2p.CallbackSender using HTTP transport
type HTTPCallbackSender struct {
transport *HTTPTransport
}
// NewHTTPCallbackSender creates a new HTTP callback sender
func NewHTTPCallbackSender(t *HTTPTransport) *HTTPCallbackSender {
return &HTTPCallbackSender{transport: t}
}
// SendCallback sends a callback message
func (s *HTTPCallbackSender) SendCallback(ctx context.Context, endpoint string, msg *types.UnifiedSynapseMessage) error {
if endpoint == "" && msg.Receiver != nil {
endpoint = msg.Receiver.Endpoint
}
return s.transport.Send(ctx, endpoint, msg)
}
// =============================================================================
// WebSocket Transport (for future extension)
// =============================================================================
// WebSocketConfig holds WebSocket transport configuration
type WebSocketConfig struct {
URL string
Timeout time.Duration
}
// WebSocketTransport implements WebSocket-based transport
type WebSocketTransport struct {
mu sync.RWMutex
config *WebSocketConfig
handler MessageCallbackHandler
}
// NewWebSocketTransport creates a new WebSocket transport
func NewWebSocketTransport(config *WebSocketConfig) *WebSocketTransport {
return &WebSocketTransport{
config: config,
}
}
// SetMessageHandler sets the message handler
func (t *WebSocketTransport) SetMessageHandler(handler MessageCallbackHandler) {
t.handler = handler
}
// Connect establishes a WebSocket connection
func (t *WebSocketTransport) Connect(ctx context.Context) error {
// Placeholder for WebSocket implementation
// In a real implementation, this would use gorilla/websocket
log.Printf("[Transport] WebSocket connect to %s (placeholder)", t.config.URL)
return nil
}
// Send sends a message via WebSocket
func (t *WebSocketTransport) Send(ctx context.Context, msg *types.UnifiedSynapseMessage) error {
// Placeholder for WebSocket send implementation
log.Printf("[Transport] WebSocket send (placeholder): %s", msg.ID)
return nil
}
// =============================================================================
// Message Utilities
// =============================================================================
// CreateSuccessResponse creates a success response
func CreateSuccessResponse(msgID string) map[string]interface{} {
return map[string]interface{}{
"success": true,
"message": "received",
"id": msgID,
}
}
// CreateErrorResponse creates an error response
func CreateErrorResponse(code int, message string) map[string]interface{} {
return map[string]interface{}{
"success": false,
"error": map[string]interface{}{
"code": code,
"message": message,
},
}
}