import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# Predicates

The three predicate methods on `OctetSdk.loc`. All three are `async` (Swift) / `suspend` (Kotlin) and do not throw. A runtime problem produces an `INDETERMINATE` verdict.

## `isWithin`

The primary predicate. Tests whether the device's provable location at `atTime` (default now) is inside `region`.

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
public func isWithin(
    region: OctetRegion,
    atTime: Date = Date(),
    sessionNonce: Data? = nil,
    decisionRef: String? = nil,
    forceFresh: Bool = false,
    content: [ContentItem] = []
) async -> OctetVerdict
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
suspend fun isWithin(
    region: OctetRegion,
    atTime: Instant = Instant.now(),
    sessionNonce: ByteArray? = null,
    decisionRef: String? = null,
    forceFresh: Boolean = false,
    content: List<ContentItem> = emptyList(),
): OctetVerdict
```

  </TabItem>
</Tabs>

### Example

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
let v = await sdk.loc.isWithin(
    region: .country(isoCode: "US"),
    atTime: Date()
)
if v.result == .yes { ship(v.proof!) }
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
val v = sdk.loc.isWithin(
    region = OctetRegion.country("US"),
    atTime = Instant.now()
)
if (v.result == OctetVerdict.Result.YES) ship(v.proof!!)
```

  </TabItem>
</Tabs>

---

## `isOutside`

Tests whether the device's provable location at `atTime` (default now) is outside `region`.

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
public func isOutside(
    region: OctetRegion,
    atTime: Date = Date(),
    sessionNonce: Data? = nil,
    decisionRef: String? = nil,
    forceFresh: Bool = false,
    content: [ContentItem] = []
) async -> OctetVerdict
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
suspend fun isOutside(
    region: OctetRegion,
    atTime: Instant = Instant.now(),
    sessionNonce: ByteArray? = null,
    decisionRef: String? = null,
    forceFresh: Boolean = false,
    content: List<ContentItem> = emptyList(),
): OctetVerdict
```

  </TabItem>
</Tabs>

:::caution

`isOutside` is a separate query, not the negation of `isWithin`. A `YES` from `isOutside` carries a proof that the device is outside `region`, and a verifier checks that proof the same way as any other. An `INDETERMINATE` from `isWithin` does not mean the device is outside. Negating it would merge two outcomes: the device is not inside `region`, and the SDK could not tell.

:::

---

## `contains`

Tests whether the point `center` is within `tol` meters of the device's provable location.

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
public func contains(
    center: LatLon,
    tol: Meters,
    atTime: Date = Date(),
    sessionNonce: Data? = nil,
    decisionRef: String? = nil,
    forceFresh: Bool = false,
    content: [ContentItem] = []
) async -> OctetVerdict
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
suspend fun contains(
    center: LatLon,
    tol: Meters,
    atTime: Instant = Instant.now(),
    sessionNonce: ByteArray? = null,
    decisionRef: String? = null,
    forceFresh: Boolean = false,
    content: List<ContentItem> = emptyList(),
): OctetVerdict
```

  </TabItem>
</Tabs>

Mathematically equivalent to `isWithin(region: .disc(center: center, radiusMeters: tol), atTime:)`. Kept as a distinct verb because the device-centric reading is the natural one for proximity use cases ("am I near the delivery dropoff", "did I arrive at the geofence").

### `PLAUSIBLE` containment

