2 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
f8ce8840b5 Fix default machine ID to respect BitsMachineID setting
Co-authored-by: YoshiyukiMineo <7577673+YoshiyukiMineo@users.noreply.github.com>
2026-02-12 05:43:24 +00:00
copilot-swe-agent[bot]
806997932e Initial plan 2026-02-12 05:40:10 +00:00
3 changed files with 102 additions and 1 deletions

86
v2/machine_bits_test.go Normal file
View File

@@ -0,0 +1,86 @@
package sonyflake
import (
"net"
"testing"
"github.com/sony/sonyflake/v2/mock"
)
// TestDefaultMachineIDWithCustomBits tests that when using default machine ID
// with custom BitsMachineID, the machine ID is properly masked to fit within
// the specified bit length.
func TestDefaultMachineIDWithCustomBits(t *testing.T) {
testCases := []struct {
name string
bitsMachineID int
mockIP net.IP
expectError bool
}{
{
name: "10 bits machine ID with IP that fits",
bitsMachineID: 10,
mockIP: net.IP{192, 168, 0, 1}, // lower 16 bits = 1, fits in 10 bits
expectError: false,
},
{
name: "10 bits machine ID with IP that exceeds without masking",
bitsMachineID: 10,
mockIP: net.IP{192, 168, 255, 255}, // lower 16 bits = 65535, needs masking to fit in 10 bits
expectError: false,
},
{
name: "8 bits machine ID",
bitsMachineID: 8,
mockIP: net.IP{192, 168, 100, 200}, // lower 16 bits = 25800
expectError: false,
},
{
name: "default 16 bits",
bitsMachineID: 0, // will use default 16
mockIP: net.IP{192, 168, 255, 255},
expectError: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Create a mock that returns our test IP
mockInterfaceAddrs := mock.NewInterfaceAddrsWithIP(tc.mockIP)
settings := Settings{
BitsMachineID: tc.bitsMachineID,
}
// Temporarily replace the default interface addrs function
oldDefaultInterfaceAddrs := defaultInterfaceAddrs
defaultInterfaceAddrs = mockInterfaceAddrs
defer func() { defaultInterfaceAddrs = oldDefaultInterfaceAddrs }()
sf, err := New(settings)
if tc.expectError {
if err == nil {
t.Errorf("expected error but got none")
}
} else {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if sf == nil {
t.Error("sonyflake instance should not be nil")
}
// Verify the machine ID fits within the specified bits
expectedBits := tc.bitsMachineID
if expectedBits == 0 {
expectedBits = defaultBitsMachine
}
maxMachineID := 1 << expectedBits
if sf.machine >= maxMachineID {
t.Errorf("machine ID %d exceeds max for %d bits (%d)", sf.machine, expectedBits, maxMachineID)
}
}
})
}
}

View File

@@ -34,3 +34,13 @@ func NewNilInterfaceAddrs() types.InterfaceAddrs {
return []net.Addr{}, nil
}
}
// NewInterfaceAddrsWithIP returns a private IP address with the given IP.
func NewInterfaceAddrsWithIP(ip net.IP) types.InterfaceAddrs {
ifat := make([]net.Addr, 0, 1)
ifat = append(ifat, &net.IPNet{IP: ip, Mask: []byte{255, 0, 0, 0}})
return func() ([]net.Addr, error) {
return ifat, nil
}
}

View File

@@ -36,7 +36,8 @@ import (
//
// MachineID returns the unique ID of a Sonyflake instance.
// If MachineID returns an error, the instance will not be created.
// If MachineID is nil, the default MachineID is used, which returns the lower 16 bits of the private IP address.
// If MachineID is nil, the default MachineID is used, which returns the lower bits
// of the private IP address, masked to fit within BitsMachineID bits.
//
// CheckMachineID validates the uniqueness of a machine ID.
// If CheckMachineID returns false, the instance will not be created.
@@ -154,6 +155,10 @@ func New(st Settings) (*Sonyflake, error) {
var err error
if st.MachineID == nil {
sf.machine, err = lower16BitPrivateIP(defaultInterfaceAddrs)
if err == nil {
// Mask to use only the required number of bits
sf.machine = sf.machine & (1<<sf.bitsMachine - 1)
}
} else {
sf.machine, err = st.MachineID()
}