Skip to content

Instantly share code, notes, and snippets.

@chaosmatrix
Last active August 12, 2023 14:16
Show Gist options
  • Save chaosmatrix/bc4a3d423273becbda3657102a12c5cd to your computer and use it in GitHub Desktop.
Save chaosmatrix/bc4a3d423273becbda3657102a12c5cd to your computer and use it in GitHub Desktop.
golang tips

check file permission bits

// executable
if stat, err := os.Stat(filename); err != nil {
  panic(err)
}
if stat.Mode().Perm() & 0111 == 0000 {
  fmt.Printf("%s not executable\n", filename)
}
@chaosmatrix
Copy link
Author

disable proxy.golang.org

go env -w GOPROXY=direct
go env -w GOSUMDB=off

@chaosmatrix
Copy link
Author

number to string

string('a') // "a"
string(97) // "a"

@chaosmatrix
Copy link
Author

chaosmatrix commented Sep 1, 2021

select {}

spec

The following rules apply to selectors:

  1. For a value x of type T or *T where T is not a pointer or interface type, x.f denotes the field or method at the shallowest depth in T where there is such an f. If there is not exactly one f with shallowest depth, the selector expression is illegal.
  2. For a value x of type I where I is an interface type, x.f denotes the actual method with name f of the dynamic value of x. If there is no method with name f in the method set of I, the selector expression is illegal.
  3. As an exception, if the type of x is a defined pointer type and (*x).f is a valid selector expression denoting a field (but not a method), x.f is shorthand for (*x).f.
  4. In all other cases, x.f is illegal.
  5. If x is of pointer type and has the value nil and x.f denotes a struct field, assigning to or evaluating x.f causes a run-time panic.
  6. If x is of interface type and has the value nil, calling or evaluating the method x.f causes a run-time panic.

example

deadlock after output 5(half) result :

  1. select { case ch <- <- input } need to done <- input on every case, then select one case to done
  2. in this code, will only output half result, and throw half result, then suck on <- input, and input read side close, cause deadlock

more:

  1. use buffer channel can't solve deadlock
  2. change select { case ch <- <- input1: } as select { case v1 := <- input: ch <- v1 } can solve deadlock and output all result
package main

import (
	"fmt"
	"time"
)

func talk(msg string, sleep int) <-chan string {
	ch := make(chan string)
	go func() {
		for i := 0; i < 5; i++ {
			//fmt.Printf("hit %s\n", msg)
			ch <- fmt.Sprintf("%s %d", msg, i)
			//fmt.Printf("[+] After Send %s\n", msg)
			time.Sleep(time.Duration(sleep) * time.Millisecond)
		}
	}()
	return ch
}

// deadlock, half send into ch, half throw
func fanIn(input1, input2 <-chan string) <-chan string {
	ch := make(chan string)
	go func() {
		for {
			select {
			case ch <- <-input1:
			case ch <- <-input2:
			}
		}
	}()
	return ch
}

// equal to
func fanIn2(input1, input2 <-chan string) <-chan string {
	ch := make(chan string)
	go func() {
		for {
			v1 := <-input1
			v2 := <-input2
			select {
			case ch <- v1:
			case ch <- v2:
			}
		}
	}()
	return ch
}

// solve deadlock, output all result
func fanIn3(input1, input2 <-chan string) <-chan string {
	ch := make(chan string)
	go func() {
		for {
			select {
			case v1 := <-input1:
				ch <- v1
			case v2 := <-input2:
				ch <- v2
			}
		}
	}()
	return ch
}

func main() {
	ch := fanIn3(talk("A", 10), talk("B", 10))
	for i := 0; i < 10; i++ {
		fmt.Printf("%q\n", <-ch)
	}
}

@chaosmatrix
Copy link
Author

chaosmatrix commented Dec 14, 2021

Golang Type and Size

result

type: ""                   size: 0      data: struct {}{}
type: "bool"               size: 1      data: true
type: "uint8"              size: 1      data: 0x62
type: "int32"              size: 4      data: 114
type: "uint8"              size: 1      data: 0x1
type: "uint16"             size: 2      data: 0x1
type: "uint32"             size: 4      data: 0x1
type: "uint64"             size: 8      data: 0x1
type: "uint"               size: 8      data: 0x1
type: "uintptr"            size: 8      data: 0x1
type: "int8"               size: 1      data: 1
type: "int16"              size: 2      data: 1
type: "int32"              size: 4      data: 1
type: "int64"              size: 8      data: 1
type: "int"                size: 8      data: 1
type: "float32"            size: 4      data: 1
type: "float64"            size: 8      data: 1
type: "complex64"          size: 8      data: (1+0i)
type: "complex128"         size: 16     data: (1+0i)
type: ""                   size: 8      data: &errors.errorString{s:"hello world"}
type: ""                   size: 8      data: &errors.errorString{s:"hello world hello world"}
type: "string"             size: 16     data: "hello world"
type: "string"             size: 16     data: "hello world hello world"
type: ""                   size: 8      data: map[int]int{}
type: ""                   size: 8      data: map[int]int{0:0, 1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8, 9:9, 10:10}
type: ""                   size: 24     data: []int{}
type: ""                   size: 24     data: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

