@@ -38,6 +38,7 @@ import (
3838 "encoding"
3939 "errors"
4040 "fmt"
41+ "slices"
4142 "strings"
4243)
4344
@@ -59,10 +60,21 @@ var internalDoc = struct{}{}
5960// ErrMalformedVector is reported when a vector is invalid in some way.
6061var ErrMalformedVector = errors .New ("malformed vector" )
6162
62- // ErrValueUnset is used by [Vector.getString ] implementations to signal a
63- // metric's value is unset.
63+ // ErrValueUnset is used by [Vector] implementations to signal a metric's value
64+ // is unset.
6465var errValueUnset = errors .New ("unset" )
6566
67+ // ErrValueDefault is used by [Vector] implementations to signal a metric's value
68+ // is unset, but a default value was used for the requested operation.
69+ var errValueDefault = fmt .Errorf ("default: %w" , errValueUnset )
70+
71+ // MarshalSize is the initial size of the backing slice for
72+ // [encoding.TextMarshaler] implementations.
73+ //
74+ // This was arrived at by trying sizes until [BenchmarkMarshal] reported a
75+ // single allocation for all but the longest V4 vectors.
76+ const marshalSize = 128
77+
6678// Value is a "packed" representation of the value of a metric.
6779//
6880// When possible, this is the first byte of the abbreviated form in the relevant
@@ -107,55 +119,62 @@ func Version(vec string) (v int) {
107119 return v
108120}
109121
110- // MarshalVector is a generic function to marshal vectors.
111- //
112- // The [ Vector.getString] method is used here.
113- func marshalVector [ M Metric , V Vector [ M ]]( prefix string , v V ) ([] byte , error ) {
114- text : = append (make ([] byte , 0 , 64 ), prefix ... ) // Guess at an initial capacity.
122+ // AppendVector is a generic function to marshal vectors via appending to the
123+ // provided byte slice.
124+ func appendVector [ M Metric , V Vector [ M ]]( b [] byte , prefix string , v V ) ([] byte , error ) {
125+ start := len ( b )
126+ b = append (b , prefix ... )
115127 var err error
116- // This is a rangefunc-style iterator.
117- v .groups (func (b [2 ]int ) bool {
128+ meta := v .meta ()
129+ g := meta .Groups
130+ for s , e := 0 , 1 ; e < len (g ); s , e = s + 2 , e + 2 {
118131 var set bool
119- orig := len (text )
120- for i := b [0 ]; i < b [1 ]; i ++ {
132+ i , lim := g [s ], g [e ]
133+ skipGroup := len (b )
134+ for ; i < lim ; i ++ {
135+ skipMetric := len (b )
121136 m := M (i )
122- val , err := v .getString (m )
137+
138+ b = append (b , '/' )
139+ b , err = m .AppendText (b )
140+ if err != nil {
141+ return nil , fmt .Errorf ("invalid cvss vector: %w" , err )
142+ }
143+ b = append (b , ':' )
144+
145+ b , err = v .appendValue (b , m )
123146 switch {
124147 case errors .Is (err , nil ):
125148 set = true
126- case errors .Is (err , errValueUnset ) && val == "" :
127- continue
149+ case errors .Is (err , errValueDefault ):
128150 case errors .Is (err , errValueUnset ):
151+ b = b [:skipMetric ]
129152 default :
130- err = errors .New ("invalid cvss vector" )
131- return false
153+ return nil , fmt .Errorf ("invalid cvss vector: %w" , err )
132154 }
133-
134- text = append (text , '/' )
135- text = append (text , m .String ()... )
136- text = append (text , ':' )
137- text = append (text , val ... )
138155 }
139156 if ! set {
140- text = text [: orig ]
157+ b = b [: skipGroup ]
141158 }
142- return true
143- })
144- if err != nil {
145- return nil , err
146159 }
147- // v2 hack
148- if prefix == "" {
149- text = text [1 :]
160+ // v2 hack: remove the leading slash.
161+ switch {
162+ case prefix == "" && start == 0 :
163+ b = b [1 :]
164+ case prefix == "" && start != 0 :
165+ // Handle the case where this function was passed a slice with a
166+ // non-zero length.
167+ b = slices .Delete (b , start , start + 1 )
150168 }
151- return text , nil
169+ return b , nil
152170}
153171
154172// Metric is a CVSS metric.
155173//
156174// The set of types this describes is namespaced per-version.
157175type Metric interface {
158176 ~ int
177+ encoding.TextAppender
159178 fmt.Stringer
160179
161180 // Valid returns the concatenation of valid values for the metric.
@@ -166,6 +185,7 @@ type Metric interface {
166185
167186// Vector is a CVSS vector of any version.
168187type Vector [M Metric ] interface {
188+ encoding.TextAppender
169189 encoding.TextUnmarshaler
170190 encoding.TextMarshaler
171191 fmt.Stringer
@@ -181,20 +201,16 @@ type Vector[M Metric] interface {
181201 // Environmental reports if the vector contains environmental metrics.
182202 Environmental () bool
183203
184- // GetString is a hook for returning the stringified version of the metric
185- // value. If the value is unset, implementations should return err
186- // [errValueUnset] rather than a specified default, as defaults are omitted
187- // from the string representation.
188- //
189- // CVSSv2 notably does not use names that are identifiable by a single byte,
190- // so they need to be packed and unpacked.
191- getString (M ) (string , error )
204+ // AppendValue is a hook for appending the stringified version of the metric
205+ // value. If the value is unset, implementations should return the input
206+ // slice and err == [errValueUnset] rather than a specified default, as
207+ // defaults are omitted from the string representation.
208+ appendValue ([]byte , M ) ([]byte , error )
192209 // GetScore returns the "packed" value representation after any default
193210 // rules are applied.
194211 getScore (M ) byte
195- // Groups is a rangefunc-style iterator returning the bounds for groups of metrics.
196- // For a returned value "b", it represents the interval "[b[0], b[1])".
197- groups (func ([2 ]int ) bool )
212+ // Meta returns the static metadata for this vector.
213+ meta () * vectorMetadata
198214}
199215
200216var (
@@ -203,6 +219,14 @@ var (
203219 _ Vector [V2Metric ] = (* V2 )(nil )
204220)
205221
222+ // VectorMetadata is static metadata about a vector.
223+ type vectorMetadata struct {
224+ // Groups is a slice of boundaries for the groups of the vector.
225+ //
226+ // The pairs of ints are [lower, upper).
227+ Groups []int
228+ }
229+
206230// Qualitative is the "Qualitative Severity" of a Vector.
207231type Qualitative int
208232
0 commit comments