package icao import ( "database/sql" "embed" "fmt" "io" "os" "strconv" _ "github.com/mattn/go-sqlite3" ) //go:embed icao.db var icaoFS embed.FS // Database handles ICAO address to country lookups type Database struct { db *sql.DB } // CountryInfo represents country information for an aircraft type CountryInfo struct { Country string `json:"country"` CountryCode string `json:"country_code"` Flag string `json:"flag"` } // NewDatabase creates a new ICAO database connection func NewDatabase() (*Database, error) { // Extract embedded database to a temporary file data, err := icaoFS.ReadFile("icao.db") if err != nil { return nil, fmt.Errorf("failed to read embedded ICAO database: %w", err) } // Create temporary file for the database tmpFile, err := os.CreateTemp("", "icao-*.db") if err != nil { return nil, fmt.Errorf("failed to create temporary file: %w", err) } tmpPath := tmpFile.Name() // Write database data to temporary file if _, err := io.WriteString(tmpFile, string(data)); err != nil { tmpFile.Close() os.Remove(tmpPath) return nil, fmt.Errorf("failed to write database to temp file: %w", err) } tmpFile.Close() // Open SQLite database db, err := sql.Open("sqlite3", tmpPath+"?mode=ro") // Read-only mode if err != nil { os.Remove(tmpPath) return nil, fmt.Errorf("failed to open SQLite database: %w", err) } // Test the database connection if err := db.Ping(); err != nil { db.Close() os.Remove(tmpPath) return nil, fmt.Errorf("failed to ping database: %w", err) } return &Database{db: db}, nil } // LookupCountry returns country information for an ICAO address func (d *Database) LookupCountry(icaoHex string) (*CountryInfo, error) { if len(icaoHex) != 6 { return &CountryInfo{ Country: "Unknown", CountryCode: "XX", Flag: "🏳️", }, nil } // Convert hex string to integer icaoInt, err := strconv.ParseInt(icaoHex, 16, 64) if err != nil { return &CountryInfo{ Country: "Unknown", CountryCode: "XX", Flag: "🏳️", }, nil } var country, countryCode, flag string query := ` SELECT country, country_code, flag FROM icao_allocations WHERE ? BETWEEN start_addr AND end_addr LIMIT 1 ` err = d.db.QueryRow(query, icaoInt).Scan(&country, &countryCode, &flag) if err != nil { if err == sql.ErrNoRows { return &CountryInfo{ Country: "Unknown", CountryCode: "XX", Flag: "🏳️", }, nil } return nil, fmt.Errorf("database query failed: %w", err) } return &CountryInfo{ Country: country, CountryCode: countryCode, Flag: flag, }, nil } // Close closes the database connection func (d *Database) Close() error { return d.db.Close() }