# How ipatool's Layered Architecture Separates CLI Commands, App Store API Client, and SAP Internals

> Discover ipatool's layered architecture separating CLI commands, App Store API client, and SAP internals. Learn how this structure enhances loose coupling and testability for robust development.

- Repository: [Majd/ipatool](https://github.com/majd/ipatool)
- Tags: architecture
- Published: 2026-09-06

---

**ipatool implements a strict three-tier architecture that isolates user-facing CLI commands in `cmd/`, HTTP business logic in `pkg/appstore/`, and low-level binary manipulation in `internal/sap/`, enabling loose coupling and testability across the codebase.**

The open-source tool `majd/ipatool` demonstrates robust software design through its clearly defined layered architecture. By separating concerns between the command-line interface, Apple's App Store communication layer, and the low-level Software Activation Protocol (SAP) handlers, the codebase maintains high modularity while handling complex IPA download workflows. This structural separation ensures that high-level command logic never directly manipulates binary data, and that low-level SAP internals remain encapsulated from external importers.

## Three Distinct Architectural Layers

### CLI Layer: Command Orchestration and User Input

The **CLI layer** resides in the `cmd/` directory and serves as the sole entry point for user interaction. Built on the Cobra framework, this layer is responsible for parsing arguments, validating flags, and orchestrating execution flow.

In [`cmd/root.go`](https://github.com/majd/ipatool/blob/main/cmd/root.go), the `rootCmd` variable establishes the command tree, defining sub-commands like *download*, *search*, and *purchase*. The layer implements a **dependency injection** pattern through an `initWithCommand` function that constructs a shared `dependencies` struct. This struct contains a logger, an `appstore.Client` instance, and a reference to the SAP runtime (`sap.Unicorn`), which is then stored in the command's `Context` using a private `dependenciesKey`.

Commands themselves remain thin. For example, [`cmd/download.go`](https://github.com/majd/ipatool/blob/main/cmd/download.go) defines `downloadCmd`, which extracts the injected `AppStore` client from the context and invokes high-level methods without concerning itself with HTTP details or binary patching.

### App Store API Client Layer: Business Logic and Network Communication

The **App Store API client layer** lives in `pkg/appstore/` and encapsulates all external communication with Apple's services. The central `AppStore` type (defined in [`pkg/appstore/appstore.go`](https://github.com/majd/ipatool/blob/main/pkg/appstore/appstore.go)) exposes methods including `Download`, `Search`, `Purchase`, and `ListVersions`.

This layer handles HTTP request construction, JSON parsing, authentication token management, and domain-specific error handling. It operates purely on data structures passed from the CLI layer and returns domain-specific structs, maintaining **zero knowledge** of Cobra command objects or terminal output formatting.

When the client requires low-level binary operations—such as decrypting payloads or applying runtime patches during a download—it delegates to the SAP layer through a clean internal API, preserving the architectural boundary.

### SAP Internals Layer: Low-Level Binary Manipulation

The **SAP internals layer** provides the cryptographic and binary manipulation capabilities required to process downloaded IPAs. Located in `internal/sap/`, this code is protected by Go's `internal/` package visibility rules, meaning it cannot be imported by code outside the `majd/ipatool` module.

Key implementations include:

- [`internal/sap/protocol.go`](https://github.com/majd/ipatool/blob/main/internal/sap/protocol.go) – Defines the SAP protocol structures and communication patterns.
- [`internal/sap/unicorn/runtime_patch.go`](https://github.com/majd/ipatool/blob/main/internal/sap/unicorn/runtime_patch.go) – Contains `patchWindowsARM64TCGMasks` and similar functions that manipulate Unicorn emulation library binaries.
- [`internal/sap/unicorn/library_unix.go`](https://github.com/majd/ipatool/blob/main/internal/sap/unicorn/library_unix.go) and platform-specific variants – Provide platform-dependent runtime helpers.

These files handle encrypted payloads, apply binary masks, and manage the Unicorn emulation environment, exposing only clean APIs like `sap.Protocol` and `sap.RuntimePatch` that the App Store client consumes.

## How Data Flows Through the Layers

The interaction between layers follows a strict top-down flow with dependency injection at the entry point:

1. **Entry Point**: [`main.go`](https://github.com/majd/ipatool/blob/main/main.go) calls `cmd.Execute()`, entering the CLI layer.
2. **Initialization**: The first time a command runs, `initWithCommand` builds the `dependencies` struct and injects it into the command context.
3. **Command Execution**: A Cobra command (e.g., `downloadCmd`) extracts the `appstore.Client` from `cmd.Context().Value(dependenciesKey)` and calls `client.Download(bundleID)`.
4. **Client Processing**: Inside [`pkg/appstore/appstore_download.go`](https://github.com/majd/ipatool/blob/main/pkg/appstore/appstore_download.go), the `Client.Download` method performs HTTP calls to fetch download metadata and raw IPA bytes.
5. **SAP Delegation**: When binary patching is required, the client calls SAP helpers such as `sap.Unicorn.PatchWindowsARM64TCGMasks(rawBytes)`.
6. **Binary Processing**: The SAP layer processes the raw byte slices, applies platform-specific patches, and returns the modified data to the client for final file system storage.

## Code Examples: Layer Interaction in Practice

The following examples illustrate the handoff between CLI, App Store client, and SAP layers during a typical download operation.

**CLI Command Handler** ([`cmd/download.go`](https://github.com/majd/ipatool/blob/main/cmd/download.go)):

```go
func downloadCmd() *cobra.Command {
    return &cobra.Command{
        Use:   "download <bundleID>",
        Short: "Download the latest IPA for a given bundle identifier",
        RunE: func(cmd *cobra.Command, args []string) error {
            // Pull the injected AppStore client from the command context
            client := cmd.Context().Value(dependenciesKey).(*Dependencies).AppStore
            // Call the high-level API
            ipaPath, err := client.Download(args[0])
            if err != nil {
                return err
            }
            fmt.Println("IPA saved to:", ipaPath)
            return nil
        },
    }
}

```

**App Store Client Method** ([`pkg/appstore/appstore_download.go`](https://github.com/majd/ipatool/blob/main/pkg/appstore/appstore_download.go)):

```go
func (c *Client) Download(bundleID string) (string, error) {
    // 1️⃣ Request the download URL from Apple
    dlInfo, err := c.fetchDownloadInfo(bundleID)
    if err != nil { return "", err }

    // 2️⃣ Retrieve the raw IPA archive
    rawBytes, err := c.httpGet(dlInfo.URL)
    if err != nil { return "", err }

    // 3️⃣ Let the SAP layer patch the binary (e.g., TCG mask fix)
    if err := sap.Unicorn.PatchWindowsARM64TCGMasks(rawBytes); err != nil {
        return "", err
    }

    // 4️⃣ Write the final file and return the path
    outPath := filepath.Join(c.cfg.DownloadDir, bundleID+".ipa")
    if err := os.WriteFile(outPath, rawBytes, 0644); err != nil {
        return "", err
    }
    return outPath, nil
}

```

**SAP Runtime Patcher** ([`internal/sap/unicorn/runtime_patch.go`](https://github.com/majd/ipatool/blob/main/internal/sap/unicorn/runtime_patch.go)):

```go
func patchWindowsARM64TCGMasks(image []byte) error {
    // Locate known mask patterns and flip the register-width bit
    // (implementation details omitted for brevity)
    return nil
}

```

## Key Files Mapping the Architecture

| Layer | File Path | Responsibility |
|-------|-----------|----------------|
| **CLI** | [`cmd/root.go`](https://github.com/majd/ipatool/blob/main/cmd/root.go) | Sets up the Cobra root command and constructs the shared `dependencies` container. |
| **CLI** | [`cmd/download.go`](https://github.com/majd/ipatool/blob/main/cmd/download.go) | Example command implementation that drives the App Store client. |
| **App Store API** | [`pkg/appstore/appstore.go`](https://github.com/majd/ipatool/blob/main/pkg/appstore/appstore.go) | Central `AppStore` type defining all App Store operations. |
| **SAP Internals** | [`internal/sap/protocol.go`](https://github.com/majd/ipatool/blob/main/internal/sap/protocol.go) | SAP protocol implementation and structures. |
| **SAP Internals** | [`internal/sap/unicorn/runtime_patch.go`](https://github.com/majd/ipatool/blob/main/internal/sap/unicorn/runtime_patch.go) | Platform-specific patching logic for Unicorn binaries. |
| **SAP Internals** | [`internal/sap/unicorn/library_unix.go`](https://github.com/majd/ipatool/blob/main/internal/sap/unicorn/library_unix.go) | Unix-specific Unicorn runtime helpers. |

## Summary

- **CLI Layer** (`cmd/`): Handles argument parsing and user interaction exclusively, delegating all business logic to the App Store client through dependency injection.
- **App Store API Client** (`pkg/appstore/`): Manages all HTTP communication, authentication, and high-level business rules, interacting with SAP only through well-defined internal APIs.
- **SAP Internals** (`internal/sap/`): Performs low-level binary manipulation and cryptographic operations, protected by Go's `internal/` visibility to prevent external coupling.
- **Architectural Benefits**: This separation enables **loose coupling** (allowing the CLI to be swapped for a GUI), **testability** (enabling mocked HTTP responses and isolated SAP testing), and **encapsulation** (preventing accidental misuse of low-level binary patching).

## Frequently Asked Questions

### Why does ipatool place SAP code in an `internal/` package?

The `internal/sap/` directory utilizes Go's `internal/` package mechanism to enforce **strict encapsulation**. Code outside the `majd/ipatool` module cannot import these packages, ensuring that low-level binary manipulation logic—such as Unicorn runtime patching and cryptographic operations—remains inaccessible to external callers. This prevents accidental misuse and maintains the architectural boundary between business logic and system-level operations.

### How does the CLI layer communicate with the App Store client?

The CLI layer uses **dependency injection via context values**. During initialization in [`cmd/root.go`](https://github.com/majd/ipatool/blob/main/cmd/root.go), the `initWithCommand` function creates a `dependencies` struct containing the `appstore.Client` instance and stores it in the command's `Context` using a private key. Individual commands retrieve this client through `cmd.Context().Value(dependenciesKey)`, allowing them to invoke high-level methods like `Download()` or `Search()` without instantiating clients directly or managing connection state.

### Can the App Store client layer be used independently of the CLI?

Yes, the `pkg/appstore/` package is designed as a **standalone library** with no dependencies on Cobra or terminal I/O. The `AppStore` type accepts configuration through standard Go structs and returns domain-specific types, making it suitable for integration into alternative interfaces such as graphical applications, web services, or automated scripts. The only requirement is satisfying the package's public API contracts for authentication and configuration.

### What is the role of the `dependencies` struct in ipatool's architecture?

The `dependencies` struct acts as a **service container** that wires together the application's core components during the initialization phase. Defined in the `cmd` package, it holds references to the logger, the `appstore.Client`, and the SAP runtime (`sap.Unicorn`). By constructing this container once and injecting it into command contexts, ipatool ensures that all layers share consistent instances of critical resources while maintaining **inversion of control**—commands depend on abstractions (the client interface) rather than concrete implementations.