Initial project snapshot
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ValidateProductionEnvelope(local PeerIdentity, envelope ProductionEnvelope, now time.Time) error {
|
||||
if envelope.FabricProtocolVersion != ProtocolVersion {
|
||||
return fmt.Errorf("%w: unsupported fabric_protocol_version", ErrForwardEnvelopeInvalid)
|
||||
}
|
||||
if envelope.MessageID == "" {
|
||||
return fmt.Errorf("%w: message_id is required", ErrForwardEnvelopeInvalid)
|
||||
}
|
||||
if envelope.RouteID == "" {
|
||||
return fmt.Errorf("%w: route_id is required", ErrForwardEnvelopeInvalid)
|
||||
}
|
||||
if envelope.ClusterID == "" || envelope.ClusterID != local.ClusterID {
|
||||
return ErrClusterMismatch
|
||||
}
|
||||
if envelope.SourceNodeID == "" || envelope.DestinationNodeID == "" {
|
||||
return fmt.Errorf("%w: source_node_id and destination_node_id are required", ErrForwardEnvelopeInvalid)
|
||||
}
|
||||
if envelope.CurrentHopNodeID != local.NodeID {
|
||||
return ErrNodeMismatch
|
||||
}
|
||||
if envelope.NextHopNodeID == "" {
|
||||
return fmt.Errorf("%w: next_hop_node_id is required", ErrForwardEnvelopeInvalid)
|
||||
}
|
||||
if len(envelope.RoutePath) > 0 {
|
||||
if err := validateProductionRoutePath(local, envelope); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if envelope.ChannelClass != ProductionChannelFabricControl {
|
||||
return ErrUnauthorizedChannel
|
||||
}
|
||||
if envelope.MessageType != ProductionMessageFabricControl {
|
||||
return fmt.Errorf("%w: unsupported message_type", ErrForwardEnvelopeInvalid)
|
||||
}
|
||||
if envelope.TTL <= 0 {
|
||||
return ErrTTLExhausted
|
||||
}
|
||||
if envelope.HopCount < 0 {
|
||||
return fmt.Errorf("%w: hop_count must not be negative", ErrForwardEnvelopeInvalid)
|
||||
}
|
||||
if envelope.CreatedAt.IsZero() || envelope.ExpiresAt.IsZero() {
|
||||
return fmt.Errorf("%w: created_at and expires_at are required", ErrForwardEnvelopeInvalid)
|
||||
}
|
||||
if envelope.CreatedAt.After(now.UTC().Add(MaxProductionEnvelopeFutureSkew)) {
|
||||
return fmt.Errorf("%w: created_at exceeds allowed future skew", ErrForwardEnvelopeInvalid)
|
||||
}
|
||||
if !envelope.ExpiresAt.After(now.UTC()) {
|
||||
return ErrRouteExpired
|
||||
}
|
||||
if envelope.PayloadLength != len(envelope.Payload) {
|
||||
return fmt.Errorf("%w: payload_length mismatch", ErrForwardEnvelopeInvalid)
|
||||
}
|
||||
if envelope.PayloadLength > MaxProductionEnvelopePayloadBytes {
|
||||
return fmt.Errorf("%w: payload exceeds fabric-control limit", ErrForwardEnvelopeInvalid)
|
||||
}
|
||||
if envelope.PayloadHash == "" {
|
||||
return fmt.Errorf("%w: payload_hash is required", ErrForwardEnvelopeInvalid)
|
||||
}
|
||||
sum := sha256.Sum256(envelope.Payload)
|
||||
if envelope.PayloadHash != hex.EncodeToString(sum[:]) {
|
||||
return fmt.Errorf("%w: payload_hash mismatch", ErrForwardEnvelopeInvalid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateProductionRoutePath(local PeerIdentity, envelope ProductionEnvelope) error {
|
||||
if len(envelope.RoutePath) < 2 {
|
||||
return ErrInvalidRoutePath
|
||||
}
|
||||
if envelope.RoutePath[0] != envelope.SourceNodeID || envelope.RoutePath[len(envelope.RoutePath)-1] != envelope.DestinationNodeID {
|
||||
return ErrInvalidRoutePath
|
||||
}
|
||||
currentIndex := -1
|
||||
seen := map[string]struct{}{}
|
||||
for index, nodeID := range envelope.RoutePath {
|
||||
if nodeID == "" {
|
||||
return ErrInvalidRoutePath
|
||||
}
|
||||
if _, duplicate := seen[nodeID]; duplicate {
|
||||
return ErrLoopDetected
|
||||
}
|
||||
seen[nodeID] = struct{}{}
|
||||
if nodeID == local.NodeID {
|
||||
currentIndex = index
|
||||
}
|
||||
}
|
||||
if currentIndex < 0 || envelope.CurrentHopNodeID != local.NodeID {
|
||||
return ErrNodeMismatch
|
||||
}
|
||||
if containsProductionNodeID(envelope.VisitedNodeIDs, local.NodeID) {
|
||||
return ErrLoopDetected
|
||||
}
|
||||
for _, visitedNodeID := range envelope.VisitedNodeIDs {
|
||||
if visitedNodeID == "" || !containsProductionNodeID(envelope.RoutePath, visitedNodeID) {
|
||||
return ErrInvalidRoutePath
|
||||
}
|
||||
}
|
||||
if envelope.DestinationNodeID == local.NodeID {
|
||||
if envelope.NextHopNodeID != local.NodeID {
|
||||
return ErrInvalidRoutePath
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if currentIndex >= len(envelope.RoutePath)-1 {
|
||||
return ErrInvalidRoutePath
|
||||
}
|
||||
if envelope.NextHopNodeID != envelope.RoutePath[currentIndex+1] {
|
||||
return ErrInvalidRoutePath
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func containsProductionNodeID(values []string, needle string) bool {
|
||||
for _, value := range values {
|
||||
if value == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func NewProductionEnvelopeObservation(envelope ProductionEnvelope, observedAt time.Time) ProductionEnvelopeObservation {
|
||||
return ProductionEnvelopeObservation{
|
||||
MessageID: envelope.MessageID,
|
||||
RouteID: envelope.RouteID,
|
||||
ClusterID: envelope.ClusterID,
|
||||
SourceNodeID: envelope.SourceNodeID,
|
||||
DestinationNodeID: envelope.DestinationNodeID,
|
||||
CurrentHopNodeID: envelope.CurrentHopNodeID,
|
||||
NextHopNodeID: envelope.NextHopNodeID,
|
||||
RoutePath: append([]string{}, envelope.RoutePath...),
|
||||
VisitedNodeIDs: append([]string{}, envelope.VisitedNodeIDs...),
|
||||
ChannelClass: envelope.ChannelClass,
|
||||
MessageType: envelope.MessageType,
|
||||
TTL: envelope.TTL,
|
||||
HopCount: envelope.HopCount,
|
||||
PayloadLength: envelope.PayloadLength,
|
||||
PayloadHash: envelope.PayloadHash,
|
||||
ObservedAt: observedAt.UTC(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user