Skip to content

refactor: Follows up on #261 #264

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
}
err = p.Transcoder.ValidateBytes(a)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to validate multiaddr %q: invalid value %q for protocol %s: %w", s, sp[0], p.Name, err)

Check warning on line 58 in codec.go

View check run for this annotation

Codecov / codecov/patch

codec.go#L58

Added line #L58 was not covered by tests
}
if p.Size < 0 { // varint size.
_, _ = b.Write(varint.ToUvarint(uint64(len(a))))
Expand All @@ -79,12 +79,16 @@
if p.Code == 0 {
return 0, Component{}, fmt.Errorf("no protocol with code %d", code)
}
pPtr := protocolPtrByCode[code]
if pPtr == nil {
return 0, Component{}, fmt.Errorf("no protocol with code %d", code)
}

Check warning on line 85 in codec.go

View check run for this annotation

Codecov / codecov/patch

codec.go#L84-L85

Added lines #L84 - L85 were not covered by tests

if p.Size == 0 {
c, err := validateComponent(Component{
bytes: string(b[:offset]),
offset: offset,
protocol: p,
bytes: string(b[:offset]),
valueStartIdx: offset,
protocol: pPtr,
})

return offset, c, err
Expand All @@ -109,9 +113,9 @@
}

c, err := validateComponent(Component{
bytes: string(b[:offset+size]),
protocol: p,
offset: offset,
bytes: string(b[:offset+size]),
protocol: pPtr,
valueStartIdx: offset,
})

return offset + size, c, err
Expand Down
85 changes: 74 additions & 11 deletions component.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package multiaddr

import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
Expand All @@ -11,9 +12,11 @@

// Component is a single multiaddr Component.
type Component struct {
bytes string // Uses the string type to ensure immutability.
protocol Protocol
offset int
// bytes is the raw bytes of the component. It includes the protocol code as
// varint, possibly the size of the value, and the value.
bytes string // string for immutability.
protocol *Protocol
valueStartIdx int // Index of the first byte of the Component's value in the bytes array
}

func (c Component) AsMultiaddr() Multiaddr {
Expand Down Expand Up @@ -107,22 +110,31 @@
}

func (c Component) Protocols() []Protocol {
return []Protocol{c.protocol}
if c.protocol == nil {
return nil
}
return []Protocol{*c.protocol}

Check warning on line 116 in component.go

View check run for this annotation

Codecov / codecov/patch

component.go#L113-L116

Added lines #L113 - L116 were not covered by tests
}

func (c Component) ValueForProtocol(code int) (string, error) {
if c.protocol == nil {
return "", fmt.Errorf("component has nil protocol")
}

Check warning on line 122 in component.go

View check run for this annotation

Codecov / codecov/patch

component.go#L120-L122

Added lines #L120 - L122 were not covered by tests
if c.protocol.Code != code {
return "", ErrProtocolNotFound
}
return c.Value(), nil
}

func (c Component) Protocol() Protocol {
return c.protocol
if c.protocol == nil {
return Protocol{}
}

Check warning on line 132 in component.go

View check run for this annotation

Codecov / codecov/patch

component.go#L131-L132

Added lines #L131 - L132 were not covered by tests
return *c.protocol
}

func (c Component) RawValue() []byte {
return []byte(c.bytes[c.offset:])
return []byte(c.bytes[c.valueStartIdx:])
}

func (c Component) Value() string {
Expand All @@ -135,10 +147,13 @@
}

func (c Component) valueAndErr() (string, error) {
if c.protocol == nil {
return "", fmt.Errorf("component has nil protocol")
}

Check warning on line 152 in component.go

View check run for this annotation

Codecov / codecov/patch

component.go#L151-L152

Added lines #L151 - L152 were not covered by tests
if c.protocol.Transcoder == nil {
return "", nil
}
value, err := c.protocol.Transcoder.BytesToString([]byte(c.bytes[c.offset:]))
value, err := c.protocol.Transcoder.BytesToString([]byte(c.bytes[c.valueStartIdx:]))
if err != nil {
return "", err
}
Expand All @@ -154,6 +169,9 @@
// writeTo is an efficient, private function for string-formatting a multiaddr.
// Trust me, we tend to allocate a lot when doing this.
func (c Component) writeTo(b *strings.Builder) {
if c.protocol == nil {
return
}

Check warning on line 174 in component.go

View check run for this annotation

Codecov / codecov/patch

component.go#L173-L174

Added lines #L173 - L174 were not covered by tests
b.WriteByte('/')
b.WriteString(c.protocol.Name)
value := c.Value()
Expand Down Expand Up @@ -185,6 +203,11 @@
}

func newComponent(protocol Protocol, bvalue []byte) (Component, error) {
protocolPtr := protocolPtrByCode[protocol.Code]
if protocolPtr == nil {
protocolPtr = &protocol
}

Check warning on line 209 in component.go

View check run for this annotation

Codecov / codecov/patch

component.go#L208-L209

Added lines #L208 - L209 were not covered by tests

size := len(bvalue)
size += len(protocol.VCode)
if protocol.Size < 0 {
Expand All @@ -205,23 +228,63 @@

return validateComponent(
Component{
bytes: string(maddr),
protocol: protocol,
offset: offset,
bytes: string(maddr),
protocol: protocolPtr,
valueStartIdx: offset,
})
}

// validateComponent MUST be called after creating a non-zero Component.
// It ensures that we will be able to call all methods on Component without
// error.
func validateComponent(c Component) (Component, error) {
if c.protocol == nil {
return Component{}, fmt.Errorf("component is missing its protocol")
}

Check warning on line 243 in component.go

View check run for this annotation

Codecov / codecov/patch

component.go#L242-L243

Added lines #L242 - L243 were not covered by tests
if c.valueStartIdx > len(c.bytes) {
return Component{}, fmt.Errorf("component valueStartIdx is greater than the length of the component's bytes")
}

Check warning on line 246 in component.go

View check run for this annotation

Codecov / codecov/patch

component.go#L245-L246

Added lines #L245 - L246 were not covered by tests

if len(c.protocol.VCode) == 0 {
return Component{}, fmt.Errorf("Component is missing its protocol's VCode field")
}

Check warning on line 250 in component.go

View check run for this annotation

Codecov / codecov/patch

component.go#L249-L250

Added lines #L249 - L250 were not covered by tests
if len(c.bytes) < len(c.protocol.VCode) {
return Component{}, fmt.Errorf("component size mismatch: %d != %d", len(c.bytes), len(c.protocol.VCode))
}

Check warning on line 253 in component.go

View check run for this annotation

Codecov / codecov/patch

component.go#L252-L253

Added lines #L252 - L253 were not covered by tests
if !bytes.Equal([]byte(c.bytes[:len(c.protocol.VCode)]), c.protocol.VCode) {
return Component{}, fmt.Errorf("component's VCode field is invalid: %v != %v", []byte(c.bytes[:len(c.protocol.VCode)]), c.protocol.VCode)
}

Check warning on line 256 in component.go

View check run for this annotation

Codecov / codecov/patch

component.go#L255-L256

Added lines #L255 - L256 were not covered by tests
if c.protocol.Size < 0 {
size, n, err := ReadVarintCode([]byte(c.bytes[len(c.protocol.VCode):]))
if err != nil {
return Component{}, err
}

Check warning on line 261 in component.go

View check run for this annotation

Codecov / codecov/patch

component.go#L260-L261

Added lines #L260 - L261 were not covered by tests
if size != len(c.bytes[c.valueStartIdx:]) {
return Component{}, fmt.Errorf("component value size mismatch: %d != %d", size, len(c.bytes[c.valueStartIdx:]))
}

Check warning on line 264 in component.go

View check run for this annotation

Codecov / codecov/patch

component.go#L263-L264

Added lines #L263 - L264 were not covered by tests

if len(c.protocol.VCode)+n+size != len(c.bytes) {
return Component{}, fmt.Errorf("component size mismatch: %d != %d", len(c.protocol.VCode)+n+size, len(c.bytes))
}

Check warning on line 268 in component.go

View check run for this annotation

Codecov / codecov/patch

component.go#L267-L268

Added lines #L267 - L268 were not covered by tests
} else {
// Fixed size value
size := c.protocol.Size / 8
if size != len(c.bytes[c.valueStartIdx:]) {
return Component{}, fmt.Errorf("component value size mismatch: %d != %d", size, len(c.bytes[c.valueStartIdx:]))
}

Check warning on line 274 in component.go

View check run for this annotation

Codecov / codecov/patch

component.go#L273-L274

Added lines #L273 - L274 were not covered by tests

if len(c.protocol.VCode)+size != len(c.bytes) {
return Component{}, fmt.Errorf("component size mismatch: %d != %d", len(c.protocol.VCode)+size, len(c.bytes))
}

Check warning on line 278 in component.go

View check run for this annotation

Codecov / codecov/patch

component.go#L277-L278

Added lines #L277 - L278 were not covered by tests
}

_, err := c.valueAndErr()
if err != nil {
return Component{}, err

}
if c.protocol.Transcoder != nil {
err = c.protocol.Transcoder.ValidateBytes([]byte(c.bytes[c.offset:]))
err = c.protocol.Transcoder.ValidateBytes([]byte(c.bytes[c.valueStartIdx:]))
if err != nil {
return Component{}, err
}
Expand Down
3 changes: 3 additions & 0 deletions matest/matest.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ type MultiaddrMatcher struct {
multiaddr.Multiaddr
}

// Implements the Matcher interface for gomock.Matcher
// Let's us use this struct in gomock tests. Example:
// Expect(mock.Method(gomock.Any(), multiaddrMatcher).Return(nil)
func (m MultiaddrMatcher) Matches(x interface{}) bool {
if m2, ok := x.(multiaddr.Multiaddr); ok {
return m.Equal(m2)
Expand Down
40 changes: 40 additions & 0 deletions multiaddr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1132,3 +1132,43 @@ func FuzzSplitRoundtrip(f *testing.F) {
}
})
}

func BenchmarkComponentValidation(b *testing.B) {
comp, err := NewComponent("ip4", "127.0.0.1")
if err != nil {
b.Fatal(err)
}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := validateComponent(comp)
if err != nil {
b.Fatal(err)
}
}
}

func FuzzComponents(f *testing.F) {
for _, v := range good {
m := StringCast(v)
for _, c := range m {
f.Add(c.Bytes())
}
}
f.Fuzz(func(t *testing.T, compBytes []byte) {
n, c, err := readComponent(compBytes)
if err != nil {
t.Skip()
}
if c.protocol == nil {
t.Fatal("component has nil protocol")
}
if c.protocol.Code == 0 {
t.Fatal("component has nil protocol code")
}
if !bytes.Equal(c.Bytes(), compBytes[:n]) {
t.Logf("component bytes: %v", c.Bytes())
t.Logf("original bytes: %v", compBytes[:n])
t.Fatal("component bytes are not equal to the original bytes")
}
})
}
7 changes: 7 additions & 0 deletions protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
var protocolsByName = map[string]Protocol{}
var protocolsByCode = map[int]Protocol{}

// Keep a map of pointers so that we can reuse the same pointer for the same protocol.
var protocolPtrByCode = map[int]*Protocol{}

// Protocols is the list of multiaddr protocols supported by this module.
var Protocols = []Protocol{}

Expand All @@ -65,10 +68,14 @@
if p.Path && p.Size >= 0 {
return fmt.Errorf("path protocols must have variable-length sizes")
}
if len(p.VCode) == 0 {
return fmt.Errorf("protocol code %d is missing its VCode field", p.Code)
}

Check warning on line 73 in protocol.go

View check run for this annotation

Codecov / codecov/patch

protocol.go#L72-L73

Added lines #L72 - L73 were not covered by tests

Protocols = append(Protocols, p)
protocolsByName[p.Name] = p
protocolsByCode[p.Code] = p
protocolPtrByCode[p.Code] = &p
return nil
}

Expand Down