package db import ( "encoding/gob" "fmt" "os" "slices" "github.com/blacktop/ipsw/internal/model" "github.com/pkg/errors" "gorm.io/gorm" ) // Memory is a database that stores data in memory. type Memory struct { IPSWs map[string]*model.Ipsw Path string } // NewInMemory creates a new in-memory database. func NewInMemory(path string) (Database, error) { if path == "" { return nil, errors.New("'path' is required") } return &Memory{ IPSWs: make(map[string]*model.Ipsw), Path: path, }, nil } // Connect connects to the database. func (m *Memory) Connect() error { f, err := os.Open(m.Path) if err != nil { return err } defer f.Close() return gob.NewDecoder(f).Decode(&m.IPSWs) } // Create creates a new entry in the database. // It returns ErrAlreadyExists if the key already exists. func (m *Memory) Create(value any) error { if ipsw, ok := value.(*model.Ipsw); ok { if _, exists := m.IPSWs[ipsw.ID]; exists { return gorm.ErrDuplicatedKey } m.IPSWs[ipsw.ID] = ipsw return nil } return fmt.Errorf("invalid type: %T", value) } // Get returns the IPSW for the given key. // It returns ErrNotFound if the key does not exist. func (m *Memory) Get(id string) (*model.Ipsw, error) { ipsw, exists := m.IPSWs[id] if !exists { return nil, errors.Errorf("no IPSW found with id: %s", id) } return ipsw, nil } // GetIpswByName returns the IPSW for the given name. // It returns ErrNotFound if the key does not exist. func (m *Memory) GetIpswByName(name string) (*model.Ipsw, error) { for _, ipsw := range m.IPSWs { if ipsw.Name == name { return ipsw, nil } } return nil, model.ErrNotFound } // GetIPSW returns the IPSW for the given version, build, and device. // It returns ErrNotFound if the IPSW does not exist. func (m *Memory) GetIPSW(version, build, device string) (*model.Ipsw, error) { for _, ipsw := range m.IPSWs { if ipsw.Version == version && ipsw.BuildID == build { var devs []string for _, dev := range ipsw.Devices { devs = append(devs, dev.Name) } if slices.Contains(devs, device) { return ipsw, nil } } } return nil, model.ErrNotFound } func (m *Memory) GetDSC(uuid string) (*model.DyldSharedCache, error) { for _, ipsw := range m.IPSWs { for _, dyld := range ipsw.DSCs { if dyld.UUID == uuid { return dyld, nil } } } return nil, model.ErrNotFound } func (m *Memory) GetDSCImage(uuid string, addr uint64) (*model.Macho, error) { for _, ipsw := range m.IPSWs { for _, dyld := range ipsw.DSCs { if dyld.UUID == uuid { for _, img := range dyld.Images { if addr >= img.TextStart && addr < img.TextEnd { return img, nil } } } } } return nil, model.ErrNotFound } func (m *Memory) GetMachO(uuid string) (*model.Macho, error) { for _, ipsw := range m.IPSWs { if macho := findMachOByUUID(ipsw, uuid); macho != nil { return macho, nil } } return nil, model.ErrNotFound } func (m *Memory) GetSymbol(uuid string, addr uint64) (*model.Symbol, error) { for _, ipsw := range m.IPSWs { if macho := findMachOByUUID(ipsw, uuid); macho != nil { if sym := findSymbolByAddr(macho.Symbols, addr); sym != nil { return sym, nil } } } return nil, model.ErrNotFound } func (m *Memory) GetSymbols(uuid string) ([]*model.Symbol, error) { for _, ipsw := range m.IPSWs { if macho := findMachOByUUID(ipsw, uuid); macho != nil { return macho.Symbols, nil } } return nil, model.ErrNotFound } func findMachOByUUID(ipsw *model.Ipsw, uuid string) *model.Macho { for _, dyld := range ipsw.DSCs { for _, img := range dyld.Images { if img.UUID == uuid { return img } } } for _, fs := range ipsw.FileSystem { if fs.UUID == uuid { return fs } } for _, kc := range ipsw.Kernels { for _, kext := range kc.Kexts { if kext.UUID == uuid { return kext } } } return nil } func findSymbolByAddr(symbols []*model.Symbol, addr uint64) *model.Symbol { for _, sym := range symbols { if addr >= sym.Start && addr < sym.End { return sym } } return nil } // Set sets the value for the given key. // It overwrites any previous value for that key. func (m *Memory) Save(value any) error { if ipsw, ok := value.(*model.Ipsw); ok { m.IPSWs[ipsw.ID] = ipsw } return nil } func (m *Memory) List(version string) ([]*model.Ipsw, error) { ipsws := []*model.Ipsw{} for _, p := range m.IPSWs { if p.Version == version { ipsws = append(ipsws, p) } } return ipsws, nil } // Delete removes the given key. // It returns ErrNotFound if the key does not exist. func (m *Memory) Delete(id string) error { delete(m.IPSWs, id) return nil } // Close closes the database. // It returns ErrClosed if the database is already closed. func (m *Memory) Close() error { f, err := os.Open(m.Path) if err != nil { return err } defer f.Close() gob.Register([]any{}) gob.Register(map[string]any{}) return gob.NewEncoder(f).Encode(m.IPSWs) }