211 lines
5.0 KiB
Go
211 lines
5.0 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"log"
|
|
"math/big"
|
|
"time"
|
|
|
|
"git.pyer.club/kingecg/gotidb/pkg/manager"
|
|
"git.pyer.club/kingecg/gotidb/pkg/model"
|
|
"github.com/quic-go/quic-go"
|
|
)
|
|
|
|
// QUICServer represents the QUIC protocol server
|
|
type QUICServer struct {
|
|
listener *quic.Listener
|
|
manager *manager.DataManager
|
|
tlsConfig *tls.Config
|
|
quicConfig *quic.Config
|
|
}
|
|
|
|
// QUICWriteRequest represents a write request from client
|
|
type QUICWriteRequest struct {
|
|
Points []DataPoint `json:"points"`
|
|
BatchID string `json:"batch_id"`
|
|
}
|
|
|
|
// DataPoint represents a single data point
|
|
type DataPoint struct {
|
|
DeviceID string `json:"device_id"`
|
|
MetricCode string `json:"metric_code"`
|
|
Labels map[string]string `json:"labels"`
|
|
Value float64 `json:"value"`
|
|
Timestamp int64 `json:"timestamp"`
|
|
}
|
|
|
|
// WriteResponse represents the response to a write request
|
|
type WriteResponse struct {
|
|
Success bool `json:"success"`
|
|
Error string `json:"error,omitempty"`
|
|
BatchID string `json:"batch_id"`
|
|
PointsCount int `json:"points_count"`
|
|
}
|
|
|
|
// NewQUICServer creates a new QUIC server instance
|
|
func NewQUICServer(dm *manager.DataManager, config *quic.Config) (*QUICServer, error) {
|
|
// Generate self-signed certificate for development
|
|
// In production, you should use proper certificates
|
|
tlsConfig, err := generateTLSConfig()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate TLS config: %v", err)
|
|
}
|
|
|
|
quicConfig := &quic.Config{
|
|
MaxIdleTimeout: 30 * time.Second,
|
|
KeepAlivePeriod: 10 * time.Second,
|
|
}
|
|
|
|
return &QUICServer{
|
|
manager: dm,
|
|
tlsConfig: tlsConfig,
|
|
quicConfig: quicConfig,
|
|
}, nil
|
|
}
|
|
|
|
// Start starts the QUIC server
|
|
func (s *QUICServer) Start(addr string) error {
|
|
listener, err := quic.ListenAddr(addr, s.tlsConfig, s.quicConfig)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create QUIC listener: %v", err)
|
|
}
|
|
s.listener = listener
|
|
|
|
go s.acceptConnections()
|
|
return nil
|
|
}
|
|
|
|
// Stop stops the QUIC server
|
|
func (s *QUICServer) Stop(ctx context.Context) error {
|
|
if s.listener != nil {
|
|
return s.listener.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *QUICServer) acceptConnections() {
|
|
for {
|
|
conn, err := s.listener.Accept(context.Background())
|
|
if err != nil {
|
|
log.Printf("Failed to accept QUIC connection: %v", err)
|
|
continue
|
|
}
|
|
|
|
go s.handleConnection(conn)
|
|
}
|
|
}
|
|
|
|
func (s *QUICServer) handleConnection(conn quic.Connection) {
|
|
for {
|
|
stream, err := conn.AcceptStream(context.Background())
|
|
if err != nil {
|
|
log.Printf("Failed to accept QUIC stream: %v", err)
|
|
return
|
|
}
|
|
|
|
go s.handleStream(stream)
|
|
}
|
|
}
|
|
|
|
func (s *QUICServer) handleStream(stream quic.Stream) {
|
|
defer stream.Close()
|
|
|
|
var req QUICWriteRequest
|
|
decoder := json.NewDecoder(stream)
|
|
if err := decoder.Decode(&req); err != nil {
|
|
s.sendError(stream, "Failed to decode request", err)
|
|
return
|
|
}
|
|
|
|
response := WriteResponse{
|
|
BatchID: req.BatchID,
|
|
PointsCount: len(req.Points),
|
|
}
|
|
|
|
// Process each data point
|
|
for _, point := range req.Points {
|
|
id := manager.CreateDataPoint(
|
|
point.DeviceID,
|
|
point.MetricCode,
|
|
point.Labels,
|
|
point.Value,
|
|
)
|
|
timestamp := time.Unix(point.Timestamp, 0)
|
|
|
|
value := model.DataValue{
|
|
Timestamp: timestamp,
|
|
Value: point.Value,
|
|
}
|
|
|
|
if err := s.manager.Write(context.Background(), id, value); err != nil {
|
|
s.sendError(stream, "Failed to write data point", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
response.Success = true
|
|
encoder := json.NewEncoder(stream)
|
|
if err := encoder.Encode(response); err != nil {
|
|
log.Printf("Failed to send response: %v", err)
|
|
}
|
|
}
|
|
|
|
func (s *QUICServer) sendError(stream quic.Stream, msg string, err error) {
|
|
response := WriteResponse{
|
|
Success: false,
|
|
Error: fmt.Sprintf("%s: %v", msg, err),
|
|
}
|
|
encoder := json.NewEncoder(stream)
|
|
if err := encoder.Encode(response); err != nil {
|
|
log.Printf("Failed to send error response: %v", err)
|
|
}
|
|
}
|
|
|
|
// generateTLSConfig generates a self-signed certificate for development
|
|
func generateTLSConfig() (*tls.Config, error) {
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
template := x509.Certificate{
|
|
SerialNumber: big.NewInt(1),
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().Add(time.Hour * 24 * 180), // Valid for 180 days
|
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
}
|
|
|
|
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
keyPEM := pem.EncodeToMemory(&pem.Block{
|
|
Type: "RSA PRIVATE KEY",
|
|
Bytes: x509.MarshalPKCS1PrivateKey(key),
|
|
})
|
|
|
|
certPEM := pem.EncodeToMemory(&pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: certDER,
|
|
})
|
|
|
|
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &tls.Config{
|
|
Certificates: []tls.Certificate{tlsCert},
|
|
NextProtos: []string{"gotidb-quic"}, // Application protocol
|
|
}, nil
|
|
}
|