(go) Deep equality for ssh.AuthMethod

Issue

I’m using crypto/ssh package and I’m trying to write a unit test for a method that constructs the ClientConfig.

One of the assertions in the unit is that the returned ClientConfig is deeply equal to the expected.
The assertion fails because both Auth and HostKeyCallback fields of ClientConfig are not deeply equal.
The HostKeyCallback is hardcoded to be ssh.InsecureIgnoreHostKey().
The only authentication method I’m testing right now is with password and I have verified that the password string is picked up correctly.

I tried to mess around in playground (see here) and I don’t understand why there is no deep equality in these cases.

package main

import (
    "fmt"
    "reflect"

    "golang.org/x/crypto/ssh"
)

func main() {
    pass := "bar"
    auth := []ssh.AuthMethod{ssh.Password(pass)}
    authLiteral := []ssh.AuthMethod{ssh.Password("bar")}
    if reflect.DeepEqual(authLiteral, auth) {
        fmt.Println("authentication methods are equal")
    } else {
        fmt.Println("authentication methods are not equal")
    }

    callback1 := ssh.InsecureIgnoreHostKey()
    callback2 := ssh.InsecureIgnoreHostKey()
    if reflect.DeepEqual(callback1, callback2) {
        fmt.Println("callbacks are equal")
    } else {
        fmt.Println("callbacks are not equal")
    }
}
authentication methods are not equal
callbacks are not equal

Could someone please explain these results?
I would also be grateful if you could suggest how I could unit test this case.

Solution

As mkopriva notes in a comment, this is formally disallowed. (Still, it might be nice if reflect.DeepEqual, which comes with the Go implementation, did something like I did below. Because it comes with the implementation, the implementor can use special knowledge to write it.)

Ultimately, of course, there’s some machine-level implementation for pointer to function taking arguments of types T1, T2, … Tna and returning values value of types R1, R2, … Rnr . But we’re not told what that representation is. It might be a single unsafe.Pointer value, or it might be some kind of inline expansion of a thunk. So we don’t know how many bytes to compare here.

If you’re willing to make rather dangerous / non-portable assumptions, and update them if and when they prove inadequate, there is a way to "cheat" here. I made this example on the Go Playground, which produces the desired output. Is this just by chance, or does this really work on your Go implementation?

The text is also below. Use at your own risk!

package main

import (
    "fmt"
    "unsafe"
)

func dummy1() {}
func dummy2() {}

func main() {
    fmt.Println("dummy1 == dummy2 =>", qe(dummy1, dummy2))
    fmt.Println("dummy1 == dummy1 =>", qe(dummy1, dummy1))
}

// "questionably equal", for function pointers
func qe(a, b func()) bool {
    pp1 := (*unsafe.Pointer)(unsafe.Pointer(&a))
    pp2 := (*unsafe.Pointer)(unsafe.Pointer(&b))
    p1 := *pp1
    p2 := *pp2
    return p1 == p2
}

Answered By – torek

Answer Checked By – Marilyn (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.