lrstanley / girc

:bomb: girc is a flexible IRC library for Go :ok_hand:

Home Page:https://pkg.go.dev/github.com/lrstanley/girc

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Use of struct{} instead of string where value is ignored

bmeh opened this issue · comments

commented

I have stumbled upon the code here, where you are using string (as in map[string][]string) and nil, instead of struct{} and struct{}{}. From a quick glance I cannot see you using the value anywhere, and using struct{} would communicate this intent better, therefore making the codebase slightly cleaner, and additionally it would consume less memory, but this memory consumption is negligible either way.

From:

var possibleCap = map[string][]string{
  "account-notify": nil,
[...]
}

to:

var possibleCap = map[string]struct{}{
  "account-notify": struct{}{},
[...]
}

You may also like to compare the assembly output: https://go.godbolt.org/z/4IMjlE

the values are used here, thus changing to struct{}{} would break this:

girc/cap.go

Lines 103 to 123 in 102f17f

possible := possibleCapList(c)
if len(e.Params) >= 2 && len(e.Trailing) > 1 && e.Params[1] == CAP_LS {
c.state.Lock()
caps := parseCap(e.Trailing)
for k := range caps {
if _, ok := possible[k]; !ok {
continue
}
if len(possible[k]) == 0 || len(caps[k]) == 0 {
c.state.tmpCap = append(c.state.tmpCap, k)
continue
}
var contains bool
for i := 0; i < len(caps[k]); i++ {
for j := 0; j < len(possible[k]); j++ {
if caps[k][i] == possible[k][j] {

commented

This is actually odd, I changed everything to struct{} and struct{}{} and modified the snippet you posted, albeit I left parseCap() alone. It compiled and the tests were successful. I even used it to connect to a server through SASL (PLAIN), and it says it has the proper capabilities, but it says it doesn't have capability sasl through c.HasCapability, nor with mine or the original version. Yet the test cap_test (where you use possibleCapList["sasl"] instead though) passes, both with your version and mine.

Although I figured the reason you use []string is that a capability can be in the form of sasl=PLAIN,EXTERNAL, and you need to store both PLAIN, and EXTERNAL, but then why does all tests pass, and seemingly have identical results after I make those modifications? Everything seems to work correctly.

Loosely-related: is it possible right now to get PLAIN and EXTERNAL? Is it exposed? c.HasCapability returns only a bool, but what if I want to know the arguments of said capabilities? Should c.HasCapability return (bool, []string) perhaps? Worth noting again that c.HasCapability("sasl") returns false, so we probably wouldn't be able to get PLAIN and EXTERNAL if it doesn't even find the capability sasl. Maybe this part needs a complete rewrite or something. I might get into it later, unless you decide to do so, but I have to finish something first that relies on this library, and I don't need to check for the capability sasl. :P

c.HasCapability("sasl") works for me without issue, both on servers that don't provide any params (so it's assumed to be PLAIN), and servers that provide PLAIN,EXTERNAL. Did you verify the SASL connection was a success? Can you provide the server you used to test this on? This is what I used (can also test with testnet.oragono.io).

package main

import (
	"log"
	"os"

	"github.com/nmeum/girc"
)

func main() {
	client := girc.New(girc.Config{
		Server: "irc.byteirc.org",
		Port:   6667,
		Nick:   "girc-test1",
		User:   "girc",
		Debug:  os.Stdout,
		SASL:   &girc.SASLPlain{User: "*truncated*", Pass: "*truncated*"},
	})

	client.Handlers.AddBg(girc.CONNECTED, func(c *girc.Client, e girc.Event) {
		log.Printlny(c.HasCapability("sasl"))
	})

	if err := client.Connect(); err != nil {
		log.Fatal(err)
	}
}

As for HasCapability, this is not the capabilities of the server, rather the capabilities that were successful between the client and server. Unfortunately, the IRCv3 spec does not specify the successful params back in the ACK response if the client happens to acknowledge one of the sasl mechanisms. So, the only way that HasCapability could return the specific ones that were acknowledged, is if the writing method appends that. That would have to be manually implemented for each IRCv3 extension which has parameters (which isn't really worth the effort or complexity), and would also break if the user decides to acknowledge things outside of the internal sasl ack in girc.

As for replacing []string with struct{}/struct{}{}/etc, it's a non-issue. The memory consumption doesn't matter since it's only used once maybe twice, in a very small scope. In addition, changing it would make the code more complex (we have to compare the possibleCap list against the SupportedCaps, why spend the time to try and differentiate struct{} vs []string when you can just compare the two). Lastly, this method supports future extensions of the IRCv3 spec, as future extensions, we may only support specific params, and this will take advantage of that. It's not really used right now, as only one entry of the spec really uses params.

commented

Ah, I just added:

if c.HasCapability("sasl") {
  log.Println("Has capability 'sasl'")
} else {
  log.Println("Does not have capability 'sasl'")
}

to my own code and it seems to work, so I am not entirely sure what the issue could have been. I did indeed check for RPL_SASLSUCCESS, and despite that, c.HasCapability("sasl") didn't work. It might have been my fault though, I have several different versions scattered around my filesystem, so it's possible that I was running a version without SASL enabled (but then why did I get SASL authentication succeeded?). Sorry about that. Sleep deprivation at its finest. :D

commented

What I meant by checking RPL_SASLSUCCESS is that I added a handler for it which prints a message that it succeeded, and then I did an INVITE (poor man's test case) afterwards which calls c.HasCapability("sasl"), and it failed. I cannot reproduce this now anymore, so I am not exactly sure what caused this issue. It all seems to work just fine now using my latest code. I will give a detailed report and dig deeper if I run into this issue.

It might have been that I used my friend's server who ran the same version of oragono, but with a different configuration. I remember not being able to type NICK <account> in case the nick was registered (hence "account"), which was odd because according to oragono's documentations, the account name has nothing to do with the nick. Sadly I cannot reproduce this either, I will have to ask my friend to set his server up which: 1) doesn't append nicknames in responses, causing more off-by-one issues, 2) requires SASL before sending the raw command NICK <name> where name is an account. It works fine with my configuration, so I really have to ask him for his. :D

Edit: I did mention some more details in the other opened issue (#19) regarding those peculiar behaviors.