@@ -59,10 +59,21 @@ var internalDoc = struct{}{}
5959// ErrMalformedVector is reported when a vector is invalid in some way.
6060var ErrMalformedVector = errors .New ("malformed vector" )
6161
62- // ErrValueUnset is used by [Vector.getString ] implementations to signal a
63- // metric's value is unset.
62+ // ErrValueUnset is used by [Vector] implementations to signal a metric's value
63+ // is unset.
6464var errValueUnset = errors .New ("unset" )
6565
66+ // ErrValueDefault is used by [Vector] implementations to signal a metric's value
67+ // is unset, but a default value was used for the requested operation.
68+ var errValueDefault = fmt .Errorf ("default: %w" , errValueUnset )
69+
70+ // MarshalSize is the initial size of the backing slice for
71+ // [encoding.TextMarshaler] implementations.
72+ //
73+ // This was arrived at by trying sizes until [BenchmarkMarshal] reported a
74+ // single allocation for all but the longest V4 vectors.
75+ const marshalSize = 128
76+
6677// Value is a "packed" representation of the value of a metric.
6778//
6879// When possible, this is the first byte of the abbreviated form in the relevant
@@ -107,55 +118,56 @@ func Version(vec string) (v int) {
107118 return v
108119}
109120
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.
121+ // AppendVector is a generic function to marshal vectors via appending to the
122+ // provided byte slice.
123+ func appendVector [M Metric , V Vector [M ]](b []byte , prefix string , v V ) ([]byte , error ) {
124+ b = append (b , prefix ... )
115125 var err error
116- // This is a rangefunc-style iterator.
117- v .groups (func (b [2 ]int ) bool {
126+ meta := v .meta ()
127+ g := meta .Groups
128+ for s , e := 0 , 1 ; e < len (g ); s , e = s + 1 , e + 1 {
118129 var set bool
119- orig := len (text )
120- for i := b [0 ]; i < b [1 ]; i ++ {
130+ i , lim := g [s ], g [e ]
131+ skipGroup := len (b )
132+ for ; i < lim ; i ++ {
133+ skipMetric := len (b )
121134 m := M (i )
122- val , err := v .getString (m )
135+
136+ b = append (b , '/' )
137+ b , err = m .AppendText (b )
138+ if err != nil {
139+ return nil , fmt .Errorf ("invalid cvss vector: %w" , err )
140+ }
141+ b = append (b , ':' )
142+
143+ b , err = v .appendValue (b , m )
123144 switch {
124145 case errors .Is (err , nil ):
125146 set = true
126- case errors .Is (err , errValueUnset ) && val == "" :
127- continue
147+ case errors .Is (err , errValueDefault ):
128148 case errors .Is (err , errValueUnset ):
149+ b = b [:skipMetric ]
129150 default :
130- err = errors .New ("invalid cvss vector" )
131- return false
151+ return nil , fmt .Errorf ("invalid cvss vector: %w" , err )
132152 }
133-
134- text = append (text , '/' )
135- text = append (text , m .String ()... )
136- text = append (text , ':' )
137- text = append (text , val ... )
138153 }
139154 if ! set {
140- text = text [: orig ]
155+ b = b [: skipGroup ]
141156 }
142- return true
143- })
144- if err != nil {
145- return nil , err
146157 }
147- // v2 hack
158+ // v2 hack: remove the leading slash.
148159 if prefix == "" {
149- text = text [1 :]
160+ b = b [1 :]
150161 }
151- return text , nil
162+ return b , nil
152163}
153164
154165// Metric is a CVSS metric.
155166//
156167// The set of types this describes is namespaced per-version.
157168type Metric interface {
158169 ~ int
170+ encoding.TextAppender
159171 fmt.Stringer
160172
161173 // Valid returns the concatenation of valid values for the metric.
@@ -166,6 +178,7 @@ type Metric interface {
166178
167179// Vector is a CVSS vector of any version.
168180type Vector [M Metric ] interface {
181+ encoding.TextAppender
169182 encoding.TextUnmarshaler
170183 encoding.TextMarshaler
171184 fmt.Stringer
@@ -181,20 +194,16 @@ type Vector[M Metric] interface {
181194 // Environmental reports if the vector contains environmental metrics.
182195 Environmental () bool
183196
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 )
197+ // AppendValue is a hook for appending the stringified version of the metric
198+ // value. If the value is unset, implementations should return the input
199+ // slice and err == [errValueUnset] rather than a specified default, as
200+ // defaults are omitted from the string representation.
201+ appendValue ([]byte , M ) ([]byte , error )
192202 // GetScore returns the "packed" value representation after any default
193203 // rules are applied.
194204 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 )
205+ // Meta returns the static metadata for this vector.
206+ meta () * vectorMetadata
198207}
199208
200209var (
@@ -203,6 +212,14 @@ var (
203212 _ Vector [V2Metric ] = (* V2 )(nil )
204213)
205214
215+ // VectorMetadata is static metadata about a vector.
216+ type vectorMetadata struct {
217+ // Groups is a slice of boundaries for the groups of the vector.
218+ //
219+ // The pairs of ints are [lower, upper).
220+ Groups []int
221+ }
222+
206223// Qualitative is the "Qualitative Severity" of a Vector.
207224type Qualitative int
208225
0 commit comments