A disc answer, from `contains(...)` or a disc `isWithin(...)`, carries an [assurance tier](/docs/mobile/api-reference/octet-verdict/#the-assurance-tier) on its proof. By default a disc query answers `YES` or `NO` only from a `VERIFIED` proof, and otherwise returns `INDETERMINATE`. New in 3.0: setting `OctetConfig.advanced.acceptPlausibleContainment = true` lets it answer from a `PLAUSIBLE` proof as well. Tight tolerances pass only with a tight location fix.

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
let config = OctetConfig(
    advanced: AdvancedConfig(acceptPlausibleContainment: true))
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
val config = OctetConfig(
    advanced = AdvancedConfig(acceptPlausibleContainment = true))
```

  </TabItem>
</Tabs>

### Custom predicates

`isWithin` is the load-bearing primitive, and `contains` is a one-line wrapper over it. Custom predicates can be built the same way:

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
extension OctetLoc {
    func isNear(_ center: LatLon, _ tol: Meters) async -> OctetVerdict {
        await isWithin(region: .disc(center: center, radiusMeters: tol))
    }
    func isInsideAny(_ regions: [OctetRegion]) async -> OctetVerdict? {
        for r in regions {
            let v = await isWithin(region: r)
            if v.result == .yes { return v }
        }
        return nil
    }
}
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
suspend fun OctetLoc.isNear(center: LatLon, tol: Meters): OctetVerdict =
    isWithin(OctetRegion.disc(center, tol))

suspend fun OctetLoc.isInsideAny(regions: List<OctetRegion>): OctetVerdict? {
    for (r in regions) {
        val v = isWithin(r)
        if (v.result == OctetVerdict.Result.YES) return v
    }
    return null
}
```

  </TabItem>
</Tabs>

## Time semantics

All three predicates take an optional `atTime`. See [Time Semantics](/docs/mobile/concepts/time-semantics/) for the live / historical / future regimes and the per-resolution validity windows. Default is "now". Future times beyond ±2 s come back `INDETERMINATE / FUTURE_TIME`.

## Proof binding and freshness

All three predicates take three optional controls over which proof answers the call:

- **`sessionNonce`** (opaque bytes, 1 to 512) binds the proof to one login. The SDK commits a hash of the nonce inside the signed proof, so a verifier can confirm the proof was made for that login. An empty or over-512-byte nonce returns `INDETERMINATE / INVALID_SESSION_NONCE`.
- **`decisionRef`** (1 to 256 characters) binds the uploaded proof to one authorization decision, tagging the upload envelope with `decision_ref` and `region_ref`. An over-256-character ref returns `INDETERMINATE / INVALID_DECISION_REF`, with no network call. Pass `nil` / `null`, not an empty string, when there is no decision.
- **`forceFresh`** bypasses the freshness buffer and mints a proof for this call, with none of the binding semantics above.

Each of `sessionNonce`, `decisionRef`, and `content` forces a fresh proof. Setting `forceFresh` as well has no additional effect.

```swift
let nonce = try await loginBackend.issueNonce()
let v = await sdk.loc.isWithin(region: .country(isoCode: "US"), sessionNonce: nonce)
if v.result == .yes { send(v.proof!, to: loginBackend) }
```

[Proof Binding](/docs/mobile/concepts/proof-binding/) explains when to use each, and how a bound proof is verified.

## Content

New in 3.1. All three predicates take an optional `content` list. The device signs a commitment to each item into the proof: its key, content type, size, and `SHA256(salt ‖ value)`. Unless you mark an item for upload, the content itself stays on the device. An empty list (the default) adds nothing.

<Tabs groupId="platform">
  <TabItem value="swift" label="Swift (iOS)">

```swift
public struct ContentItem: Sendable, Equatable {
    public let key: String
    public let contentType: String
    public let locator: String?                // where you will keep the content; not signed
    public var salt: Data { get }              // the salt the item is committed with
    public static func bytes(key: String, contentType: String, _ data: Data, salt: Data? = nil,
                             upload: Bool = false, locator: String? = nil) -> ContentItem
    public static func file(key: String, contentType: String, _ url: URL, salt: Data? = nil,
                            upload: Bool = false, locator: String? = nil) -> ContentItem
    public static func digest(key: String, contentType: String, _ digest: ContentDigest,
                              uploadFrom: URL? = nil, uploadSalt: Data? = nil,
                              locator: String? = nil) -> ContentItem
    public static func generateSalt() -> Data
}
public struct ContentDigest: Sendable, Equatable {
    public let entryDigest: Data
    public let size: UInt64
    public let salt: Data
}
public struct ContentError: Error, Sendable, Equatable { public let message: String }

extension Octet {
    public static func hashContent(file: URL, salt: Data? = nil) async throws -> ContentDigest
}
```

  </TabItem>
  <TabItem value="kotlin" label="Kotlin (Android)">

```kotlin
class ContentItem {
    val key: String
    val contentType: String
    val locator: String?                       // where you will keep the content; not signed
    val salt: ByteArray                        // the salt the item is committed with
    companion object {
        fun bytes(key: String, contentType: String, data: ByteArray, salt: ByteArray? = null,
                  upload: Boolean = false, locator: String? = null): ContentItem
        fun file(key: String, contentType: String, file: File, salt: ByteArray? = null,
                 upload: Boolean = false, locator: String? = null): ContentItem
        fun digest(key: String, contentType: String, digest: ContentDigest,
                   uploadFrom: File? = null, uploadSalt: ByteArray? = null,
                   locator: String? = null): ContentItem
        fun generateSalt(): ByteArray
    }
}
class ContentDigest(entryDigest: ByteArray, val size: Long, val salt: ByteArray)
class ContentError(message: String) : Exception(message)

// on Octet:
suspend fun hashContent(file: File, salt: ByteArray? = null): ContentDigest
```

  </TabItem>
</Tabs>

- **`bytes`** hashes a value in memory during the call.
- **`file`** streams a file during the call, up to 64 MiB.
- **`digest`** takes a digest from `Octet.hashContent`, which hashes a file of any size before the call. `hashContent` throws `ContentError` for a bad salt or an unreadable file.
- **`salt`** defaults to a random 32-byte salt that the SDK generates. Read it back from `ContentItem.salt` (or `ContentDigest.salt`) and keep it with the content. Pass an empty value to commit unsalted, or your own salt of 16 or 32 bytes. `generateSalt()` returns 32 random bytes.
- **`upload`** (or `uploadFrom` on a `digest` item) has the SDK upload the content to your own storage after the proof is made. It needs caller content uploads enabled for your license. See [Uploading content](/docs/mobile/concepts/content-commitments/#uploading-content-to-your-storage).
- **`locator`** is an identifier you choose for where the content will be kept, up to 256 bytes. It travels with the proof unsigned and is passed to your upload provider. Never put a credential in it.

Content that breaks a rule (1 to 16 items, unique keys of 1 to 64 bytes, a `type/subtype` content type with no parameters, a valid salt, a readable file of at most 64 MiB) returns `INDETERMINATE / INVALID_CALLER_CONTENT` with no proof. If caller content isn't enabled for your license, a query with `content` returns `INDETERMINATE / CALLER_CONTENT_NOT_ENABLED` with no proof. Without location authorization, a query with `content` returns `INDETERMINATE / PERMISSION_REQUIRED`.

[Content Commitments](/docs/mobile/concepts/content-commitments/) explains what a commitment proves and how a verifier checks it.

## See also

- [`OctetRegion`](/docs/mobile/api-reference/octet-region/). The shapes you can pass.
- [`OctetVerdict`](/docs/mobile/api-reference/octet-verdict/). What you get back.
- [Proof Binding](/docs/mobile/concepts/proof-binding/). Binding a proof to a login or a decision.
- [Content Commitments](/docs/mobile/concepts/content-commitments/). Binding content to a proof.
- [Verdicts](/docs/mobile/concepts/verdicts/). The trichotomy and the `ReasonCode` taxonomy.
