| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- package service
- import (
- "crypto/ed25519"
- "crypto/rand"
- "crypto/sha256"
- "encoding/base64"
- "encoding/json"
- "fmt"
- "net"
- "testing"
- "github.com/glebarez/sqlite"
- "gorm.io/gorm"
- "wails-app/internal/dao"
- )
- func TestValidateAddressRejectsUnsafeInputs(t *testing.T) {
- tests := []struct {
- name string
- ip string
- port int
- }{
- {name: "ipv4 loopback", ip: "127.0.0.1", port: 8443},
- {name: "localhost", ip: "localhost", port: 8443},
- {name: "ipv4 unspecified", ip: "0.0.0.0", port: 8443},
- {name: "limited broadcast", ip: "255.255.255.255", port: 8443},
- {name: "ipv4 multicast", ip: "239.1.2.3", port: 8443},
- {name: "ipv6 multicast", ip: "ff02::1", port: 8443},
- {name: "ipv6 loopback", ip: "::1", port: 8443},
- {name: "ipv4 mapped loopback", ip: "::ffff:127.0.0.1", port: 8443},
- {name: "ipv4 mapped unspecified", ip: "::ffff:0.0.0.0", port: 8443},
- {name: "port zero", ip: "192.0.2.10", port: 0},
- {name: "port exceeds range", ip: "192.0.2.10", port: 65536},
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- if _, err := ValidateAddress(test.ip, test.port); err == nil {
- t.Fatalf("ValidateAddress(%q, %d) should be rejected", test.ip, test.port)
- }
- })
- }
- }
- func TestFindProvisionReaderReusesSoftDeletedSameDevice(t *testing.T) {
- db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
- if err != nil {
- t.Fatal(err)
- }
- if err := db.AutoMigrate(&dao.UHFReader{}); err != nil {
- t.Fatal(err)
- }
- row := &dao.UHFReader{DeviceCode: "MOCK-GATE-001", DeviceID: "MOCK-EDGE-001", DeviceName: "old", DeviceType: "gate"}
- if err := db.Create(row).Error; err != nil {
- t.Fatal(err)
- }
- if err := db.Delete(row).Error; err != nil {
- t.Fatal(err)
- }
- found, ok, err := findProvisionReader(db, "MOCK-EDGE-001", "MOCK-GATE-001")
- if err != nil || !ok || found.ID != row.ID || !found.DeletedAt.Valid {
- t.Fatalf("expected soft-deleted row to be reused, row=%+v found=%v ok=%v err=%v", row, found, ok, err)
- }
- found.DeletedAt = gorm.DeletedAt{}
- found.DeviceName = "restored"
- if err := db.Unscoped().Save(&found).Error; err != nil {
- t.Fatal(err)
- }
- var active dao.UHFReader
- if err := db.First(&active, row.ID).Error; err != nil {
- t.Fatal(err)
- }
- if active.DeviceName != "restored" {
- t.Fatalf("expected restored row, got %+v", active)
- }
- }
- func TestFindProvisionReaderRejectsCodeOwnedByOtherDevice(t *testing.T) {
- db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
- if err != nil {
- t.Fatal(err)
- }
- if err := db.AutoMigrate(&dao.UHFReader{}); err != nil {
- t.Fatal(err)
- }
- if err := db.Create(&dao.UHFReader{DeviceCode: "MOCK-GATE-001", DeviceID: "OTHER-EDGE", DeviceName: "other", DeviceType: "gate"}).Error; err != nil {
- t.Fatal(err)
- }
- if _, ok, err := findProvisionReader(db, "MOCK-EDGE-001", "MOCK-GATE-001"); err == nil || ok {
- t.Fatalf("expected code conflict, ok=%v err=%v", ok, err)
- }
- }
- func TestValidateDiscoveryResponse(t *testing.T) {
- publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
- if err != nil {
- t.Fatal(err)
- }
- keyHash := sha256.Sum256(publicKey)
- request := discoveryRequestPacket{Schema: "discovery.request.v1", RequestID: "request-1", Nonce: "nonce-1"}
- packet := discoveryResponsePacket{
- Schema: "discovery.response.v1", RequestID: request.RequestID, Nonce: request.Nonce,
- DeviceID: "SN-1", DeviceCode: "EDGE-1", DeviceType: "lpr-gate", DeviceModel: "LPR-200",
- FirmwareVersion: "2.1.0", IP: "192.0.2.10", ConfigURL: "https://192.0.2.10:8443/api/v1/provision",
- PublicKeyFingerprint: "SHA256:" + fmt.Sprintf("%x", keyHash), SigningPublicKey: base64.StdEncoding.EncodeToString(publicKey),
- }
- packet.Signature = base64.StdEncoding.EncodeToString(ed25519.Sign(privateKey, discoverySigningPayload(packet)))
- raw, err := json.Marshal(packet)
- if err != nil {
- t.Fatal(err)
- }
- result, err := validateDiscoveryResponse(raw, net.ParseIP("192.0.2.10"), request)
- if err != nil {
- t.Fatalf("expected valid discovery response: %v", err)
- }
- if !result.SignatureVerified || result.ConfigPort != 8443 || result.DeviceID != "SN-1" {
- t.Fatalf("unexpected discovery result: %#v", result)
- }
- packet.Signature = base64.StdEncoding.EncodeToString([]byte("invalid"))
- raw, _ = json.Marshal(packet)
- if _, err := validateDiscoveryResponse(raw, net.ParseIP("192.0.2.10"), request); err == nil {
- t.Fatal("invalid signature must be rejected")
- }
- packet.Signature = base64.StdEncoding.EncodeToString(ed25519.Sign(privateKey, discoverySigningPayload(packet)))
- packet.Nonce = "unexpected-nonce"
- raw, _ = json.Marshal(packet)
- if _, err := validateDiscoveryResponse(raw, net.ParseIP("192.0.2.10"), request); err == nil {
- t.Fatal("response with a different nonce must be rejected")
- }
- }
- func TestDiscoveryBroadcastTargetsAcceptsExplicitIPv4(t *testing.T) {
- targets, err := discoveryBroadcastTargets("192.168.10.255")
- if err != nil || len(targets) != 1 || !targets[0].Equal(net.ParseIP("192.168.10.255")) {
- t.Fatalf("unexpected broadcast targets: %#v, %v", targets, err)
- }
- if _, err := discoveryBroadcastTargets("ff02::1"); err == nil {
- t.Fatal("IPv6 multicast must not be accepted as an IPv4 UDP discovery target")
- }
- }
- func TestValidateAddressAllowsUnicastIPs(t *testing.T) {
- for _, rawIP := range []string{"192.0.2.10", "2001:db8::10"} {
- ip, err := ValidateAddress(rawIP, 8443)
- if err != nil {
- t.Fatalf("ValidateAddress(%q) returned error: %v", rawIP, err)
- }
- if !ip.Equal(net.ParseIP(rawIP)) {
- t.Fatalf("ValidateAddress(%q) returned %v", rawIP, ip)
- }
- }
- }
- func TestDirectedBroadcastCheck(t *testing.T) {
- network := net.ParseIP("192.168.50.12")
- mask := net.CIDRMask(24, 32)
- if !isDirectedBroadcastForNetwork(net.ParseIP("192.168.50.255"), network, mask) {
- t.Fatal("expected subnet broadcast address to be rejected")
- }
- if isDirectedBroadcastForNetwork(net.ParseIP("192.168.50.26"), network, mask) {
- t.Fatal("ordinary unicast address must not be classified as broadcast")
- }
- }
|