//!
//! Small Zig reimplementation of gcc's libssp.
//!
//! This library implements most of the builtins required by the stack smashing
//! protection as implemented by gcc&clang.
//! Missing exports:
//! - __gets_chk
//! - __mempcpy_chk
//! - __snprintf_chk
//! - __sprintf_chk
//! - __stpcpy_chk
//! - __vsnprintf_chk
//! - __vsprintf_chk

const std = @import("std");
const compiler_rt = @import("../compiler_rt.zig");
const symbol = compiler_rt.symbol;
const builtin = @import("builtin");

extern fn memset(dest: ?[*]u8, c: u8, n: usize) callconv(.c) ?[*]u8;
extern fn memcpy(noalias dest: ?[*]u8, noalias src: ?[*]const u8, n: usize) callconv(.c) ?[*]u8;
extern fn memmove(dest: ?[*]u8, src: ?[*]const u8, n: usize) callconv(.c) ?[*]u8;

comptime {
    @export(&__stack_chk_fail, .{ .name = if (builtin.os.tag == .openbsd) "__stack_smash_handler" else "__stack_chk_fail", .linkage = compiler_rt.linkage, .visibility = compiler_rt.visibility });
    symbol(&__chk_fail, "__chk_fail");
    symbol(&__stack_chk_guard, "__stack_chk_guard");
    symbol(&__strcpy_chk, "__strcpy_chk");
    symbol(&__strncpy_chk, "__strncpy_chk");
    symbol(&__strcat_chk, "__strcat_chk");
    symbol(&__strncat_chk, "__strncat_chk");
    symbol(&__memcpy_chk, "__memcpy_chk");
    symbol(&__memmove_chk, "__memmove_chk");
    symbol(&__memset_chk, "__memset_chk");
}

fn __stack_chk_fail() callconv(.c) noreturn {
    @panic("stack smashing detected");
}

fn __chk_fail() callconv(.c) noreturn {
    @panic("buffer overflow detected");
}

// TODO: Initialize the canary with random data
var __stack_chk_guard: usize = blk: {
    var buf = [1]u8{0} ** @sizeOf(usize);
    buf[@sizeOf(usize) - 1] = 255;
    buf[@sizeOf(usize) - 2] = '\n';
    break :blk @as(usize, @bitCast(buf));
};

fn __strcpy_chk(dest: [*:0]u8, src: [*:0]const u8, dest_n: usize) callconv(.c) [*:0]u8 {
    @setRuntimeSafety(false);

    var i: usize = 0;
    while (i < dest_n and src[i] != 0) : (i += 1) {
        dest[i] = src[i];
    }

    if (i == dest_n) __chk_fail();

    dest[i] = 0;

    return dest;
}

fn __strncpy_chk(dest: [*:0]u8, src: [*:0]const u8, n: usize, dest_n: usize) callconv(.c) [*:0]u8 {
    @setRuntimeSafety(false);
    if (dest_n < n) __chk_fail();
    var i: usize = 0;
    while (i < n and src[i] != 0) : (i += 1) {
        dest[i] = src[i];
    }
    while (i < n) : (i += 1) {
        dest[i] = 0;
    }
    return dest;
}

fn __strcat_chk(dest: [*:0]u8, src: [*:0]const u8, dest_n: usize) callconv(.c) [*:0]u8 {
    @setRuntimeSafety(false);

    var avail = dest_n;

    var dest_end: usize = 0;
    while (avail > 0 and dest[dest_end] != 0) : (dest_end += 1) {
        avail -= 1;
    }

    if (avail < 1) __chk_fail();

    var i: usize = 0;
    while (avail > 0 and src[i] != 0) : (i += 1) {
        dest[dest_end + i] = src[i];
        avail -= 1;
    }

    if (avail < 1) __chk_fail();

    dest[dest_end + i] = 0;

    return dest;
}

fn __strncat_chk(dest: [*:0]u8, src: [*:0]const u8, n: usize, dest_n: usize) callconv(.c) [*:0]u8 {
    @setRuntimeSafety(false);

    var avail = dest_n;

    var dest_end: usize = 0;
    while (avail > 0 and dest[dest_end] != 0) : (dest_end += 1) {
        avail -= 1;
    }

    if (avail < 1) __chk_fail();

    var i: usize = 0;
    while (avail > 0 and i < n and src[i] != 0) : (i += 1) {
        dest[dest_end + i] = src[i];
        avail -= 1;
    }

    if (avail < 1) __chk_fail();

    dest[dest_end + i] = 0;

    return dest;
}

fn __memcpy_chk(noalias dest: ?[*]u8, noalias src: ?[*]const u8, n: usize, dest_n: usize) callconv(.c) ?[*]u8 {
    if (dest_n < n) __chk_fail();
    return memcpy(dest, src, n);
}

fn __memmove_chk(dest: ?[*]u8, src: ?[*]const u8, n: usize, dest_n: usize) callconv(.c) ?[*]u8 {
    if (dest_n < n) __chk_fail();
    return memmove(dest, src, n);
}

fn __memset_chk(dest: ?[*]u8, c: u8, n: usize, dest_n: usize) callconv(.c) ?[*]u8 {
    if (dest_n < n) __chk_fail();
    return memset(dest, c, n);
}
