@@ -77,6 +77,20 @@ trait ChannelTypeFeature extends PermanentChannelFeature
7777
7878case class UnknownFeature (bitIndex : Int )
7979
80+ // @formatter:off
81+ sealed trait FeatureCompatibilityResult {
82+ def areCompatible : Boolean = this == FeatureCompatibilityResult .Compatible
83+ def errorHints : Set [String ] = this match {
84+ case FeatureCompatibilityResult .Compatible => Set .empty
85+ case r : FeatureCompatibilityResult .NotCompatible => r.hints
86+ }
87+ }
88+ object FeatureCompatibilityResult {
89+ case object Compatible extends FeatureCompatibilityResult
90+ case class NotCompatible (hints : Set [String ]) extends FeatureCompatibilityResult
91+ }
92+ // @formatter:on
93+
8094case class Features [T <: Feature ](activated : Map [T , FeatureSupport ], unknown : Set [UnknownFeature ] = Set .empty) {
8195
8296 def isEmpty : Boolean = activated.isEmpty && unknown.isEmpty
@@ -87,17 +101,20 @@ case class Features[T <: Feature](activated: Map[T, FeatureSupport], unknown: Se
87101 }
88102
89103 /** NB: this method is not reflexive, see [[Features.areCompatible ]] if you want symmetric validation. */
90- def areSupported (remoteFeatures : Features [T ]): Boolean = {
104+ def testSupported (remoteFeatures : Features [T ]): FeatureCompatibilityResult = {
91105 // we allow unknown odd features (it's ok to be odd)
92- val unknownFeaturesOk = remoteFeatures.unknown.forall (_.bitIndex % 2 == 1 )
106+ val incompatibleUnknownFeatures = remoteFeatures.unknown.filter (_.bitIndex % 2 == 0 )
93107 // we verify that we activated every mandatory feature they require
94- val knownFeaturesOk = remoteFeatures.activated.forall {
95- case (_, Optional ) => true
96- case (feature, Mandatory ) => hasFeature(feature)
97- }
98- unknownFeaturesOk && knownFeaturesOk
108+ val incompatibleKnownFeatures = remoteFeatures.activated.filter {
109+ case (_, Optional ) => false
110+ case (feature, Mandatory ) => ! hasFeature(feature)
111+ }.keySet
112+ val incompatibleFeatures = incompatibleUnknownFeatures.map(u => s " unknown_ ${u.bitIndex}" ) ++ incompatibleKnownFeatures.map(_.rfcName)
113+ if (incompatibleFeatures.isEmpty) FeatureCompatibilityResult .Compatible else FeatureCompatibilityResult .NotCompatible (incompatibleFeatures)
99114 }
100115
116+ def areSupported (remoteFeatures : Features [T ]): Boolean = testSupported(remoteFeatures).areCompatible
117+
101118 def initFeatures (): Features [InitFeature ] = Features (activated.collect { case (f : InitFeature , s) => (f, s) }, unknown)
102119
103120 def nodeAnnouncementFeatures (): Features [NodeFeature ] = Features (activated.collect { case (f : NodeFeature , s) => (f, s) }, unknown)
@@ -354,8 +371,13 @@ object Features {
354371 FeatureException (s " $feature is set but is missing a dependency ( ${dependencies.filter(d => ! features.unscoped().hasFeature(d)).mkString(" and " )}) " )
355372 }
356373
374+ def testCompatible [T <: Feature ](ours : Features [T ], theirs : Features [T ]): FeatureCompatibilityResult = (ours.testSupported(theirs), theirs.testSupported(ours)) match {
375+ case (FeatureCompatibilityResult .Compatible , FeatureCompatibilityResult .Compatible ) => FeatureCompatibilityResult .Compatible
376+ case (r1, r2) => FeatureCompatibilityResult .NotCompatible (r1.errorHints ++ r2.errorHints)
377+ }
378+
357379 /** Returns true if both feature sets are compatible. */
358- def areCompatible [T <: Feature ](ours : Features [T ], theirs : Features [T ]): Boolean = ours.areSupported(theirs) && theirs.areSupported(ours)
380+ def areCompatible [T <: Feature ](ours : Features [T ], theirs : Features [T ]): Boolean = testCompatible(ours, theirs).areCompatible
359381
360382 /** returns true if both have at least optional support */
361383 def canUseFeature [T <: Feature ](localFeatures : Features [T ], remoteFeatures : Features [T ], feature : T ): Boolean = {
0 commit comments