// Package device wrappt go-ios's DeviceEntry. Liefert die hochlevel-Calls // die wir brauchen: Info dumpen, IsSupervised checken, FMI-Status, Reboot, // WaitForReconnect. package device import ( "errors" "fmt" "time" ios "github.com/danielpaulus/go-ios/ios" "github.com/danielpaulus/go-ios/ios/diagnostics" ) // Conn ist unser Wrapper. Hält die go-ios DeviceEntry + cached UDID. type Conn struct { device ios.DeviceEntry udid string } // Connect öffnet eine Verbindung zum (ersten) verbundenen iPhone. // Wenn udid != "" filtert auf spezifisches Gerät. func Connect(udid string) (*Conn, error) { list, err := ios.ListDevices() if err != nil { return nil, fmt.Errorf("device: list devices: %w", err) } if len(list.DeviceList) == 0 { return nil, errors.New("device: no iPhone/iPad detected via USB — connect device + tap 'Trust this computer'") } for _, d := range list.DeviceList { if udid == "" || d.Properties.SerialNumber == udid { return &Conn{ device: d, udid: d.Properties.SerialNumber, }, nil } } return nil, fmt.Errorf("device: no device matching UDID %s", udid) } // UDID exposes the device serial. func (c *Conn) UDID() string { return c.udid } // Device returnt das underlying go-ios DeviceEntry für direct API-Calls // (z.B. mcinstall.New(conn.Device())). func (c *Conn) Device() ios.DeviceEntry { return c.device } // Info dumps Lockdown-Values als map. Top-Level (kein Domain). func (c *Conn) Info() (map[string]interface{}, error) { return ios.GetValuesPlist(c.device) } // IsSupervised — wird via lazy-injected mcinstall-call gechecked. Default // returnt false; callers können CheckSupervisedFunc setzen um authoritative // MCInstall-check zu enablen (vermeidet circular import). // // Wir nutzen einen package-level function-pointer der vom main-package gesetzt // wird (siehe cmd/supervise/main.go init). So bleibt device-package unabhängig // von mcinstall. func (c *Conn) IsSupervised() (bool, error) { if CheckSupervisedFunc != nil { return CheckSupervisedFunc(c.device) } return false, nil } // CheckSupervisedFunc — set by main package to inject mcinstall-based check. // Signature: takes go-ios DeviceEntry, returns (supervised, error). var CheckSupervisedFunc func(ios.DeviceEntry) (bool, error) // FindMyEnabled — parsed NonVolatileRAM["fm-activation-locked"] als ASCII- // String. "YES" = FMI an, "NO" = aus. Empirisch verifiziert 2026-05-26 (iOS 26.5). func (c *Conn) FindMyEnabled() (bool, error) { info, err := c.Info() if err != nil { return false, nil } nvram, ok := info["NonVolatileRAM"].(map[string]interface{}) if !ok { return false, nil } lock, ok := nvram["fm-activation-locked"] if !ok { return false, nil } switch v := lock.(type) { case []byte: return string(v) == "YES", nil case []interface{}: bytes := make([]byte, 0, len(v)) for _, b := range v { if f, ok := b.(uint64); ok { bytes = append(bytes, byte(f)) } else if f, ok := b.(float64); ok { bytes = append(bytes, byte(f)) } } return string(bytes) == "YES", nil case string: return v == "YES", nil } return false, nil } // ActivationState — interessant für check-CLI. func (c *Conn) ActivationState() (string, error) { info, err := c.Info() if err != nil { return "", err } if s, ok := info["ActivationState"].(string); ok { return s, nil } return "", nil } // GetValueForDomain — wrapper über go-ios's Lockdown-Domain-Query. // Brauchen wir für restriction-Domain-Heuristik. func (c *Conn) GetValueForDomain(domain, key string) (interface{}, error) { conn, err := ios.ConnectLockdownWithSession(c.device) if err != nil { return nil, err } defer conn.Close() return conn.GetValueForDomain(key, domain) } // Reboot triggert iPhone-Restart via Diagnostics-Service. func (c *Conn) Reboot() error { return diagnostics.Reboot(c.device) } // WaitForReconnect pollt bis das Gerät nach Reboot wieder via usbmux // sichtbar ist (oder Timeout). Default 90s. func WaitForReconnect(udid string, timeout time.Duration) (*Conn, error) { deadline := time.Now().Add(timeout) for time.Now().Before(deadline) { conn, err := Connect(udid) if err == nil { return conn, nil } time.Sleep(2 * time.Second) } return nil, fmt.Errorf("device: reconnect timeout after %v", timeout) } // Close — go-ios DeviceEntry hat keinen expliziten Close, alles wird per-call connected. func (c *Conn) Close() error { return nil }