const std = @import("std");
const builtin = @import("builtin");
const root = @import("root");
const mem = std.mem;
const os = std.os;
pub const interface = std.rand.Random{
    .ptr = undefined,
    .fillFn = tlsCsprngFill,
};
const os_has_fork = switch (builtin.os.tag) {
    .dragonfly,
    .freebsd,
    .ios,
    .kfreebsd,
    .linux,
    .macos,
    .netbsd,
    .openbsd,
    .solaris,
    .tvos,
    .watchos,
    .haiku,
    => true,
    else => false,
};
const os_has_arc4random = builtin.link_libc and @hasDecl(std.c, "arc4random_buf");
const want_fork_safety = os_has_fork and !os_has_arc4random and
    (std.meta.globalOption("crypto_fork_safety", bool) orelse true);
const maybe_have_wipe_on_fork = builtin.os.isAtLeast(.linux, .{
    .major = 4,
    .minor = 14,
}) orelse true;
const is_haiku = builtin.os.tag == .haiku;
const Context = struct {
    init_state: enum(u8) { uninitialized = 0, initialized, failed },
    gimli: std.crypto.core.Gimli,
};
var install_atfork_handler = std.once(struct {
    
    
    
    fn do() void {
        const r = std.c.pthread_atfork(null, null, childAtForkHandler);
        std.debug.assert(r == 0);
    }
}.do);
threadlocal var wipe_mem: []align(mem.page_size) u8 = &[_]u8{};
fn tlsCsprngFill(_: *anyopaque, buffer: []u8) void {
    if (builtin.link_libc and @hasDecl(std.c, "arc4random_buf")) {
        
        return std.c.arc4random_buf(buffer.ptr, buffer.len);
    }
    
    
    
    if (comptime std.meta.globalOption("crypto_always_getrandom", bool) orelse false) {
        return fillWithOsEntropy(buffer);
    }
    if (wipe_mem.len == 0) {
        
        if (want_fork_safety and maybe_have_wipe_on_fork or is_haiku) {
            
            
            wipe_mem = os.mmap(
                null,
                @sizeOf(Context),
                os.PROT.READ | os.PROT.WRITE,
                os.MAP.PRIVATE | os.MAP.ANONYMOUS,
                -1,
                0,
            ) catch {
                
                
                return fillWithOsEntropy(buffer);
            };
            
        } else {
            
            const S = struct {
                threadlocal var buf: Context align(mem.page_size) = .{
                    .init_state = .uninitialized,
                    .gimli = undefined,
                };
            };
            wipe_mem = mem.asBytes(&S.buf);
        }
    }
    const ctx = @ptrCast(*Context, wipe_mem.ptr);
    switch (ctx.init_state) {
        .uninitialized => {
            if (!want_fork_safety) {
                return initAndFill(buffer);
            }
            if (maybe_have_wipe_on_fork) wof: {
                
                
                
                if (os.madvise(wipe_mem.ptr, 0, 0xffffffff)) |_| {
                    break :wof;
                } else |_| {}
                if (os.madvise(wipe_mem.ptr, wipe_mem.len, os.MADV.WIPEONFORK)) |_| {
                    return initAndFill(buffer);
                } else |_| {}
            }
            if (std.Thread.use_pthreads) {
                return setupPthreadAtforkAndFill(buffer);
            }
            
            
            ctx.init_state = .failed;
            return fillWithOsEntropy(buffer);
        },
        .initialized => {
            return fillWithCsprng(buffer);
        },
        .failed => {
            if (want_fork_safety) {
                return fillWithOsEntropy(buffer);
            } else {
                unreachable;
            }
        },
    }
}
fn setupPthreadAtforkAndFill(buffer: []u8) void {
    install_atfork_handler.call();
    return initAndFill(buffer);
}
fn childAtForkHandler() callconv(.C) void {
    
    
    if (wipe_mem.len == 0) return;
    std.crypto.utils.secureZero(u8, wipe_mem);
}
fn fillWithCsprng(buffer: []u8) void {
    const ctx = @ptrCast(*Context, wipe_mem.ptr);
    if (buffer.len != 0) {
        ctx.gimli.squeeze(buffer);
    } else {
        ctx.gimli.permute();
    }
    mem.set(u8, ctx.gimli.toSlice()[0..std.crypto.core.Gimli.RATE], 0);
}
fn fillWithOsEntropy(buffer: []u8) void {
    os.getrandom(buffer) catch @panic("getrandom() failed to provide entropy");
}
fn initAndFill(buffer: []u8) void {
    var seed: [std.crypto.core.Gimli.BLOCKBYTES]u8 = undefined;
    
    
    
    
    if (@hasDecl(root, "cryptoRandomSeed")) {
        root.cryptoRandomSeed(&seed);
    } else {
        fillWithOsEntropy(&seed);
    }
    const ctx = @ptrCast(*Context, wipe_mem.ptr);
    ctx.gimli = std.crypto.core.Gimli.init(seed);
    
    
    ctx.init_state = .initialized;
    return fillWithCsprng(buffer);
}