code

package main

import (
	"errors"
	"fmt"
	"reflect"
)

func main() {
	types := []interface{}{
		struct{}{},
		true,
		byte('b'),
		rune('r'),
		uint8(1), uint16(1), uint32(1), uint64(1), uint(1),
		uintptr(1),
		int8(1), int16(1), int32(1), int64(1), int(1),
		float32(1.0), float64(1.0),
		complex64(1),
		complex128(1),
		errors.New("hello world"),             // return uintptr refer to real data
		errors.New("hello world hello world"), // return uintptr refer to real data
		"hello world",
		"hello world hello world",
		make(map[int]int, 10),
		map[int]int{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10},
		make([]int, 0, 10),
		[]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
	}
	for _, t := range types {
		fmt.Printf("type: %#-20v size: %-6d data: %#v\n", reflect.TypeOf(t).Name(), reflect.TypeOf(t).Size(), t)
	}
}

@chaosmatrix
Copy link
Author

chaosmatrix commented Dec 18, 2021

sync

Once

func (o *Once) Do(f func()) use atomic make concurrency caller return immediately, use Mutex protect calling f

golang source code: src/sync/once.go

type Once struct {
	// done indicates whether the action has been performed.
	// It is first in the struct because it is used in the hot path.
	// The hot path is inlined at every call site.
	// Placing done first allows more compact instructions on some architectures (amd64/386),
	// and fewer instructions (to calculate offset) on other architectures.
	done uint32
	m    Mutex
}

func (o *Once) Do(f func()) {
	// Note: Here is an incorrect implementation of Do:
	//
	//	if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
	//		f()
	//	}
	//
	// Do guarantees that when it returns, f has finished.
	// This implementation would not implement that guarantee:
	// given two simultaneous calls, the winner of the cas would
	// call f, and the second would return immediately, without
	// waiting for the first's call to f to complete.
	// This is why the slow path falls back to a mutex, and why
	// the atomic.StoreUint32 must be delayed until after f returns.

	if atomic.LoadUint32(&o.done) == 0 {
		// Outlined slow-path to allow inlining of the fast-path.
		o.doSlow(f)
	}
}

func (o *Once) doSlow(f func()) {
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 {
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}

@chaosmatrix
Copy link
Author

strconv

// "000123" -> 123
// "123" -> 123
strconv.Atoi

@chaosmatrix
Copy link
Author

chaosmatrix commented Jan 6, 2022

mode operations

Basic

Golang: x % y is negative or positive base on x > 0 or x < 0, x % y != (x % y + y) % y
Python: x % y is always positive not matter x > 0 or x < 0, x % y = (x %y + y) % y

(x % y + y) % y = (x % y) + (y % y) to handle case x < 0, make sure result alwasy >= 0

OR

if x % mod < 0 {
    x = x % mod + mod
}

x * y % m = (( x % m) * (y % m)) % m = (x % m) * y % m

Example

Golang:

	fmt.Printf("%d %% %d = %d\n", 6, 8, 6%8)
	fmt.Printf("%d %% %d = %d\n", -6, 8, -6%8)
	fmt.Printf("(%d %% %d + %d) %% %d = %d\n", -6, 8, 8, 8, (-6%8+8)%8)
	fmt.Printf("(%d %% %d + %d) %% %d = %d\n", 6, 8, 8, 8, (6%8+8)%8)

Output:

6 % 8 = 6
-6 % 8 = -6
(-6 % 8 + 8) % 8 = 2
(6 % 8 + 8) % 8 = 6

Python:

>>> 6 % 8
6
>>> -6 % 8
2
>>> (-6 % 8 + 8) % 8
2
>>> (6 % 8 + 8) % 8
6
>>>

@chaosmatrix
Copy link
Author

unsafe

get pointer address

*(*uint64)(unsafe.Pointer(&v))

@chaosmatrix
Copy link
Author

net.Dialer

net.Dialer will resolve domain to get addresses, both ipv4 and ipv6.

abstruct:

  1. in dual stack, first try IPv6, then fallback into IPv4 in net.Dialer.FallbackDelay
  2. not dual stack, try all addresses in sequence before timeout.

more details:

  1. code net/dial.go
  2. rfc: https://www.rfc-editor.org/rfc/rfc6555.html

@chaosmatrix
Copy link
Author

chaosmatrix commented Jun 20, 2022

reduce binary package size

  1. go build -ldflags '-s -w' ~70%
    • bad for debuging
  2. upx https://github.com/upx/upx/releases ~50%

@chaosmatrix
Copy link
Author

chaosmatrix commented Jun 20, 2022

defer

When:

  1. execute before function return

Rules:

  1. A deferred function’s arguments are evaluated when the defer statement is evaluated.
    • defer f(v) // v is argument
    • defer f1().f() // f1() is argument
  2. Deferred function calls are executed in Last In First Out order after the surrounding function returns.
  3. Deferred functions may read and assign to the returning function’s named return values.

pidfall

defer f1().f2().f3() // consider f1().f2() as argument for function f3()

equal with

v := f1().f2()
defer v.f3()

@chaosmatrix
Copy link
Author

Golang Upgrade Version

  1. go get only check library's version, won't re-compile tools after go version change

@chaosmatrix
Copy link
Author

Example

s = "ab"

s[i] is uint8 type

for _, r := range s {
    // r is rune type, is int32 type
}

int(s[0] - s[1]) => int(uint8(uint8(s[1]) - uint8(s[0])))

s[0] < s[1], so, s[0] - s[1] = -1, as uint8, will overflow as 255

int(s[0]) - int(s[1]) = -1

@chaosmatrix
Copy link
Author

chaosmatrix commented Aug 2, 2023

net.IP

Rules:

  1. Both IPv4 and IPv6 address store as IPv6 address
  2. To16() can convert IPv4 to IPv6 (because IPv4 store as IPv6 format)
  3. To4() will check if the IP address is valid IPv4 or not (::ffff:808:808 is valid IPv4)
  4. String(): all valid IPv4 address will be show as IPv4 format (::ffff:808:808 will be show as 8.8.8.8)
  5. When you want to know a string is valid IP and distinguish between IPv4 and IPv6, it's good to use netip.ParseAddr() in net/netip
Example
input string net.ParseIP() String() To4()
2607:f8b0:4006:81f::2004 net.IP{0x26, 0x7, 0xf8, 0xb0, 0x40, 0x6, 0x8, 0x1f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x4} 2607:f8b0:4006:81f::2004 nil
8.8.8.8 net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x8, 0x8, 0x8, 0x8} 8.8.8.8
::ffff:808:808 net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x8, 0x8, 0x8, 0x8} 8.8.8.8

