package info import ( "bytes" "compress/gzip" _ "embed" "encoding/json" "fmt" "regexp" "strings" "github.com/apex/log" "github.com/blacktop/ipsw/internal/utils" "github.com/blacktop/ipsw/pkg/ota/types" "github.com/blacktop/ipsw/pkg/xcode" "github.com/fatih/color" ) //go:embed data/ipsw_db.gz var ipswDbData []byte type Board struct { CPU string `json:"cpu,omitempty"` Platform string `json:"platform,omitempty"` PlatformName string `json:"platform_name,omitempty"` ChipID string `json:"cpuid,omitempty"` Arch string `json:"arch,omitempty"` CpuISA string `json:"cpuisa,omitempty"` BoardID string `json:"board_id,omitempty"` BasebandChipID string `json:"bbid,omitempty"` KernelCacheType string `json:"kc_type,omitempty"` ResearchSupported bool `json:"research_support,omitempty"` } type Device struct { Name string `json:"name,omitempty"` Product string `json:"product,omitempty"` Description string `json:"desc,omitempty"` Boards map[string]Board `json:"boards,omitempty"` MemClass uint64 `json:"mem_class,omitempty"` SDKPlatform string `json:"sdk,omitempty"` Type string `json:"type,omitempty"` } type Devices map[string]Device var colorName = color.New(color.Bold).SprintFunc() var colorBoard = color.New(color.Bold, color.FgHiMagenta).SprintFunc() var colorField = color.New(color.Bold, color.FgHiBlue).SprintFunc() func (d Device) String() string { var sb strings.Builder sb.WriteString("\n" + colorName(d.Name) + "\n") if len(d.Description) > 0 { sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Description"), d.Description)) } if len(d.Product) > 0 { sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Prod"), d.Product)) } sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Type"), d.Type)) sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("SDK"), d.SDKPlatform)) sb.WriteString(fmt.Sprintf(" %s: %d\n", colorField("Memory Class"), d.MemClass)) sb.WriteString(fmt.Sprintf(" %s:\n", colorField("Boards"))) for board, b := range d.Boards { sb.WriteString(fmt.Sprintf(" %s:\n", colorBoard(board))) sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("CPU"), b.CPU)) sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("CPU ISA"), b.CpuISA)) sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Chip ID"), b.ChipID)) sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Platform"), b.Platform)) sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Platform Name"), b.PlatformName)) sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Arch"), b.Arch)) sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Board ID"), b.BoardID)) sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Baseband Chip ID"), b.BasebandChipID)) sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Kernel Cache Type"), b.KernelCacheType)) if b.ResearchSupported { sb.WriteString(fmt.Sprintf(" %s: %t\n", colorField("Research Supported"), b.ResearchSupported)) } } return sb.String() } type DeviceQuery struct { Name string Prod string Model string Board string CPU string Platform string CPID string BDID string } func GetIpswDB() (*Devices, error) { var db Devices zr, err := gzip.NewReader(bytes.NewReader(ipswDbData)) if err != nil { return nil, err } defer zr.Close() if err := json.NewDecoder(zr).Decode(&db); err != nil { return nil, fmt.Errorf("failed unmarshaling ipsw_db data: %w", err) } return &db, nil } func (ds Devices) Query(q *DeviceQuery) *Devices { db := make(Devices) for prod, dev := range ds { dev.Product = prod if strings.EqualFold(dev.Name, "iFPGA") || strings.HasSuffix(strings.ToLower(dev.Name), "sim") || strings.HasSuffix(strings.ToLower(dev.Name), "xxx") || strings.HasSuffix(strings.ToLower(dev.Name), "ref") { continue } if len(dev.Type) == 0 || strings.EqualFold(dev.Type, "unknown") { continue } // Name if len(q.Name) > 0 { // Try regex matching first if re, err := regexp.Compile("(?i)" + q.Name); err == nil { if re.MatchString(dev.Name) || re.MatchString(dev.Description) { db[prod] = dev goto next } } else { // Fallback to exact matching if regex is invalid if strings.EqualFold(dev.Name, q.Name) || strings.EqualFold(dev.Description, q.Name) { db[prod] = dev goto next } } } // Prod if len(q.Prod) > 0 { if re, err := regexp.Compile("(?i)" + q.Prod); err == nil { if re.MatchString(prod) { db[prod] = dev goto next } } else { if strings.EqualFold(prod, q.Prod) { db[prod] = dev goto next } } } // Model if len(q.Model) > 0 { if re, err := regexp.Compile("(?i)" + q.Model); err == nil { for m := range dev.Boards { if re.MatchString(m) { db[prod] = dev goto next } } } else { for m := range dev.Boards { if strings.EqualFold(m, q.Model) { db[prod] = dev goto next } } } } // Board if len(q.Board) > 0 { if re, err := regexp.Compile("(?i)" + q.Board); err == nil { for id := range dev.Boards { if re.MatchString(id) { db[prod] = dev goto next } } } else { for id := range dev.Boards { if strings.EqualFold(id, q.Board) { db[prod] = dev goto next } } } } // CPU if len(q.CPU) > 0 { if re, err := regexp.Compile("(?i)" + q.CPU); err == nil { for _, b := range dev.Boards { if re.MatchString(b.CPU) { db[prod] = dev goto next } } } else { for _, b := range dev.Boards { if strings.EqualFold(b.CPU, q.CPU) { db[prod] = dev goto next } } } } // Platform if len(q.Platform) > 0 { if re, err := regexp.Compile("(?i)" + q.Platform); err == nil { for _, b := range dev.Boards { if re.MatchString(b.Platform) { db[prod] = dev goto next } } } else { for _, b := range dev.Boards { if strings.EqualFold(b.Platform, q.Platform) { db[prod] = dev goto next } } } } // CPID if len(q.CPID) > 0 { if re, err := regexp.Compile("(?i)" + q.CPID); err == nil { for _, b := range dev.Boards { if re.MatchString(b.ChipID) { db[prod] = dev goto next } } } else { for _, b := range dev.Boards { if strings.EqualFold(b.ChipID, q.CPID) { db[prod] = dev goto next } } } } // BDID if len(q.BDID) > 0 { if re, err := regexp.Compile("(?i)" + q.BDID); err == nil { for _, b := range dev.Boards { if re.MatchString(b.BoardID) { db[prod] = dev goto next } } } else { for _, b := range dev.Boards { if strings.EqualFold(b.BoardID, q.BDID) { db[prod] = dev goto next } } } } next: } return &db } func (ds Devices) LookupDevice(prod string) (Device, error) { if d, ok := ds[prod]; ok { return d, nil } return Device{}, fmt.Errorf("device %s not found", prod) } func (ds Devices) GetProductForModel(model string) (string, error) { for prod, dev := range ds { for m := range dev.Boards { if strings.EqualFold(m, model) { return prod, nil } } } return "", fmt.Errorf("model %s not found", model) } func (ds Devices) GetDevicesForType(typ string) (*Devices, error) { devs := make(Devices) for prod, dev := range ds { if dev.Type == typ { devs[prod] = dev } } return &devs, nil } func (ds Devices) GetDeviceForName(name string) (string, Device, error) { for prod, dev := range ds { if strings.EqualFold(dev.Name, name) || strings.EqualFold(dev.Description, name) { return prod, dev, nil } } return "", Device{}, fmt.Errorf("device not found with name %s", name) } func (ds Devices) GetDevicesForSDK(sdk string) (*Devices, error) { devs := make(Devices) for prod, dev := range ds { if dev.SDKPlatform == sdk { devs[prod] = dev } } return &devs, nil } func (i *Info) GetDevices(devs *Devices) error { if len(i.DeviceTrees) > 0 { for _, dtree := range i.DeviceTrees { dt, err := dtree.Summary() if err != nil { return fmt.Errorf("error getting device tree summary: %v", err) } var kctype string if kcs := i.Plists.BuildManifest.GetKernelForModel(strings.ToLower(dt.BoardConfig)); kcs != nil { if len(kcs) == 1 { kctype = kcs[0][strings.LastIndex(kcs[0], ".")+1:] } else { kctype = kcs[0][strings.LastIndex(kcs[0], ".")+1:] // FIXME: what? } } xdev, err := xcode.GetDeviceForProd(dt.ProductType) if err != nil { xdev = &xcode.Device{} log.Errorf("error getting device %s in xcode device list: %v", dt.ProductType, err) // return fmt.Errorf("error getting device %s in xcode device list: %v", dt.ProductType, err) } devType := "unknown" devSDK := "unknown" switch { case strings.HasPrefix(dt.ProductType, "iPod"): fallthrough case strings.HasPrefix(dt.ProductType, "iPad"): fallthrough case strings.HasPrefix(dt.ProductType, "iPhone"): devType = "ios" devSDK = "iphoneos" case strings.HasPrefix(dt.ProductType, "Watch"): devType = "watchos" devSDK = "watchos" case strings.HasPrefix(dt.ProductType, "AudioAccessory"): devType = "audioos" devSDK = "appletvos" case strings.HasPrefix(dt.ProductType, "AppleTV"): devType = "tvos" devSDK = "appletvos" case strings.HasPrefix(dt.ProductType, "Mac"): devType = "macos" devSDK = "macosx" case strings.HasPrefix(dt.ProductType, "AppleDisplay"): devType = "accessory" devSDK = "iphoneos" } if len(dt.ProductType) > 0 { if _, ok := (*devs)[dt.ProductType]; !ok { if dt.ProductName != dt.ProductDescription { (*devs)[dt.ProductType] = Device{ Name: dt.ProductName, Description: dt.ProductDescription, Boards: make(map[string]Board), MemClass: uint64(xdev.DeviceTrait.DevicePerformanceMemoryClass), Type: devType, SDKPlatform: devSDK, } } else { (*devs)[dt.ProductType] = Device{ Name: dt.ProductName, Boards: make(map[string]Board), MemClass: uint64(xdev.DeviceTrait.DevicePerformanceMemoryClass), Type: devType, SDKPlatform: devSDK, } } } } if i.Plists.Restore != nil && i.Plists.BuildManifest != nil { if dev := i.Plists.GetDeviceForBoardConfig(dt.BoardConfig); dev != nil { proc := resolveProcessor(dev.Platform, fmt.Sprintf("%s/%s", dt.ProductType, dt.BoardConfig)) (*devs)[dt.ProductType].Boards[dt.BoardConfig] = Board{ CPU: proc.Name, Platform: dev.Platform, PlatformName: dt.SocGeneration, ChipID: fmt.Sprintf("%#x", dev.CPID), CpuISA: proc.CPUISA, Arch: xdev.DeviceTrait.PreferredArchitecture, BoardID: fmt.Sprintf("%#x", dev.BDID), BasebandChipID: i.Plists.BuildManifest.BuildIdentities[0].BbChipID, KernelCacheType: kctype, // ResearchSupported: d.ResearchSupported, } } } else { return fmt.Errorf("no restore/BuildManifest plist found for %s", dt.ProductType) } } } else { if i.Plists.Restore != nil { var prodType string var prodName string var arch string var memClass uint64 if len(i.Plists.Restore.SupportedProductTypes) == 1 { prodType = i.Plists.Restore.SupportedProductTypes[0] } else { prodType = i.Plists.Restore.SupportedProductTypes[0] log.Error("BAD ASSUMPTIONS: multiple product types in restore plist") } kcs := i.Plists.BuildManifest.GetKernelCaches() for _, dev := range i.Plists.Restore.DeviceMap { if xdev, err := xcode.GetDeviceForProd(prodType); err == nil { prodName = xdev.ProductDescription arch = xdev.DeviceTrait.PreferredArchitecture memClass = uint64(xdev.DeviceTrait.DevicePerformanceMemoryClass) } if len(prodName) == 0 { if i.Plists.OTAInfo != nil { prodName = "FIXME - " + i.Plists.OTAInfo.MobileAssetProperties.DeviceName } else { prodName = "FIXME" } } if _, ok := (*devs)[prodType]; !ok { proc := resolveProcessor(dev.Platform, fmt.Sprintf("%s/%s", prodType, strings.ToUpper(dev.BoardConfig))) (*devs)[prodType] = Device{ Name: prodName, Boards: map[string]Board{ strings.ToUpper(dev.BoardConfig): { CPU: proc.Name, Platform: dev.Platform, // PlatformName: d.PlatformName, ChipID: i.Plists.BuildManifest.BuildIdentities[0].ApChipID, CpuISA: proc.CPUISA, Arch: arch, BoardID: i.Plists.BuildManifest.BuildIdentities[0].ApBoardID, BasebandChipID: i.Plists.BuildManifest.BuildIdentities[0].BbChipID, KernelCacheType: kcs[dev.BoardConfig][0][strings.LastIndex(kcs[dev.BoardConfig][0], ".")+1:], // ResearchSupported: d.ResearchSupported, }, }, MemClass: memClass, } } } } else { panic("unsupported IPSW/OTA type") } } return nil } func mLBTypeToBoardConfig(bc string, mlbType string) string { return mlbType + strings.ToUpper(strings.TrimPrefix(bc, strings.ToLower(mlbType))) } func resolveProcessor(cpuid, context string) processors { proc, err := getProcessor(cpuid) if err != nil { log.WithError(err).WithFields(log.Fields{ "cpuid": cpuid, "context": context, }).Warn("missing processor metadata; using CPU ID fallback") return processors{Name: cpuid} } if len(proc.Name) == 0 { log.WithFields(log.Fields{ "cpuid": cpuid, "context": context, }).Warn("processor metadata missing name; using CPU ID fallback") proc.Name = cpuid } return proc } func (i *Info) GetDevicesFromMap(dmap *types.DeviceMap, devs *Devices) error { for bc, d := range *dmap { if len(d.ProductType) > 0 { if _, ok := (*devs)[d.ProductType]; !ok { var err error var memClass uint64 if len(d.DevicePerformanceMemoryClass) > 0 { memClass, err = utils.ConvertStrToInt(d.DevicePerformanceMemoryClass) if err != nil { return fmt.Errorf("failed to parse int: %v", err) } } devType := "unknown" switch { case strings.HasPrefix(d.ProductType, "iPod"): fallthrough case strings.HasPrefix(d.ProductType, "iPad"): fallthrough case strings.HasPrefix(d.ProductType, "iPhone"): devType = "ios" case strings.HasPrefix(d.ProductType, "Watch"): devType = "watchos" case strings.HasPrefix(d.ProductType, "AudioAccessory"): devType = "audioos" case strings.HasPrefix(d.ProductType, "AppleTV") || d.SDKPlatform == "appletvos": devType = "tvos" case strings.HasPrefix(d.ProductType, "Mac") || d.SDKPlatform == "macosx": devType = "macos" case strings.HasPrefix(d.ProductType, "AppleDisplay"): devType = "accessory" } if d.ProductName != d.ProductDescription { (*devs)[d.ProductType] = Device{ Name: d.ProductName, Description: d.ProductDescription, Boards: make(map[string]Board), MemClass: memClass, SDKPlatform: d.SDKPlatform, Type: devType, } } else { (*devs)[d.ProductType] = Device{ Name: d.ProductName, Boards: make(map[string]Board), MemClass: memClass, SDKPlatform: d.SDKPlatform, Type: devType, } } } proc := resolveProcessor(d.Platform, fmt.Sprintf("%s/%s", d.ProductType, bc)) (*devs)[d.ProductType].Boards[mLBTypeToBoardConfig(bc, d.MLBType)] = Board{ CPU: proc.Name, Platform: d.Platform, PlatformName: d.PlatformName, ChipID: d.ChipID, CpuISA: proc.CPUISA, Arch: d.KernelMachOArchitecture, BoardID: d.BoardID, BasebandChipID: d.BasebandChipID, KernelCacheType: d.KernelCacheType, ResearchSupported: d.ResearchSupported, } } else { log.Debugf("Board %s has no product type", bc) } } return nil }