├── README.md ├── src ├── main.zig ├── private.zig ├── private │ └── MobileDevice.zig ├── Security.zig └── CoreFoundation.zig ├── shell.nix ├── .github └── workflows │ └── ci.yml ├── LICENSE ├── flake.nix └── flake.lock /README.md: -------------------------------------------------------------------------------- 1 | # ZigKit 2 | 3 | Native Zig bindings for low-level macOS frameworks. 4 | 5 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | pub const CoreFoundation = @import("CoreFoundation.zig"); 2 | pub const Security = @import("Security.zig"); 3 | pub const private = @import("private.zig"); 4 | 5 | const std = @import("std"); 6 | 7 | test { 8 | _ = std.testing.refAllDecls(@This()); 9 | } 10 | -------------------------------------------------------------------------------- /src/private.zig: -------------------------------------------------------------------------------- 1 | //! This file contains wrappers of Apple's private frameworks such as MobileDevice framework. 2 | //! These frameworks are not officially released for public use and therefore are subject 3 | //! to change without notice. 4 | //! Proceed with caution. 5 | 6 | pub const MobileDevice = @import("private/MobileDevice.zig"); 7 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | (import 2 | ( 3 | let 4 | flake-compat = (builtins.fromJSON (builtins.readFile ./flake.lock)).nodes.flake-compat; 5 | in 6 | fetchTarball { 7 | url = "https://github.com/edolstra/flake-compat/archive/${flake-compat.locked.rev}.tar.gz"; 8 | sha256 = flake-compat.locked.narHash; 9 | } 10 | ) 11 | {src = ./.;}) 12 | .shellNix 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: Build and test 8 | runs-on: ${{ matrix.os }}-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | os: [macos] 13 | 14 | steps: 15 | - if: matrix.os == 'windows' 16 | run: git config --global core.autocrlf false 17 | - uses: actions/checkout@v2 18 | with: 19 | submodules: true 20 | - uses: mlugg/setup-zig@v2 21 | with: 22 | version: master 23 | - run: zig fmt --check src 24 | - run: zig build test 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jakub Konka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "ZigKit lib"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | zig.url = "github:mitchellh/zig-overlay"; 8 | zls.url = "github:zigtools/zls"; 9 | 10 | # Used for shell.nix 11 | flake-compat = { 12 | url = "github:edolstra/flake-compat"; 13 | flake = false; 14 | }; 15 | }; 16 | 17 | outputs = 18 | { 19 | self, 20 | nixpkgs, 21 | flake-utils, 22 | ... 23 | }@inputs: 24 | let 25 | overlays = [ 26 | # Other overlays 27 | (final: prev: { 28 | zigpkgs = inputs.zig.packages.${prev.system}; 29 | zlspkgs = inputs.zls.packages.${prev.system}; 30 | }) 31 | ]; 32 | 33 | # Our supported systems are the same supported systems as the Zig binaries 34 | systems = builtins.attrNames inputs.zig.packages; 35 | in 36 | flake-utils.lib.eachSystem systems ( 37 | system: 38 | let 39 | pkgs = import nixpkgs { inherit overlays system; }; 40 | in 41 | rec { 42 | commonInputs = with pkgs; [ zigpkgs.master ] ++ darwinInputs; 43 | 44 | darwinInputs = pkgs.lib.optionals pkgs.stdenv.isDarwin ( 45 | with pkgs; 46 | [ 47 | darwin.apple_sdk.frameworks.Security 48 | darwin.apple_sdk.frameworks.Foundation 49 | ] 50 | ); 51 | 52 | sysroot = pkgs.lib.optionalString pkgs.stdenv.isDarwin "--sysroot $SDKROOT"; 53 | 54 | packages.default = packages.zigkit; 55 | packages.zigkit = pkgs.stdenv.mkDerivation { 56 | name = "ZigKit"; 57 | version = "master"; 58 | src = ./.; 59 | nativeBuildInputs = commonInputs; 60 | dontConfigure = true; 61 | dontInstall = true; 62 | doCheck = false; 63 | buildPhase = '' 64 | mkdir -p .cache 65 | zig build install ${sysroot} -Doptimize=ReleaseSafe --prefix $out --cache-dir $(pwd)/.zig-cache --global-cache-dir $(pwd)/.cache 66 | ''; 67 | }; 68 | 69 | devShells.default = pkgs.mkShell { 70 | buildInputs = commonInputs ++ (with pkgs; [ zlspkgs.default ]); 71 | }; 72 | 73 | # For compatibility with older versions of the `nix` binary 74 | devShell = self.devShells.${system}.default; 75 | } 76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /src/private/MobileDevice.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const assert = std.debug.assert; 3 | const testing = std.testing; 4 | 5 | const Allocator = std.mem.Allocator; 6 | const CoreFoundation = @import("../CoreFoundation.zig"); 7 | const CFArray = CoreFoundation.CFArray; 8 | const CFDictionary = CoreFoundation.CFDictionary; 9 | const CFString = CoreFoundation.CFString; 10 | const CFUrl = CoreFoundation.CFUrl; 11 | 12 | pub const ADNCI_MSG = enum(u8) { 13 | CONNECTED = 1, 14 | DISCONNECTED = 2, 15 | UNKNOWN = 3, 16 | _, 17 | }; 18 | 19 | pub const AMDeviceNotificationCallbackInfo = extern struct { 20 | device: *AMDevice, 21 | msg: u32, 22 | }; 23 | 24 | pub const AMDevice = extern struct { 25 | _0: [16]u8, 26 | device_id: u32, 27 | product_id: u32, 28 | serial: [4]u8, 29 | _1: u32, 30 | _2: [4]u8, 31 | lockdown_conn: u32, 32 | _3: [8]u8, 33 | 34 | pub fn release(self: *AMDevice) void { 35 | AMDeviceRelease(self); 36 | } 37 | 38 | pub fn connect(self: *AMDevice) !void { 39 | if (AMDeviceConnect(self) != 0) { 40 | return error.Failed; 41 | } 42 | } 43 | 44 | pub fn disconnect(self: *AMDevice) !void { 45 | if (AMDeviceDisconnect(self) != 0) { 46 | return error.Failed; 47 | } 48 | } 49 | 50 | pub fn isPaired(self: *AMDevice) bool { 51 | return AMDeviceIsPaired(self) == 1; 52 | } 53 | 54 | pub fn getName(self: *AMDevice, allocator: Allocator) error{OutOfMemory}![]const u8 { 55 | const key = CFString.createWithBytes("DeviceName"); 56 | defer key.release(); 57 | const cfstr = AMDeviceCopyValue(self, null, key); 58 | defer cfstr.release(); 59 | return cfstr.cstr(allocator); 60 | } 61 | 62 | pub fn getInterfaceType(self: *AMDevice) IntefaceType { 63 | return @as(IntefaceType, @enumFromInt(AMDeviceGetInterfaceType(self))); 64 | } 65 | 66 | pub fn secureTransferPath(self: *AMDevice, url: *CFUrl, opts: *CFDictionary, cb: Callback) !void { 67 | if (AMDeviceSecureTransferPath(0, self, url, opts, cb, 0) != 0) { 68 | return error.Failed; 69 | } 70 | } 71 | 72 | pub fn secureInstallApplication(self: *AMDevice, url: *CFUrl, opts: *CFDictionary, cb: Callback) !void { 73 | if (AMDeviceSecureInstallApplication(0, self, url, opts, cb, 0) != 0) { 74 | return error.Failed; 75 | } 76 | } 77 | 78 | pub fn validatePairing(self: *AMDevice) !void { 79 | if (AMDeviceValidatePairing(self) != 0) { 80 | return error.Failed; 81 | } 82 | } 83 | 84 | pub fn startSession(self: *AMDevice) !void { 85 | if (AMDeviceStartSession(self) != 0) { 86 | return error.Failed; 87 | } 88 | } 89 | 90 | pub fn stopSession(self: *AMDevice) !void { 91 | if (AMDeviceStopSession(self) != 0) { 92 | return error.Failed; 93 | } 94 | } 95 | 96 | pub fn installBundle( 97 | self: *AMDevice, 98 | bundle_path: []const u8, 99 | transfer_cb: Callback, 100 | install_cb: Callback, 101 | ) !void { 102 | const rel_url = CFUrl.createWithPath(bundle_path, false); 103 | defer rel_url.release(); 104 | const url = rel_url.copyAbsoluteUrl(); 105 | defer url.release(); 106 | 107 | const keys = &[_]*CFString{CFString.createWithBytes("PackageType")}; 108 | const values = &[_]*CFString{CFString.createWithBytes("Developer")}; 109 | const opts = try CFDictionary.create(CFString, CFString, keys, values); 110 | defer { 111 | for (keys) |key| { 112 | key.release(); 113 | } 114 | for (values) |value| { 115 | value.release(); 116 | } 117 | opts.release(); 118 | } 119 | 120 | try self.secureTransferPath(url, opts, transfer_cb); 121 | 122 | try self.connect(); 123 | defer self.disconnect() catch {}; 124 | 125 | assert(self.isPaired()); 126 | try self.validatePairing(); 127 | 128 | try self.startSession(); 129 | defer self.stopSession() catch {}; 130 | 131 | try self.secureInstallApplication(url, opts, install_cb); 132 | } 133 | 134 | /// Caller owns the returned memory. 135 | pub fn copyDeviceAppUrl(self: *AMDevice, allocator: Allocator, bundle_id: []const u8) !?[]const u8 { 136 | try self.connect(); 137 | defer self.disconnect() catch {}; 138 | 139 | assert(self.isPaired()); 140 | try self.validatePairing(); 141 | 142 | try self.startSession(); 143 | defer self.stopSession() catch {}; 144 | 145 | var maybe_out: ?*CFDictionary = undefined; 146 | defer if (maybe_out) |out| out.release(); 147 | 148 | const value_obj = try CFArray.create(CFString, &[_]*CFString{ 149 | CFString.createWithBytes("CFBundleIdentifier"), 150 | CFString.createWithBytes("Path"), 151 | }); 152 | defer value_obj.release(); 153 | 154 | const values = &[_]*CFArray{value_obj}; 155 | const keys = &[_]*CFString{CFString.createWithBytes("ReturnAttributes")}; 156 | const opts = try CFDictionary.create(CFString, CFArray, keys, values); 157 | defer opts.release(); 158 | 159 | if (AMDeviceLookupApplications(self, opts, &maybe_out) != 0) { 160 | return error.Failed; 161 | } 162 | const out = maybe_out orelse return error.Failed; 163 | 164 | const bid = CFString.createWithBytes(bundle_id); 165 | defer bid.release(); 166 | const raw_app_info = out.getValue(CFString, CFDictionary, bid) orelse return null; 167 | const app_dict = @as(*CFDictionary, @ptrCast(raw_app_info)); 168 | const raw_path = app_dict.getValue(CFString, CFString, CFString.createWithBytes("Path")).?; 169 | const path = @as(*CFString, @ptrCast(raw_path)); 170 | const url = CFUrl.CFURLCreateWithFileSystemPath(null, path, .posix, true); 171 | defer url.release(); 172 | return try url.copyPath(allocator); 173 | } 174 | 175 | pub const Callback = fn (*CFDictionary, c_int) callconv(.c) c_int; 176 | 177 | extern "c" fn AMDeviceRelease(device: *AMDevice) void; 178 | extern "c" fn AMDeviceConnect(device: *AMDevice) c_int; 179 | extern "c" fn AMDeviceDisconnect(device: *AMDevice) c_int; 180 | extern "c" fn AMDeviceIsPaired(device: *AMDevice) c_int; 181 | extern "c" fn AMDeviceValidatePairing(device: *AMDevice) c_int; 182 | extern "c" fn AMDeviceStartSession(device: *AMDevice) c_int; 183 | extern "c" fn AMDeviceStopSession(device: *AMDevice) c_int; 184 | extern "c" fn AMDeviceCopyValue(device: *AMDevice, ?*anyopaque, key: *CFString) *CFString; 185 | extern "c" fn AMDeviceGetInterfaceType(device: *AMDevice) c_int; 186 | extern "c" fn AMDeviceSecureTransferPath( 187 | c_int, 188 | device: *AMDevice, 189 | url: *CFUrl, 190 | opts: *CFDictionary, 191 | cb: Callback, 192 | cbarg: c_int, 193 | ) c_int; 194 | extern "c" fn AMDeviceSecureInstallApplication( 195 | c_int, 196 | device: *AMDevice, 197 | url: *CFUrl, 198 | opts: *CFDictionary, 199 | cb: Callback, 200 | cbarg: c_int, 201 | ) c_int; 202 | extern "c" fn AMDeviceLookupApplications( 203 | device: *AMDevice, 204 | opts: *CFDictionary, 205 | out: *?*CFDictionary, 206 | ) c_int; 207 | }; 208 | 209 | pub const IntefaceType = enum(isize) { 210 | usb = 1, 211 | wifi, 212 | companion, 213 | _, 214 | }; 215 | 216 | pub const AMDeviceNotification = extern struct { 217 | _0: u32, 218 | _1: u32, 219 | _2: u32, 220 | callback: AMDeviceNotificationCallback, 221 | _3: u32, 222 | }; 223 | 224 | pub const AMDeviceNotificationCallback = fn ( 225 | *AMDeviceNotificationCallbackInfo, 226 | ?*anyopaque, 227 | ) callconv(.c) void; 228 | 229 | pub fn subscribe(cb: AMDeviceNotificationCallback, opts: *CFDictionary) !*AMDeviceNotification { 230 | var notify: *AMDeviceNotification = undefined; 231 | if (AMDeviceNotificationSubscribeWithOptions(cb, 0, 0, null, ¬ify, opts) != 0) { 232 | return error.Failed; 233 | } 234 | return notify; 235 | } 236 | 237 | extern "c" fn AMDeviceNotificationSubscribeWithOptions( 238 | callback: AMDeviceNotificationCallback, 239 | u32, 240 | u32, 241 | ?*anyopaque, 242 | notification: **AMDeviceNotification, 243 | options: ?*CFDictionary, 244 | ) c_int; 245 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-compat": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1747046372, 7 | "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", 8 | "owner": "edolstra", 9 | "repo": "flake-compat", 10 | "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "edolstra", 15 | "repo": "flake-compat", 16 | "type": "github" 17 | } 18 | }, 19 | "flake-compat_2": { 20 | "flake": false, 21 | "locked": { 22 | "lastModified": 1696426674, 23 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 24 | "owner": "edolstra", 25 | "repo": "flake-compat", 26 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 27 | "type": "github" 28 | }, 29 | "original": { 30 | "owner": "edolstra", 31 | "repo": "flake-compat", 32 | "type": "github" 33 | } 34 | }, 35 | "flake-compat_3": { 36 | "flake": false, 37 | "locked": { 38 | "lastModified": 1696426674, 39 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 40 | "owner": "edolstra", 41 | "repo": "flake-compat", 42 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 43 | "type": "github" 44 | }, 45 | "original": { 46 | "owner": "edolstra", 47 | "repo": "flake-compat", 48 | "type": "github" 49 | } 50 | }, 51 | "flake-utils": { 52 | "inputs": { 53 | "systems": "systems" 54 | }, 55 | "locked": { 56 | "lastModified": 1731533236, 57 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 58 | "owner": "numtide", 59 | "repo": "flake-utils", 60 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 61 | "type": "github" 62 | }, 63 | "original": { 64 | "owner": "numtide", 65 | "repo": "flake-utils", 66 | "type": "github" 67 | } 68 | }, 69 | "flake-utils_2": { 70 | "inputs": { 71 | "systems": "systems_2" 72 | }, 73 | "locked": { 74 | "lastModified": 1705309234, 75 | "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", 76 | "owner": "numtide", 77 | "repo": "flake-utils", 78 | "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", 79 | "type": "github" 80 | }, 81 | "original": { 82 | "owner": "numtide", 83 | "repo": "flake-utils", 84 | "type": "github" 85 | } 86 | }, 87 | "flake-utils_3": { 88 | "inputs": { 89 | "systems": "systems_3" 90 | }, 91 | "locked": { 92 | "lastModified": 1705309234, 93 | "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", 94 | "owner": "numtide", 95 | "repo": "flake-utils", 96 | "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", 97 | "type": "github" 98 | }, 99 | "original": { 100 | "owner": "numtide", 101 | "repo": "flake-utils", 102 | "type": "github" 103 | } 104 | }, 105 | "gitignore": { 106 | "inputs": { 107 | "nixpkgs": [ 108 | "zls", 109 | "nixpkgs" 110 | ] 111 | }, 112 | "locked": { 113 | "lastModified": 1709087332, 114 | "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", 115 | "owner": "hercules-ci", 116 | "repo": "gitignore.nix", 117 | "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", 118 | "type": "github" 119 | }, 120 | "original": { 121 | "owner": "hercules-ci", 122 | "repo": "gitignore.nix", 123 | "type": "github" 124 | } 125 | }, 126 | "nixpkgs": { 127 | "locked": { 128 | "lastModified": 1751274312, 129 | "narHash": "sha256-/bVBlRpECLVzjV19t5KMdMFWSwKLtb5RyXdjz3LJT+g=", 130 | "owner": "nixos", 131 | "repo": "nixpkgs", 132 | "rev": "50ab793786d9de88ee30ec4e4c24fb4236fc2674", 133 | "type": "github" 134 | }, 135 | "original": { 136 | "owner": "nixos", 137 | "ref": "nixos-24.11", 138 | "repo": "nixpkgs", 139 | "type": "github" 140 | } 141 | }, 142 | "nixpkgs_2": { 143 | "locked": { 144 | "lastModified": 1708161998, 145 | "narHash": "sha256-6KnemmUorCvlcAvGziFosAVkrlWZGIc6UNT9GUYr0jQ=", 146 | "owner": "NixOS", 147 | "repo": "nixpkgs", 148 | "rev": "84d981bae8b5e783b3b548de505b22880559515f", 149 | "type": "github" 150 | }, 151 | "original": { 152 | "owner": "NixOS", 153 | "ref": "nixos-23.11", 154 | "repo": "nixpkgs", 155 | "type": "github" 156 | } 157 | }, 158 | "nixpkgs_3": { 159 | "locked": { 160 | "lastModified": 1753345091, 161 | "narHash": "sha256-CdX2Rtvp5I8HGu9swBmYuq+ILwRxpXdJwlpg8jvN4tU=", 162 | "owner": "NixOS", 163 | "repo": "nixpkgs", 164 | "rev": "3ff0e34b1383648053bba8ed03f201d3466f90c9", 165 | "type": "github" 166 | }, 167 | "original": { 168 | "owner": "NixOS", 169 | "ref": "nixos-25.05", 170 | "repo": "nixpkgs", 171 | "type": "github" 172 | } 173 | }, 174 | "root": { 175 | "inputs": { 176 | "flake-compat": "flake-compat", 177 | "flake-utils": "flake-utils", 178 | "nixpkgs": "nixpkgs", 179 | "zig": "zig", 180 | "zls": "zls" 181 | } 182 | }, 183 | "systems": { 184 | "locked": { 185 | "lastModified": 1681028828, 186 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 187 | "owner": "nix-systems", 188 | "repo": "default", 189 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 190 | "type": "github" 191 | }, 192 | "original": { 193 | "owner": "nix-systems", 194 | "repo": "default", 195 | "type": "github" 196 | } 197 | }, 198 | "systems_2": { 199 | "locked": { 200 | "lastModified": 1681028828, 201 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 202 | "owner": "nix-systems", 203 | "repo": "default", 204 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 205 | "type": "github" 206 | }, 207 | "original": { 208 | "owner": "nix-systems", 209 | "repo": "default", 210 | "type": "github" 211 | } 212 | }, 213 | "systems_3": { 214 | "locked": { 215 | "lastModified": 1681028828, 216 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 217 | "owner": "nix-systems", 218 | "repo": "default", 219 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 220 | "type": "github" 221 | }, 222 | "original": { 223 | "owner": "nix-systems", 224 | "repo": "default", 225 | "type": "github" 226 | } 227 | }, 228 | "zig": { 229 | "inputs": { 230 | "flake-compat": "flake-compat_2", 231 | "flake-utils": "flake-utils_2", 232 | "nixpkgs": "nixpkgs_2" 233 | }, 234 | "locked": { 235 | "lastModified": 1753663230, 236 | "narHash": "sha256-24um6FSyprsjp7RLhFQKUasyg50NP04ykrTvOdwylkQ=", 237 | "owner": "mitchellh", 238 | "repo": "zig-overlay", 239 | "rev": "b680835e0e74de69f4a195dd25d8affd953d7b31", 240 | "type": "github" 241 | }, 242 | "original": { 243 | "owner": "mitchellh", 244 | "repo": "zig-overlay", 245 | "type": "github" 246 | } 247 | }, 248 | "zig-overlay": { 249 | "inputs": { 250 | "flake-compat": "flake-compat_3", 251 | "flake-utils": "flake-utils_3", 252 | "nixpkgs": [ 253 | "zls", 254 | "nixpkgs" 255 | ] 256 | }, 257 | "locked": { 258 | "lastModified": 1753445652, 259 | "narHash": "sha256-3ICoa7kfkLjDXUMBAcvc2py/8QKoY+cjqfKwO2g7CMY=", 260 | "owner": "mitchellh", 261 | "repo": "zig-overlay", 262 | "rev": "153f0fc99e985f32eb3f0f3a4aabbedd474d2df0", 263 | "type": "github" 264 | }, 265 | "original": { 266 | "owner": "mitchellh", 267 | "repo": "zig-overlay", 268 | "type": "github" 269 | } 270 | }, 271 | "zls": { 272 | "inputs": { 273 | "gitignore": "gitignore", 274 | "nixpkgs": "nixpkgs_3", 275 | "zig-overlay": "zig-overlay" 276 | }, 277 | "locked": { 278 | "lastModified": 1753492458, 279 | "narHash": "sha256-x3gf+ISc+MtQ03Nt6BZopMQFiWf3Wn1R179VbxshFoo=", 280 | "owner": "zigtools", 281 | "repo": "zls", 282 | "rev": "2765a6b7250b034272171b70a41db67d2700ed19", 283 | "type": "github" 284 | }, 285 | "original": { 286 | "owner": "zigtools", 287 | "repo": "zls", 288 | "type": "github" 289 | } 290 | } 291 | }, 292 | "root": "root", 293 | "version": 7 294 | } 295 | -------------------------------------------------------------------------------- /src/Security.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const testing = std.testing; 4 | 5 | const Allocator = mem.Allocator; 6 | const CoreFoundation = @import("CoreFoundation.zig"); 7 | const CFData = CoreFoundation.CFData; 8 | const CFRelease = CoreFoundation.CFRelease; 9 | const CFString = CoreFoundation.CFString; 10 | 11 | /// Wraps CMSEncoderRef type. 12 | pub const CMSEncoder = opaque { 13 | pub fn create() !*CMSEncoder { 14 | var encoder: *CMSEncoder = undefined; 15 | if (CMSEncoderCreate(&encoder) != 0) { 16 | return error.Failed; 17 | } 18 | return encoder; 19 | } 20 | 21 | pub fn release(self: *CMSEncoder) void { 22 | CFRelease(self); 23 | } 24 | 25 | pub fn addSigner(self: *CMSEncoder, signer: *SecIdentity) !void { 26 | if (CMSEncoderAddSigners(self, signer) != 0) { 27 | return error.Failed; 28 | } 29 | } 30 | 31 | pub fn setSignerAlgorithm(self: *CMSEncoder, alg: SignerAlgorithm) !void { 32 | const res = switch (alg) { 33 | .sha256 => CMSEncoderSetSignerAlgorithm(self, kCMSEncoderDigestAlgorithmSHA256), 34 | }; 35 | if (res != 0) { 36 | return error.Failed; 37 | } 38 | } 39 | 40 | pub fn setCertificateChainMode(self: *CMSEncoder, mode: CertificateChainMode) !void { 41 | if (CMSEncoderSetCertificateChainMode(self, mode) != 0) { 42 | return error.Failed; 43 | } 44 | } 45 | 46 | pub fn setHasDetachedContent(self: *CMSEncoder, value: bool) !void { 47 | if (CMSEncoderSetHasDetachedContent(self, value) != 0) { 48 | return error.Failed; 49 | } 50 | } 51 | 52 | pub fn updateContent(self: *CMSEncoder, content: []const u8) !void { 53 | if (CMSEncoderUpdateContent(self, content.ptr, content.len) != 0) { 54 | return error.Failed; 55 | } 56 | } 57 | 58 | pub fn finalize(self: *CMSEncoder) !*CFData { 59 | var out: *CFData = undefined; 60 | if (CMSEncoderCopyEncodedContent(self, &out) != 0) { 61 | return error.Failed; 62 | } 63 | return out; 64 | } 65 | 66 | extern "c" var kCMSEncoderDigestAlgorithmSHA256: *CFString; 67 | 68 | extern "c" fn CMSEncoderCreate(**CMSEncoder) c_int; 69 | extern "c" fn CMSEncoderAddSigners(encoder: *CMSEncoder, signer_or_array: *anyopaque) c_int; 70 | extern "c" fn CMSEncoderSetSignerAlgorithm(encoder: *CMSEncoder, digest_alg: *CFString) c_int; 71 | extern "c" fn CMSEncoderSetCertificateChainMode(encoder: *CMSEncoder, chain_mode: CertificateChainMode) c_int; 72 | extern "c" fn CMSEncoderSetHasDetachedContent(encoder: *CMSEncoder, detached_content: bool) c_int; 73 | extern "c" fn CMSEncoderUpdateContent(encoder: *CMSEncoder, content: *const anyopaque, len: usize) c_int; 74 | extern "c" fn CMSEncoderCopyEncodedContent(encoder: *CMSEncoder, out: **CFData) c_int; 75 | }; 76 | 77 | pub const CMSDecoder = opaque { 78 | pub fn create() !*CMSDecoder { 79 | var decoder: *CMSDecoder = undefined; 80 | if (CMSDecoderCreate(&decoder) != 0) { 81 | return error.Failed; 82 | } 83 | return decoder; 84 | } 85 | 86 | pub fn release(self: *CMSDecoder) void { 87 | CFRelease(self); 88 | } 89 | 90 | pub fn updateMessage(self: *CMSDecoder, msg: []const u8) !void { 91 | const res = CMSDecoderUpdateMessage(self, msg.ptr, msg.len); 92 | if (res != 0) { 93 | return error.Failed; 94 | } 95 | } 96 | 97 | pub fn setDetachedContent(self: *CMSDecoder, bytes: []const u8) !void { 98 | const dref = CFData.create(bytes); 99 | defer dref.release(); 100 | 101 | if (CMSDecoderSetDetachedContent(self, dref) != 0) { 102 | return error.Failed; 103 | } 104 | } 105 | 106 | pub fn finalizeMessage(self: *CMSDecoder) !void { 107 | if (CMSDecoderFinalizeMessage(self) != 0) { 108 | return error.Failed; 109 | } 110 | } 111 | 112 | pub fn getNumSigners(self: *CMSDecoder) !usize { 113 | var out: usize = undefined; 114 | if (CMSDecoderGetNumSigners(self, &out) != 0) { 115 | return error.Failed; 116 | } 117 | return out; 118 | } 119 | 120 | pub fn signerEmailAddress(self: *CMSDecoder, allocator: Allocator, index: usize) ![]const u8 { 121 | var ref: ?*CFString = null; 122 | if (ref) |r| r.release(); 123 | const res = CMSDecoderCopySignerEmailAddress(self, index, &ref); 124 | if (res != 0) { 125 | return error.Failed; 126 | } 127 | return ref.?.cstr(allocator); 128 | } 129 | 130 | pub fn copyDetachedContent(self: *CMSDecoder) !?*CFData { 131 | var out: ?*CFData = null; 132 | const res = CMSDecoderCopyDetachedContent(self, &out); 133 | if (res != 0) { 134 | return error.Failed; 135 | } 136 | return out; 137 | } 138 | 139 | pub fn copyContent(self: *CMSDecoder) !?*CFData { 140 | var out: ?*CFData = null; 141 | const res = CMSDecoderCopyContent(self, &out); 142 | if (res != 0) { 143 | return error.Failed; 144 | } 145 | return out; 146 | } 147 | 148 | pub fn getSignerStatus(self: *CMSDecoder, index: usize) !CMSSignerStatus { 149 | const policy = SecPolicy.createiPhoneProfileApplicationSigning(); 150 | defer policy.release(); 151 | 152 | var status: CMSSignerStatus = undefined; 153 | if (CMSDecoderCopySignerStatus(self, index, policy, false, &status, null, null) != 0) { 154 | return error.Failed; 155 | } 156 | return status; 157 | } 158 | 159 | extern "c" fn CMSDecoderCreate(**CMSDecoder) c_int; 160 | extern "c" fn CMSDecoderSetDetachedContent(decoder: *CMSDecoder, detached_content: *CFData) c_int; 161 | extern "c" fn CMSDecoderUpdateMessage( 162 | decoder: *CMSDecoder, 163 | msg_bytes: *const anyopaque, 164 | msg_len: usize, 165 | ) c_int; 166 | extern "c" fn CMSDecoderFinalizeMessage(decoder: *CMSDecoder) c_int; 167 | extern "c" fn CMSDecoderGetNumSigners(decoder: *CMSDecoder, out: *usize) c_int; 168 | extern "c" fn CMSDecoderCopyDetachedContent(decoder: *CMSDecoder, out: *?*CFData) c_int; 169 | extern "c" fn CMSDecoderCopyContent(decoder: *CMSDecoder, out: *?*CFData) c_int; 170 | extern "c" fn CMSDecoderCopySignerEmailAddress( 171 | decoder: *CMSDecoder, 172 | index: usize, 173 | out: *?*CFString, 174 | ) c_int; 175 | extern "c" fn CMSDecoderCopySignerStatus( 176 | decoder: *CMSDecoder, 177 | index: usize, 178 | policy_or_array: *const anyopaque, 179 | eval_sec_trust: bool, 180 | out_status: *CMSSignerStatus, 181 | out_trust: ?*anyopaque, 182 | out_cert_verify_code: ?*c_int, 183 | ) c_int; 184 | }; 185 | 186 | pub const SecPolicy = opaque { 187 | pub fn createiPhoneApplicationSigning() *SecPolicy { 188 | return SecPolicyCreateiPhoneApplicationSigning(); 189 | } 190 | 191 | pub fn createiPhoneProfileApplicationSigning() *SecPolicy { 192 | return SecPolicyCreateiPhoneProfileApplicationSigning(); 193 | } 194 | 195 | pub fn createMacOSProfileApplicationSigning() *SecPolicy { 196 | return SecPolicyCreateMacOSProfileApplicationSigning(); 197 | } 198 | 199 | pub fn release(self: *SecPolicy) void { 200 | CFRelease(self); 201 | } 202 | 203 | extern "c" fn SecPolicyCreateiPhoneApplicationSigning() *SecPolicy; 204 | extern "c" fn SecPolicyCreateiPhoneProfileApplicationSigning() *SecPolicy; 205 | extern "c" fn SecPolicyCreateMacOSProfileApplicationSigning() *SecPolicy; 206 | }; 207 | 208 | pub const SecCertificate = opaque { 209 | pub fn initWithData(bytes: []const u8) !*SecCertificate { 210 | const data = CFData.create(bytes); 211 | defer data.release(); 212 | 213 | if (SecCertificateCreateWithData(null, data)) |cert| { 214 | return cert; 215 | } else return error.InvalidX509Certificate; 216 | } 217 | 218 | pub fn release(self: *SecCertificate) void { 219 | CFRelease(self); 220 | } 221 | 222 | extern "c" fn SecCertificateCreateWithData(allocator: ?*anyopaque, data: *CFData) ?*SecCertificate; 223 | }; 224 | 225 | pub const SecIdentity = opaque { 226 | pub fn initWithCertificate(cert: *SecCertificate) !*SecIdentity { 227 | var ident: *SecIdentity = undefined; 228 | if (SecIdentityCreateWithCertificate(null, cert, &ident) != 0) { 229 | return error.Failed; 230 | } 231 | return ident; 232 | } 233 | 234 | pub fn release(self: *SecIdentity) void { 235 | CFRelease(self); 236 | } 237 | 238 | extern "c" fn SecIdentityCreateWithCertificate( 239 | keychain_or_array: ?*anyopaque, 240 | cert: *SecCertificate, 241 | ident: **SecIdentity, 242 | ) c_int; 243 | }; 244 | 245 | pub const SignerAlgorithm = enum { 246 | sha256, 247 | }; 248 | 249 | pub const CertificateChainMode = enum(u32) { 250 | none = 0, 251 | signer_only, 252 | chain, 253 | chain_with_root, 254 | chain_with_root_or_fail, 255 | }; 256 | 257 | pub const CMSSignerStatus = enum(u32) { 258 | unsigned = 0, 259 | valid, 260 | needs_detached_content, 261 | invalid_signature, 262 | invalid_cert, 263 | invalid_index, 264 | }; 265 | 266 | test { 267 | _ = testing.refAllDecls(@This()); 268 | _ = testing.refAllDecls(CMSEncoder); 269 | _ = testing.refAllDecls(CMSDecoder); 270 | _ = testing.refAllDecls(SecCertificate); 271 | _ = testing.refAllDecls(SecIdentity); 272 | _ = testing.refAllDecls(SecPolicy); 273 | } 274 | -------------------------------------------------------------------------------- /src/CoreFoundation.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const assert = std.debug.assert; 4 | const mem = std.mem; 5 | const testing = std.testing; 6 | 7 | const Allocator = mem.Allocator; 8 | 9 | // A workaround until stage 2 is shipped 10 | fn getFunctionPointer(comptime function_type: type) type { 11 | return switch (builtin.zig_backend) { 12 | .stage1 => function_type, 13 | else => *const function_type, 14 | }; 15 | } 16 | 17 | // Basic types 18 | pub const CFIndex = c_long; 19 | pub const CFOptionFlags = c_long; 20 | pub const CFTypeID = c_ulong; 21 | pub const CFTimeInterval = f64; // c_double 22 | pub const CFAbsoluteTime = CFTimeInterval; 23 | pub const Boolean = bool; 24 | pub const CFComparisonResult = enum(CFIndex) { 25 | kCFCompareLessThan = -1, 26 | kCFCompareEqualTo = 0, 27 | kCFCompareGreaterThan = 1, 28 | }; 29 | 30 | pub const CFRange = extern struct { 31 | location: CFIndex, 32 | length: CFIndex, 33 | }; 34 | 35 | pub const CFComparatorFunction = fn (*const anyopaque, *const anyopaque, *anyopaque) callconv(.c) CFComparisonResult; 36 | 37 | // A struct to help manage how the allocator callbacks wrap a zig allocator. 38 | // Also used as the underlying type pointed to by the context struct we initialise 39 | // to wrap zig allocators using CFAllocator. 40 | // Because zig allocators work with slices - we have to allocate a little bit of 41 | // extra memory than we normally would to recreate slice lengths. 42 | const ZigAllocatorCallbacks = struct { 43 | pub fn allocateCallback(alloc_size: CFIndex, hint: CFOptionFlags, opaque_info: ?*anyopaque) callconv(.c) ?*anyopaque { 44 | _ = hint; // hint is always unused 45 | const allocator = opaqueInfoToAllocator(opaque_info); 46 | const actual_alloc_size = @sizeOf(usize) + @as(usize, @intCast(alloc_size)); 47 | const bytes = allocator.alignedAlloc(u8, @enumFromInt(@alignOf(usize)), actual_alloc_size) catch { 48 | return null; 49 | }; 50 | return rootSliceToData(bytes); 51 | } 52 | 53 | pub fn deallocateCallback(data: *anyopaque, opaque_info: ?*anyopaque) callconv(.c) void { 54 | const allocator = opaqueInfoToAllocator(opaque_info); 55 | allocator.free(dataToRootSlice(data)); 56 | } 57 | 58 | pub fn reallocateCallback(old_data: *anyopaque, new_size: CFIndex, hint: CFOptionFlags, opaque_info: ?*anyopaque) callconv(.c) ?*anyopaque { 59 | _ = hint; // hint is always unused 60 | const allocator = opaqueInfoToAllocator(opaque_info); 61 | const actual_new_size = @sizeOf(usize) + @as(usize, @intCast(new_size)); 62 | const new_data = allocator.realloc(dataToRootSlice(old_data), actual_new_size) catch { 63 | return null; 64 | }; 65 | return rootSliceToData(new_data); 66 | } 67 | 68 | fn opaqueInfoToAllocator(opaque_info: ?*anyopaque) *const Allocator { 69 | return @as(*const Allocator, @ptrCast(@alignCast(opaque_info.?))); 70 | } 71 | 72 | fn dataToRootSlice(data: *anyopaque) []u8 { 73 | const data_root = @as([*]u8, @ptrCast(data)) - @sizeOf(usize); 74 | const length = mem.bytesToValue(usize, data_root[0..@sizeOf(usize)]); 75 | return data_root[0..length]; 76 | } 77 | 78 | fn rootSliceToData(root_slice: []u8) *anyopaque { 79 | mem.bytesAsValue(usize, root_slice[0..@sizeOf(usize)]).* = root_slice.len; 80 | return @as(*anyopaque, @ptrCast(root_slice[@sizeOf(usize)..])); 81 | } 82 | }; 83 | 84 | /// Wraps the CFAllocatorRef type. 85 | pub const CFAllocator = opaque { 86 | /// Construct a CFAllocator from a zig allocator, the second allocator argument can optionally be 87 | /// if you want to allocator the data used to manager the allocator itself using a different 88 | /// allocator. 89 | /// 90 | /// It is recommended that you construct a CFAllocator from a zig allocator once up-front & re-use it 91 | /// as allocations are needed to construct an allocator... 92 | /// 93 | /// The allocator pointed to must be valid for the lifetime of the CFAllocator. 94 | pub fn createFromZigAllocator(allocator: *const Allocator, allocator_allocator: ?*CFAllocator) !*CFAllocator { 95 | var allocator_context = CFAllocatorContext{ 96 | .version = 0, // the ony valid value 97 | .info = @as(*anyopaque, @ptrFromInt(@intFromPtr(allocator))), 98 | .allocate = ZigAllocatorCallbacks.allocateCallback, 99 | .deallocate = ZigAllocatorCallbacks.deallocateCallback, 100 | .reallocate = ZigAllocatorCallbacks.reallocateCallback, 101 | .preferredSize = null, 102 | .copyDescription = null, 103 | .retain = null, 104 | .release = null, 105 | }; 106 | 107 | if (CFAllocatorCreate(allocator_allocator, &allocator_context)) |cf_allocator| { 108 | return cf_allocator; 109 | } else { 110 | return error.OutOfMemory; 111 | } 112 | } 113 | 114 | extern "C" const kCFAllocatorDefault: ?*CFAllocator; 115 | extern "C" const kCFAllocatorMalloc: *CFAllocator; 116 | extern "C" const kCFAllocatorMallocZone: *CFAllocator; 117 | extern "C" const kCFAllocatorSystemDefault: *CFAllocator; 118 | extern "C" const kCFAllocatorNull: *CFAllocator; 119 | extern "C" const kCFAllocatorUseContext: *CFAllocator; 120 | 121 | extern "c" fn CFAllocatorCreate(allocator: ?*CFAllocator, context: *CFAllocatorContext) ?*CFAllocator; 122 | 123 | extern "C" fn CFAllocatorAllocate(allocator: ?*CFAllocator, size: CFIndex, hint: CFOptionFlags) ?*anyopaque; 124 | extern "C" fn CFAllocatorDeallocate(allocator: ?*CFAllocator, ptr: *anyopaque) void; 125 | extern "C" fn CFAllocatorGetPreferredSizeForSize(allocator: ?*CFAllocator, size: CFIndex, hint: CFOptionFlags) CFIndex; 126 | extern "C" fn CFAllocatorReallocate(allocator: ?*CFAllocator, ptr: ?*anyopaque, newsize: CFIndex, hint: CFOptionFlags) ?*anyopaque; 127 | 128 | extern "C" fn CFAllocatorGetDefault() *CFAllocator; 129 | extern "C" fn CFAllocatorSetDefault(allocator: *CFAllocator) void; 130 | 131 | extern "c" fn CFAllocatorGetContext(allocator: ?*CFAllocator, out_context: *CFAllocatorContext) void; 132 | 133 | extern "C" fn CFAllocatorGetTypeID() CFTypeID; 134 | 135 | pub const CFAllocatorAllocateCallBack = fn (CFIndex, CFOptionFlags, ?*anyopaque) callconv(.c) ?*anyopaque; 136 | pub const CFAllocatorCopyDescriptionCallBack = fn (?*anyopaque) callconv(.c) *CFString; 137 | pub const CFAllocatorDeallocateCallback = fn (*anyopaque, ?*anyopaque) callconv(.c) void; 138 | pub const CFAllocatorPreferredSizeCallBack = fn (CFIndex, CFOptionFlags, ?*anyopaque) callconv(.c) CFIndex; 139 | pub const CFAllocatorReallocateCallBack = fn (*anyopaque, CFIndex, CFOptionFlags, ?*anyopaque) callconv(.c) ?*anyopaque; 140 | pub const CFAllocatorReleaseCallBack = fn (?*anyopaque) callconv(.c) *anyopaque; 141 | pub const CFAllocatorRetainCallBack = fn (?*anyopaque) callconv(.c) *anyopaque; 142 | 143 | /// Provides the layout of the CFAllocatorContext type. 144 | pub const CFAllocatorContext = extern struct { 145 | version: CFIndex, 146 | info: ?*anyopaque, 147 | retain: ?getFunctionPointer(CFAllocatorRetainCallBack), 148 | release: ?getFunctionPointer(CFAllocatorReleaseCallBack), 149 | copyDescription: ?getFunctionPointer(CFAllocatorCopyDescriptionCallBack), 150 | allocate: getFunctionPointer(CFAllocatorAllocateCallBack), 151 | reallocate: getFunctionPointer(CFAllocatorReallocateCallBack), 152 | deallocate: ?getFunctionPointer(CFAllocatorDeallocateCallback), 153 | preferredSize: ?getFunctionPointer(CFAllocatorPreferredSizeCallBack), 154 | }; 155 | }; 156 | 157 | /// Wraps the CFArrayRef type 158 | pub const CFArray = opaque { 159 | pub fn create(comptime T: type, values: []*const T) error{OutOfMemory}!*CFArray { 160 | return CFArrayCreate( 161 | null, 162 | @as([*]*const anyopaque, @ptrCast(values)), 163 | @as(CFIndex, @intCast(values.len)), 164 | &kCFTypeArrayCallBacks, 165 | ) orelse error.OutOfMemory; 166 | } 167 | 168 | pub fn createWithAllocator( 169 | comptime T: type, 170 | allocator: *CFAllocator, 171 | values: []*const T, 172 | call_backs: ?*CFArrayCallBacks, 173 | ) error{OutOfMemory}!*CFArray { 174 | return CFArrayCreate( 175 | allocator, 176 | @as([*]*const anyopaque, @ptrCast(values)), 177 | @as(CFIndex, @intCast(values.len)), 178 | call_backs, 179 | ) orelse error.OutOfMemory; 180 | } 181 | 182 | pub fn release(self: *CFArray) void { 183 | CFRelease(self); 184 | } 185 | 186 | pub fn count(self: *CFArray) usize { 187 | return @as(usize, @intCast(CFArrayGetCount(self))); 188 | } 189 | 190 | extern "c" var kCFTypeArrayCallBacks: CFArrayCallBacks; 191 | 192 | extern "C" fn CFArrayCreate( 193 | allocator: ?*CFAllocator, 194 | values: [*]*const anyopaque, 195 | num_values: CFIndex, 196 | call_backs: ?*CFArrayCallBacks, 197 | ) ?*CFArray; 198 | extern "C" fn CFArrayCreateCopy(allocator: ?*CFAllocator, the_array: *CFArray) ?*CFArray; 199 | 200 | extern "C" fn CFArrayBSearchValues( 201 | the_array: *CFArray, 202 | range: CFRange, 203 | value: *const anyopaque, 204 | comparator: ?getFunctionPointer(CFComparatorFunction), 205 | context: ?*anyopaque, 206 | ) CFIndex; 207 | extern "C" fn CFArrayContainsValue(the_array: *CFArray, range: CFRange, value: *const anyopaque) Boolean; 208 | extern "C" fn CFArrayGetCount(the_array: *CFArray) CFIndex; 209 | extern "C" fn CFArrayGetCountOfValue(the_array: *CFArray, range: CFRange, value: *const anyopaque) CFIndex; 210 | extern "C" fn CFArrayGetFirstIndexOfValue(the_array: *CFArray, range: CFRange, value: *const anyopaque) CFIndex; 211 | extern "C" fn CFArrayGetLastIndexOfValue(the_array: *CFArray, range: CFRange, value: *const anyopaque) CFIndex; 212 | extern "C" fn CFArrayGetValues(the_array: *CFArray, range: CFRange, values: [*]*const anyopaque) void; 213 | extern "C" fn CFArrayGetValueAtIndex(the_array: *CFArray, index: CFIndex) *const anyopaque; 214 | 215 | pub const CFArrayRetainCallBack = fn (*CFAllocator, *const anyopaque) callconv(.c) *const anyopaque; 216 | pub const CFArrayReleaseCallBack = fn (*CFAllocator, *const anyopaque) callconv(.c) void; 217 | pub const CFArrayCopyDescriptionCallBack = fn (*const anyopaque) callconv(.c) *CFString; 218 | pub const CFArrayEqualCallBack = fn (*const anyopaque, *const anyopaque) callconv(.c) Boolean; 219 | 220 | pub const CFArrayCallBacks = extern struct { 221 | version: CFIndex, 222 | retain: ?getFunctionPointer(CFArrayRetainCallBack), 223 | release: ?getFunctionPointer(CFArrayReleaseCallBack), 224 | copy_description: ?getFunctionPointer(CFArrayCopyDescriptionCallBack), 225 | equal: ?getFunctionPointer(CFArrayEqualCallBack), 226 | }; 227 | }; 228 | 229 | /// Wraps CFDataRef type. 230 | pub const CFData = opaque { 231 | pub fn create(bytes: []const u8) *CFData { 232 | return CFDataCreate(null, bytes.ptr, bytes.len); 233 | } 234 | 235 | pub fn release(self: *CFData) void { 236 | CFRelease(self); 237 | } 238 | 239 | pub fn len(self: *CFData) usize { 240 | return @as(usize, @intCast(CFDataGetLength(self))); 241 | } 242 | 243 | pub fn asSlice(self: *CFData) []const u8 { 244 | const ptr = CFDataGetBytePtr(self); 245 | return @as([*]const u8, @ptrCast(ptr))[0..self.len()]; 246 | } 247 | 248 | extern "c" fn CFDataCreate(allocator: ?*anyopaque, bytes: [*]const u8, length: usize) *CFData; 249 | extern "c" fn CFDataGetBytePtr(*CFData) *const u8; 250 | extern "c" fn CFDataGetLength(*CFData) i32; 251 | }; 252 | 253 | /// Wraps CFDateRef type. 254 | pub const CFDate = opaque { 255 | extern "C" fn CFDateCompare(the_date: *CFDate, other_date: *CFDate, context: ?*anyopaque) CFComparisonResult; 256 | extern "C" fn CFDateCreate(allocator: ?*CFAllocator, at: CFAbsoluteTime) ?*CFDate; 257 | extern "C" fn CFDateGetAbsoluteTime(the_date: *CFDate) CFAbsoluteTime; 258 | extern "C" fn CFDateGetTimeIntervalSinceDate(the_date: *CFDate, other_date: *CFDate) CFTimeInterval; 259 | extern "C" fn CFDateGetTypeID() CFTypeID; 260 | }; 261 | 262 | /// Wraps CFStringRef type. 263 | pub const CFString = opaque { 264 | pub fn createWithBytes(bytes: []const u8) *CFString { 265 | return CFStringCreateWithBytes(null, bytes.ptr, bytes.len, UTF8_ENCODING, false); 266 | } 267 | 268 | pub fn release(self: *CFString) void { 269 | CFRelease(self); 270 | } 271 | 272 | /// Caller owns the memory. 273 | pub fn cstr(self: *CFString, allocator: Allocator) error{OutOfMemory}![]u8 { 274 | if (CFStringGetCStringPtr(self, UTF8_ENCODING)) |ptr| { 275 | const c_str = mem.sliceTo(@as([*:0]const u8, @ptrCast(ptr)), 0); 276 | return allocator.dupe(u8, c_str); 277 | } 278 | 279 | const buf_size = 1024; 280 | var buf = std.ArrayList(u8).init(allocator); 281 | defer buf.deinit(); 282 | try buf.resize(buf_size); 283 | 284 | while (!CFStringGetCString(self, buf.items.ptr, buf.items.len, UTF8_ENCODING)) { 285 | try buf.resize(buf.items.len + buf_size); 286 | } 287 | 288 | const len = mem.sliceTo(@as([*:0]const u8, @ptrCast(buf.items.ptr)), 0).len; 289 | try buf.resize(len); 290 | 291 | return buf.toOwnedSlice(); 292 | } 293 | 294 | extern "c" fn CFStringCreateWithBytes( 295 | allocator: ?*anyopaque, 296 | bytes: [*]const u8, 297 | len: usize, 298 | encooding: u32, 299 | is_extern: bool, 300 | ) *CFString; 301 | extern "c" fn CFStringGetLength(str: *CFString) usize; 302 | extern "c" fn CFStringGetCStringPtr(str: *CFString, encoding: u32) ?*const u8; 303 | extern "c" fn CFStringGetCString(str: *CFString, buffer: [*]u8, size: usize, encoding: u32) bool; 304 | }; 305 | 306 | /// Wraps CFDictionaryRef type. 307 | pub const CFDictionary = opaque { 308 | pub fn create( 309 | comptime Key: type, 310 | comptime Value: type, 311 | keys: []*const Key, 312 | values: []*const Value, 313 | ) error{OutOfMemory}!*CFDictionary { 314 | assert(keys.len == values.len); 315 | return CFDictionaryCreate( 316 | null, 317 | @as([*]*const anyopaque, @ptrCast(keys)), 318 | @as([*]*const anyopaque, @ptrCast(values)), 319 | keys.len, 320 | &kCFTypeDictionaryKeyCallBacks, 321 | &kCFTypeDictionaryValueCallBacks, 322 | ) orelse error.OutOfMemory; 323 | } 324 | 325 | pub fn release(self: *CFDictionary) void { 326 | CFRelease(self); 327 | } 328 | 329 | pub fn getValue(self: *CFDictionary, comptime Key: type, comptime Value: type, key: *const Key) ?*Value { 330 | const ptr = CFDictionaryGetValue(self, @as(*const anyopaque, @ptrCast(key))) orelse return null; 331 | return @as(*Value, @ptrCast(ptr)); 332 | } 333 | 334 | extern "c" fn CFDictionaryCreate( 335 | allocator: ?*anyopaque, 336 | keys: [*]*const anyopaque, 337 | values: [*]*const anyopaque, 338 | num_values: usize, 339 | key_cb: *const anyopaque, 340 | value_cb: *const anyopaque, 341 | ) ?*CFDictionary; 342 | extern "c" fn CFDictionaryGetValue(dict: *CFDictionary, key: *const anyopaque) ?*anyopaque; 343 | 344 | extern "c" var kCFTypeDictionaryKeyCallBacks: anyopaque; 345 | extern "c" var kCFTypeDictionaryValueCallBacks: anyopaque; 346 | }; 347 | 348 | /// Wraps CFBooleanRef type. 349 | pub const CFBoolean = opaque { 350 | pub fn @"true"() *CFBoolean { 351 | return kCFBooleanTrue; 352 | } 353 | 354 | pub fn @"false"() *CFBoolean { 355 | return kCFBooleanFalse; 356 | } 357 | 358 | pub fn release(self: *CFBoolean) void { 359 | CFRelease(self); 360 | } 361 | 362 | extern "c" var kCFBooleanTrue: *CFBoolean; 363 | extern "c" var kCFBooleanFalse: *CFBoolean; 364 | }; 365 | 366 | /// Wraps CFUrl type. 367 | pub const CFUrl = opaque { 368 | pub fn createWithPath(path: []const u8, is_dir: bool) *CFUrl { 369 | const cpath = CFString.createWithBytes(path); 370 | defer cpath.release(); 371 | return CFURLCreateWithFileSystemPath(null, cpath, .posix, is_dir); 372 | } 373 | 374 | pub fn copyAbsoluteUrl(self: *CFUrl) *CFUrl { 375 | return CFURLCopyAbsoluteURL(self); 376 | } 377 | 378 | pub fn copyPath(self: *CFUrl, allocator: Allocator) ![]const u8 { 379 | const cpath = CFURLCopyFileSystemPath(self, .posix); 380 | defer cpath.release(); 381 | return cpath.cstr(allocator); 382 | } 383 | 384 | pub fn release(self: *CFUrl) void { 385 | CFRelease(self); 386 | } 387 | 388 | pub extern "c" fn CFURLCreateWithFileSystemPath( 389 | ?*anyopaque, 390 | path: *CFString, 391 | path_style: PathStyle, 392 | is_dir: bool, 393 | ) *CFUrl; 394 | extern "c" fn CFURLCopyAbsoluteURL(*CFUrl) *CFUrl; 395 | extern "c" fn CFURLCopyFileSystemPath(*CFUrl, PathStyle) *CFString; 396 | }; 397 | 398 | pub const PathStyle = enum(usize) { 399 | posix = 0, 400 | hfs, 401 | windows, 402 | }; 403 | 404 | pub const UTF8_ENCODING: u32 = 0x8000100; 405 | 406 | pub extern "c" fn CFRelease(*anyopaque) void; 407 | 408 | test { 409 | _ = testing.refAllDecls(@This()); 410 | _ = testing.refAllDecls(ZigAllocatorCallbacks); 411 | _ = testing.refAllDecls(CFAllocator); 412 | _ = testing.refAllDecls(CFAllocator.CFAllocatorContext); 413 | _ = testing.refAllDecls(CFArray); 414 | _ = testing.refAllDecls(CFArray.CFArrayCallBacks); 415 | _ = testing.refAllDecls(CFData); 416 | _ = testing.refAllDecls(CFDate); 417 | _ = testing.refAllDecls(CFString); 418 | _ = testing.refAllDecls(CFDictionary); 419 | _ = testing.refAllDecls(CFBoolean); 420 | _ = testing.refAllDecls(CFUrl); 421 | } 422 | --------------------------------------------------------------------------------