Cookbooks:

Check an Input string is valid IPv4 address (dotted decimal notations)
ip := net.ParseIP(s)
if ip != nil && ip.To4() != nil && !strings.Contains(s, ":") {
    return true
}
return false
Check an IP is valid IPv6 address

All IP addresses are IPv6 address, you can only check an IP address is valid IPv4 or not

ip := net.ParseIP(s)
if ip != nil && ip.To4() == nil {
    return false
}
return ip != nil

@chaosmatrix
Copy link
Author

net/netip

package main

import (
	"fmt"
	"net/netip"
)

func main() {

	ts := []string {
        "2001:db8::68",
        "8.8.8.8",
        "::ffff:808:808",
        "0:0:0:0:0:ffff:0808:0808",
        "0000:0000:0000:0000:0000:ffff:0808:0808",
    }
    for _, s := range ts {
		addr, err := netip.ParseAddr(s)
		if err == nil {
			fmt.Printf("ip: %s, is_ipv4: %v, is_ipv4Toipv6: %v, is_ipv6: %v, string: %s\n", s, addr.Is4(), addr.Is4In6(), addr.Is6(), addr.String())
		} else {
			fmt.Printf("[+] invalid ip %s\n", s)
		}
	}
}

Output:

ip: 2001:db8::68, is_ipv4: false, is_ipv4Toipv6: false, is_ipv6: true, string: 2001:db8::68
ip: 8.8.8.8, is_ipv4: true, is_ipv4Toipv6: false, is_ipv6: false, string: 8.8.8.8
ip: ::ffff:808:808, is_ipv4: false, is_ipv4Toipv6: true, is_ipv6: true, string: ::ffff:8.8.8.8
ip: 0:0:0:0:0:ffff:0808:0808, is_ipv4: false, is_ipv4Toipv6: true, is_ipv6: true, string: ::ffff:8.8.8.8
ip: 0000:0000:0000:0000:0000:ffff:0808:0808, is_ipv4: false, is_ipv4Toipv6: true, is_ipv6: true, string: ::ffff:8.8.8.8

@chaosmatrix
Copy link
Author

chaosmatrix commented Aug 4, 2023

time.Timer vs time.Ticker

Diff:

  1. Timer need to Reset every time when it fired
  2. Ticker auto reset every time when it fired

Warning:

  1. when the job wast more than ticker duration, there might has more than one job executed or lose some ticker

The difference of them show as code

// timer
for {
    time.Sleep(d)
    do_some_thing
    // timer reset
}

// ticker
for {
    time.Sleep(d)
    go func() {
        do_some_thing
    }()
   // wait next ticker
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment