Merge branch 'master' into bill/odin-windows-amd64-abi-improvements

This commit is contained in:
gingerBill
2026-04-21 15:28:56 +01:00
80 changed files with 7262 additions and 1027 deletions

4
.gitattributes vendored
View File

@@ -10,3 +10,7 @@ vendor/sdl3/SDL3.dll filter=lfs diff=lfs merge=lfs -text
vendor/sdl3/SDL3.lib filter=lfs diff=lfs merge=lfs -text
vendor/sdl3/mixer/*.dll filter=lfs diff=lfs merge=lfs -text
vendor/sdl3/mixer/*.lib filter=lfs diff=lfs merge=lfs -text
vendor/wgpu/lib/wgpu-windows-x86_64-msvc-release/lib/wgpu_native.dll filter=lfs diff=lfs merge=lfs -text
vendor/wgpu/lib/wgpu-windows-x86_64-msvc-release/lib/wgpu_native.dll.lib filter=lfs diff=lfs merge=lfs -text
vendor/wgpu/lib/wgpu-windows-x86_64-msvc-release/lib/wgpu_native.lib filter=lfs diff=lfs merge=lfs -text
vendor/wgpu/lib/wgpu-windows-x86_64-msvc-release/lib/wgpu_native.pdb filter=lfs diff=lfs merge=lfs -text

View File

@@ -140,8 +140,9 @@ jobs:
- name: Optimized Core library tests
run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Wycheproof tests
run: ./odin test tests/core/crypto/wycheproof -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed
- name: Noise Protocol Framework tests
run: ./odin test tests/core/crypto/noise -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed
- name: Vendor library tests
run: ./odin test tests/vendor -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
if: matrix.os != 'macos-15-intel' && matrix.os != 'macos-latest'
@@ -244,6 +245,11 @@ jobs:
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
odin test tests/core/crypto/wycheproof -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed -microarch:native
- name: Noise Protocol Framework tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
odin test tests/core/crypto/noise -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed -microarch:native
- name: Vendor library tests
shell: cmd
run: |

View File

@@ -23,6 +23,12 @@ HAS_HARDWARE_SIMD :: false when (ODIN_ARCH == .amd64 || ODIN_ARCH == .i386) && !
false when (ODIN_ARCH == .riscv64) && !intrinsics.has_target_feature("v") else
true
// Size of a native SIMD register for the current compilation target
NATIVE_SIMD_BIT_WIDTH ::
512 when (ODIN_ARCH == .amd64) && intrinsics.has_target_feature("avx512f") else
256 when (ODIN_ARCH == .amd64) && (intrinsics.has_target_feature("avx2") || intrinsics.has_target_feature("avx")) else
// Fallback for no hardware SIMD, but also SSE, NEON, SVE, RVV and WASM SIMD128.
128
@(private)
byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byte #no_bounds_check {

View File

@@ -375,7 +375,7 @@ array_pop_safe :: proc(x: ^$X/Array($T, $SHIFT)) -> (val: T, ok: bool) {
Example:
import "core:encoding/xar"
import "core:container/xar"
unordered_remove_example :: proc() {
x: xar.Array(int, 4)

View File

@@ -16,7 +16,8 @@ HAS_RAND_BYTES :: runtime.HAS_RAND_BYTES
//
// The execution time of this routine is constant regardless of the contents
// of the slices being compared, as long as the length of the slices is equal.
// If the length of the two slices is dif and only if (⟺)erent, it will early-return 0.
// If and only if (⟺) the length of the two slices is diferent, it will
// early-return 0.
compare_constant_time :: proc "contextless" (a, b: []byte) -> int {
// If the length of the slices is different, early return.
//

View File

@@ -222,6 +222,40 @@ private_key_generate_public :: proc(priv_key: ^Private_Key) {
priv_key._pub_key._curve = priv_key._curve
}
// private_key_set sets priv_key to src.
private_key_set :: proc(priv_key, src: ^Private_Key) {
if src == nil || src._curve == .Invalid {
private_key_clear(priv_key)
return
}
priv_key._curve = src._curve
reflect.set_union_variant_typeid(
priv_key._impl,
_PRIV_IMPL_IDS[priv_key._curve],
)
#partial switch priv_key._curve {
case .SECP256R1:
secec.sc_set(&priv_key._impl.(secec.Scalar_p256r1), &src._impl.(secec.Scalar_p256r1))
case .SECP384R1:
secec.sc_set(&priv_key._impl.(secec.Scalar_p384r1), &src._impl.(secec.Scalar_p384r1))
case .X25519:
priv_buf := &(priv_key._impl.(X25519_Buf))
src_buf := &(src._impl.(X25519_Buf))
copy(priv_buf[:], src_buf[:])
case .X448:
priv_buf := &(priv_key._impl.(X448_Buf))
src_buf := &(src._impl.(X448_Buf))
copy(priv_buf[:], src_buf[:])
case:
panic("crypto/ecdh: invalid curve")
}
public_key_set(&priv_key._pub_key, &src._pub_key)
}
// private_key_bytes sets dst to byte-encoding of priv_key.
private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
ensure(priv_key._curve != .Invalid, "crypto/ecdh: uninitialized private key")
@@ -325,6 +359,16 @@ public_key_set_bytes :: proc(pub_key: ^Public_Key, curve: Curve, b: []byte) -> b
return true
}
// public_key_set sets pub_key to src.
public_key_set :: proc(pub_key, src: ^Public_Key) {
if src == nil || src._curve == .Invalid {
public_key_clear(pub_key)
return
}
pub_key^ = src^
}
// public_key_set_priv sets pub_key to the public component of priv_key.
public_key_set_priv :: proc(pub_key: ^Public_Key, priv_key: ^Private_Key) {
ensure(priv_key._curve != .Invalid, "crypto/ecdh: uninitialized private key")

View File

@@ -194,6 +194,32 @@ private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
}
}
// private_key_set sets priv_key to src.
private_key_set :: proc(priv_key, src: ^Private_Key) {
if src == nil || src._curve == .Invalid {
private_key_clear(priv_key)
return
}
priv_key._curve = src._curve
reflect.set_union_variant_typeid(
priv_key._impl,
_PRIV_IMPL_IDS[priv_key._curve],
)
#partial switch priv_key._curve {
case .SECP256R1:
secec.sc_set(&priv_key._impl.(secec.Scalar_p256r1), &src._impl.(secec.Scalar_p256r1))
case .SECP384R1:
secec.sc_set(&priv_key._impl.(secec.Scalar_p384r1), &src._impl.(secec.Scalar_p384r1))
case:
panic("crypto/ecdh: invalid curve")
}
public_key_set(&priv_key._pub_key, &src._pub_key)
}
// private_key_equal returns true if and only if (⟺) the private keys are equal,
// in constant time.
private_key_equal :: proc(p, q: ^Private_Key) -> bool {
@@ -262,6 +288,16 @@ public_key_set_bytes :: proc(pub_key: ^Public_Key, curve: Curve, b: []byte) -> b
return true
}
// public_key_set sets pub_key to src.
public_key_set :: proc(pub_key, src: ^Public_Key) {
if src == nil || src._curve == .Invalid {
public_key_clear(pub_key)
return
}
pub_key^ = src^
}
// public_key_set_priv sets pub_key to the public component of priv_key.
public_key_set_priv :: proc(pub_key: ^Public_Key, priv_key: ^Private_Key) {
ensure(priv_key._curve != .Invalid, "crypto/ecdsa: uninitialized private key")

View File

@@ -97,6 +97,21 @@ private_key_set_bytes :: proc(priv_key: ^Private_Key, b: []byte) -> bool {
return true
}
// private_key_set sets priv_key to src.
private_key_set :: proc(priv_key, src: ^Private_Key) {
if src == nil || !src._is_initialized {
private_key_clear(priv_key)
return
}
copy(priv_key._b[:], src._b[:])
grp.sc_set(&priv_key._s, &src._s)
copy(priv_key._hdigest2[:], src._hdigest2[:])
public_key_set(&priv_key._pub_key, &src._pub_key)
priv_key._is_initialized = true
}
// private_key_bytes sets dst to byte-encoding of priv_key.
private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
ensure(priv_key._is_initialized, "crypto/ed25519: uninitialized private key")
@@ -186,6 +201,16 @@ public_key_set_bytes :: proc "contextless" (pub_key: ^Public_Key, b: []byte) ->
return true
}
// public_key_set sets pub_key to src.
public_key_set :: proc(pub_key, src: ^Public_Key) {
if src == nil || !src._is_initialized {
public_key_clear(pub_key)
return
}
pub_key^ = src^
}
// public_key_set_priv sets pub_key to the public component of priv_key.
public_key_set_priv :: proc(pub_key: ^Public_Key, priv_key: ^Private_Key) {
ensure(priv_key._is_initialized, "crypto/ed25519: uninitialized private key")
@@ -212,6 +237,11 @@ public_key_equal :: proc(pub_key, other: ^Public_Key) -> bool {
return crypto.compare_constant_time(pub_key._b[:], other._b[:]) == 1
}
// public_key_clear clears pub_key to the uninitialized state.
public_key_clear :: proc "contextless" (pub_key: ^Public_Key) {
crypto.zero_explicit(pub_key, size_of(Public_Key))
}
// verify returns true if and only if (⟺) sig is a valid signature by pub_key over msg.
//
// The optional `allow_small_order_A` parameter will make this

590
core/crypto/noise/api.odin Normal file
View File

@@ -0,0 +1,590 @@
package noise
import "base:runtime"
import "core:crypto/aead"
import "core:crypto/ecdh"
import "core:crypto/hash"
import "core:strings"
// MAX_PACKET_SIZE is the maximum Noise message size, including TAG_SIZE
// if relevant (`seal_message`, `open_message`).
MAX_PACKET_SIZE :: 65535
// PSK_SIZE is the size of an optional handshake pre-shared symmetric key.
PSK_SIZE :: 32
// TAG_SIZE is the size of the AEAD authentication tag.
TAG_SIZE :: 16
// MAX_STEP_MSG_SIZE is the maximum per-handshake step message size,
// excluding the optional payload.
//
// `e` is DH_LEN, `s` is either DH_LEN or DH_LEN + TAG_SIZE, and there
// is a maximum of one per each message, and a possible mandatory tag.
MAX_STEP_MSG_SIZE :: (MAX_DH_SIZE*2)+TAG_SIZE+TAG_SIZE
// Status is the status of Noise protocol operation.
Status :: enum {
Ok,
// States
Handshake_Pending,
Handshake_Complete,
Handshake_Split,
Handshake_Failed,
// Errors
Invalid_Protocol_String,
Invalid_Pre_Shared_Key,
Invalid_DH_Key,
No_Self_Identity,
No_Peer_Identity,
Unexpected_Peer_Identity,
Unexpected_Pre_Shared_Key,
DH_Failure,
Invalid_Handshake_Message,
Decryption_Failure,
IV_Exhausted,
Invalid_Cipher_State,
Invalid_Destination_Buffer,
Invalid_Payload_Message,
Max_Packet_Size,
Out_Of_Memory,
}
// Handshake_State is the per-handshake state.
Handshake_State :: struct {
s: ecdh.Private_Key,
e: ecdh.Private_Key,
rs: ecdh.Public_Key,
re: ecdh.Public_Key,
psk: [PSK_SIZE]byte,
symmetric_state: Symmetric_State,
message_pattern: ^Message_Pattern,
current_message: int,
status: Status,
initiator: bool,
pre_set_e: bool,
}
// Cipher_States are the keyed AEAD instances and associated state,
// derived from a successful handshake.
Cipher_States :: struct {
c1_i_to_r: Cipher_State,
c2_r_to_i: Cipher_State,
initiator: bool,
}
// handshake_init initializes a Handshake_State with the provided parameters.
// The relevant values are copied into the Handshake_State instance, and
// can be discarded/sanitized right after handshake_init returns (eg: psk).
//
// Note: While this implementation supports setting `e`, this is primarily
// intended for testing, or cases where the runtime cryptographic entropy
// source is unavailable. Use of this functionality is STRONGLY
// discouraged.
@(require_results)
handshake_init :: proc(
self: ^Handshake_State,
initiator: bool,
prologue: []byte,
s: ^ecdh.Private_Key, // Our static key
rs: ^ecdh.Public_Key, // Peer static key
protocol_name: string,
psk: []byte = nil,
_e: ^ecdh.Private_Key = nil, // Our ephemeral key (for testing/RNG-less systems)
) -> Status {
return handshakestate_initialize(
self,
initiator,
prologue,
s,
_e,
rs,
nil,
protocol_name,
psk,
)
}
// handshake_initiator_step takes an input_message received from the responder
// if any and an optional payload to be sent to the responder, and performs
// one step of the Noise handshake process, returning the message to be sent
// to the responder if any, the payload received from the responder if any,
// and the status of the handshake.
//
// The output message MUST be sent to the responder even if the status code
// returned is .Handshake_Complete.
//
// If the dst parameter is provided, the message and payload will be written
// to dst, otherwise new buffers will be allocated.
@(require_results)
handshake_initiator_step :: proc(
self: ^Handshake_State,
input_message: []byte,
payload: []byte = nil,
dst: []byte = nil,
allocator := context.allocator,
) -> ([]byte, []byte, Status) {
output_message: []byte
payload_buffer: []byte
status: Status
dst := dst
if input_message == nil {
output_message, status = handshakestate_write_message(self, payload, dst, allocator)
} else {
payload_buffer, status = handshakestate_read_message(self, input_message, dst, allocator)
if status == .Handshake_Pending {
if dst != nil {
dst = dst[len(payload_buffer):]
}
output_message, status = handshakestate_write_message(self, payload, dst, allocator)
}
}
return output_message, payload_buffer, status
}
// handshake_responder_step takes a input_message received from the initiator,
// and and an optional payload to be sent to the initiator, and performs
// one step of the Noise handshake process, returning the message to be sent
// to the initiator if any, the payload received from the initiator if any,
// and the status of the handshake.
//
// The output message MUST be sent to the initiator even if the status code
// returned is .Handshake_Complete.
//
// If the dst parameter is provided, the message and payload will be written
// to dst, otherwise new buffers will be allocated.
@(require_results)
handshake_responder_step :: proc(
self: ^Handshake_State,
input_message: []byte,
payload: []byte = nil,
dst: []byte = nil,
allocator := context.allocator,
) -> ([]byte, []byte, Status) {
output_message: []byte
if input_message == nil {
return nil, nil, .Invalid_Handshake_Message
}
dst := dst
payload_buffer, status := handshakestate_read_message(self, input_message, dst, allocator)
if status == .Handshake_Pending {
if dst != nil {
dst = dst[len(payload_buffer):]
}
output_message, status = handshakestate_write_message(self, payload, dst, allocator)
}
return output_message, payload_buffer, status
}
// handshake_write_message calls the Noise HandshakeState's WriteMessage
// function directly. In most cases you are better off using
// handshake_initiator_step or handshake_responder_step.
//
// If the dst parameter is provided, the message and payload will be written
// to dst, otherwise new buffers will be allocated.
@(require_results)
handshake_write_message :: proc(
self: ^Handshake_State,
payload: []byte,
dst: []byte = nil,
allocator := context.allocator,
) -> ([]byte, Status) {
return handshakestate_write_message(self, payload, dst, allocator)
}
// handshake_read_message calls the Noise HandshakeState's ReadMessage
// function directly. In most cases you are better off using
// handshake_initiator_step or handshake_responder_step.
//
// If the dst parameter is provided, the message and payload will be written
// to dst, otherwise new buffers will be allocated.
@(require_results)
handshake_read_message :: proc(
self: ^Handshake_State,
message: []byte,
dst: []byte = nil,
allocator := context.allocator,
) -> ([]byte, Status) {
return handshakestate_read_message(self, message, dst, allocator)
}
// handshake_split initializes a Cipher_States instance from a completed
// handshake. This can be called once and only once per Handshake_State
// instance.
@(require_results)
handshake_split :: proc(self: ^Handshake_State, cipher_states: ^Cipher_States) -> Status {
if self.status != .Handshake_Complete {
return self.status
}
symmetricstate_split(&self.symmetric_state, cipher_states)
if self.message_pattern.is_one_way {
cipherstate_reset(&cipher_states.c2_r_to_i)
cipher_states.c2_r_to_i.is_invalid = true
}
cipher_states.initiator = self.initiator
self.status = .Handshake_Split
return .Ok
}
// handshake_peer_identity returns the peer's static DH key used by
// a completed handshake.
//
// This returns a pointer to the Handshake_State's copy of the peer's
// public key, that will get wiped by handshake_reset. If the key is
// needed after a call to handshake_reset, it must be copied.
@(require_results)
handshake_peer_identity :: proc(self: ^Handshake_State) -> (^ecdh.Public_Key, Status) {
#partial switch self.status {
case .Handshake_Complete, .Handshake_Split:
case:
return nil, self.status
}
if ecdh.curve(&self.rs) == .Invalid {
return nil, .No_Peer_Identity
}
return &self.rs, .Ok
}
// handshake_hash returns the handshake transcript hash of a completed
// handshake, for the purposes of channel binding. See 11.2 of the
// specification for details on usage.
//
// This returns a slice to an internal buffer that will get wiped by
// handshake_reset. If the hash is needed after a call to handshake_reset,
// the slice must be copied.
@(require_results)
handshake_hash :: proc(self: ^Handshake_State) -> ([]byte, Status) {
#partial switch self.status {
case .Handshake_Complete, .Handshake_Split:
case:
return nil, self.status
}
return symmetricstate_get_handshake_hash(&self.symmetric_state), .Ok
}
// handshake_reset sanitizes the Handshake_State. It is both safe and
// recommended to call this as soon as practical (after any calls to
// handshake_peer_identity, handshake_hash, and handshake_split are
// complete).
handshake_reset :: proc(self: ^Handshake_State) {
handshakestate_reset(self)
}
// seal_message encrypts the provided data, authenticates the aad and
// ciphertext, and returns the resulting ciphertext. The ciphertext
// will ALWAYS be `len(plaintext) + TAG_SIZE` bytes in length.
//
// If the dst parameter is provided, the ciphertext will be written
// to dst, otherwise a new buffer will be allocated.
@(require_results)
seal_message :: proc(self: ^Cipher_States, aad, plaintext: []byte, dst: []byte = nil, allocator := context.allocator) -> ([]byte, Status) {
data_len := len(plaintext)
dst := dst
did_alloc: bool
switch {
case dst == nil:
err: runtime.Allocator_Error
dst, err = make([]byte, data_len + TAG_SIZE, allocator)
if err != nil {
return nil, .Out_Of_Memory
}
did_alloc = true
case:
if len(dst) != data_len + TAG_SIZE {
return nil, .Invalid_Destination_Buffer
}
}
status: Status
switch self.initiator {
case true:
dst, status = cipherstate_encrypt_with_ad(&self.c1_i_to_r, aad, plaintext, dst)
case false:
dst, status = cipherstate_encrypt_with_ad(&self.c2_r_to_i, aad, plaintext, dst)
}
if status != .Ok && did_alloc {
delete(dst, allocator)
dst = nil
}
return dst, status
}
// open_message authenticates the aad and ciphertext, decrypts the
// ciphertext and returns the resulting plaintext. The plaintext will
// ALWAYS be `len(ciphertext) - TAG_SIZE` bytes in length.
//
// If the dst parameter is provided, the plaintext will be written to
// dst, otherwise a new buffer will be allocated.
@(require_results)
open_message :: proc(self: ^Cipher_States, aad, ciphertext: []byte, dst: []byte = nil, allocator := context.allocator) -> ([]byte, Status) {
if len(ciphertext) < TAG_SIZE {
return nil, .Invalid_Payload_Message
}
data_len := len(ciphertext) - TAG_SIZE
dst := dst
did_alloc: bool
switch {
case dst == nil:
if data_len > 0 {
err: runtime.Allocator_Error
dst, err = make([]byte, data_len, allocator)
if err != nil {
return nil, .Out_Of_Memory
}
did_alloc = true
}
case:
if len(dst) != data_len {
return nil, .Invalid_Destination_Buffer
}
}
status: Status
switch self.initiator {
case true:
dst, status = cipherstate_decrypt_with_ad(&self.c2_r_to_i, aad, ciphertext, dst)
case false:
dst, status = cipherstate_decrypt_with_ad(&self.c1_i_to_r, aad, ciphertext, dst)
}
if status != .Ok && did_alloc {
delete(dst, allocator)
dst = nil
}
return dst, status
}
// cipherstates_rekey updates the selected AEAD key, using a one way function.
// See 11.3 of the specification for examples of usage.
//
// Note: If one side updates the seal_key, the other side must update
// the non-seal_key and vice versa.
@(require_results)
cipherstates_rekey :: proc(self: ^Cipher_States, seal_key: bool) -> Status {
cs := cipherstates_cs(self, seal_key)
if cs.is_invalid {
return .Invalid_Cipher_State
}
if !cipherstate_has_key(cs) {
return .Handshake_Pending
}
cipherstate_rekey(cs)
return .Ok
}
// cipherstates_set_n sets the interal counter used to generate the AEAD
// IV to an explicit value. This can be used to deal with out-of-order
// transport messages. See 11.4 of the specification.
//
// WARNING: Reusing n across different aad/messages with the same Cipher_States
// will result in catastrophic loss of security.
@(require_results)
cipherstates_set_n :: proc(self: ^Cipher_States, seal_key: bool, n: u64) -> Status {
cs := cipherstates_cs(self, seal_key)
if cs.is_invalid {
return .Invalid_Cipher_State
}
if !cipherstate_has_key(cs) {
return .Handshake_Pending
}
cs.n = n
return .Ok
}
// cipherstates_n returns the interal counter used to generate the AEAD
// IV. This can be used to deal with out-of-order transport messages.
// See 11.4 of the specification.
//
// WARNING: Reusing n across different aad/messages with the same Cipher_States
// will result in catastrophic loss of security.
@(require_results)
cipherstates_n :: proc(self: ^Cipher_States, seal_key: bool, n: u64) -> (u64, Status) {
cs := cipherstates_cs(self, seal_key)
if cs.is_invalid {
return 0, .Invalid_Cipher_State
}
if !cipherstate_has_key(cs) {
return 0, .Handshake_Pending
}
return cs.n, .Ok
}
// cipherstates_reset sanitizes the Cipher_States.
cipherstates_reset :: proc(self: ^Cipher_States) {
self.initiator = false
cipherstate_reset(&self.c1_i_to_r)
cipherstate_reset(&self.c2_r_to_i)
}
@(private = "file")
cipherstates_cs :: proc(self: ^Cipher_States, seal_key: bool) -> ^Cipher_State {
switch self.initiator {
case true:
switch seal_key {
case true:
return &self.c1_i_to_r
case false:
return &self.c2_r_to_i
}
case false:
switch seal_key {
case true:
return &self.c2_r_to_i
case false:
return &self.c1_i_to_r
}
}
unreachable()
}
// split_protocol_string splits a protocol string into individual components.
@(require_results)
split_protocol_string :: proc(protocol_name: string) -> (Handshake_Pattern, ecdh.Curve, aead.Algorithm, hash.Algorithm, Status) {
str := protocol_name
if len(str) > 255 {
return .Invalid, .Invalid, .Invalid, .Invalid, .Invalid_Protocol_String
}
s, ok := strings.split_by_byte_iterator(&str, '_')
if !ok || s != "Noise" {
return .Invalid, .Invalid, .Invalid, .Invalid, .Invalid_Protocol_String
}
if s, ok = strings.split_by_byte_iterator(&str, '_'); !ok {
return .Invalid, .Invalid, .Invalid, .Invalid, .Invalid_Protocol_String
}
pattern: Handshake_Pattern
switch s {
case "N" : pattern = .N
case "K" : pattern = .K
case "X" : pattern = .X
case "XX": pattern = .XX
case "NK": pattern = .NK
case "NN": pattern = .NN
case "KN": pattern = .KN
case "KK": pattern = .KK
case "NX": pattern = .NX
case "KX": pattern = .KX
case "XN": pattern = .XN
case "IN": pattern = .IN
case "XK": pattern = .XK
case "IK": pattern = .IK
case "IX": pattern = .IX
case "NK1": pattern = .NK1
case "NX1": pattern = .NX1
case "X1N": pattern = .X1N
case "X1K": pattern = .X1K
case "XK1": pattern = .XK1
case "X1K1": pattern = .X1K1
case "X1X": pattern = .X1X
case "XX1": pattern = .XX1
case "X1X1": pattern = .X1X1
case "K1N": pattern = .K1N
case "K1K": pattern = .K1K
case "KK1": pattern = .KK1
case "K1K1": pattern = .K1K1
case "K1X": pattern = .K1X
case "KX1": pattern = .KX1
case "K1X1": pattern = .K1X1
case "I1N": pattern = .I1N
case "I1K": pattern = .I1K
case "IK1": pattern = .IK1
case "I1K1": pattern = .I1K1
case "I1X": pattern = .I1X
case "IX1": pattern = .IX1
case "I1X1": pattern = .I1X1
case "Npsk0": pattern = .Npsk0
case "Kpsk0": pattern = .Kpsk0
case "Xpsk1": pattern = .Xpsk1
case "NNpsk0": pattern = .NNpsk0
case "NNpsk2": pattern = .NNpsk2
case "NKpsk0": pattern = .NKpsk0
case "NKpsk2": pattern = .NKpsk2
case "NXpsk2": pattern = .NXpsk2
case "XNpsk3": pattern = .XNpsk3
case "XKpsk3": pattern = .XKpsk3
case "XXpsk3": pattern = .XXpsk3
case "KNpsk0": pattern = .KNpsk0
case "KNpsk2": pattern = .KNpsk2
case "KKpsk0": pattern = .KKpsk0
case "KKpsk2": pattern = .KKpsk2
case "KXpsk2": pattern = .KXpsk2
case "INpsk1": pattern = .INpsk1
case "INpsk2": pattern = .INpsk2
case "IKpsk1": pattern = .IKpsk1
case "IKpsk2": pattern = .IKpsk2
case "IXpsk2": pattern = .IXpsk2
case: pattern = .Invalid
}
if s, ok = strings.split_by_byte_iterator(&str, '_'); !ok {
return .Invalid, .Invalid, .Invalid, .Invalid, .Invalid_Protocol_String
}
dh: ecdh.Curve
switch s {
case "25519": dh = .X25519
case "448": dh = .X448
case: dh = .Invalid
}
if s, ok = strings.split_by_byte_iterator(&str, '_'); !ok {
return .Invalid, .Invalid, .Invalid, .Invalid, .Invalid_Protocol_String
}
cipher: aead.Algorithm
switch s {
case "AESGCM": cipher = .AES_GCM_256
case "ChaChaPoly": cipher = .CHACHA20POLY1305
case: cipher = .Invalid
}
if s, ok = strings.split_by_byte_iterator(&str, '_'); !ok {
return .Invalid, .Invalid, .Invalid, .Invalid, .Invalid_Protocol_String
}
hash: hash.Algorithm
switch s {
case "SHA512": hash = .SHA512
case "SHA256": hash = .SHA256
case "BLAKE2s": hash = .BLAKE2S
case "BLAKE2b": hash = .BLAKE2B
case: hash = .Invalid
}
status: Status
if len(str) != 0 {
status = .Invalid_Protocol_String
}
if pattern == .Invalid || dh == .Invalid || cipher == .Invalid || hash == .Invalid {
status = .Invalid_Protocol_String
}
return pattern, dh, cipher, hash, status
}

View File

@@ -0,0 +1,35 @@
/*
An implementation of the Noise Protocol Framework (Revision 34).
The `fallback` modifier and multi-PSK patterns are not supported
for the sake of simplicity.
See:
- [[ https://noiseprotocol.org/ ]]
*/
package noise
// In general, to complete a noise handshake you must:
//
// - If you are initiating the connection, call `handshake_initiator_step`
// passing `nil` as the `input_message` parameter.
//
// - Send the resulting `[]byte` to the responder (generally a server) via
// the method of your choice. This MUST be done even if the status code
// returned is `.Handshake_Complete`.
//
// - If the status code returned by `handshake_initiator_step` was
// `.Handshake_Complete`, the handshake completed successfully,
// and it is now possible to validate the peer identity, obtain the
// handshake transcript hash, and most usefully call `handshake_split`
// to populate the `Cipher_States` struct that will be used to
// encrypt/decrypt data.
//
// Otherwise, read the response from the responder and feed the response
// data as the `input_message` to the next `handshake_initiator_step`
// until it returns `.Handshake_Complete`.
//
// - If you are the responder, the method is much the same, except you
// must pass a valid `input_message` received from an initiator to the
// first call to `handshake_responder_step`. Repeat until the returned
// status is `.Handshake_Complete`.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,982 @@
#+private
package noise
import "base:runtime"
import "core:crypto"
import "core:crypto/aead"
import "core:crypto/ecdh"
import "core:crypto/hash"
import "core:crypto/hkdf"
import "core:encoding/endian"
import "core:slice"
AEAD_KEY_SIZE :: 32
MIN_DH_SIZE :: 32
MAX_DH_SIZE :: 56
MAX_HASH_SIZE :: 64
Protocol :: struct {
handshake_pattern: Handshake_Pattern,
dh: ecdh.Curve,
cipher: aead.Algorithm,
hash: hash.Algorithm,
}
Symmetric_State :: struct {
protocol: Protocol,
cipher_state: Cipher_State,
_ck: [MAX_HASH_SIZE]byte,
_h: [MAX_HASH_SIZE]byte,
}
Cipher_State :: struct {
ctx: aead.Context,
n: u64,
n_exhausted: bool,
is_invalid: bool,
}
@(require_results)
dh_len :: proc(protocol: ^Protocol) -> int {
return ecdh.PUBLIC_KEY_SIZES[protocol.dh]
}
@(require_results)
hash_len :: proc(protocol: ^Protocol) -> int {
return hash.DIGEST_SIZES[protocol.hash]
}
// Generates a new Diffie-Hellman key pair. A DH key pair consists of
// public_key and private_key elements. public_key represents an encoding
// of a DH public key into a byte sequence of length DHLEN. The public_key
// encoding details are specific to each set of DH functions.
generate_keypair :: proc(protocol: ^Protocol, private_key: ^ecdh.Private_Key) {
#partial switch protocol.dh {
case .X25519, .X448:
case: panic("crypto/noise: unsupported DH curve in protocol")
}
ecdh.private_key_generate(private_key, protocol.dh)
}
// Performs a Diffie-Hellman calculation between the private key in key_pair
// and the public_key and returns an output sequence of bytes of length DHLEN.
// For security, the Gap-DH problem based on this function must be unsolvable
// by any practical cryptanalytic adversary [2].
//
// The public_key either encodes some value which is a generator in a
// large prime-order group (which value may have multiple equivalent
// encodings), or is an invalid value. Implementations must handle invalid
// public keys either by returning some output which is purely a function
// of the public key and does not depend on the private key, or by signaling
// an error to the caller.
//
// The DH function may define more specific rules for handling invalid values.
@(require_results)
_dh :: proc(our_private_key: ^ecdh.Private_Key, their_public_key: ^ecdh.Public_Key, dst: []byte) -> Status {
if ok := ecdh.ecdh(our_private_key, their_public_key, dst); !ok {
return .DH_Failure
}
return .Ok
}
// Encrypts plaintext using the cipher key k of 32 bytes and an 8-byte
// unsigned integer nonce n which must be unique for the key k.
// Returns the ciphertext. Encryption must be done with an "AEAD"
// encryption mode with the associated data(AD) (using the terminology
// from [1]) and returns a ciphertext that is the same size as the plaintext
// plus 16 bytes for authentication data. The entire ciphertext must be
// indistinguishable from random if the key is secret (note that this is
// an additional requirement that isn't necessarily met by all AEAD schemes).
_encrypt :: proc(ctx: ^aead.Context, n: u64, ad, plaintext, dst: []byte) {
pt_len := len(plaintext)
ensure(len(dst) == pt_len + TAG_SIZE, "crypto/noise: invalid AEAD encrypt destination")
iv: [12]byte
#partial switch aead.algorithm(ctx) {
case .AES_GCM_256: endian.unchecked_put_u64be(iv[4:], n)
case .CHACHA20POLY1305: endian.unchecked_put_u64le(iv[4:], n)
}
ciphertext, tag := dst[:pt_len], dst[pt_len:]
aead.seal_ctx(ctx, ciphertext, tag, iv[:], ad, plaintext)
}
// Decrypts ciphertext using a cipher key k of 32 bytes, an 8-byte unsigned
// integer nonce n, and associated data ad. Returns the plaintext, unless
// authentication fails, in which case an error is signaled to the caller.
@(require_results)
_decrypt :: proc(ctx: ^aead.Context, n: u64, ad, ciphertext, dst: []byte) -> Status {
if len(ciphertext) < TAG_SIZE {
return .Decryption_Failure
}
iv: [12]byte
#partial switch aead.algorithm(ctx) {
case .AES_GCM_256: endian.unchecked_put_u64be(iv[4:], n)
case .CHACHA20POLY1305: endian.unchecked_put_u64le(iv[4:], n)
}
ct_len := len(ciphertext) - TAG_SIZE
ct, tag := ciphertext[:ct_len], ciphertext[ct_len:]
if ok := aead.open_ctx(ctx, dst, iv[:], ad, ct, tag); !ok {
return .Decryption_Failure
}
return .Ok
}
// Hashes some arbitrary-length data with a collision-resistant cryptographic
// hash function and returns an output of HASHLEN bytes.
_hash :: proc(dst: []byte, protocol: ^Protocol, data: ..[]byte) {
ctx: hash.Context
hash.init(&ctx, protocol.hash)
for datum in data {
hash.update(&ctx, datum)
}
hash.final(&ctx, dst)
}
// Takes a chaining_key byte sequence of length HASHLEN, and an
// input_key_material byte sequence with length either zero bytes,
// 32 bytes, or DHLEN bytes. Returns a pair or triple of byte sequences
// each of length HASHLEN, depending on whether num_outputs is two or three:
// - Sets temp_key = HMAC-HASH(chaining_key, input_key_material).
// - Sets output1 = HMAC-HASH(temp_key, byte(0x01)).
// - Sets output2 = HMAC-HASH(temp_key, output1 || byte(0x02)).
// - If num_outputs == 2 then returns the pair (output1, output2).
// - Sets output3 = HMAC-HASH(temp_key, output2 || byte(0x03)).
// - Returns the triple (output1, output2, output3).
//
// Note that temp_key, output1, output2, and output3 are all HASHLEN
// bytes in length. Also note that the HKDF() function is simply HKDF
// from [4] with the chaining_key as HKDF salt, and zero-length HKDF info.
@(require_results)
_hkdf :: proc(dst, chaining_key, input_key_material: []byte, protocol: ^Protocol) -> ([]byte, []byte, []byte) {
assert(len(input_key_material) == 0 || len(input_key_material) == 32 || len(input_key_material) == dh_len(protocol))
hkdf.extract_and_expand(protocol.hash, chaining_key, input_key_material, nil, dst)
h_len := hash_len(protocol)
assert(len(dst) == h_len * 2 || len(dst) == h_len * 3)
r1, r2 := dst[:h_len], dst[h_len:h_len*2]
if len(dst) == h_len * 2 {
return r1, r2, nil
}
return r1, r2, dst[h_len*2:]
}
// Sets k = key. Sets n = 0.
cipherstate_initialize_key :: proc(self: ^Cipher_State, key: []byte, protocol: ^Protocol) {
k_len := len(key)
switch {
case k_len == 0:
// k = empty
aead.reset(&self.ctx)
self.n = 0
case k_len < AEAD_KEY_SIZE:
panic("crypto/noise: invalid AEAD key size")
case:
aead.init(&self.ctx, protocol.cipher, key[:AEAD_KEY_SIZE])
self.n = 0
}
}
// Returns true if k is non-empty, false otherwise.
@(require_results)
cipherstate_has_key :: proc(self: ^Cipher_State) -> bool {
return aead.algorithm(&self.ctx) != .Invalid
}
// If k is non-empty returns ENCRYPT(k, n++, ad, plaintext). Otherwise
// returns plaintext.
@(require_results)
cipherstate_encrypt_with_ad :: proc(self: ^Cipher_State, ad, plaintext, dst: []byte) -> ([]byte, Status) {
if self.is_invalid {
return nil, .Invalid_Cipher_State
}
if self.n_exhausted {
return nil, .IV_Exhausted
}
pt_len := len(plaintext)
if pt_len > MAX_PACKET_SIZE - 16 {
return nil, .Max_Packet_Size
}
if cipherstate_has_key(self) {
if len(dst) != pt_len + TAG_SIZE {
return nil, .Invalid_Destination_Buffer
}
_encrypt(&self.ctx, self.n, ad, plaintext, dst)
self.n += 1
if self.n == 0 {
self.n_exhausted = true
}
} else {
if len(dst) != pt_len {
return nil, .Invalid_Destination_Buffer
}
if raw_data(dst) != raw_data(plaintext) {
copy(dst, plaintext)
}
}
return dst, .Ok
}
// If k is non-empty returns DECRYPT(k, n++, ad, ciphertext). Otherwise
// returns ciphertext. If an authentication failure occurs in DECRYPT()
// then n is not incremented and an error is signaled to the caller.
@(require_results)
cipherstate_decrypt_with_ad :: proc(self: ^Cipher_State, ad, ciphertext, dst: []byte) -> ([]byte, Status) {
if self.is_invalid {
return nil, .Invalid_Cipher_State
}
if self.n_exhausted {
return nil, .IV_Exhausted
}
if cipherstate_has_key(self) {
if status := _decrypt(&self.ctx, self.n, ad, ciphertext, dst); status != .Ok {
return nil, status
}
self.n += 1
if self.n == 0 {
self.n_exhausted = true
}
} else {
if len(dst) != len(ciphertext) {
return nil, .Invalid_Destination_Buffer
}
if raw_data(dst) != raw_data(ciphertext) {
copy(dst, ciphertext)
}
}
return dst, .Ok
}
// Sets k = REKEY(k).
cipherstate_rekey :: proc(self: ^Cipher_State) {
if cipherstate_has_key(self) {
algorithm := aead.algorithm(&self.ctx)
// The "sensible" way to implement this is to inlike REKEY(k),
// so we do.
//
// Returns a new 32-byte cipher key as a pseudorandom function
// of k. If this function is not specifically defined for some
// set of cipher functions, then it defaults to returning the
// first 32 bytes from `ENCRYPT(k, maxnonce, zerolen, zeros)`,
// where maxnonce equals (2^64)-1, zerolen is a zero-length
// byte sequence, and zeros is a sequence of 32 bytes filled
// with zeros.
zeroes: [AEAD_KEY_SIZE + TAG_SIZE]byte
defer crypto.zero_explicit(&zeroes, size_of(zeroes))
// 1 2 3 4 5 6 7 8
n: u64 = 0xFF_FF_FF_FF_FF_FF_FF_FF
_encrypt(&self.ctx, n, nil, zeroes[:AEAD_KEY_SIZE], zeroes[:])
aead.init(&self.ctx, algorithm, zeroes[:AEAD_KEY_SIZE])
}
}
cipherstate_reset :: proc(self: ^Cipher_State) {
aead.reset(&self.ctx)
crypto.zero_explicit(self, size_of(Cipher_State))
}
// Takes an arbitrary-length protocol_name byte sequence (see Section 8).
// Executes the following steps:
// - If protocol_name is less than or equal to HASHLEN bytes in length,
// sets h equal to protocol_name with zero bytes appended to make
// HASHLEN bytes.
// - Otherwise sets h = HASH(protocol_name).
// - Sets ck = h.
// - Calls InitializeKey(empty).
@(require_results)
symmetricstate_initialize :: proc(ss: ^Symmetric_State, protocol_name: string) -> Status {
if status := protocol_from_string(&ss.protocol, protocol_name); status != .Ok {
return status
}
cipherstate_initialize_key(&ss.cipher_state, nil, &ss.protocol)
h_len := hash_len(&ss.protocol)
h := ss._h[:h_len]
if len(protocol_name) <= h_len {
copy(h, protocol_name)
} else {
_hash(h, &ss.protocol, transmute([]byte)protocol_name)
}
copy(ss._ck[:h_len], h)
return .Ok
}
// Sets h = HASH(h || data).
symmetricstate_mix_hash :: proc(self: ^Symmetric_State, data: ..[]byte) {
h := self._h[:hash_len(&self.protocol)]
if len(data) == 1 {
_hash(h, &self.protocol, h, data[0])
} else if len(data) == 2 {
_hash(h, &self.protocol, h, data[0], data[1])
} else if len(data) == 3 {
_hash(h, &self.protocol, h, data[0], data[1], data[2])
} else {
panic("crypto/noise: invalid MixHash inputs")
}
}
// Executes the following steps:
// - Sets ck, temp_k = HKDF(ck, input_key_material, 2).
// - If HASHLEN is 64, then truncates temp_k to 32 bytes.
// - Calls InitializeKey(temp_k).
symmetricstate_mix_key :: proc(self: ^Symmetric_State, input_key_material: []byte) {
h_len := hash_len(&self.protocol)
dst_len := h_len * 2
dst: [2*MAX_HASH_SIZE]byte = ---
defer crypto.zero_explicit(&dst, dst_len)
ck, temp_k, _ := _hkdf(dst[:dst_len], self._ck[:h_len], input_key_material, &self.protocol)
copy(self._ck[:], ck)
cipherstate_initialize_key(&self.cipher_state, temp_k, &self.protocol)
}
// This function is used for handling pre-shared symmetric keys, as described
// in Section 9. It executes the following steps:
// - Sets ck, temp_h, temp_k = HKDF(ck, input_key_material, 3).
// - Calls MixHash(temp_h).
// - If HASHLEN is 64, then truncates temp_k to 32 bytes.
// - Calls InitializeKey(temp_k).
symmetricstate_mix_key_and_hash :: proc(self: ^Symmetric_State, input_key_material: []byte) {
h_len := hash_len(&self.protocol)
dst_len := h_len * 3
dst: [3*MAX_HASH_SIZE]byte = ---
defer crypto.zero_explicit(&dst, dst_len)
ck, temp_h, temp_k := _hkdf(dst[:dst_len], self._ck[:h_len], input_key_material, &self.protocol)
copy(self._ck[:], ck)
symmetricstate_mix_hash(self, temp_h)
cipherstate_initialize_key(&self.cipher_state, temp_k, &self.protocol)
}
// Returns h. This function should only be called at the end of a handshake,
// i.e. after the Split() function has been called.
//
// This function is used for channel binding, as described in Section 11.2
@(require_results)
symmetricstate_get_handshake_hash :: proc(self: ^Symmetric_State) -> []byte {
return self._h[:hash_len(&self.protocol)]
}
// Sets ciphertext = EncryptWithAd(h, plaintext), calls MixHash(ciphertext),
// and returns ciphertext.
//
// Note that if k is empty, the EncryptWithAd() call will set ciphertext
// equal to plaintext.
@(require_results)
symmetricstate_encrypt_and_hash :: proc(self: ^Symmetric_State, plaintext, dst: []byte) -> ([]byte, Status) {
ciphertext, status := cipherstate_encrypt_with_ad(&self.cipher_state, self._h[:hash_len(&self.protocol)], plaintext, dst)
if status != .Ok {
return nil, status
}
symmetricstate_mix_hash(self, ciphertext)
return ciphertext, status
}
// Sets plaintext = DecryptWithAd(h, ciphertext), calls MixHash(ciphertext),
// and returns plaintext.
//
// Note that if k is empty, the DecryptWithAd() call will set plaintext
// equal to ciphertext.
@(require_results)
symmetricstate_decrypt_and_hash :: proc(self: ^Symmetric_State, ciphertext, dst: []byte) -> ([]byte, Status) {
h_len := hash_len(&self.protocol)
h: [MAX_HASH_SIZE]byte = ---
copy(h[:], self._h[:h_len])
defer crypto.zero_explicit(&h, size_of(h))
// We reverse the order to save having to copy the ciphertext, in
// the case that ciphertext and dst alias.
symmetricstate_mix_hash(self, ciphertext)
return cipherstate_decrypt_with_ad(&self.cipher_state, h[:h_len], ciphertext, dst)
}
// Returns a pair of CipherState objects for encrypting transport messages.
// Executes the following steps, where zerolen is a zero-length byte sequence:
// - Sets temp_k1, temp_k2 = HKDF(ck, zerolen, 2).
// - If HASHLEN is 64, then truncates temp_k1 and temp_k2 to 32 bytes.
// - Creates two new CipherState objects c1 and c2.
// - Calls c1.InitializeKey(temp_k1) and c2.InitializeKey(temp_k2).
// - Returns the pair (c1, c2).
symmetricstate_split :: proc(self: ^Symmetric_State, cipher_states: ^Cipher_States) {
h_len := hash_len(&self.protocol)
dst_len := h_len * 2
dst: [2*MAX_HASH_SIZE]byte = ---
defer crypto.zero_explicit(&dst, dst_len)
temp_k1, temp_k2, _ := _hkdf(dst[:dst_len], self._ck[:h_len], nil, &self.protocol)
cipherstate_initialize_key(&cipher_states.c1_i_to_r, temp_k1, &self.protocol)
cipherstate_initialize_key(&cipher_states.c2_r_to_i, temp_k2, &self.protocol)
}
symmetricstate_reset :: proc(self: ^Symmetric_State) {
cipherstate_reset(&self.cipher_state)
crypto.zero_explicit(self, size_of(Symmetric_State))
}
// Takes a valid handshake_pattern (see Section 7) and an initiator boolean
// specifying this party's role as either initiator or responder.
// Takes a prologue byte sequence which may be zero-length, or which may
// contain context information that both parties want to confirm is identical
// (see Section 6).
//
// Takes a set of DH key pairs (s, e) and public keys (rs, re) for
// initializing local variables, any of which may be empty. Public keys
// are only passed in if the handshake_pattern uses pre-messages
// (see Section 7). The ephemeral values (e, re) are typically left empty,
// since they are created and exchanged during the handshake; but there
// are exceptions (see Section 10).
//
// Performs the following steps:
// - Derives a protocol_name byte sequence by combining the names for
// the handshake pattern and crypto functions, as specified in Section 8.
// - Calls InitializeSymmetric(protocol_name).
// - Calls MixHash(prologue).
// - Sets the initiator, s, e, rs, and re variables to the corresponding
// arguments.
// - Calls MixHash() once for each public key listed in the pre-messages
// from handshake_pattern, with the specified public key as input
// (see Section 7 for an explanation of pre-messages).
// - If both initiator and responder have pre-messages, the initiator's
// public keys are hashed first.
// - If multiple public keys are listed in either party's pre-message,
// the public keys are hashed in the order that they are listed.
// - Sets message_pattern to the message patterns from handshake_pattern.
@(require_results)
handshakestate_initialize :: proc(
handshake_state: ^Handshake_State,
initiator: bool,
prologue: []byte,
s: ^ecdh.Private_Key,
e: ^ecdh.Private_Key, // Only set for testing.
rs: ^ecdh.Public_Key,
re: ^ecdh.Public_Key, // Only set for testing.
protocol_name: string,
psk: []byte = nil,
) -> Status {
crypto.zero_explicit(handshake_state, size_of(Handshake_State))
symmetric_state := &handshake_state.symmetric_state
status: Status
do_init: {
if status = symmetricstate_initialize(symmetric_state, protocol_name); status != .Ok {
break do_init
}
curve := symmetric_state.protocol.dh
if s != nil && ecdh.curve(s) != curve {
status = .Invalid_DH_Key
break do_init
}
if e != nil && ecdh.curve(e) != curve {
status = .Invalid_DH_Key
break do_init
}
if rs != nil && ecdh.curve(rs) != curve {
status = .Invalid_DH_Key
break do_init
}
if re != nil && ecdh.curve(re) != curve {
status = .Invalid_DH_Key
break do_init
}
// Check if we will require s later down the line.
s_pre, s_hs: bool
if initiator {
s_pre, s_hs = pattern_requires_initiator_s(symmetric_state.protocol.handshake_pattern)
} else {
s_pre, s_hs = pattern_requires_responder_s(symmetric_state.protocol.handshake_pattern)
}
if (s_pre || s_hs) && s == nil {
status = .No_Self_Identity
break do_init
}
message_pattern := HANDSHAKE_PATTERNS[symmetric_state.protocol.handshake_pattern]
if message_pattern.pre_messages != nil {
if initiator {
if slice.contains(message_pattern.pre_messages, Pre_Token.res_s) {
if rs == nil {
status = .No_Peer_Identity
break do_init
}
}
} else {
if slice.contains(message_pattern.pre_messages, Pre_Token.ini_s) {
if rs == nil {
status = .No_Peer_Identity
break do_init
}
}
}
} else {
if rs != nil {
status = .Unexpected_Peer_Identity
break do_init
}
}
symmetricstate_mix_hash(symmetric_state, prologue)
// In all supported patterns, `ini_s` will always precede `res_s`.
if message_pattern.pre_messages != nil {
tmp: [MAX_DH_SIZE]byte = ---
d_len := dh_len(&symmetric_state.protocol)
dst := tmp[:d_len]
if initiator {
if slice.contains(message_pattern.pre_messages, Pre_Token.ini_s) {
ecdh.public_key_bytes(&s._pub_key, dst)
symmetricstate_mix_hash(symmetric_state, dst)
}
if slice.contains(message_pattern.pre_messages, Pre_Token.res_s) {
ecdh.public_key_bytes(rs, dst)
symmetricstate_mix_hash(symmetric_state, dst)
}
} else {
if slice.contains(message_pattern.pre_messages, Pre_Token.ini_s) {
ecdh.public_key_bytes(rs, dst)
symmetricstate_mix_hash(symmetric_state, dst)
}
if slice.contains(message_pattern.pre_messages, Pre_Token.res_s) {
ecdh.public_key_bytes(&s._pub_key, dst)
symmetricstate_mix_hash(symmetric_state, dst)
}
}
}
if message_pattern.is_psk {
if len(psk) != PSK_SIZE {
status = .Invalid_Pre_Shared_Key
break do_init
}
} else if len(psk) != 0 {
status = .Unexpected_Pre_Shared_Key
break do_init
}
}
if status != .Ok {
symmetricstate_reset(symmetric_state)
return status
}
if s != nil {
ecdh.private_key_set(&handshake_state.s, s)
}
if e != nil {
ecdh.private_key_set(&handshake_state.e, e)
handshake_state.pre_set_e = true
}
if rs != nil {
ecdh.public_key_set(&handshake_state.rs, rs)
}
if re != nil {
ecdh.public_key_set(&handshake_state.re, re)
}
copy(handshake_state.psk[:], psk)
handshake_state.message_pattern = HANDSHAKE_PATTERNS[symmetric_state.protocol.handshake_pattern]
handshake_state.current_message = 0
handshake_state.status = .Handshake_Pending
handshake_state.initiator = initiator
return .Ok
}
handshakestate_reset :: proc(self: ^Handshake_State) {
symmetricstate_reset(&self.symmetric_state)
ecdh.private_key_clear(&self.s)
ecdh.private_key_clear(&self.e)
crypto.zero_explicit(self, size_of(Handshake_State))
}
// Takes a payload byte sequence which may be zero-length, and a
// message_buffer to write the output into.
// Performs the following steps, aborting if any EncryptAndHash() call
// returns an error:
// - Fetches and deletes the next message pattern from message_pattern,
// then sequentially processes each token from the message pattern:
// - For "e": Sets e (which must be empty) to GENERATE_KEYPAIR().
// Appends e.public_key to the buffer. Calls MixHash(e.public_key).
// - For "s": Appends EncryptAndHash(s.public_key) to the buffer.
// - For "ee": Calls MixKey(DH(e, re)).
// - For "es": Calls MixKey(DH(e, rs)) if initiator, MixKey(DH(s, re))
// if responder.
// - For "se": Calls MixKey(DH(s, re)) if initiator, MixKey(DH(e, rs))
// if responder.
// - For "ss": Calls MixKey(DH(s, rs)).
// - Appends EncryptAndHash(payload) to the buffer.
// (SKIPPED) If there are no more message patterns returns two new
// CipherState objects by calling Split().
//
// Calling Split() is left to a separate function, although it is technically
// part of the specification.
@(require_results)
handshakestate_write_message :: proc(self: ^Handshake_State, payload, dst: []byte, allocator := context.allocator) -> ([]byte, Status) {
ensure(self.status == .Handshake_Pending, "crypto/noise: invalid state for WriteMessage")
protocol := &self.symmetric_state.protocol
d_len := dh_len(protocol)
pattern_buf: [dynamic; MAX_STEP_MSG_SIZE]byte
dh_buf: [MAX_DH_SIZE]byte = ---
defer crypto.zero_explicit(&dh_buf, size_of(dh_buf))
pattern := self.message_pattern.messages[self.current_message]
for token in pattern {
switch token {
case .e:
switch self.pre_set_e {
case true:
// Note: "which must be empty", but we allow pre-generated `e`
// for testing/rng-less systems.
self.pre_set_e = false
case false:
if ecdh.curve(&self.e) != .Invalid {
panic("crypto/noise: e was not empty when processing token 'e' during WriteMessage")
}
generate_keypair(protocol, &self.e)
}
e_public := dh_buf[:d_len]
ecdh.public_key_bytes(&self.e._pub_key, e_public)
n := append(&pattern_buf, ..e_public)
ensure(n == d_len, "crypto/noise: truncated append `e`")
symmetricstate_mix_hash(&self.symmetric_state, e_public)
if self.message_pattern.is_psk {
symmetricstate_mix_key(&self.symmetric_state, e_public)
}
case .s:
s_public := dh_buf[:d_len]
ecdh.public_key_bytes(&self.s._pub_key, s_public)
tmp: [MAX_DH_SIZE+TAG_SIZE]byte = ---
dh_buf := tmp[:d_len+TAG_SIZE]
if !cipherstate_has_key(&self.symmetric_state.cipher_state) {
dh_buf = tmp[:d_len]
}
ct, status := symmetricstate_encrypt_and_hash(&self.symmetric_state, s_public, dh_buf)
if status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
n := append(&pattern_buf, ..ct)
ensure(n == len(ct), "crypto/noise: truncated append `s`")
case .ee:
dh := dh_buf[:d_len]
if status := _dh(&self.e, &self.re, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
case .es:
dh := dh_buf[:d_len]
if self.initiator {
if status := _dh(&self.e, &self.rs, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
} else {
if status := _dh(&self.s, &self.re, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
}
case .se:
dh := dh_buf[:d_len]
if self.initiator {
if status := _dh(&self.s, &self.re, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
} else {
if status := _dh(&self.e, &self.rs, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
}
case .ss:
dh := dh_buf[:d_len]
if status := _dh(&self.s, &self.rs, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
case .psk:
symmetricstate_mix_key_and_hash(&self.symmetric_state, self.psk[:])
}
}
self.current_message += 1 // Advance after the current message is successful.
pattern_len := len(pattern_buf)
payload_len := len(payload)
msg_len := pattern_len + payload_len
if cipherstate_has_key(&self.symmetric_state.cipher_state) {
msg_len += TAG_SIZE
}
msg: []byte
if msg_len != 0 {
did_alloc: bool
if dst != nil {
if len(dst) < msg_len {
self.status = .Handshake_Failed
return nil, .Out_Of_Memory
}
msg = dst[:msg_len]
} else {
err: runtime.Allocator_Error
msg, err = make([]byte, msg_len, allocator)
if err != nil {
self.status = .Handshake_Failed
return nil, .Out_Of_Memory
}
did_alloc = true
}
copy(msg, pattern_buf[:])
ciphertext := msg[pattern_len:]
if _, status := symmetricstate_encrypt_and_hash(&self.symmetric_state, payload, ciphertext); status != .Ok {
if did_alloc {
delete(msg)
}
self.status = .Handshake_Failed
return nil, status
}
}
if self.current_message == len(self.message_pattern.messages) {
self.current_message = -1
self.status = .Handshake_Complete
}
return msg, self.status
}
// Takes a byte sequence containing a Noise handshake message, and a
// payload_buffer to write the message's plaintext payload into.
// Performs the following steps, aborting if any DecryptAndHash()
// call returns an error:
// - Fetches and deletes the next message pattern from message_pattern,
// then sequentially processes each token from the message pattern:
// - For "e": Sets re (which must be empty) to the next DHLEN bytes
// from the message. Calls MixHash(re.public_key).
// - For "s": Sets temp to the next DHLEN + 16 bytes of the message
// if HasKey() == True, or to the next DHLEN bytes otherwise.
// Sets rs (which must be empty) to DecryptAndHash(temp).
// - For "ee": Calls MixKey(DH(e, re)).
// - For "es": Calls MixKey(DH(e, rs)) if initiator, MixKey(DH(s, re))
// if responder.
// - For "se": Calls MixKey(DH(s, re)) if initiator, MixKey(DH(e, rs))
// if responder.
// -For "ss": Calls MixKey(DH(s, rs)).
// - Calls DecryptAndHash() on the remaining bytes of the message and stores
// the output into payload_buffer.
// (SKIPPED) If there are no more message patterns returns two new
// CipherState objects by calling Split().
//
// Calling Split() is left to a separate function, although it is technically
// part of the specification.
@(require_results)
handshakestate_read_message :: proc(self: ^Handshake_State, message, dst: []byte, allocator := context.allocator) -> ([]byte, Status) {
ensure(self.status == .Handshake_Pending, "crypto/noise: invalid state for ReadMessage")
protocol := &self.symmetric_state.protocol
d_len := dh_len(&self.symmetric_state.protocol)
dh_buf: [MAX_DH_SIZE]byte = ---
defer crypto.zero_explicit(&dh_buf, size_of(dh_buf))
msg := message
pattern := self.message_pattern.messages[self.current_message]
for token in pattern {
switch token {
case .e:
if len(msg) < d_len {
return nil, .Invalid_Handshake_Message
}
re := msg[:d_len]
if ecdh.curve(&self.re) != .Invalid {
panic("crypto/noise: re was not empty when processing token 'e' during ReadMessage")
}
ecdh.public_key_set_bytes(&self.re, protocol.dh, re)
symmetricstate_mix_hash(&self.symmetric_state, re)
if self.message_pattern.is_psk {
symmetricstate_mix_key(&self.symmetric_state, re)
}
msg = msg[d_len:]
case .s:
rs_len := d_len
if cipherstate_has_key(&self.symmetric_state.cipher_state) {
rs_len += TAG_SIZE
}
if len(msg) < rs_len {
self.status = .Handshake_Failed
return nil, .Invalid_Handshake_Message
}
rs := dh_buf[:d_len]
if _, status := symmetricstate_decrypt_and_hash(&self.symmetric_state, msg[:rs_len], rs); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
if ecdh.curve(&self.rs) != .Invalid {
panic("crypto/noise: rs was not empty when processing token 's' during ReadMessage")
}
ecdh.public_key_set_bytes(&self.rs, protocol.dh, rs)
msg = msg[rs_len:]
case .ee:
dh := dh_buf[:d_len]
if status := _dh(&self.e, &self.re, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
case .es:
dh := dh_buf[:d_len]
if self.initiator {
if status := _dh(&self.e, &self.rs, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
} else {
if status := _dh(&self.s, &self.re, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
}
case .se:
dh := dh_buf[:d_len]
if self.initiator {
if status := _dh(&self.s, &self.re, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
} else {
if status := _dh(&self.e, &self.rs, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
}
case .ss:
dh := dh_buf[:d_len]
if status := _dh(&self.s, &self.rs, dh); status != .Ok {
self.status = .Handshake_Failed
return nil, status
}
symmetricstate_mix_key(&self.symmetric_state, dh)
case .psk:
symmetricstate_mix_key_and_hash(&self.symmetric_state, self.psk[:])
}
}
self.current_message += 1 // Advance after the current message is successful.
payload: []byte
payload_len := len(msg)
if cipherstate_has_key(&self.symmetric_state.cipher_state) {
if payload_len < TAG_SIZE {
self.status = .Handshake_Failed
return nil, self.status
}
payload_len -= TAG_SIZE
}
did_alloc: bool
if dst != nil {
if len(dst) < payload_len {
self.status = .Handshake_Failed
return nil, .Out_Of_Memory
}
payload = dst[:payload_len]
} else if payload_len > 0 {
err: runtime.Allocator_Error
payload, err = make([]byte, payload_len, allocator)
if err != nil {
self.status = .Handshake_Failed
return nil, .Out_Of_Memory
}
did_alloc = true
}
if _, status := symmetricstate_decrypt_and_hash(&self.symmetric_state, msg, payload); status != .Ok {
if did_alloc {
delete(payload)
}
self.status = .Handshake_Failed
return nil, self.status
}
if self.current_message == len(self.message_pattern.messages) {
self.current_message = -1
self.status = .Handshake_Complete
}
return payload, self.status
}
@(require_results)
protocol_from_string :: proc(self: ^Protocol, protocol_name: string) -> Status {
self^ = Protocol{}
pattern, dh, cipher, hash, status := split_protocol_string(protocol_name)
if status != .Ok {
return status
}
self.handshake_pattern = pattern
self.dh = dh
self.cipher = cipher
self.hash = hash
return .Ok
}

View File

@@ -3132,12 +3132,14 @@ fmt_map :: proc(fi: ^Info, v: any, info: runtime.Type_Info_Map, verb: rune) {
value := runtime.map_cell_index_dynamic(vs, info.map_info.vs, bucket_index)
fmt_arg(&Info{writer = fi.writer}, any{rawptr(key), info.key.id}, verb)
if hash {
io.write_string(fi.writer, " = ", &fi.n)
} else {
io.write_string(fi.writer, "=", &fi.n)
if info.value.size > 0 {
if hash {
io.write_string(fi.writer, " = ", &fi.n)
} else {
io.write_string(fi.writer, "=", &fi.n)
}
fmt_arg(fi, any{rawptr(value), info.value.id}, verb)
}
fmt_arg(fi, any{rawptr(value), info.value.id}, verb)
if do_trailing_comma { io.write_string(fi.writer, ",\n", &fi.n) }
}

View File

@@ -14,7 +14,6 @@ import "core:bufio"
fprint :: proc(f: ^os.File, args: ..any, sep := " ", flush := true) -> int {
buf: [1024]byte
b: bufio.Writer
defer bufio.writer_flush(&b)
bufio.writer_init_with_buf(&b, os.to_stream(f), buf[:])
w := bufio.writer_to_writer(&b)
@@ -25,7 +24,6 @@ fprint :: proc(f: ^os.File, args: ..any, sep := " ", flush := true) -> int {
fprintln :: proc(f: ^os.File, args: ..any, sep := " ", flush := true) -> int {
buf: [1024]byte
b: bufio.Writer
defer bufio.writer_flush(&b)
bufio.writer_init_with_buf(&b, os.to_stream(f), buf[:])
@@ -36,7 +34,6 @@ fprintln :: proc(f: ^os.File, args: ..any, sep := " ", flush := true) -> int {
fprintf :: proc(f: ^os.File, fmt: string, args: ..any, flush := true, newline := false) -> int {
buf: [1024]byte
b: bufio.Writer
defer bufio.writer_flush(&b)
bufio.writer_init_with_buf(&b, os.to_stream(f), buf[:])
@@ -50,7 +47,6 @@ fprintfln :: proc(f: ^os.File, fmt: string, args: ..any, flush := true) -> int {
fprint_type :: proc(f: ^os.File, info: ^runtime.Type_Info, flush := true) -> (n: int, err: io.Error) {
buf: [1024]byte
b: bufio.Writer
defer bufio.writer_flush(&b)
bufio.writer_init_with_buf(&b, os.to_stream(f), buf[:])
@@ -60,7 +56,6 @@ fprint_type :: proc(f: ^os.File, info: ^runtime.Type_Info, flush := true) -> (n:
fprint_typeid :: proc(f: ^os.File, id: typeid, flush := true) -> (n: int, err: io.Error) {
buf: [1024]byte
b: bufio.Writer
defer bufio.writer_flush(&b)
bufio.writer_init_with_buf(&b, os.to_stream(f), buf[:])

View File

@@ -1,6 +1,7 @@
package linalg
import "base:builtin"
import "base:intrinsics"
import "core:math"
@(require_results)
@@ -413,26 +414,12 @@ pow :: proc "contextless" (x, e: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
@(require_results)
ceil :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
when IS_ARRAY(T) {
for i in 0..<len(T) {
out[i] = #force_inline math.ceil(x[i])
}
} else {
out = #force_inline math.ceil(x)
}
return
return _from_simd4(T, intrinsics.simd_ceil(_to_simd4(x)))
}
@(require_results)
floor :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
when IS_ARRAY(T) {
for i in 0..<len(T) {
out[i] = #force_inline math.floor(x[i])
}
} else {
out = #force_inline math.floor(x)
}
return
return _from_simd4(T, intrinsics.simd_floor(_to_simd4(x)))
}
@(require_results)
@@ -447,6 +434,11 @@ round :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) {
return
}
@(require_results)
trunc :: proc "contextless" (x: $T) -> (out: T) where IS_NUMERIC(ELEM_TYPE(T)) {
return _from_simd4(T, intrinsics.simd_trunc(_to_simd4(x)))
}
@(require_results)
fract :: proc "contextless" (x: $T) -> T where IS_FLOAT(ELEM_TYPE(T)) {
f := #force_inline floor(x)
@@ -613,3 +605,46 @@ not :: proc "contextless" (x: $A/[$N]bool) -> (out: A) {
}
return
}
@(require_results)
_to_simd4 :: #force_inline proc "contextless" (a: $T) -> (out: #simd[4]ELEM_TYPE(T)) where IS_NUMERIC(ELEM_TYPE(T)) #no_bounds_check {
when IS_ARRAY(T) {
when len(T) == 1 {
_a: [4]ELEM_TYPE(T)
_a.x = a.x
return transmute(#simd[4]ELEM_TYPE(T))_a
} else when len(T) == 2 {
_a: [4]ELEM_TYPE(T)
_a.xy = a
return transmute(#simd[4]ELEM_TYPE(T))_a
} else when len(T) == 3 {
_a: [4]ELEM_TYPE(T)
_a.xyz = a
return transmute(#simd[4]ELEM_TYPE(T))_a
} else {
return transmute(#simd[4]ELEM_TYPE(T))a
}
} else {
_a: [4]ELEM_TYPE(T)
_a.x = a
return transmute(#simd[4]ELEM_TYPE(T))_a
}
}
@(require_results)
_from_simd4 :: #force_inline proc "contextless" ($T: typeid, a: $V/#simd[4]$E) -> T where IS_NUMERIC(ELEM_TYPE(T)) #no_bounds_check {
when IS_ARRAY(T) {
when len(T) == 1 {
return (transmute([4]ELEM_TYPE(T))a).x
} else when len(T) == 2 {
return (transmute([4]ELEM_TYPE(T))a).xy
} else when len(T) == 3 {
return (transmute([4]ELEM_TYPE(T))a).xyz
} else {
return transmute([4]ELEM_TYPE(T))a
}
} else {
return (transmute([4]ELEM_TYPE(T))a).x
}
}

View File

@@ -46,11 +46,23 @@ scalar_dot :: proc "contextless" (a, b: $T) -> T where IS_FLOAT(T), !IS_ARRAY(T)
@(require_results)
vector_dot :: proc "contextless" (a, b: $T/[$N]$E) -> (c: E) where IS_NUMERIC(E) #no_bounds_check {
for i in 0..<N {
c += a[i] * b[i]
ab := a * b
when N == 1 {
return ab.x
} else when N == 2 {
return ab.x + ab.y
} else when N == 3 {
return ab.x + ab.y + ab.z
} else when N == 4 {
return ab.x + ab.y + ab.z + ab.w
} else {
for elem in ab {
c += elem
}
return c
}
return
}
@(require_results)
quaternion64_dot :: proc "contextless" (a, b: $T/quaternion64) -> (c: f16) {
return a.w*b.w + a.x*b.x + a.y*b.y + a.z*b.z
@@ -86,11 +98,8 @@ vector_cross2 :: proc "contextless" (a, b: $T/[2]$E) -> E where IS_NUMERIC(E) {
}
@(require_results)
vector_cross3 :: proc "contextless" (a, b: $T/[3]$E) -> (c: T) where IS_NUMERIC(E) {
c[0] = a[1]*b[2] - b[1]*a[2]
c[1] = a[2]*b[0] - b[2]*a[0]
c[2] = a[0]*b[1] - b[0]*a[1]
return
vector_cross3 :: proc "contextless" (a, b: $T/[3]$E) -> (c: T) where IS_NUMERIC(E) #no_bounds_check {
return a.yzx*b.zxy - b.yzx*a.zxy
}
@(require_results)
@@ -130,12 +139,12 @@ normalize0 :: proc{vector_normalize0, quaternion_normalize0}
@(require_results)
vector_length :: proc "contextless" (v: $T/[$N]$E) -> E where IS_FLOAT(E) {
return math.sqrt(dot(v, v))
return #force_inline math.sqrt(#force_inline dot(v, v))
}
@(require_results)
vector_length2 :: proc "contextless" (v: $T/[$N]$E) -> E where IS_NUMERIC(E) {
return dot(v, v)
return #force_inline dot(v, v)
}
@(require_results)

View File

@@ -141,9 +141,9 @@ arena_alloc_unguarded :: proc(arena: ^Arena, size: uint, alignment: uint, loc :=
needed := mem.align_forward_uint(size, alignment)
needed = max(needed, arena.default_commit_size)
block_size := max(needed, arena.minimum_block_size) + alignment
block_size := max(needed, arena.minimum_block_size)
new_block := memory_block_alloc(needed, block_size) or_return
new_block := memory_block_alloc(needed, block_size, alignment) or_return
new_block.prev = arena.curr_block
arena.curr_block = new_block
arena.total_reserved += new_block.reserved

View File

@@ -65,8 +65,8 @@ Example:
import vmem "core:mem/virtual"
main :: proc() {
data, err := virtual.map_file_from_path(#file, {.Read})
defer virtual.unmap_file(data)
data, err := vmem.map_file_from_path(#file, {.Read})
defer vmem.unmap_file(data)
fmt.printfln("Error: %v", err)
fmt.printfln("Data: %s", data)
}

View File

@@ -16,8 +16,8 @@ platform_memory_init :: proc "contextless" () {
Allocator_Error :: mem.Allocator_Error
@(require_results, no_sanitize_address)
reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
return _reserve(size)
reserve :: proc "contextless" (size: uint, address_hint := uintptr(0)) -> (data: []byte, err: Allocator_Error) {
return _reserve(size, address_hint)
}
@(no_sanitize_address)
@@ -115,7 +115,7 @@ memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags
}
pmblock.block.committed = committed
pmblock.block.reserved = reserved
pmblock.block.reserved = total_size - uint(base_offset)
return &pmblock.block, nil

View File

@@ -2,8 +2,8 @@ package mem_virtual
import "core:sys/posix"
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
result := posix.mmap(nil, size, {}, {.ANONYMOUS, .PRIVATE})
_reserve :: proc "contextless" (size: uint, address_hint: uintptr) -> (data: []byte, err: Allocator_Error) {
result := posix.mmap(rawptr(address_hint), size, {}, {.ANONYMOUS, .PRIVATE})
if result == posix.MAP_FAILED {
assert_contextless(posix.errno() == .ENOMEM)
return nil, .Out_Of_Memory

View File

@@ -2,14 +2,14 @@ package mem_virtual
import "core:sys/posix"
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
_reserve :: proc "contextless" (size: uint, address_hint: uintptr) -> (data: []byte, err: Allocator_Error) {
PROT_MAX :: proc "contextless" (flags: posix.Prot_Flags) -> posix.Prot_Flags {
_PROT_MAX_SHIFT :: 16
return transmute(posix.Prot_Flags)(transmute(i32)flags << _PROT_MAX_SHIFT)
}
result := posix.mmap(nil, size, PROT_MAX({.READ, .WRITE, .EXEC}), {.ANONYMOUS, .PRIVATE})
result := posix.mmap(rawptr(address_hint), size, PROT_MAX({.READ, .WRITE, .EXEC}), {.ANONYMOUS, .PRIVATE})
if result == posix.MAP_FAILED {
assert_contextless(posix.errno() == .ENOMEM)
return nil, .Out_Of_Memory

View File

@@ -4,8 +4,8 @@ package mem_virtual
import "core:sys/linux"
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
addr, errno := linux.mmap(0, size, {}, {.PRIVATE, .ANONYMOUS})
_reserve :: proc "contextless" (size: uint, address_hint: uintptr) -> (data: []byte, err: Allocator_Error) {
addr, errno := linux.mmap(address_hint, size, {}, {.PRIVATE, .ANONYMOUS})
if errno == .ENOMEM {
return nil, .Out_Of_Memory
} else if errno == .EINVAL {
@@ -68,4 +68,4 @@ _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags)
_unmap_file :: proc "contextless" (data: []byte) {
_release(raw_data(data), uint(len(data)))
}
}

View File

@@ -2,13 +2,13 @@ package mem_virtual
import "core:sys/posix"
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
_reserve :: proc "contextless" (size: uint, address_hint: uintptr) -> (data: []byte, err: Allocator_Error) {
PROT_MPROTECT :: proc "contextless" (flags: posix.Prot_Flags) -> posix.Prot_Flags {
return transmute(posix.Prot_Flags)(transmute(i32)flags << 3)
}
result := posix.mmap(nil, size, PROT_MPROTECT({.READ, .WRITE, .EXEC}), {.ANONYMOUS, .PRIVATE})
result := posix.mmap(rawptr(address_hint), size, PROT_MPROTECT({.READ, .WRITE, .EXEC}), {.ANONYMOUS, .PRIVATE})
if result == posix.MAP_FAILED {
assert_contextless(posix.errno() == .ENOMEM)
return nil, .Out_Of_Memory

View File

@@ -2,8 +2,8 @@ package mem_virtual
import "core:sys/posix"
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
result := posix.mmap(nil, size, {}, {.ANONYMOUS, .PRIVATE})
_reserve :: proc "contextless" (size: uint, address_hint: uintptr) -> (data: []byte, err: Allocator_Error) {
result := posix.mmap(rawptr(address_hint), size, {}, {.ANONYMOUS, .PRIVATE})
if result == posix.MAP_FAILED {
assert_contextless(posix.errno() == .ENOMEM)
return nil, .Out_Of_Memory

View File

@@ -7,7 +7,7 @@
#+build !windows
package mem_virtual
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
_reserve :: proc "contextless" (size: uint, address_hint: uintptr) -> (data: []byte, err: Allocator_Error) {
return nil, nil
}
@@ -35,4 +35,4 @@ _map_file :: proc "contextless" (f: any, size: i64, flags: Map_File_Flags) -> (d
_unmap_file :: proc "contextless" (data: []byte) {
}
}

View File

@@ -87,8 +87,8 @@ foreign Kernel32 {
}
@(no_sanitize_address)
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
result := VirtualAlloc(nil, size, MEM_RESERVE, PAGE_READWRITE)
_reserve :: proc "contextless" (size: uint, address_hint: uintptr) -> (data: []byte, err: Allocator_Error) {
result := VirtualAlloc(rawptr(address_hint), size, MEM_RESERVE, PAGE_READWRITE)
if result == nil {
err = .Out_Of_Memory
return
@@ -191,4 +191,4 @@ _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags)
@(no_sanitize_address)
_unmap_file :: proc "contextless" (data: []byte) {
UnmapViewOfFile(raw_data(data))
}
}

View File

@@ -743,6 +743,11 @@ poll_exec :: proc(op: ^Operation) -> Op_Result {
return .Done
}
if .EOF in op._impl.flags {
op.poll.result = .Ready
return .Done
}
filter: kq.Filter
switch op.poll.event {
case .Receive: filter = .Read

View File

@@ -413,10 +413,15 @@ e.g.
'name.tar.gz' -> 'name.tar'
'name.txt' -> 'name'
Returns an empty string if the path is empty
Returns an empty string if there is no stem. e.g: '.gitignore'.
Returns an empty string if there's a trailing path separator.
*/
stem :: proc(path: string) -> string {
if path == "" {
return ""
}
// If the last character is a path separator, there is no file.
if is_path_separator(path[len(path) - 1]) {
return ""

View File

@@ -377,17 +377,25 @@ _volume_name_len :: proc(path: string) -> (length: int) {
// UNC path, minimum version of the volume is `\\h\s` for host, share.
// Can also contain an IP address in the host position.
// (path might be purely path separators)
slash_count := 0
found_host: bool
for i in prefix..<len(path) {
// Host needs to be at least 1 character
if _is_path_separator(path[i]) && i > 0 {
if _is_path_separator(path[i]) {
slash_count += 1
if slash_count == 2 {
if slash_count == 2 && found_host {
return i
}
} else {
found_host = true
// Found a host but no trailing slash `\\h\s`
if slash_count == 1 && i == len(path)-1 {
return len(path)
}
}
}
return len(path)
return 0
}

View File

@@ -2,6 +2,7 @@
// To process paths such as URLs that depend on forward slashes regardless of the OS, use the slashpath package.
package filepath
import "base:runtime"
import "core:os"
import "core:strings"
@@ -32,7 +33,15 @@ Join all `elems` with the system's path separator and normalize the result.
For example, `join_path({"/home", "foo", "bar.txt"})` will result in `"/home/foo/bar.txt"`.
*/
join :: os.join_path
join :: proc(
elems: []string,
allocator := context.allocator,
) -> (
joined: string,
err: runtime.Allocator_Error,
) {
return os.join_path(elems, allocator)
}
/*
Returns leading volume name.
@@ -136,7 +145,15 @@ long_ext :: os.long_ext
If the result of the path is an empty string, the returned path with be `"."`.
*/
clean :: os.clean_path
clean :: proc(
path: string,
allocator := context.allocator,
) -> (
cleaned: string,
err: runtime.Allocator_Error,
) {
return os.clean_path(path, allocator)
}
/*
Returns the result of replacing each path separator character in the path
@@ -144,7 +161,16 @@ with the specific character `new_sep`.
*Allocates Using Provided Allocator*
*/
replace_path_separators := os.replace_path_separators
replace_separators :: proc(
path: string,
new_sep: rune,
allocator := context.allocator,
) -> (
new_path: string,
err: os.Error,
) {
return os.replace_path_separators(path, new_sep, allocator)
}
/*
Return true if `path` is an absolute path as opposed to a relative one.
@@ -156,7 +182,16 @@ Get the absolute path to `path` with respect to the process's current directory.
*Allocates Using Provided Allocator*
*/
abs :: os.get_absolute_path
@(require_results)
abs :: proc(
path: string,
allocator := context.allocator,
) -> (
absolute_path: string,
error: os.Error,
) {
return os.get_absolute_path(path, allocator)
}
Relative_Error :: enum {
None,
@@ -249,17 +284,7 @@ rel :: proc(base_path, target_path: string, allocator := context.allocator) -> (
`dir` calls `clean` on the path and trailing separators are removed. If the path is empty or consists purely
of separators, then `"."` is returned.
*/
dir :: proc(path: string, allocator := context.allocator) -> string {
i := len(path) > 0 ? len(path) - 1 : 0
for i > 0 && !is_separator(path[i]) {
i -= 1
}
res, dir_err := clean(path[:i], allocator)
if dir_err != nil { return "" }
return res
}
dir :: os.dir
// Splits the PATH-like `path` string, returning an array of its separated components (delete after use).

1854
core/simd/x86/avx.odin Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -3,3 +3,17 @@ package sys_freebsd
/* Get window size */
TIOCGWINSZ :: 0x40087468
/*
Standard input file descriptor
*/
STDIN_FILENO :: Fd(0)
/*
Standard output file descriptor
*/
STDOUT_FILENO :: Fd(1)
/*
Standard error file descriptor
*/
STDERR_FILENO :: Fd(2)

View File

@@ -23,6 +23,7 @@ SYS_recvfrom : uintptr : 29
SYS_accept : uintptr : 30
SYS_getpeername: uintptr : 31
SYS_getsockname: uintptr : 32
SYS_ioctl : uintptr : 54
SYS_fcntl : uintptr : 92
SYS_fsync : uintptr : 95
SYS_socket : uintptr : 97
@@ -634,3 +635,13 @@ accept4_nil :: proc "contextless" (s: Fd, flags: Socket_Flags = {}) -> (Fd, Errn
}
accept4 :: proc { accept4_nil, accept4_T }
ioctl :: proc "contextless" (fd: Fd, request: c.ulong, arg: uintptr) -> (int, Errno) {
result, ok := intrinsics.syscall_bsd(SYS_ioctl, cast(uintptr)fd, arg)
if !ok {
return -1, cast(Errno)result
}
return cast(int)result, nil
}

View File

@@ -2207,7 +2207,7 @@ when ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 {
Available since Linux 1.0.
*/
adjtimex :: proc "contextless" (buf: ^Timex) -> (Clock_State, Errno) {
ret := syscall(SYS_adjtimex)
ret := syscall(SYS_adjtimex, buf)
return errno_unwrap(ret, Clock_State)
}

View File

@@ -0,0 +1,106 @@
#+build windows
package sys_windows
foreign import ioringapi "system:kernel32.lib"
HIORING :: distinct rawptr
IORING_SQE_FLAG :: enum u32 {
DRAIN_PRECEDING_OPS = 0, // 0x00000001
}
IORING_SQE_FLAGS :: bit_set[IORING_SQE_FLAG; u32]
// Reserved for future use; currently no flags defined
IORING_CREATE_REQUIRED_FLAG :: enum u32 {}
IORING_CREATE_REQUIRED_FLAGS :: bit_set[IORING_CREATE_REQUIRED_FLAG; u32]
IORING_CREATE_ADVISORY_FLAG :: enum u32 {
SKIP_BUILDER_PARAM_CHECKS = 0, // 0x00000001
}
IORING_CREATE_ADVISORY_FLAGS :: bit_set[IORING_CREATE_ADVISORY_FLAG; u32]
IORING_CREATE_FLAGS :: struct {
Required: IORING_CREATE_REQUIRED_FLAGS,
Advisory: IORING_CREATE_ADVISORY_FLAGS,
}
IORING_INFO :: struct {
Version: IORING_VERSION,
Flags: IORING_CREATE_FLAGS,
SubmissionQueueSize: UINT32,
CompletionQueueSize: UINT32,
}
IORING_CAPABILITIES :: struct {
MaxVersion: IORING_VERSION,
MaxSubmissionQueueSize: UINT32,
MaxCompletionQueueSize: UINT32,
FeatureFlags: IORING_FEATURE_FLAGS,
}
IORING_REF_KIND :: enum i32 {
RAW,
REGISTERED,
}
IORING_HANDLE_REF :: struct {
Kind: IORING_REF_KIND,
HandleUnion: struct #raw_union {
Handle: HANDLE,
Index: UINT32,
},
}
IORING_BUFFER_REF :: struct {
Kind: IORING_REF_KIND,
BufferUnion: struct #raw_union {
Address: rawptr,
IndexAndOffset: IORING_REGISTERED_BUFFER,
},
}
IORING_CQE :: struct {
UserData: UINT_PTR,
ResultCode: HRESULT,
Information: ULONG_PTR,
}
// Types below are from winbase.h and winnt.h
FILE_WRITE_FLAG :: enum u32 {
WRITE_THROUGH = 0, // 0x000000001
}
FILE_WRITE_FLAGS :: bit_set[FILE_WRITE_FLAG ;u32]
FILE_FLUSH_MODE :: enum i32 {
DEFAULT,
DATA,
MIN_METADATA,
NO_SYNC,
}
FILE_SEGMENT_ELEMENT :: struct #raw_union {
Buffer: PVOID64,
Alignment: ULONGLONG,
}
@(default_calling_convention="system")
foreign ioringapi {
QueryIoRingCapabilities :: proc(capabilities: ^IORING_CAPABILITIES) -> HRESULT ---
IsIoRingOpSupported :: proc(ioRing: HIORING, op: IORING_OP_CODE) -> BOOL ---
CreateIoRing :: proc(ioringVersion: IORING_VERSION, flags: IORING_CREATE_FLAGS, submissionQueueSize: UINT32, completionQueueSize: UINT32, h: ^HIORING) -> HRESULT ---
GetIoRingInfo :: proc(ioRing: HIORING, info: ^IORING_INFO) -> HRESULT ---
SubmitIoRing :: proc(ioRing: HIORING, waitOperations: UINT32, milliseconds: UINT32, submittedEntries: ^UINT32) -> HRESULT ---
CloseIoRing :: proc(ioRing: HIORING) -> HRESULT ---
PopIoRingCompletion :: proc(ioRing: HIORING, cqe: ^IORING_CQE) -> HRESULT ---
SetIoRingCompletionEvent :: proc(ioRing: HIORING, hEvent: HANDLE) -> HRESULT ---
BuildIoRingCancelRequest :: proc(ioRing: HIORING, file: IORING_HANDLE_REF, opToCancel: UINT_PTR, userData: UINT_PTR) -> HRESULT ---
BuildIoRingReadFile :: proc(ioRing: HIORING, fileRef: IORING_HANDLE_REF, dataRef: IORING_BUFFER_REF, numberOfBytesToRead: UINT32, fileOffset: UINT64, userData: UINT_PTR, sqeFlags: IORING_SQE_FLAGS) -> HRESULT ---
BuildIoRingRegisterFileHandles :: proc(ioRing: HIORING, count: UINT32, handles: [^]HANDLE, userData: UINT_PTR) -> HRESULT ---
BuildIoRingRegisterBuffers :: proc(ioRing: HIORING, count: UINT32, buffers: [^]IORING_BUFFER_INFO, userData: UINT_PTR) -> HRESULT ---
BuildIoRingWriteFile :: proc(ioRing: HIORING, fileRef: IORING_HANDLE_REF, bufferRef: IORING_BUFFER_REF, numberOfBytesToWrite: UINT32, fileOffset: UINT64, writeFlags: FILE_WRITE_FLAGS, userData: UINT_PTR, sqeFlags: IORING_SQE_FLAGS) -> HRESULT ---
BuildIoRingFlushFile :: proc(ioRing: HIORING, fileRef: IORING_HANDLE_REF, flushMode: FILE_FLUSH_MODE, userData: UINT_PTR, sqeFlags: IORING_SQE_FLAGS) -> HRESULT ---
BuildIoRingReadFileScatter :: proc(ioRing: HIORING, fileRef: IORING_HANDLE_REF, segmentCount: UINT32, segmentArray: [^]FILE_SEGMENT_ELEMENT, numberOfBytesToRead: UINT32, fileOffset: UINT64, userData: UINT_PTR, sqeFlags: IORING_SQE_FLAGS) -> HRESULT ---
BuildIoRingWriteFileGather :: proc(ioRing: HIORING, fileRef: IORING_HANDLE_REF, segmentCount: UINT32, segmentArray: [^]FILE_SEGMENT_ELEMENT, numberOfBytesToWrite: UINT32, fileOffset: UINT64, writeFlags: FILE_WRITE_FLAGS, userData: UINT_PTR, sqeFlags: IORING_SQE_FLAGS) -> HRESULT ---
}

View File

@@ -50,6 +50,22 @@ foreign ntdll_lib {
EaBuffer: PVOID,
EaLength: ULONG,
) -> NTSTATUS ---
NtAssociateWaitCompletionPacket :: proc(
WaitCompletionPacketHandle: HANDLE,
IoCompletionHandle: HANDLE,
TargetObjectHandle: HANDLE,
KeyContext: PVOID,
ApcContext: PVOID,
IoStatus: NTSTATUS,
IoStatusInformation: ULONG_PTR,
AlreadySignaled: ^BOOLEAN,
) -> NTSTATUS ---
NtDelayExecution :: proc(Alertable: BOOL, DelayInterval: PLARGE_INTEGER) -> NTSTATUS ---
ZwSetTimerResolution :: proc(RequestedResolution: ULONG, Set: BOOLEAN, ActualResolution: PULONG) -> NTSTATUS ---
}

View File

@@ -0,0 +1,42 @@
#+build windows
package sys_windows
IORING_SUBMIT_WAIT_ALL :: max(u32)
IORING_VERSION :: enum i32 {
INVALID,
_1,
_2,
_3 = 300,
_4 = 400,
}
IORING_FEATURE_FLAG :: enum u32 {
UM_EMULATION = 0, // 0x00000001
SET_COMPLETION_EVENT = 1, // 0x00000002
}
IORING_FEATURE_FLAGS :: bit_set[IORING_FEATURE_FLAG; u32]
IORING_OP_CODE :: enum i32 {
NOP,
READ,
REGISTER_FILES,
REGISTER_BUFFERS,
CANCEL,
WRITE,
FLUSH,
READ_SCATTER,
WRITE_GATHER,
}
IORING_BUFFER_INFO :: struct {
Address: rawptr,
Length: u32,
}
IORING_REGISTERED_BUFFER :: struct {
BufferIndex: u32,
Offset: u32,
}

View File

@@ -132,6 +132,7 @@ LPSTARTUPINFOW :: ^STARTUPINFOW
LPTRACKMOUSEEVENT :: ^TRACKMOUSEEVENT
VOID :: rawptr
PVOID :: rawptr
PVOID64 :: rawptr
LPVOID :: rawptr
PINT :: ^INT
LPINT :: ^INT

View File

@@ -3,44 +3,42 @@ The implementation of the `odin test` runner and procedures user tests can use f
Defineables through `#config`:
```odin
// Specify how many threads to use when running tests.
TEST_THREADS : int : #config(ODIN_TEST_THREADS, 0)
// Track the memory used by each test.
TRACKING_MEMORY : bool : #config(ODIN_TEST_TRACK_MEMORY, true)
// Always report how much memory is used, even when there are no leaks or bad frees.
ALWAYS_REPORT_MEMORY : bool : #config(ODIN_TEST_ALWAYS_REPORT_MEMORY, false)
// Treat memory leaks and bad frees as errors.
FAIL_ON_BAD_MEMORY : bool : #config(ODIN_TEST_FAIL_ON_BAD_MEMORY, false)
// Specify how much memory each thread allocator starts with.
PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem. ROLLBACK_STACK_DEFAULT_BLOCK_SIZE)
// Select a specific set of tests to run by name.
// Each test is separated by a comma and may optionally include the package name.
// This may be useful when running tests on multiple packages with `-all-packages`.
// The format is: `package.test_name,test_name_only,...`
TEST_NAMES : string : #config(ODIN_TEST_NAMES, "")
// Show the fancy animated progress report.
// This requires terminal color support, as well as STDOUT to not be redirected to a file.
FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true)
// Copy failed tests to the clipboard when done.
USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false)
// How many test results to show at a time per package.
PROGRESS_WIDTH : int : #config(ODIN_TEST_PROGRESS_WIDTH, 24)
// This is the random seed that will be sent to each test.
// If it is unspecified, it will be set to the system cycle counter at startup.
SHARED_RANDOM_SEED : u64 : #config(ODIN_TEST_RANDOM_SEED, 0)
// Set the lowest log level for this test run.
LOG_LEVEL_DEFAULT : string : "debug" when ODIN_DEBUG else "info"
LOG_LEVEL : string : #config(ODIN_TEST_LOG_LEVEL, LOG_LEVEL_DEFAULT)
// Report a message at the info level when a test has changed its state.
LOG_STATE_CHANGES : bool : #config(ODIN_TEST_LOG_STATE_CHANGES, false)
// Show only the most necessary logging information.
USING_SHORT_LOGS : bool : #config(ODIN_TEST_SHORT_LOGS, false)
// Output a report of the tests to the given path.
JSON_REPORT : string : #config(ODIN_TEST_JSON_REPORT, "")
// Print the full file path for failed test cases on a new line
// in a way that's friendly to regex capture for an editor's "go to error".
GO_TO_ERROR : bool : #config(ODIN_TEST_GO_TO_ERROR, false)
```
// Specify how many threads to use when running tests.
TEST_THREADS : int : #config(ODIN_TEST_THREADS, 0)
// Track the memory used by each test.
TRACKING_MEMORY : bool : #config(ODIN_TEST_TRACK_MEMORY, true)
// Always report how much memory is used, even when there are no leaks or bad frees.
ALWAYS_REPORT_MEMORY : bool : #config(ODIN_TEST_ALWAYS_REPORT_MEMORY, false)
// Treat memory leaks and bad frees as errors.
FAIL_ON_BAD_MEMORY : bool : #config(ODIN_TEST_FAIL_ON_BAD_MEMORY, false)
// Specify how much memory each thread allocator starts with.
PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem. ROLLBACK_STACK_DEFAULT_BLOCK_SIZE)
// Select a specific set of tests to run by name.
// Each test is separated by a comma and may optionally include the package name.
// This may be useful when running tests on multiple packages with `-all-packages`.
// The format is: `package.test_name,test_name_only,...`
TEST_NAMES : string : #config(ODIN_TEST_NAMES, "")
// Show the fancy animated progress report.
// This requires terminal color support, as well as STDOUT to not be redirected to a file.
FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true)
// Copy failed tests to the clipboard when done.
USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false)
// How many test results to show at a time per package.
PROGRESS_WIDTH : int : #config(ODIN_TEST_PROGRESS_WIDTH, 24)
// This is the random seed that will be sent to each test.
// If it is unspecified, it will be set to the system cycle counter at startup.
SHARED_RANDOM_SEED : u64 : #config(ODIN_TEST_RANDOM_SEED, 0)
// Set the lowest log level for this test run.
LOG_LEVEL_DEFAULT : string : "debug" when ODIN_DEBUG else "info"
LOG_LEVEL : string : #config(ODIN_TEST_LOG_LEVEL, LOG_LEVEL_DEFAULT)
// Report a message at the info level when a test has changed its state.
LOG_STATE_CHANGES : bool : #config(ODIN_TEST_LOG_STATE_CHANGES, false)
// Show only the most necessary logging information.
USING_SHORT_LOGS : bool : #config(ODIN_TEST_SHORT_LOGS, false)
// Output a report of the tests to the given path.
JSON_REPORT : string : #config(ODIN_TEST_JSON_REPORT, "")
// Print the full file path for failed test cases on a new line
// in a way that's friendly to regex capture for an editor's "go to error".
GO_TO_ERROR : bool : #config(ODIN_TEST_GO_TO_ERROR, false)
*/
package testing
package testing

View File

@@ -134,7 +134,7 @@ This is a dire bug and should be reported to the Odin developers.
}
signal := local_test_expected_failures.signal
switch signal {
case libc.SIGILL: passed = code == win32.EXCEPTION_ILLEGAL_INSTRUCTION
case libc.SIGILL: passed = code == win32.EXCEPTION_ILLEGAL_INSTRUCTION || code == win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED
case libc.SIGSEGV: passed = code == win32.EXCEPTION_ACCESS_VIOLATION
case libc.SIGFPE:
switch code {
@@ -163,6 +163,7 @@ _setup_signal_handler :: proc() {
// For tests:
// Catch the following:
// - Asserts and panics;
// - Out of Bounds exeptions;
// - Arithmetic errors; and
// - Segmentation faults (illegal memory access).
win32.AddVectoredExceptionHandler(0, stop_test_callback)
@@ -194,10 +195,11 @@ _should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool)
reason = .Successful_Stop
} else {
switch intrinsics.atomic_load(&stop_test_signal) {
case win32.EXCEPTION_ILLEGAL_INSTRUCTION: reason = .Illegal_Instruction
case win32.EXCEPTION_ACCESS_VIOLATION: reason = .Segmentation_Fault
case win32.EXCEPTION_BREAKPOINT: reason = .Unhandled_Trap
case win32.EXCEPTION_SINGLE_STEP: reason = .Unhandled_Trap
case win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED: reason = .Illegal_Instruction
case win32.EXCEPTION_ILLEGAL_INSTRUCTION: reason = .Illegal_Instruction
case win32.EXCEPTION_ACCESS_VIOLATION: reason = .Segmentation_Fault
case win32.EXCEPTION_BREAKPOINT: reason = .Unhandled_Trap
case win32.EXCEPTION_SINGLE_STEP: reason = .Unhandled_Trap
case win32.EXCEPTION_FLT_DENORMAL_OPERAND ..= win32.EXCEPTION_INT_OVERFLOW:
reason = .Arithmetic_Error

View File

@@ -43,6 +43,7 @@ package all
@(require) import "core:crypto/legacy/keccak"
@(require) import "core:crypto/legacy/md5"
@(require) import "core:crypto/legacy/sha1"
@(require) import cnoise "core:crypto/noise"
@(require) import "core:crypto/pbkdf2"
@(require) import "core:crypto/poly1305"
@(require) import "core:crypto/ristretto255"

View File

@@ -48,6 +48,7 @@ package all
@(require) import "core:crypto/legacy/keccak"
@(require) import "core:crypto/legacy/md5"
@(require) import "core:crypto/legacy/sha1"
@(require) import cnoise "core:crypto/noise"
@(require) import "core:crypto/pbkdf2"
@(require) import "core:crypto/poly1305"
@(require) import "core:crypto/ristretto255"

View File

@@ -578,7 +578,7 @@ struct BuildContext {
bool internal_by_value;
bool internal_weak_monomorphization;
bool internal_ignore_llvm_verification;
bool internal_llvm_mem2reg;
bool internal_llvm_no_sroa;
bool enable_rvo;
@@ -2215,18 +2215,18 @@ gb_internal bool check_target_feature_is_enabled(String const &feature, String *
}
if (feature_str == "") break;
if (!string_set_exists(&build_context.target_features_set, str)) {
String plus_str = concatenate_strings(temporary_allocator(), make_string_c("+"), feature_str);
if (want_enabled && !string_set_exists(&build_context.target_features_set, plus_str)) {
if (not_enabled) *not_enabled = str;
return false;
}
}
String plus_str = concatenate_strings(temporary_allocator(), make_string_c("+"), feature_str);
String minus_str = concatenate_strings(temporary_allocator(), make_string_c("-"), feature_str);
if (!want_enabled && !string_set_exists(&build_context.target_features_set, minus_str)) {
bool has_raw = string_set_exists(&build_context.target_features_set, feature_str);
bool has_plus = string_set_exists(&build_context.target_features_set, plus_str);
bool has_minus = string_set_exists(&build_context.target_features_set, minus_str);
// NOTE(jakubtomsu): this way "feature" and "+feature" is ALWAYS equivalent,
// and also allows the minus sign to do a final override.
bool is_enabled = (has_plus || has_raw) && !has_minus;
if (want_enabled != is_enabled) {
if (not_enabled) *not_enabled = str;
return false;
}

View File

@@ -1130,8 +1130,8 @@ gb_internal void check_unroll_range_stmt(CheckerContext *ctx, Ast *node, u32 mod
}
}
check_stmt(ctx, irs->body, mod_flags);
u32 new_flags = mod_flags & ~Stmt_BreakAllowed & ~Stmt_ContinueAllowed;
check_stmt(ctx, irs->body, new_flags);
}
gb_internal void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {

View File

@@ -2840,6 +2840,12 @@ gb_internal i64 check_array_count(CheckerContext *ctx, Operand *o, Ast *e) {
error(e, "Array count too large, %.*s", LIT(str));
gb_free(a, str.text);
return 0;
} else if (o->value.kind == ExactValue_Float) {
u64 u = cast(u64)o->value.value_float;
f64 f = cast(f64)u;
if (f == o->value.value_float) {
return u;
}
}
}

View File

@@ -9,7 +9,9 @@
#if defined(GB_SYSTEM_WINDOWS)
#define NOMINMAX 1
#define NOMINMAX 1
#define WINDOWS_LEAN_AND_MEAN 1
#define VC_EXTRALEAN 1
#include <windows.h>
#undef NOMINMAX
#endif

View File

@@ -2,6 +2,25 @@
#include <malloc.h>
#endif
#ifdef __ANDROID__
// Bionic may not provide aligned_alloc.
// Provide a fallback using posix_memalign and ensure the alignment
// satisfies its requirement (multiple of pointer size).
static void* android_aligned_alloc(size_t align, size_t size) {
void* allocated;
const size_t ptr_size = sizeof(void*);
align = (align + (ptr_size - 1)) / ptr_size * ptr_size;
if (posix_memalign(&allocated, align, size) != 0)
return NULL;
return allocated;
}
#define aligned_alloc android_aligned_alloc
#endif
template <typename U, typename V>
gb_internal gb_inline U bit_cast(V &v) { return reinterpret_cast<U &>(v); }

View File

@@ -289,7 +289,7 @@ gb_internal void print_doc_package(CheckerInfo *info, AstPackage *pkg) {
}
curr_file = e->file;
String filename = remove_directory_from_path(curr_file->fullpath);
print_doc_line(1, "file: %s", filename);
print_doc_line(1, "file: %.*s", LIT(filename));
}
} else {
if (curr_entity_kind != e->kind) {

View File

@@ -4680,6 +4680,8 @@ gb_internal lbAddr lb_build_addr_index_expr(lbProcedure *p, Ast *expr) {
}
}
lbValue val = lb_emit_ptr_offset(p, field, index);
// make sure it's ^T and not [^]T
val.type = alloc_type_multi_pointer_to_pointer(val.type);
return lb_addr(val);
}
@@ -6092,8 +6094,8 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) {
if (sub_sel.index.count > 0) {
item = lb_emit_deep_field_gep(p, item, sub_sel);
}
// make sure it's ^T and not [^]T
item.type = alloc_type_multi_pointer_to_pointer(item.type);
// make sure it's ^T and not [^]T
item.type = alloc_type_multi_pointer_to_pointer(item.type);
return lb_addr(item);
} else if (addr.kind == lbAddr_Swizzle) {

View File

@@ -701,13 +701,20 @@ gb_internal void lb_set_file_line_col(lbProcedure *p, Array<lbValue> arr, TokenP
arr[2] = lb_const_int(p->module, t_i32, col);
}
gb_internal bool lb_bounds_check_short_circuit(lbProcedure *p, lbValue index, lbValue len) {
gb_internal bool lb_bounds_check_disabled(lbProcedure *p) {
if (build_context.no_bounds_check) {
return true;
}
if ((p->state_flags & StateFlag_no_bounds_check) != 0) {
return true;
}
return false;
}
gb_internal bool lb_bounds_check_short_circuit(lbProcedure *p, lbValue index, lbValue len) {
if (lb_bounds_check_disabled(p)) {
return true;
}
if (LLVMIsConstant(index.value) && LLVMIsConstant(len.value)) {
i64 i = LLVMConstIntGetSExtValue(index.value);
@@ -757,10 +764,7 @@ gb_internal void lb_emit_bounds_check(lbProcedure *p, Token token, lbValue index
}
gb_internal void lb_emit_matrix_bounds_check(lbProcedure *p, Token token, lbValue row_index, lbValue column_index, lbValue row_count, lbValue column_count) {
if (build_context.no_bounds_check) {
return;
}
if ((p->state_flags & StateFlag_no_bounds_check) != 0) {
if (lb_bounds_check_disabled(p)) {
return;
}
@@ -783,10 +787,7 @@ gb_internal void lb_emit_matrix_bounds_check(lbProcedure *p, Token token, lbValu
gb_internal void lb_emit_multi_pointer_slice_bounds_check(lbProcedure *p, Token token, lbValue low, lbValue high) {
if (build_context.no_bounds_check) {
return;
}
if ((p->state_flags & StateFlag_no_bounds_check) != 0) {
if (lb_bounds_check_disabled(p)) {
return;
}
@@ -811,6 +812,9 @@ gb_internal void lb_emit_multi_pointer_slice_bounds_check(lbProcedure *p, Token
}
gb_internal void lb_emit_slice_bounds_check(lbProcedure *p, Token token, lbValue low, lbValue high, lbValue len, bool lower_value_used) {
if (lb_bounds_check_disabled(p)) {
return;
}
if (!lower_value_used && lb_bounds_check_short_circuit(p, high, len)) {
return;
}

View File

@@ -3,9 +3,17 @@
array_add(&passes, "function(annotation-remarks)");
break;
case 0:
array_add(&passes, "always-inline");
if (build_context.internal_llvm_mem2reg) {
array_add(&passes, "function<eager-inv>(mem2reg)");
if (build_context.internal_llvm_no_sroa) {
// Old -o:minimal behavior
array_add(&passes, "always-inline");
} else {
array_add(&passes, "annotation2metadata");
array_add(&passes, "inferattrs");
array_add(&passes, "forceattrs");
array_add(&passes, "function<eager-inv>(sroa<modify-cfg>,early-cse<>)");
array_add(&passes, "always-inline");
array_add(&passes, "function<eager-inv>(sroa<modify-cfg>,instsimplify,simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;no-switch-to-lookup;keep-loops;no-hoist-common-insts;no-sink-common-insts;speculate-blocks;simplify-cond-branch>)");
// array_add(&passes, "verify");
}
array_add(&passes, "function(annotation-remarks)");
break;

View File

@@ -402,7 +402,7 @@ enum BuildFlagKind {
BuildFlag_InternalByValue,
BuildFlag_InternalWeakMonomorphization,
BuildFlag_InternalLLVMVerification,
BuildFlag_InternalLLVMMem2Reg,
BuildFlag_InternalLLVMNoSROA,
BuildFlag_InternalEnableRVO,
BuildFlag_Sanitize,
@@ -634,7 +634,7 @@ gb_internal bool parse_build_flags(Array<String> args) {
add_flag(&build_flags, BuildFlag_InternalByValue, str_lit("internal-by-value"), BuildFlagParam_None, Command_all);
add_flag(&build_flags, BuildFlag_InternalWeakMonomorphization, str_lit("internal-weak-monomorphization"), BuildFlagParam_None, Command_all);
add_flag(&build_flags, BuildFlag_InternalLLVMVerification, str_lit("internal-ignore-llvm-verification"), BuildFlagParam_None, Command_all);
add_flag(&build_flags, BuildFlag_InternalLLVMMem2Reg, str_lit("internal-llvm-mem2reg"), BuildFlagParam_None, Command_all);
add_flag(&build_flags, BuildFlag_InternalLLVMNoSROA, str_lit("internal-llvm-no-sroa"), BuildFlagParam_None, Command_all);
add_flag(&build_flags, BuildFlag_InternalEnableRVO, str_lit("internal-enable-rvo"), BuildFlagParam_None, Command_all);
@@ -1628,8 +1628,8 @@ gb_internal bool parse_build_flags(Array<String> args) {
case BuildFlag_InternalLLVMVerification:
build_context.internal_ignore_llvm_verification = true;
break;
case BuildFlag_InternalLLVMMem2Reg:
build_context.internal_llvm_mem2reg = true;
case BuildFlag_InternalLLVMNoSROA:
build_context.internal_llvm_no_sroa = true;
break;
case BuildFlag_InternalEnableRVO:
build_context.enable_rvo = true;
@@ -3973,15 +3973,16 @@ int main(int arg_count, char const **arg_ptr) {
for (;;) {
String item = string_split_iterator(&target_it, ',');
if (item == "") break;
if (*item.text == '+' || *item.text == '-') {
item.text++;
item.len--;
String stripped_item = item;
if (*stripped_item.text == '+' || *stripped_item.text == '-') {
stripped_item.text++;
stripped_item.len--;
}
String invalid;
if (!check_target_feature_is_valid_for_target_arch(item, &invalid) && item != str_lit("help")) {
if (item != str_lit("?")) {
if (!check_target_feature_is_valid_for_target_arch(stripped_item, &invalid) && stripped_item != str_lit("help")) {
if (stripped_item != str_lit("?")) {
gb_printf_err("Unkown target feature '%.*s'.\n", LIT(invalid));
}
gb_printf("Possible -target-features for target %.*s are:\n", LIT(target_arch_names[build_context.metrics.arch]));
@@ -4005,8 +4006,25 @@ int main(int arg_count, char const **arg_ptr) {
return 1;
}
string_set_add(&build_context.target_features_set, item);
// Ensure the feature name always has +/- prefix. If there isn't, default to '+'
String feature_str = item;
if (*feature_str.text != '+' && *feature_str.text != '-') {
feature_str = concatenate_strings(temporary_allocator(), make_string_c("+"), feature_str);
}
// Ensure there is only a single entry for each feature in the target set.
// If the negative exists, override the existing value with the current one.
String neg_feature_str = clone_string(temporary_allocator(), feature_str);
switch (*neg_feature_str.text) {
case '+': *neg_feature_str.text = '-'; break;
case '-': *neg_feature_str.text = '+'; break;
default: GB_ASSERT(false); break;
}
string_set_remove(&build_context.target_features_set, neg_feature_str);
string_set_add(&build_context.target_features_set, feature_str);
}
}

View File

@@ -508,6 +508,13 @@ gb_internal String filename_without_directory(String s) {
return substring(s, gb_max(j+1, 0), s.len);
}
gb_internal String clone_string(gbAllocator a, String const &x) {
u8 *data = gb_alloc_array(a, u8, x.len+1);
gb_memmove(data, x.text, x.len);
data[x.len] = 0;
return make_string(data, x.len);
}
gb_internal String concatenate_strings(gbAllocator a, String const &x, String const &y) {
isize len = x.len+y.len;
u8 *data = gb_alloc_array(a, u8, len+1);

1
tests/core/assets/Noise/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.txt

View File

@@ -0,0 +1,25 @@
package test_crypto_common
import "core:bytes"
import "core:encoding/hex"
// Common helpers for cryptography tests.
Hex_Bytes :: string
hexbytes_compare :: proc(x: Hex_Bytes, b: []byte, allocator := context.allocator) -> bool {
dst := hexbytes_decode(x)
defer delete(dst)
return bytes.equal(dst, b)
}
hexbytes_decode :: proc(x: Hex_Bytes, allocator := context.allocator) -> []byte {
dst, ok := hex.decode(transmute([]byte)(x), allocator)
if !ok {
panic("Hex_Bytes: invalid hex encoding")
}
return dst
}

View File

@@ -0,0 +1,440 @@
package test_noise
import "core:crypto/ecdh"
import "core:crypto/noise"
import "core:log"
import "core:mem"
import "core:os"
import "core:strings"
import "core:testing"
import "../common"
ARENA_SIZE :: 8 * 1024 * 1024
BASE_PATH :: ODIN_ROOT + "tests/core/assets/Noise"
@(test)
print_test_vector_path :: proc(t: ^testing.T) {
log.infof("noise path: %s", BASE_PATH)
}
@(test)
test_vectors_snow :: proc(t: ^testing.T) {
arena: mem.Arena
arena_backing := make([]byte, ARENA_SIZE)
defer delete(arena_backing)
mem.arena_init(&arena, arena_backing)
context.allocator = mem.arena_allocator(&arena)
log.debug("noise/snow: starting")
F :: "snow.txt"
fn, _ := os.join_path([]string{BASE_PATH, F}, context.allocator)
defer delete(fn)
test_vectors: Test_Vectors
testing.expectf(t, load(&test_vectors, fn), "unable to load {}", fn)
run_test_vectors(t, F, &test_vectors)
}
@(test)
test_vectors_noise_c_basic :: proc(t: ^testing.T) {
arena: mem.Arena
arena_backing := make([]byte, ARENA_SIZE)
defer delete(arena_backing)
mem.arena_init(&arena, arena_backing)
context.allocator = mem.arena_allocator(&arena)
log.debug("noise/noise-c-basic: starting")
F :: "noise-c-basic.txt"
fn, _ := os.join_path([]string{BASE_PATH, F}, context.allocator)
defer delete(fn)
test_vectors: Test_Vectors
testing.expectf(t, load(&test_vectors, fn), "unable to load {}", fn)
run_test_vectors(t, F, &test_vectors)
}
@(test)
test_vectors_cacophony :: proc(t: ^testing.T) {
arena: mem.Arena
arena_backing := make([]byte, ARENA_SIZE)
defer delete(arena_backing)
mem.arena_init(&arena, arena_backing)
context.allocator = mem.arena_allocator(&arena)
log.debug("noise/cacophony: starting")
F :: "cacophony.txt"
fn, _ := os.join_path([]string{BASE_PATH, F}, context.allocator)
defer delete(fn)
test_vectors: Test_Vectors
testing.expectf(t, load(&test_vectors, fn), "unable to load {}", fn)
run_test_vectors(t, F, &test_vectors)
}
run_test_vectors :: proc(t: ^testing.T, f: string, tvs: ^Test_Vectors) {
num_ran, num_passed, num_failed, num_skipped: int
tv_loop: for &v, i in tvs.vectors {
num_ran += 1
protocol_name: string
switch {
case v.protocol_name != "":
protocol_name = v.protocol_name
case v.name != "":
// Old test vector format used by the C impl.
v.protocol_name = v.name
protocol_name = v.name
}
// Skip unsupported test vectors
if v.fail {
num_skipped += 1
log.debugf("%s[%d]: %s - skipped, fail tests not supported", f, i, protocol_name)
continue
}
if v.fallback {
num_skipped += 1
log.debugf("%s[%d]: %s - skipped, fallback patterns not supported", f, i, protocol_name)
continue
}
if strings.has_prefix(protocol_name, "NoisePSK") {
num_skipped += 1
log.debugf("%s[%d]: %s - skipped, Old PSK not supported", f, i, protocol_name)
continue
}
if len(v.init_psks) > 1 || len(v.resp_psks) > 1 {
num_skipped += 1
log.debugf("%s[%d]: %s - skipped, Multi-PSK not supported", f, i, protocol_name)
continue
}
// Initialize Handshake_Statuses
pattern, dh, _, _, status := noise.split_protocol_string(protocol_name)
if !testing.expectf(t, status == .Ok, "%s[%d]: failed to parse protocol '%s': %v", f, i, protocol_name, status) {
num_failed += 1
continue
}
ini_hs, res_hs: noise.Handshake_State
if !testing.expect(t, handshake_states_from_tv(t, &ini_hs, &res_hs, &v, dh, f, i)) {
num_failed += 1
continue
}
defer noise.handshake_reset(&ini_hs)
defer noise.handshake_reset(&res_hs)
// Play back the messages
if !testing.expectf(
t,
replay_messages_from_tv_rw(t, &ini_hs, &v, pattern, f),
"%s[%d]: %s - failed to playback messages (initiator)", f, i, protocol_name,
) {
num_failed += 1
continue
}
if !testing.expectf(
t,
replay_messages_from_tv_rw(t, &res_hs, &v, pattern, f),
"%s[%d]: %s - failed to playback messages (responder)", f, i, protocol_name,
) {
num_failed += 1
continue
}
// Check handshake hash/peer identities.
if v.handshake_hash != "" {
ini_hash, _ := noise.handshake_hash(&ini_hs)
res_hash, _ := noise.handshake_hash(&res_hs)
if !testing.expectf(
t,
common.hexbytes_compare(v.handshake_hash, ini_hash),
"%s[%d]: %s - invalid initiator handshake hash: %x expected: %s", f, i, protocol_name, ini_hash, v.handshake_hash,
) {
num_failed += 1
continue
}
if !testing.expectf(
t,
common.hexbytes_compare(v.handshake_hash, res_hash),
"%s[%d]: %s - invalid responder handshake hash: %x expected: %s", f, i, protocol_name, res_hash, v.handshake_hash,
) {
num_failed += 1
continue
}
}
if ecdh.curve(&ini_hs.s) != .Invalid {
pub_key, _ := noise.handshake_peer_identity(&res_hs)
if !testing.expectf(
t,
pub_key != nil && ecdh.public_key_equal(&ini_hs.s._pub_key, pub_key),
"%s[%d]: %s - invalid initiator static public key known by responder", f, i, protocol_name,
) {
num_failed += 1
continue
}
}
if ecdh.curve(&res_hs.s) != .Invalid {
pub_key, _ := noise.handshake_peer_identity(&ini_hs)
if !testing.expectf(
t,
pub_key != nil && ecdh.public_key_equal(&res_hs.s._pub_key, pub_key),
"%s[%d]: %s - invalid responder static public key known by initiator", f, i, protocol_name,
) {
num_failed += 1
continue
}
}
log.debugf("%s[%d]: %s - Passed", f, i, protocol_name)
num_passed += 1
}
assert(num_ran == len(tvs.vectors))
assert(num_passed + num_failed + num_skipped == num_ran)
log.infof(
"%s: ran %d, passed %d, failed %d, skipped %d",
f,
num_ran,
num_passed,
num_failed,
num_skipped,
)
}
handshake_states_from_tv :: proc(
t: ^testing.T,
ini_hs, res_hs: ^noise.Handshake_State,
v: ^Vector,
dh: ecdh.Curve,
f: string,
i: int,
) -> bool {
protocol_name := v.protocol_name
ini_static, ini_ephemeral: ecdh.Private_Key
res_static, res_ephemeral: ecdh.Private_Key
ini_res_static, res_ini_static: ecdh.Public_Key
ini_s, ini_e: ^ecdh.Private_Key
ini_s_p, ini_e_p: ^ecdh.Public_Key
ini_r_s: ^ecdh.Public_Key
if len(v.init_static) != 0 {
if !testing.expectf(
t,
ecdh.private_key_set_bytes(&ini_static, dh, common.hexbytes_decode(v.init_static)),
"%s[%d]: %s - failed to parse init_static", f, i, protocol_name,
) {
return false
}
ini_s = &ini_static
ini_s_p = &ini_s._pub_key
}
if len(v.init_ephemeral) != 0 {
if !testing.expectf(
t,
ecdh.private_key_set_bytes(&ini_ephemeral, dh, common.hexbytes_decode(v.init_ephemeral)),
"%s[%d]: %s - failed to parse init_ephemeral", f, i, protocol_name,
) {
return false
}
ini_e = &ini_ephemeral
ini_e_p = &ini_e._pub_key
}
if len(v.init_remote_static) != 0 {
if !testing.expectf(
t,
ecdh.public_key_set_bytes(&ini_res_static, dh, common.hexbytes_decode(v.init_remote_static)),
"%s[%d]: %s - failed to parse init_remote_static", f, i, protocol_name,
) {
return false
}
ini_r_s = &ini_res_static
}
res_s, res_e: ^ecdh.Private_Key
res_s_p, res_e_p: ^ecdh.Public_Key
res_i_s: ^ecdh.Public_Key
if len(v.resp_static) != 0 {
if !testing.expectf(
t,
ecdh.private_key_set_bytes(&res_static, dh, common.hexbytes_decode(v.resp_static)),
"%s[%d]: %s - failed to parse resp_static", f, i, protocol_name,
) {
return false
}
res_s = &res_static
res_s_p = &res_s._pub_key
}
if len(v.resp_ephemeral) != 0 {
if !testing.expectf(
t,
ecdh.private_key_set_bytes(&res_ephemeral, dh, common.hexbytes_decode(v.resp_ephemeral)),
"%s[%d]: %s - failed to parse resp_ephemeral", f, i, protocol_name,
) {
return false
}
res_e = &res_ephemeral
res_e_p = &res_e._pub_key
}
if len(v.resp_remote_static) != 0 {
if !testing.expectf(
t,
ecdh.public_key_set_bytes(&res_ini_static, dh, common.hexbytes_decode(v.resp_remote_static)),
"%s[%d]: %s - failed to parse remote_init_static", f, i, protocol_name,
) {
return false
}
res_i_s = &res_ini_static
}
ini_psk, res_psk: []byte
if len(v.init_psks) > 0 {
ini_psk = common.hexbytes_decode(v.init_psks[0])
}
if len(v.resp_psks) > 0 {
res_psk = common.hexbytes_decode(v.resp_psks[0])
}
status := noise.handshake_init(
ini_hs,
true,
common.hexbytes_decode(v.init_prologue),
ini_s,
ini_r_s,
protocol_name,
ini_psk,
ini_e,
)
if !testing.expectf(
t,
status == .Ok,
"%s[%d]: %s - failed to initialize ini_hs: %v", f, i, protocol_name, status,
) {
return false
}
status = noise.handshake_init(
res_hs,
false,
common.hexbytes_decode(v.resp_prologue),
res_s,
res_i_s,
protocol_name,
res_psk,
res_e,
)
if !testing.expectf(
t,
status == .Ok,
"%s[%d]: %s - failed to initialize res_hs: %v", f, i, protocol_name, status,
) {
return false
}
return true
}
replay_messages_from_tv_rw :: proc(
t: ^testing.T,
hs: ^noise.Handshake_State,
v: ^Vector,
pattern: noise.Handshake_Pattern,
f: string,
) -> bool {
protocol_name := v.protocol_name
pattern_is_one_way := noise.pattern_is_one_way(pattern)
is_initiator := hs.initiator
cs: noise.Cipher_States
defer noise.cipherstates_reset(&cs)
hs_done: bool
for &msg, i in &v.messages {
dst: []byte
status: noise.Status
expected: common.Hex_Bytes
switch hs_done {
case false:
if (i & 1 == 0) == is_initiator {
dst, status = noise.handshake_write_message(hs, common.hexbytes_decode(msg.payload))
expected = msg.ciphertext
} else {
dst, status = noise.handshake_read_message(hs, common.hexbytes_decode(msg.ciphertext))
expected = msg.payload
}
defer delete(dst)
if !testing.expectf(
t,
status == .Handshake_Pending || status == .Handshake_Complete,
"%s: %s[%d] - unexpected handshake status: %v", f, protocol_name, i, status,
) {
return false
}
if !testing.expectf(
t,
common.hexbytes_compare(expected, dst),
"%s: %s[%d] - unexpected message/payload: %x expected: %s", f, protocol_name, i, dst, expected,
) {
return false
}
if status == .Handshake_Complete {
status = noise.handshake_split(hs, &cs)
if !testing.expectf(
t,
status == .Ok,
"%s: %s[%d] - handshake_split failed: %v", f, protocol_name, i, status,
) {
return false
}
hs_done = true
}
case true:
// The messages that use the derived cipherstates just follow the
// handshake message(s), and the flow continues.
if pattern_is_one_way {
// Except one-way patterns which go from initiator to responder.
if is_initiator {
dst, status = noise.seal_message(&cs, nil, common.hexbytes_decode(msg.payload))
expected = msg.ciphertext
} else {
dst, status = noise.open_message(&cs, nil, common.hexbytes_decode(msg.ciphertext))
expected = msg.payload
}
} else {
if (i & 1 == 0) == is_initiator {
dst, status = noise.seal_message(&cs, nil, common.hexbytes_decode(msg.payload))
expected = msg.ciphertext
} else {
dst, status = noise.open_message(&cs, nil, common.hexbytes_decode(msg.ciphertext))
expected = msg.payload
}
}
defer delete(dst)
if !testing.expectf(
t,
status == .Ok,
"%s: %s[%d] - seal/open failed: %v", f, protocol_name, i, status,
) {
return false
}
if !testing.expectf(
t,
common.hexbytes_compare(expected, dst),
"%s: %s[%d] - unexpected ciphertext/plaintext: %x expected: %s", f, protocol_name, i, dst, expected,
) {
return false
}
}
}
return true
}

View File

@@ -0,0 +1,56 @@
package test_noise
import "core:encoding/json"
import "core:log"
import "core:os"
import "../common"
Message :: struct {
payload: common.Hex_Bytes `json:"payload"`,
ciphertext: common.Hex_Bytes `json:"ciphertext"`,
}
Vector :: struct {
name: string `json:"name"`,
protocol_name: string `json:"protocol_name"`,
fail: bool `json:"fail"`,
fallback: bool `json:"fallback"`,
fallback_pattern: string `json:"fallback_pattern"`,
init_prologue: common.Hex_Bytes `json:"init_prologue"`,
init_psks: []common.Hex_Bytes `json:"init_psks"`,
init_static: common.Hex_Bytes `json:"init_static"`,
init_ephemeral: common.Hex_Bytes `json:"init_ephemeral"`,
init_remote_static: common.Hex_Bytes `json:"init_remote_static"`,
resp_prologue: common.Hex_Bytes `json:"resp_prologue"`,
resp_psks: []common.Hex_Bytes `json:"resp_psks"`,
resp_static: common.Hex_Bytes `json:"resp_static"`,
resp_ephemeral: common.Hex_Bytes `json:"resp_ephemeral"`,
resp_remote_static: common.Hex_Bytes `json:"resp_remote_static"`,
handshake_hash: common.Hex_Bytes `json:"handshake_hash"`,
messages: []Message `json:"messages"`,
}
Test_Vectors :: struct {
vectors: []Vector `json:"vectors"`,
}
load :: proc(tvs: ^Test_Vectors, fn: string) -> bool {
raw_json, err := os.read_entire_file_from_path(fn, context.allocator)
if err != os.ERROR_NONE {
log.error("failed to load raw JSON")
return false
}
if err := json.unmarshal(raw_json, tvs); err != nil {
log.errorf("failed to parse JSON: %v", err)
return false
}
return true
}

View File

@@ -0,0 +1,306 @@
package test_core_crypto
import "core:bytes"
import "core:crypto"
import "core:crypto/aead"
import "core:crypto/ecdh"
import "core:crypto/hash"
import "core:crypto/noise"
import "core:fmt"
import "core:log"
import "core:math/rand"
import "core:testing"
@(private = "file")
DH_CURVES :: []ecdh.Curve {
.X25519,
.X448,
}
@(private = "file")
CIPHERS :: []aead.Algorithm{
.AES_GCM_256,
.CHACHA20POLY1305,
}
@(private = "file")
HASHES :: []hash.Algorithm{
.SHA256,
.SHA512,
.BLAKE2S,
.BLAKE2B,
}
@(test)
test_supported_protocols :: proc(t: ^testing.T) {
if !crypto.HAS_RAND_BYTES {
log.info("rand_bytes not supported - skipping")
return
}
protocol: Test_Protocol
for pattern in noise.Handshake_Pattern {
if pattern == .Invalid {
continue
}
protocol.handshake_pattern = pattern
for dh in DH_CURVES {
protocol.dh = dh
for cipher in CIPHERS {
protocol.cipher = cipher
for hash in HASHES {
protocol.hash = hash
if !testing.expectf(
t,
test_noise_one_protocol(t, &protocol, context.temp_allocator),
"Failed protocol: %v", protocol,
) {
testing.fail(t)
break
}
}
}
}
}
}
@(private = "file")
test_noise_one_protocol :: proc(t: ^testing.T, protocol: ^Test_Protocol, allocator := context.allocator) -> bool {
protocol_name := test_protocol_string(protocol, allocator)
defer delete(protocol_name, allocator)
log.debugf("crypto/noise: %s", protocol_name)
is_one_way := noise.pattern_is_one_way(protocol.handshake_pattern)
initiator_s, responder_s: ecdh.Private_Key
ini_s, res_s: ^ecdh.Private_Key
ini_s_pub, res_s_pub: ^ecdh.Public_Key
pre, hs := noise.pattern_requires_initiator_s(protocol.handshake_pattern)
if pre || hs {
if !testing.expect(t, ecdh.private_key_generate(&initiator_s, protocol.dh), "failed to generate initiator s") {
return false
}
ini_s = &initiator_s
if pre {
ini_s_pub = &initiator_s._pub_key
}
}
pre, hs = noise.pattern_requires_responder_s(protocol.handshake_pattern)
if pre || hs {
if !testing.expect(t, ecdh.private_key_generate(&responder_s, protocol.dh), "failed to generate responder s") {
return false
}
res_s = &responder_s
if pre {
res_s_pub = &responder_s._pub_key
}
}
psk_buf: [32]byte = ---
psk: []byte
if noise.pattern_is_psk(protocol.handshake_pattern) {
crypto.rand_bytes(psk_buf[:])
psk = psk_buf[:]
}
ini_hs, res_hs: noise.Handshake_State
status := noise.handshake_init(&ini_hs, true, nil, ini_s, res_s_pub, protocol_name, psk)
if !testing.expectf(t, status == .Ok, "failed to initialize initiator Handshake_State: %v", status) {
return false
}
status = noise.handshake_init(&res_hs, false, nil, res_s, ini_s_pub, protocol_name, psk)
if !testing.expectf(t, status == .Ok, "failed to initialize responder Handshake_State: %v", status) {
return false
}
ini_status, res_status: noise.Status
ini_msg, res_msg: []byte
ini_payload, res_payload: []byte
hs_msg_buf: [noise.MAX_STEP_MSG_SIZE]byte
for i := 0; ; i += 1 {
if ini_status == .Handshake_Complete && res_status == .Handshake_Complete {
break
}
// Test the allocation path
res_msg, res_payload, ini_status = noise.handshake_initiator_step(&ini_hs, ini_msg, allocator = allocator)
ini_msg = nil
if ini_status == .Handshake_Complete && res_status == .Handshake_Complete {
break
}
if !testing.expectf(t, len(res_payload) == 0, "step %d: unexpected responder payload: %x", i, res_payload) {
return false
}
if !testing.expectf(t, ini_status == .Handshake_Pending || ini_status == .Handshake_Complete, "step %d: initiator step failed: %v", i, ini_status) {
return false
}
// Test the non-allocation path
ini_msg, ini_payload, res_status = noise.handshake_responder_step(&res_hs, res_msg, nil, dst = hs_msg_buf[:])
delete(res_msg, allocator)
res_msg = nil
if !testing.expectf(t, len(ini_payload) == 0, "step %d: unexpected initiator payload: %x", i, ini_payload) {
return false
}
if !testing.expectf(t, res_status == .Handshake_Pending || res_status == .Handshake_Complete, "step %d: responder step failed: %v", i, res_status) {
return false
}
}
delete(res_msg, allocator)
hs_pub: ^ecdh.Public_Key
if ini_s != nil {
hs_pub, status = noise.handshake_peer_identity(&res_hs)
if !testing.expect(t, status == .Ok) {
return false
}
if !testing.expectf(t, ecdh.public_key_equal(&ini_s._pub_key, hs_pub), "responder has incorrect initiator identity") {
return false
}
}
if res_s != nil {
hs_pub, status = noise.handshake_peer_identity(&ini_hs)
if !testing.expect(t, status == .Ok) {
return false
}
if !testing.expectf(t, ecdh.public_key_equal(&res_s._pub_key, hs_pub), "initiator has incorrect responder identity") {
return false
}
}
h1, h2: []byte
h1, status = noise.handshake_hash(&ini_hs)
if !testing.expect(t, status == .Ok) {
return false
}
h2, status = noise.handshake_hash(&res_hs)
if !testing.expect(t, status == .Ok) {
return false
}
if !testing.expectf(t, bytes.equal(h1, h2), "handshake hash mismatch: %x != %x", h1, h2) {
return false
}
ini_cs, res_cs: noise.Cipher_States
if !testing.expectf(t, .Ok == noise.handshake_split(&ini_hs, &ini_cs), "failed to split initiator: %v") {
return false
}
if !testing.expectf(t, .Ok == noise.handshake_split(&res_hs, &res_cs), "failed to split responder: %v") {
return false
}
noise.handshake_reset(&ini_hs)
noise.handshake_reset(&res_hs)
if !testing.expect(t, test_messages(t, &ini_cs, &res_cs, is_one_way, allocator), "message tests failed") {
return false
}
noise.cipherstates_reset(&ini_cs)
noise.cipherstates_reset(&res_cs)
return true
}
@(private = "file")
test_messages :: proc(t: ^testing.T, ini_cs, res_cs: ^noise.Cipher_States, is_one_way: bool, allocator := context.allocator) -> bool {
ad_buf: [256]byte = ---
payload_buf: [noise.MAX_PACKET_SIZE-noise.TAG_SIZE]byte = ---
for i in 0..<10 {
ad := ad_buf[:rand.int_max(len(ad_buf))]
payload := payload_buf[:rand.int_max(len(payload_buf))]
_ = rand.read(payload)
_ = rand.read(ad)
// Initiator -> Responder (allocate buffers)
tx_msg, status := noise.seal_message(ini_cs, ad, payload, allocator = allocator)
defer delete(tx_msg, allocator)
if !testing.expectf(t, status == .Ok, "i->r %d: seal failed: %v", i, status) {
return false
}
rx_dst: []byte
rx_dst, status = noise.open_message(res_cs, ad, tx_msg, allocator = allocator)
defer delete(rx_dst, allocator)
if !testing.expectf(t, status == .Ok, "i->r %d: open failed: %v", i, status) {
return false
}
if !testing.expectf(t, bytes.equal(rx_dst, payload), "i->r %d: payload mismatch") {
return false
}
if i == 5 {
status = noise.cipherstates_rekey(ini_cs, true)
if !testing.expectf(t, status == .Ok, "i %d: rekey failed: %v", i, status) {
return false
}
status = noise.cipherstates_rekey(res_cs, false)
if !testing.expectf(t, status == .Ok, "r %d: rekey failed: %v", i, status) {
return false
}
}
if is_one_way {
continue
}
// Responder -> Initiator (reuse allocated buffers)
tx_msg, status = noise.seal_message(res_cs, ad, payload, tx_msg)
if !testing.expectf(t, status == .Ok, "r->i %d: seal failed: %v", i, status) {
return false
}
_, status = noise.open_message(ini_cs, ad, tx_msg, rx_dst)
if !testing.expectf(t, status == .Ok, "r->i %d: open failed: %v", i, status) {
return false
}
if !testing.expectf(t, bytes.equal(rx_dst, payload), "r-i %d: payload mismatch") {
return false
}
}
return true
}
@(private = "file")
Test_Protocol :: struct {
handshake_pattern: noise.Handshake_Pattern,
dh: ecdh.Curve,
cipher: aead.Algorithm,
hash: hash.Algorithm,
}
@(private = "file")
test_protocol_string :: proc(protocol: ^Test_Protocol, allocator := context.allocator) -> string {
dh: string
#partial switch protocol.dh {
case .X25519: dh = "25519"
case .X448: dh = "448"
case: panic("crypto/noise: unsupported DH")
}
cipher: string
#partial switch protocol.cipher {
case .AES_GCM_256: cipher = "AESGCM"
case .CHACHA20POLY1305: cipher = "ChaChaPoly"
case: panic("crypto/noise: unsupported cipher")
}
hash: string
#partial switch protocol.hash {
case .SHA256: hash = "SHA256"
case .SHA512: hash = "SHA512"
case .BLAKE2S: hash = "BLAKE2s"
case .BLAKE2B: hash = "BLAKE2b"
case: panic("crypto/noise: unsupported hash")
}
return fmt.aprintf("Noise_%v_%v_%v_%v", protocol.handshake_pattern, dh, cipher, hash, allocator = allocator)
}

View File

@@ -4,8 +4,6 @@ import "core:crypto/hash"
import "core:fmt"
import "core:strings"
panic_fn :: proc(arg: any)
hash_name_to_algorithm :: proc(alg_str: string) -> (hash.Algorithm, bool) {
alg_enums := [][hash.Algorithm]string {
hash.ALGORITHM_NAMES,

View File

@@ -1,10 +0,0 @@
#+build !linux
package test_wycheproof
case_should_panic :: proc(fn: panic_fn, fn_arg: any, panic_str: string) -> bool {
panic("helpers: testing for panic is unsupported on this target")
}
can_test_panic :: proc() -> bool {
return false
}

View File

@@ -1,71 +0,0 @@
#+build linux
package test_wycheproof
import "core:log"
import "core:os"
import "core:strings"
import "core:sys/linux"
@(private)
PIPE_BUF :: 4096
case_should_panic :: proc(fn: panic_fn, fn_arg: any, panic_str: string) -> bool {
stderr_pipe: [2]linux.Fd
if err := linux.pipe2(&stderr_pipe, linux.Open_Flags{}); err != .NONE {
log.errorf("panic_case: failed to create pipe: %v", err)
return false
}
pid, err := linux.fork()
switch {
case err != .NONE:
log.errorf("panic_case: failed to fork: %v", err)
return false
case pid == 0:
// In the child, redirect stderr to the pipe, run the function that
// is supposed to panic, and exit normally.
linux.dup2(stderr_pipe[1], 2)
fn(fn_arg)
os.exit(0)
}
// Parent.
defer linux.close(stderr_pipe[0])
defer linux.close(stderr_pipe[1])
// Wait for the child to terminate, and ensure it terminated
// abnormally (SIGILL/SIGTRAP).
wait_status: u32
linux.wait4(pid, &wait_status, linux.Wait_Options{}, nil)
if !linux.WIFSIGNALED(wait_status) {
log.errorf("panic_case: child did not terminate via signal: %x", wait_status)
return false
}
term_sig := linux.Signal(linux.WTERMSIG(wait_status))
if term_sig != .SIGILL && term_sig != .SIGTRAP {
log.errorf("panic_case: child terminated via wrong signal: %v", term_sig)
return false
}
// Consume the child's stderr output from the pipe buffer.
//
// Note: POSIX requires PIPE_BUF be >= 512 bytes, Linux defaults
// to 4096 bytes. Either is sufficient to buffer output for our
// test cases.
buf: [PIPE_BUF]byte
n, _ := linux.read(stderr_pipe[0], buf[:])
if n == 0 {
log.errorf("panic_case: child stderr empty")
return false
}
s := string(buf[:n])
log.debugf("panic case: child stderr: '%s'", s)
return strings.contains(s, panic_str)
}
can_test_panic :: proc() -> bool {
return true
}

View File

@@ -25,6 +25,8 @@ import "core:crypto/pbkdf2"
import "core:crypto/siphash"
import "core:crypto/deoxysii"
import "../common"
// Covered:
// - crypto/aegis
// - aegis128L_test.json
@@ -95,8 +97,6 @@ print_test_vector_path :: proc(t: ^testing.T) {
log.infof("wycheproof path: %s", BASE_PATH)
}
test_proc :: proc(_: string) -> bool
supported_aegis_impls :: proc() -> [dynamic]aes.Implementation {
impls := make([dynamic]aes.Implementation, 0, 2, context.temp_allocator)
append(&impls, aes.Implementation.Portable)
@@ -167,12 +167,12 @@ test_aead_aegis_impl :: proc(
)
}
key := hexbytes_decode(test_vector.key)
iv := hexbytes_decode(test_vector.iv)
aad := hexbytes_decode(test_vector.aad)
msg := hexbytes_decode(test_vector.msg)
ct := hexbytes_decode(test_vector.ct)
tag := hexbytes_decode(test_vector.tag)
key := common.hexbytes_decode(test_vector.key)
iv := common.hexbytes_decode(test_vector.iv)
aad := common.hexbytes_decode(test_vector.aad)
msg := common.hexbytes_decode(test_vector.msg)
ct := common.hexbytes_decode(test_vector.ct)
tag := common.hexbytes_decode(test_vector.tag)
if len(iv) == 0 {
log.infof(
@@ -192,7 +192,7 @@ test_aead_aegis_impl :: proc(
tag_ := make([]byte, len(tag))
aegis.seal(&ctx, ct_, tag_, iv, aad, msg)
ok := hexbytes_compare(test_vector.ct, ct_)
ok := common.hexbytes_compare(test_vector.ct, ct_)
if !result_check(test_vector.result, ok) {
x := transmute(string)(hex.encode(ct_))
log.errorf(
@@ -206,7 +206,7 @@ test_aead_aegis_impl :: proc(
continue
}
ok = hexbytes_compare(test_vector.tag, tag_)
ok = common.hexbytes_compare(test_vector.tag, tag_)
if !result_check(test_vector.result, ok) {
x := transmute(string)(hex.encode(tag_))
log.errorf(
@@ -229,7 +229,7 @@ test_aead_aegis_impl :: proc(
continue
}
if ok && !hexbytes_compare(test_vector.msg, msg_) {
if ok && !common.hexbytes_compare(test_vector.msg, msg_) {
x := transmute(string)(hex.encode(msg_))
log.errorf(
"aead/aegis/%v/%d: decrypt msg: expected %s actual %s",
@@ -317,12 +317,12 @@ test_aead_aes_gcm_impl :: proc(
)
}
key := hexbytes_decode(test_vector.key)
iv := hexbytes_decode(test_vector.iv)
aad := hexbytes_decode(test_vector.aad)
msg := hexbytes_decode(test_vector.msg)
ct := hexbytes_decode(test_vector.ct)
tag := hexbytes_decode(test_vector.tag)
key := common.hexbytes_decode(test_vector.key)
iv := common.hexbytes_decode(test_vector.iv)
aad := common.hexbytes_decode(test_vector.aad)
msg := common.hexbytes_decode(test_vector.msg)
ct := common.hexbytes_decode(test_vector.ct)
tag := common.hexbytes_decode(test_vector.tag)
if len(iv) == 0 {
log.infof(
@@ -342,7 +342,7 @@ test_aead_aes_gcm_impl :: proc(
tag_ := make([]byte, len(tag))
aes.seal_gcm(&ctx, ct_, tag_, iv, aad, msg)
ok := hexbytes_compare(test_vector.ct, ct_)
ok := common.hexbytes_compare(test_vector.ct, ct_)
if !result_check(test_vector.result, ok) {
x := transmute(string)(hex.encode(ct_))
log.errorf(
@@ -356,7 +356,7 @@ test_aead_aes_gcm_impl :: proc(
continue
}
ok = hexbytes_compare(test_vector.tag, tag_)
ok = common.hexbytes_compare(test_vector.tag, tag_)
if !result_check(test_vector.result, ok) {
x := transmute(string)(hex.encode(tag_))
log.errorf(
@@ -379,7 +379,7 @@ test_aead_aes_gcm_impl :: proc(
continue
}
if ok && !hexbytes_compare(test_vector.msg, msg_) {
if ok && !common.hexbytes_compare(test_vector.msg, msg_) {
x := transmute(string)(hex.encode(msg_))
log.errorf(
"aead/aes-gcm/%v/%d: decrypt msg: expected %s actual %s",
@@ -488,12 +488,12 @@ test_aead_chacha20_poly1305_impl :: proc(
)
}
key := hexbytes_decode(test_vector.key)
iv := hexbytes_decode(test_vector.iv)
aad := hexbytes_decode(test_vector.aad)
msg := hexbytes_decode(test_vector.msg)
ct := hexbytes_decode(test_vector.ct)
tag := hexbytes_decode(test_vector.tag)
key := common.hexbytes_decode(test_vector.key)
iv := common.hexbytes_decode(test_vector.iv)
aad := common.hexbytes_decode(test_vector.aad)
msg := common.hexbytes_decode(test_vector.msg)
ct := common.hexbytes_decode(test_vector.ct)
tag := common.hexbytes_decode(test_vector.tag)
if slice.contains(test_vector.flags, FLAG_INVALID_NONCE_SIZE) {
log.infof(
@@ -519,7 +519,7 @@ test_aead_chacha20_poly1305_impl :: proc(
tag_ := make([]byte, len(tag))
chacha20poly1305.seal(&ctx, ct_, tag_, iv, aad, msg)
ok := hexbytes_compare(test_vector.ct, ct_)
ok := common.hexbytes_compare(test_vector.ct, ct_)
if !result_check(test_vector.result, ok) {
x := transmute(string)(hex.encode(ct_))
log.errorf(
@@ -534,7 +534,7 @@ test_aead_chacha20_poly1305_impl :: proc(
continue
}
ok = hexbytes_compare(test_vector.tag, tag_)
ok = common.hexbytes_compare(test_vector.tag, tag_)
if !result_check(test_vector.result, ok) {
x := transmute(string)(hex.encode(tag_))
log.errorf(
@@ -562,7 +562,7 @@ test_aead_chacha20_poly1305_impl :: proc(
continue
}
if ok && !hexbytes_compare(test_vector.msg, msg_) {
if ok && !common.hexbytes_compare(test_vector.msg, msg_) {
x := transmute(string)(hex.encode(msg_))
log.errorf(
"aead/%s/%v/%d: decrypt msg: expected %s actual %s",
@@ -628,7 +628,7 @@ test_eddsa_ed25519 :: proc(t: ^testing.T) {
num_ran, num_passed, num_failed, num_skipped: int
for &test_group, i in test_vectors.test_groups {
mem.free_all() // Probably don't need this, but be safe.
pk_bytes := hexbytes_decode(test_group.public_key.pk)
pk_bytes := common.hexbytes_decode(test_group.public_key.pk)
pk: ed25519.Public_Key
pk_ok := ed25519.public_key_set_bytes(&pk, pk_bytes)
@@ -652,8 +652,8 @@ test_eddsa_ed25519 :: proc(t: ^testing.T) {
log.debugf("eddsa/ed25519/%d: %+v", test_vector.tc_id, test_vector.flags)
}
msg := hexbytes_decode(test_vector.msg)
sig := hexbytes_decode(test_vector.sig)
msg := common.hexbytes_decode(test_vector.msg)
sig := common.hexbytes_decode(test_vector.sig)
verify_ok := ed25519.verify(&pk, msg, sig)
result_ok := result_check(test_vector.result, verify_ok)
@@ -747,7 +747,7 @@ test_ecdsa_impl :: proc(t: ^testing.T, test_vectors: ^Test_Vectors(Ecdsa_Test_Gr
num_ran, num_passed, num_failed, num_skipped: int
for &test_group, i in test_vectors.test_groups {
pk_bytes := hexbytes_decode(test_group.public_key.uncompressed)
pk_bytes := common.hexbytes_decode(test_group.public_key.uncompressed)
pk: ecdsa.Public_Key
pk_ok := ecdsa.public_key_set_bytes(&pk, curve_alg, pk_bytes)
@@ -773,8 +773,8 @@ test_ecdsa_impl :: proc(t: ^testing.T, test_vectors: ^Test_Vectors(Ecdsa_Test_Gr
log.debugf("ecdsa/%s/%s/%d: %+v", curve_str, hash_str, test_vector.tc_id, test_vector.flags)
}
msg := hexbytes_decode(test_vector.msg)
sig := hexbytes_decode(test_vector.sig)
msg := common.hexbytes_decode(test_vector.msg)
sig := common.hexbytes_decode(test_vector.sig)
verify_ok := ecdsa.verify_asn1(&pk, hash_alg, msg, sig)
result_ok := result_check(test_vector.result, verify_ok)
@@ -876,9 +876,9 @@ test_hkdf_impl :: proc(test_vectors: ^Test_Vectors(Hkdf_Test_Group)) -> bool {
log.debugf("hkdf/%s/%d: %+v", alg_str, test_vector.tc_id, test_vector.flags)
}
ikm := hexbytes_decode(test_vector.ikm)
salt := hexbytes_decode(test_vector.salt)
info := hexbytes_decode(test_vector.info)
ikm := common.hexbytes_decode(test_vector.ikm)
salt := common.hexbytes_decode(test_vector.salt)
info := common.hexbytes_decode(test_vector.info)
if slice.contains(test_vector.flags, FLAG_SIZE_TOO_LARGE) {
log.infof(
@@ -893,7 +893,7 @@ test_hkdf_impl :: proc(test_vectors: ^Test_Vectors(Hkdf_Test_Group)) -> bool {
okm_ := make([]byte, test_vector.size)
hkdf.extract_and_expand(alg, salt, ikm, info, okm_)
ok = hexbytes_compare(test_vector.okm, okm_)
ok = common.hexbytes_compare(test_vector.okm, okm_)
if !result_check(test_vector.result, ok) {
x := transmute(string)(hex.encode(okm_))
log.errorf(
@@ -1001,8 +1001,8 @@ test_mac_impl :: proc(test_vectors: ^Test_Vectors(Mac_Test_Group)) -> bool {
log.debugf("%s/%d: %+v", alg_str, test_vector.tc_id, test_vector.flags)
}
key := hexbytes_decode(test_vector.key)
msg := hexbytes_decode(test_vector.msg)
key := common.hexbytes_decode(test_vector.key)
msg := common.hexbytes_decode(test_vector.msg)
tag_ := make([]byte, len(test_vector.tag) / 2)
@@ -1037,7 +1037,7 @@ test_mac_impl :: proc(test_vectors: ^Test_Vectors(Mac_Test_Group)) -> bool {
siphash.sum_4_8(msg, key, tag_)
}
ok = hexbytes_compare(test_vector.tag, tag_)
ok = common.hexbytes_compare(test_vector.tag, tag_)
if !result_check(test_vector.result, ok) {
x := transmute(string)(hex.encode(tag_))
log.errorf(
@@ -1146,13 +1146,13 @@ test_pbkdf2_impl :: proc(
continue
}
password := hexbytes_decode(test_vector.password)
salt := hexbytes_decode(test_vector.salt)
password := common.hexbytes_decode(test_vector.password)
salt := common.hexbytes_decode(test_vector.salt)
dk_ := make([]byte, test_vector.dk_len)
pbkdf2.derive(alg, password, salt, test_vector.iteration_count, dk_)
ok = hexbytes_compare(test_vector.dk, dk_)
ok = common.hexbytes_compare(test_vector.dk, dk_)
if !result_check(test_vector.result, ok) {
x := transmute(string)(hex.encode(dk_))
log.errorf(
@@ -1255,8 +1255,8 @@ test_ecdh_impl :: proc(
log.debugf("ecdh/%s/%d: %+v", alg_str, test_vector.tc_id, test_vector.flags)
}
raw_pub := hexbytes_decode(test_vector.public)
raw_priv := hexbytes_decode(test_vector.private)
raw_pub := common.hexbytes_decode(test_vector.public)
raw_priv := common.hexbytes_decode(test_vector.private)
curve: ecdh.Curve
priv_key: ecdh.Private_Key
@@ -1366,7 +1366,7 @@ test_ecdh_impl :: proc(
continue
}
ok = hexbytes_compare(test_vector.shared, shared)
ok = common.hexbytes_compare(test_vector.shared, shared)
// "acceptable" results are fine from here because we have
// checked for the all-zero shared secret XDH case already.
if !result_check(test_vector.result, ok, false) {

View File

@@ -1,28 +1,10 @@
package test_wycheproof
import "core:bytes"
import "core:encoding/hex"
@(require) import "core:encoding/json"
@(require) import "core:log"
@(require) import "core:os"
Hex_Bytes :: string
hexbytes_compare :: proc(x: Hex_Bytes, b: []byte, allocator := context.allocator) -> bool {
dst := hexbytes_decode(x)
defer delete(dst)
return bytes.equal(dst, b)
}
hexbytes_decode :: proc(x: Hex_Bytes, allocator := context.allocator) -> []byte {
dst, ok := hex.decode(transmute([]byte)(x), allocator)
if !ok {
panic("wycheproof/common/Hex_Bytes: invalid hex encoding")
}
return dst
}
import "../common"
Result :: string
@@ -90,16 +72,16 @@ Aead_Test_Group :: struct {
}
Aead_Test_Vector :: struct {
tc_id: int `json:"tcId"`,
comment: string `json:"comment"`,
key: Hex_Bytes `json:"key"`,
iv: Hex_Bytes `json:"iv"`,
aad: Hex_Bytes `json:"aad"`,
msg: Hex_Bytes `json:"msg"`,
ct: Hex_Bytes `json:"ct"`,
tag: Hex_Bytes `json:"tag"`,
result: Result `json:"result"`,
flags: []string `json:"flags"`,
tc_id: int `json:"tcId"`,
comment: string `json:"comment"`,
key: common.Hex_Bytes `json:"key"`,
iv: common.Hex_Bytes `json:"iv"`,
aad: common.Hex_Bytes `json:"aad"`,
msg: common.Hex_Bytes `json:"msg"`,
ct: common.Hex_Bytes `json:"ct"`,
tag: common.Hex_Bytes `json:"tag"`,
result: Result `json:"result"`,
flags: []string `json:"flags"`,
}
Hkdf_Test_Group :: struct {
@@ -108,15 +90,15 @@ Hkdf_Test_Group :: struct {
}
Hkdf_Test_Vector :: struct {
tc_id: int `json:"tcId"`,
comment: string `json:"comment"`,
ikm: Hex_Bytes `json:"ikm"`,
salt: Hex_Bytes `json:"salt"`,
info: Hex_Bytes `json:"info"`,
size: int `json:"size"`,
okm: Hex_Bytes `json:"okm"`,
result: Result `json:"result"`,
flags: []string `json:"flags"`,
tc_id: int `json:"tcId"`,
comment: string `json:"comment"`,
ikm: common.Hex_Bytes `json:"ikm"`,
salt: common.Hex_Bytes `json:"salt"`,
info: common.Hex_Bytes `json:"info"`,
size: int `json:"size"`,
okm: common.Hex_Bytes `json:"okm"`,
result: Result `json:"result"`,
flags: []string `json:"flags"`,
}
Mac_Test_Group :: struct {
@@ -126,13 +108,13 @@ Mac_Test_Group :: struct {
}
Mac_Test_Vector :: struct {
tc_id: int `json:"tcId"`,
comment: string `json:"comment"`,
key: Hex_Bytes `json:"key"`,
msg: Hex_Bytes `json:"msg"`,
tag: Hex_Bytes `json:"tag"`,
result: Result `json:"result"`,
flags: []string `json:"flags"`,
tc_id: int `json:"tcId"`,
comment: string `json:"comment"`,
key: common.Hex_Bytes `json:"key"`,
msg: common.Hex_Bytes `json:"msg"`,
tag: common.Hex_Bytes `json:"tag"`,
result: Result `json:"result"`,
flags: []string `json:"flags"`,
}
Ecdh_Test_Group :: struct {
@@ -141,18 +123,18 @@ Ecdh_Test_Group :: struct {
}
Ecdh_Test_Vector :: struct {
tc_id: int `json:"tcId"`,
comment: string `json:"comment"`,
public: Hex_Bytes `json:"public"`,
private: Hex_Bytes `json:"private"`,
shared: Hex_Bytes `json:"shared"`,
result: Result `json:"result"`,
flags: []string `json:"flags"`,
tc_id: int `json:"tcId"`,
comment: string `json:"comment"`,
public: common.Hex_Bytes `json:"public"`,
private: common.Hex_Bytes `json:"private"`,
shared: common.Hex_Bytes `json:"shared"`,
result: Result `json:"result"`,
flags: []string `json:"flags"`,
}
Eddsa_Test_Group :: struct {
public_key: Eddsa_Key `json:"publicKey"`,
public_key_der: Hex_Bytes `json:"publicKeyDer"`,
public_key_der: common.Hex_Bytes `json:"publicKeyDer"`,
public_key_pem: string `json:"publicKeyPem"`,
public_key_jwk: Eddsa_Jwk `json:"publicKeyJwk"`,
type: string `json:"type"`,
@@ -160,10 +142,10 @@ Eddsa_Test_Group :: struct {
}
Eddsa_Key :: struct {
type: string `json:"type"`,
curve: string `json:"curve"`,
key_size: int `json:"keySize"`,
pk: Hex_Bytes `json:"pk"`,
type: string `json:"type"`,
curve: string `json:"curve"`,
key_size: int `json:"keySize"`,
pk: common.Hex_Bytes `json:"pk"`,
}
Eddsa_Jwk :: struct {
@@ -174,17 +156,17 @@ Eddsa_Jwk :: struct {
}
Ecdsa_Key :: struct {
type: string `json:"type"`,
curve: string `json:"curve"`,
key_size: int `json:"keySize"`,
uncompressed: Hex_Bytes `json:"uncompressed"`,
wx: Hex_Bytes `json:"wx"`,
wy: Hex_Bytes `json:"wy"`,
type: string `json:"type"`,
curve: string `json:"curve"`,
key_size: int `json:"keySize"`,
uncompressed: common.Hex_Bytes `json:"uncompressed"`,
wx: common.Hex_Bytes `json:"wx"`,
wy: common.Hex_Bytes `json:"wy"`,
}
Ecdsa_Test_Group :: struct {
public_key: Ecdsa_Key `json:"publicKey"`,
public_key_der: Hex_Bytes `json:"publicKeyDer"`,
public_key_der: common.Hex_Bytes `json:"publicKeyDer"`,
public_key_pem: string `json:"publicKeyPem"`,
type: string `json:"type"`,
sha: string `json:"sha"`,
@@ -192,12 +174,12 @@ Ecdsa_Test_Group :: struct {
}
Dsa_Test_Vector :: struct {
tc_id: int `json:"tcId"`,
comment: string `json:"comment"`,
msg: Hex_Bytes `json:"msg"`,
sig: Hex_Bytes `json:"sig"`,
result: Result `json:"result"`,
flags: []string `json:"flags"`,
tc_id: int `json:"tcId"`,
comment: string `json:"comment"`,
msg: common.Hex_Bytes `json:"msg"`,
sig: common.Hex_Bytes `json:"sig"`,
result: Result `json:"result"`,
flags: []string `json:"flags"`,
}
Pbkdf_Test_Group :: struct {
@@ -206,13 +188,13 @@ Pbkdf_Test_Group :: struct {
}
Pbkdf_Test_Vector :: struct {
tc_id: int `json:"tcId"`,
comment: string `json:"comment"`,
password: Hex_Bytes `json:"password"`,
salt: Hex_Bytes `json:"salt"`,
iteration_count: u32 `json:"iterationCount"`,
dk_len: int `json:"dkLen"`,
dk: Hex_Bytes `json:"dk"`,
result: Result `json:"result"`,
flags: []string `json:"flags"`,
tc_id: int `json:"tcId"`,
comment: string `json:"comment"`,
password: common.Hex_Bytes `json:"password"`,
salt: common.Hex_Bytes `json:"salt"`,
iteration_count: u32 `json:"iterationCount"`,
dk_len: int `json:"dkLen"`,
dk: common.Hex_Bytes `json:"dk"`,
result: Result `json:"result"`,
flags: []string `json:"flags"`,
}

View File

@@ -10,7 +10,9 @@ import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
TEST_SUITES = ['PNG', 'XML', 'BMP', 'JPG', 'Wycheproof']
# The OpenSSL CLI tool installed can be used to easily generate digests:
# $ openssl sha3-512 -mac HMAC -macopt key:"$HMAC_KEY" $FILE_NAME
TEST_SUITES = ['PNG', 'XML', 'BMP', 'JPG', 'Wycheproof', 'Noise']
ASSETS_BASE_URL = "https://raw.githubusercontent.com/odin-lang/test-assets/master/{}/{}"
HMAC_KEY = "https://odin-lang.org"
HMAC_HASH = hashlib.sha3_512
@@ -625,6 +627,14 @@ HMAC_DIGESTS = {
'x448_pem_test.json': "718ef327a5e8cc3a34467974193a051efe89323352f32f248c874457f28f6a2836cb6c2c15f40044df96428051591766fc0b44771236145a17835e714360ca51",
'x448_test.json': "ca811349ede46fc253d656c26b058e2fe903aae8a225d7f6703bef3dec122df4c48af190a070df4c4066690f50ed4996d69089794b5484b57f2c4233216f98f5",
'xchacha20_poly1305_test.json': "5debf381018af54fa2f9041044a257f8faff85b9a2eda314f21a05a55de2aacd2767a4962ecc58f69ed65404dc7d2b1eb8526038e7641610ce3a988456838fce",
'LICENSE-APACHE-snow.txt': "6d94a2c13b32d3c0db347511a19f71e5c2ec15025f3f3fdb333ae72e3c671b0b4d8796a9adf3e9b1ec941acea28a054e7108914ee93ebae673d894d2ac4a7cad",
'LICENSE-MIT-snow.txt': "c0d9d5f399729560ffbabcb46a1e282e9a214d436a59b86dd8765556290a93c204c126234401f935f6a18aff50d69cc8af4decc6f0bb1574f899412c77fb9b31",
'LICENSE-cacophony.txt': "cab56e8608950b3e9d5c0d8262f8c67e34254569726e78968896a964bab84e67fd66ccce43653118152fba5a3e670e0de23c725ecd85f65cacce4d987b1258e8",
'LICENSE-noise-c.txt': "ca6ef9d13d0832659efa04f03d06998af3050b0f65886f1b4f33ef4bd9b6df639fcaac0c73a7c28bc47c502e7915e30608863340245eac7620ab50ee66e99ff6",
'cacophony.txt': "d2c492f02287ee562a9cf43a4cd2a055f5235734779d091bc404aede4c37a552029e76c12e873632e52423804ad24b86138cd3d4fe54506108571d9f2f925033",
'noise-c-basic.txt': "7e25c5d692c53dd045060f27a562332178ca030d17687fc2ab58216b4298fb577ddc9d703db11c920fb04c28604fdc1a30337f58aad3617e07201a62c3a9b295",
'snow.txt': "35088303de90e6bac22656e1e0ac4a5ea73d8cdb5ada23a078d703e14714ffe19e08437bf46108e0419acd8b5be6fa797a68a621c20cdd063d94a4a970aa8634",
}
def try_download_file(url, out_file):

View File

@@ -50,3 +50,9 @@ test_expected_signal :: proc(t: ^testing.T) {
testing.expect_signal(t, libc.SIGILL)
libc.raise(libc.SIGILL)
}
@test
test_array_bounds_trap_signal :: proc(t: ^testing.T) {
testing.expect_signal(t, libc.SIGILL)
_ = make([]u8, -1)
}

View File

@@ -36,6 +36,8 @@ $ODIN test ../test_issue_5699.odin $COMMON
$ODIN test ../test_issue_6068.odin $COMMON
$ODIN test ../test_issue_6101.odin $COMMON
$ODIN test ../test_issue_6165.odin $COMMON
$ODIN test ../test_issue_6344.odin $COMMON
$ODIN test ../test_issue_6344.odin $COMMON -o:speed
$ODIN test ../test_issue_6396.odin $COMMON
$ODIN test ../test_pr_6476.odin $COMMON

View File

@@ -0,0 +1,72 @@
// Tests issue #6344 https://github.com/odin-lang/Odin/issues/6344
package test_issues
import "core:testing"
@(test)
test_soa_dynamic_field_write :: proc(t: ^testing.T) {
V :: struct {
x: f32,
y: f32,
}
array := make(#soa[dynamic]V, 4, 8)
defer delete(array)
for i in 0 ..< 4 {
array[i] = V{f32(i), f32(i) * 2}
}
// Simple write through field-first indexing (was: compiler panic)
for i in 0 ..< 4 {
array.x[i] = f32(i) * 10
}
testing.expect_value(t, array[0].x, 0)
testing.expect_value(t, array[1].x, 10)
testing.expect_value(t, array[2].x, 20)
testing.expect_value(t, array[3].x, 30)
// Compound write through field-first indexing (was: compiler panic)
for i in 0 ..< 4 {
array.x[i] += array.y[i]
}
testing.expect_value(t, array[0].x, 0)
testing.expect_value(t, array[1].x, 12)
testing.expect_value(t, array[2].x, 24)
testing.expect_value(t, array[3].x, 36)
}
@(test)
test_soa_slice_field_write :: proc(t: ^testing.T) {
V :: struct {
x: f32,
y: f32,
}
array := make(#soa[dynamic]V, 4, 8)
defer delete(array)
for i in 0 ..< 4 {
array[i] = V{f32(i), f32(i) * 2}
}
slice := array[:]
// Write through slice field-first indexing (was: compiler panic)
for i in 0 ..< 4 {
slice.x[i] = f32(i) * 10
}
testing.expect_value(t, array[0].x, 0)
testing.expect_value(t, array[1].x, 10)
testing.expect_value(t, array[2].x, 20)
testing.expect_value(t, array[3].x, 30)
// Compound write through slice field-first indexing
for i in 0 ..< 4 {
slice.x[i] += slice.y[i]
}
testing.expect_value(t, array[0].x, 0)
testing.expect_value(t, array[1].x, 12)
testing.expect_value(t, array[2].x, 24)
testing.expect_value(t, array[3].x, 36)
}

View File

@@ -5458,12 +5458,12 @@ TEXTURE_BARRIER_FLAGS :: enum i32 {
}
BARRIER_SUBRESOURCE_RANGE :: struct {
IndexOrFirstMipLevel: uint,
NumMipLevels: uint,
FirstArraySlice: uint,
NumArraySlices: uint,
FirstPlane: uint,
NumPlanes: uint,
IndexOrFirstMipLevel: u32,
NumMipLevels: u32,
FirstArraySlice: u32,
NumArraySlices: u32,
FirstPlane: u32,
NumPlanes: u32,
}
GLOBAL_BARRIER :: struct {

View File

@@ -76,8 +76,8 @@ foreign lib {
Quit :: proc() ---
GetNumAudioDecoders :: proc() -> c.int ---
GetAudioDecoder :: proc(index: c.int) -> cstring ---
CreateMixerDevice :: proc(devid: SDL.AudioDeviceID, #by_ptr spec: SDL.AudioSpec) -> ^Mixer ---
CreateMixer :: proc(#by_ptr spec: SDL.AudioSpec) -> ^Mixer ---
CreateMixerDevice :: proc(devid: SDL.AudioDeviceID, spec: Maybe(^SDL.AudioSpec)) -> ^Mixer ---
CreateMixer :: proc(spec: Maybe(^SDL.AudioSpec)) -> ^Mixer ---
DestroyMixer :: proc(mixer: ^Mixer) ---
GetMixerProperties :: proc(mixer: ^Mixer) -> SDL.PropertiesID ---
GetMixerFormat :: proc(mixer: ^Mixer, spec: ^SDL.AudioSpec) -> c.bool ---
@@ -145,8 +145,8 @@ foreign lib {
SetTrackFrequencyRatio :: proc(track: ^Track, ratio: c.float) -> c.bool ---
GetTrackFrequencyRatio :: proc(track: ^Track) -> c.float ---
SetTrackOutputChannelMap :: proc(track: ^Track, chmap: [^]c.int, count: c.int) -> c.bool ---
SetTrackStereo :: proc(track: ^Track, #by_ptr gains: StereoGains) -> c.bool ---
SetTrack3DPosition :: proc(track: ^Track, #by_ptr position: Point3D) -> c.bool ---
SetTrackStereo :: proc(track: ^Track, gains: Maybe(^StereoGains)) -> c.bool ---
SetTrack3DPosition :: proc(track: ^Track, position: Maybe(^Point3D)) -> c.bool ---
GetTrack3DPosition :: proc(track: ^Track, position: ^Point3D) -> c.bool ---
CreateGroup :: proc(mixer: ^Mixer) -> ^Group ---
DestroyGroup :: proc(group: ^Group) ---

View File

@@ -899,6 +899,7 @@ load_proc_addresses :: proc{
with open("../core.odin", 'w', encoding='utf-8') as f:
f.write(BASE)
f.write(PACKAGE_LINE)
f.write("\n")
f.write("""
// Core API
API_VERSION_1_0 :: (1<<22) | (0<<12) | (0)
@@ -907,10 +908,38 @@ API_VERSION_1_2 :: (1<<22) | (2<<12) | (0)
API_VERSION_1_3 :: (1<<22) | (3<<12) | (0)
API_VERSION_1_4 :: (1<<22) | (4<<12) | (0)
MAKE_API_VERSION :: proc "contextless" (variant, major, minor, patch: u32) -> u32 {
\treturn (variant<<29) | (major<<22) | (minor<<12) | (patch)
}
MAKE_VERSION :: proc "contextless" (major, minor, patch: u32) -> u32 {
\treturn (major<<22) | (minor<<12) | (patch)
}
API_VERSION_MAJOR :: proc "contextless" (version: u32) -> u32 {
\treturn (version>>22) & 0x7F
}
VERSION_MAJOR :: proc "contextless" (version: u32) -> u32 {
\treturn (version>>22)
}
API_VERSION_MINOR :: proc "contextless" (version: u32) -> u32 {
\treturn (version>>12) & 0x3FF
}
VERSION_MINOR :: API_VERSION_MINOR
API_VERSION_PATCH :: proc "contextless" (version: u32) -> u32 {
\treturn (version & 0xFFF)
}
VERSION_PATCH :: API_VERSION_PATCH
API_VERSION_VARIANT :: proc "contextless" (version: u32) -> u32 {
\treturn (version>>29)
}
// Base types
Flags :: distinct u32
Flags64 :: distinct u64
@@ -973,12 +1002,13 @@ MAKE_VIDEO_STD_VERSION :: MAKE_VERSION
parse_flags_def(f)
with open("../enums.odin", 'w', encoding='utf-8') as f:
f.write(PACKAGE_LINE)
f.write("\n")
f.write("\n\n")
parse_enums(f)
parse_fake_enums(f)
f.write("\n\n")
with open("../structs.odin", 'w', encoding='utf-8') as f:
f.write(PACKAGE_LINE)
f.write("\n")
f.write("""
import "core:c"
@@ -1040,7 +1070,7 @@ MTLCommandQueue_id :: rawptr
f.write("\n\n")
with open("../procedures.odin", 'w', encoding='utf-8') as f:
f.write(PACKAGE_LINE)
f.write("\n")
f.write("\n\n")
parse_procedures(f)
f.write("\n")
group_functions(f)

View File

@@ -7,10 +7,38 @@ API_VERSION_1_2 :: (1<<22) | (2<<12) | (0)
API_VERSION_1_3 :: (1<<22) | (3<<12) | (0)
API_VERSION_1_4 :: (1<<22) | (4<<12) | (0)
MAKE_API_VERSION :: proc "contextless" (variant, major, minor, patch: u32) -> u32 {
return (variant<<29) | (major<<22) | (minor<<12) | (patch)
}
MAKE_VERSION :: proc "contextless" (major, minor, patch: u32) -> u32 {
return (major<<22) | (minor<<12) | (patch)
}
API_VERSION_MAJOR :: proc "contextless" (version: u32) -> u32 {
return (version>>22) & 0x7F
}
VERSION_MAJOR :: proc "contextless" (version: u32) -> u32 {
return (version>>22)
}
API_VERSION_MINOR :: proc "contextless" (version: u32) -> u32 {
return (version>>12) & 0x3FF
}
VERSION_MINOR :: API_VERSION_MINOR
API_VERSION_PATCH :: proc "contextless" (version: u32) -> u32 {
return (version & 0xFFF)
}
VERSION_PATCH :: API_VERSION_PATCH
API_VERSION_VARIANT :: proc "contextless" (version: u32) -> u32 {
return (version>>29)
}
// Base types
Flags :: distinct u32
Flags64 :: distinct u64

View File

@@ -12,8 +12,8 @@ You can find a number of examples on [[Odin's official examples repository; http
**Getting the wgpu-native libraries**
For native support (not the browser), some libraries are required. Fortunately this is
extremely easy, just download them from the [[releases on GitHub; https://github.com/gfx-rs/wgpu-native/releases/tag/v27.0.2.0]].
the bindings are for v27.0.2.0 at the moment.
extremely easy, just download them from the [[releases on GitHub; https://github.com/gfx-rs/wgpu-native/releases/tag/v29.0.0.0]].
the bindings are for v29.0.0.0 at the moment.
These are expected in the `lib` folder under the same name as they are released (just unzipped).
By default it will look for a static release version (`wgpu-OS-ARCH-release.a|lib`),

275
vendor/wgpu/wgpu.js vendored
View File

@@ -4,7 +4,7 @@ const STATUS_SUCCESS = 1;
const STATUS_ERROR = 2;
const ENUMS = {
FeatureName: [undefined, "depth-clip-control", "depth32float-stencil8", "timestamp-query", "texture-compression-bc", "texture-compression-bc-sliced-3d", "texture-compression-etc2", "texture-compression-astc", "texture-compression-astc-sliced-3d", "indirect-first-instance", "shader-f16", "rg11b10ufloat-renderable", "bgra8unorm-storage", "float32-filterable", "float32-blendable", "clip-distances", "dual-source-blending" ],
FeatureName: [undefined, "core-features-and-limits", "depth-clip-control", "depth32float-stencil8", "texture-compression-bc", "texture-compression-bc-sliced-3d", "texture-compression-etc2", "texture-compression-astc", "texture-compression-astc-sliced-3d", "timestamp-query", "indirect-first-instance", "shader-f16", "rg11b10ufloat-renderable", "bgra8unorm-storage", "float32-filterable", "float32-blendable", "clip-distances", "dual-source-blending", "subgroups", "texture-formats-tier1", "texture-formats-tier2", "primitive-index", "texture-component-swizzle" ],
StoreOp: [undefined, "store", "discard", ],
LoadOp: [undefined, "load", "clear", ],
BufferBindingType: [null, undefined, "uniform", "storage", "read-only-storage", ],
@@ -12,9 +12,9 @@ const ENUMS = {
TextureSampleType: [null, undefined, "float", "unfilterable-float", "depth", "sint", "uint", ],
TextureViewDimension: [undefined, "1d", "2d", "2d-array", "cube", "cube-array", "3d", ],
StorageTextureAccess: [null, undefined, "write-only", "read-only", "read-write", ],
TextureFormat: [undefined, "r8unorm", "r8snorm", "r8uint", "r8sint", "r16uint", "r16sint", "r16float", "rg8unorm", "rg8snorm", "rg8uint", "rg8sint", "r32float", "r32uint", "r32sint", "rg16uint", "rg16sint", "rg16float", "rgba8unorm", "rgba8unorm-srgb", "rgba8snorm", "rgba8uint", "rgba8sint", "bgra8unorm", "bgra8unorm-srgb", "rgb10a2uint", "rgb10a2unorm", "rg11b10ufloat", "rgb9e5ufloat", "rg32float", "rg32uint", "rg32sint", "rgba16uint", "rgba16sint", "rgba16float", "rgba32float", "rgba32uint", "rgba32sint", "stencil8", "depth16unorm", "depth24plus", "depth24plus-stencil8", "depth32float", "depth32float-stencil8", "bc1-rgba-unorm", "bc1-rgba-unorm-srgb", "bc2-rgba-unorm", "bc2-rgba-unorm-srgb", "bc3-rgba-unorm", "bc3-rgba-unorm-srgb", "bc4-r-unorm", "bc4-r-snorm", "bc5-rg-unorm", "bc5-rg-snorm", "bc6h-rgb-ufloat", "bc6h-rgb-float", "bc7-rgba-unorm", "bc7-rgba-unorm-srgb", "etc2-rgb8unorm", "etc2-rgb8unorm-srgb", "etc2-rgb8a1unorm", "etc2-rgb8a1unorm-srgb", "etc2-rgba8unorm", "etc2-rgba8unorm-srgb", "eac-r11unorm", "eac-r11snorm", "eac-rg11unorm", "eac-rg11snorm", "astc-4x4-unorm", "astc-4x4-unorm-srgb", "astc-5x4-unorm", "astc-5x4-unorm-srgb", "astc-5x5-unorm", "astc-5x5-unorm-srgb", "astc-6x5-unorm", "astc-6x5-unorm-srgb", "astc-6x6-unorm", "astc-6x6-unorm-srgb", "astc-8x5-unorm", "astc-8x5-unorm-srgb", "astc-8x6-unorm", "astc-8x6-unorm-srgb", "astc-8x8-unorm", "astc-8x8-unorm-srgb", "astc-10x5-unorm", "astc-10x5-unorm-srgb", "astc-10x6-unorm", "astc-10x6-unorm-srgb", "astc-10x8-unorm", "astc-10x8-unorm-srgb", "astc-10x10-unorm", "astc-10x10-unorm-srgb", "astc-12x10-unorm", "astc-12x10-unorm-srgb", "astc-12x12-unorm", "astc-12x12-unorm-srgb", ],
TextureFormat: [undefined, "r8unorm", "r8snorm", "r8uint", "r8sint", "r16unorm", "r16snorm", "r16uint", "r16sint", "r16float", "rg8unorm", "rg8snorm", "rg8uint", "rg8sint", "r32float", "r32uint", "r32sint", "rg16unorm", "rg16snorm", "rg16uint", "rg16sint", "rg16float", "rgba8unorm", "rgba8unorm-srgb", "rgba8snorm", "rgba8uint", "rgba8sint", "bgra8unorm", "bgra8unorm-srgb", "rgb10a2uint", "rgb10a2unorm", "rg11b10ufloat", "rgb9e5ufloat", "rg32float", "rg32uint", "rg32sint", "rgba16unorm", "rgba16snorm", "rgba16uint", "rgba16sint", "rgba16float", "rgba32float", "rgba32uint", "rgba32sint", "stencil8", "depth16unorm", "depth24plus", "depth24plus-stencil8", "depth32float", "depth32float-stencil8", "bc1-rgba-unorm", "bc1-rgba-unorm-srgb", "bc2-rgba-unorm", "bc2-rgba-unorm-srgb", "bc3-rgba-unorm", "bc3-rgba-unorm-srgb", "bc4-r-unorm", "bc4-r-snorm", "bc5-rg-unorm", "bc5-rg-snorm", "bc6h-rgb-ufloat", "bc6h-rgb-float", "bc7-rgba-unorm", "bc7-rgba-unorm-srgb", "etc2-rgb8unorm", "etc2-rgb8unorm-srgb", "etc2-rgb8a1unorm", "etc2-rgb8a1unorm-srgb", "etc2-rgba8unorm", "etc2-rgba8unorm-srgb", "eac-r11unorm", "eac-r11snorm", "eac-rg11unorm", "eac-rg11snorm", "astc-4x4-unorm", "astc-4x4-unorm-srgb", "astc-5x4-unorm", "astc-5x4-unorm-srgb", "astc-5x5-unorm", "astc-5x5-unorm-srgb", "astc-6x5-unorm", "astc-6x5-unorm-srgb", "astc-6x6-unorm", "astc-6x6-unorm-srgb", "astc-8x5-unorm", "astc-8x5-unorm-srgb", "astc-8x6-unorm", "astc-8x6-unorm-srgb", "astc-8x8-unorm", "astc-8x8-unorm-srgb", "astc-10x5-unorm", "astc-10x5-unorm-srgb", "astc-10x6-unorm", "astc-10x6-unorm-srgb", "astc-10x8-unorm", "astc-10x8-unorm-srgb", "astc-10x10-unorm", "astc-10x10-unorm-srgb", "astc-12x10-unorm", "astc-12x10-unorm-srgb", "astc-12x12-unorm", "astc-12x12-unorm-srgb", ],
QueryType: [undefined, "occlusion", "timestamp", ],
VertexStepMode: [null, undefined, "vertex", "instance", ],
VertexStepMode: [undefined, "vertex", "instance", ],
VertexFormat: [undefined, "uint8", "uint8x2", "uint8x4", "sint8", "sint8x2", "sint8x4", "unorm8", "unorm8x2", "unorm8x4", "snorm8", "snorm8x2", "snorm8x4", "uint16", "uint16x2", "uint16x4", "sint16", "sint16x2", "sint16x4", "unorm16", "unorm16x2", "unorm16x4", "snorm16", "snorm16x2", "snorm16x4", "float16", "float16x2", "float16x4", "float32", "float32x2", "float32x3", "float32x4", "uint32", "uint32x2", "uint32x3", "uint32x4", "sint32", "sint32x2", "sint32x3", "sint32x4", "unorm10-10-2", "unorm8x4-bgra" ],
PrimitiveTopology: [undefined, "point-list", "line-list", "line-strip", "triangle-list", "triangle-strip", ],
IndexFormat: [undefined, "uint16", "uint32", ],
@@ -26,7 +26,7 @@ const ENUMS = {
CompareFunction: [undefined, "never", "less", "equal", "less-equal", "greater", "not-equal", "greater-equal", "always", ],
TextureDimension: [undefined, "1d", "2d", "3d", ],
ErrorType: [undefined, "no-error", "validation", "out-of-memory", "internal", "unknown", ],
WGSLLanguageFeatureName: [undefined, "readonly_and_readwrite_storage_textures", "packed_4x8_integer_dot_product", "unrestricted_pointer_parameters", "pointer_composite_access", ],
WGSLLanguageFeatureName: [undefined, "readonly_and_readwrite_storage_textures", "packed_4x8_integer_dot_product", "unrestricted_pointer_parameters", "pointer_composite_access", "uniform_buffer_standard_layout", "subgroup_id", "texture_and_sampler_let", "subgroup_uniformity", "texture_formats_tier1" ],
PowerPreference: [undefined, "low-power", "high-performance", ],
CompositeAlphaMode: ["auto", "opaque", "premultiplied", "unpremultiplied", "inherit", ],
StencilOperation: [undefined, "keep", "zero", "replace", "invert", "increment-clamp", "decrement-clamp", "increment-wrap", "decrement-wrap", ],
@@ -34,20 +34,25 @@ const ENUMS = {
BlendFactor: [undefined, "zero", "one", "src", "one-minus-src", "src-alpha", "one-minus-src-alpha", "dst", "one-minus-dst", "dst-alpha", "one-minus-dst-alpha", "src-alpha-saturated", "constant", "one-minus-constant", "src1", "one-minus-src1", "src1-alpha", "one-minus-src1-alpha" ],
PresentMode: [undefined, "fifo", "fifo-relaxed", "immediate", "mailbox", ],
TextureAspect: [undefined, "all", "stencil-only", "depth-only"],
DeviceLostReason: [undefined, "unknown", "destroyed", "instance-dropped", "failed-creation"],
DeviceLostReason: [undefined, "unknown", "destroyed", "callback-cancelled", "failed-creation"],
BufferMapState: [undefined, "unmapped", "pending", "mapped"],
OptionalBool: [false, true, undefined],
ComponentSwizzle: [undefined, "0", "1", "r", "g", "b", "a"],
PredefinedColorSpace: [undefined, "srgb", "display-p3"],
ToneMappingMode: [undefined, "standard", "extended"],
FeatureLevel: [undefined, "compatibility", "core"],
TextureViewDimensions: [undefined, "1d", "2d", "2d-array", "cube", "cube-array", "3d"],
// WARN: used with indexOf to pass to WASM, if we would pass to JS, this needs to use official naming convention (not like Odin enums) like the ones above.
BackendType: [undefined, null, "WebGPU", "D3D11", "D3D12", "Metal", "Vulkan", "OpenGL", "OpenGLES"],
AdapterType: [undefined, "DiscreteGPU", "IntegratedGPU", "CPU", "Unknown"],
RequestDeviceStatus: [undefined, "Success", "InstanceDropped", "Error", "Unknown"],
MapAsyncStatus: [undefined, "Success", "InstanceDropped", "Error", "Aborted", "Unknown"],
CreatePipelineAsyncStatus: [undefined, "Success", "InstanceDropped", "ValidationError", "InternalError", "Unknown"],
PopErrorScopeStatus: [undefined, "Success", "InstanceDropped", "EmptyStack"],
RequestAdapterStatus: [undefined, "Success", "InstanceDropped", "Unavailable", "Error", "Unknown"],
QueueWorkDoneStatus: [undefined, "Success", "InstanceDropped", "Error", "Unknown"],
CompilationInfoRequestStatus: [undefined, "Success", "InstanceDropped", "Error", "Unknown"],
RequestDeviceStatus: [undefined, "Success", "CallbackCancelled", "Error"],
MapAsyncStatus: [undefined, "Success", "CallbackCancelled", "Error", "Aborted"],
CreatePipelineAsyncStatus: [undefined, "Success", "CallbackCancelled", "ValidationError", "InternalError"],
PopErrorScopeStatus: [undefined, "Success", "CallbackCancelled", "Error"],
RequestAdapterStatus: [undefined, "Success", "CallbackCancelled", "Unavailable", "Error"],
QueueWorkDoneStatus: [undefined, "Success", "CallbackCancelled", "Error"],
CompilationInfoRequestStatus: [undefined, "Success", "CallbackCancelled"],
};
/**
@@ -71,7 +76,7 @@ class WebGPUInterface {
StorageTextureBindingLayout: [16, 4],
StringView: [2*this.mem.intSize, this.mem.intSize],
ConstantEntry: [this.mem.intSize === 8 ? 32 : 24, 8],
ProgrammableStageDescriptor: [8 + this.mem.intSize*4, this.mem.intSize],
ComputeState: [8 + this.mem.intSize*4, this.mem.intSize],
VertexBufferLayout: [16 + this.mem.intSize*2, 8],
VertexAttribute: [24, 8],
VertexState: [8 + this.mem.intSize*6, this.mem.intSize],
@@ -87,7 +92,7 @@ class WebGPUInterface {
UncapturedErrorCallbackInfo: [16, 4],
RenderPassColorAttachment: [56, 8],
BindGroupEntry: [40, 8],
BindGroupLayoutEntry: [80, 8],
BindGroupLayoutEntry: [88, 8],
Extent3D: [12, 4],
CompilationMessage: [this.mem.intSize == 8 ? 64 : 48, 8],
};
@@ -158,6 +163,9 @@ class WebGPUInterface {
/** @type {WebGPUObjectManager<GPUTextureView>} */
this.textureViews = new WebGPUObjectManager("TextureView", this.mem);
/** @type {WebGPUObjectManager<GPUExternalTexture>} */
this.externalTextures = new WebGPUObjectManager("ExternalTexture", this.mem);
this.zeroMessageAddr = 0;
}
@@ -341,21 +349,38 @@ class WebGPUInterface {
return STATUS_SUCCESS;
}
genericGetAdapterInfo(infoPtr) {
/**
* @param {number} infoPtr
* @param {GPUAdapterInfo} info
*/
genericGetAdapterInfo(infoPtr, info) {
this.assert(infoPtr != 0);
const off = this.struct(infoPtr);
off(4); // nextInChain
off(this.sizes.StringView); // vendor
off(this.sizes.StringView); // architecture
off(this.sizes.StringView); // device
off(this.sizes.StringView); // description
const storeString = (start, str) => {
const len = new TextEncoder().encode(str).length;
const strAddr = this.mem.exports.wgpu_alloc(len);
this.mem.storeString(strAddr, str);
this.mem.storeI32(start, strAddr);
this.mem.storeUint(start + this.mem.intSize, len);
};
storeString(off(this.sizes.StringView), info.vendor);
storeString(off(this.sizes.StringView), info.architecture);
storeString(off(this.sizes.StringView), info.device);
storeString(off(this.sizes.StringView), info.description);
this.mem.storeI32(off(4), ENUMS.BackendType.indexOf("WebGPU"));
this.mem.storeI32(off(4), ENUMS.AdapterType.indexOf("Unknown"));
// NOTE: I don't think getting the other fields in this struct is possible.
// `adapter.requestAdapterInfo` is deprecated.
off(4); // vendorID
off(4); // deviceID
this.mem.storeI32(off(4), info.subGroupMinSize);
this.mem.storeI32(off(4), info.subGroupMaxSize);
return STATUS_SUCCESS;
}
@@ -461,15 +486,15 @@ class WebGPUInterface {
/**
* @param {number} ptr
* @returns {GPUComputePassTimestampWrites}
*/
ComputePassTimestampWritesPtr(ptr) {
PassTimestampWritesPtr(ptr) {
const start = this.mem.loadPtr(ptr);
if (start == 0) {
return undefined;
}
const off = this.struct(start);
off(4); // nextInChain
return {
querySet: this.querySets.get(this.mem.loadPtr(off(4))),
beginningOfPassWriteIndex: this.mem.loadU32(off(4)),
@@ -529,7 +554,7 @@ class WebGPUInterface {
}
const off = this.struct(start);
off(4); // nextInChain
return {
view: this.textureViews.get(this.mem.loadPtr(off(4))),
depthLoadOp: this.enumeration("LoadOp", off(4)),
@@ -556,14 +581,6 @@ class WebGPUInterface {
return this.querySets.get(ptr);
}
/**
* @param {number} ptr
* @returns {GPURenderPassTimestampWrites}
*/
RenderPassTimestampWritesPtr(ptr) {
return this.ComputePassTimestampWritesPtr(ptr);
}
/**
* @param {number} start
* @returns {GPUOrigin3D}
@@ -626,12 +643,13 @@ class WebGPUInterface {
off(4);
const entry = {
binding: this.mem.loadU32(off(4)),
visibility: this.mem.loadU64(off(8)),
buffer: this.BufferBindingLayout(off(this.sizes.BufferBindingLayout)),
sampler: this.SamplerBindingLayout(off(this.sizes.SamplerBindingLayout)),
texture: this.TextureBindingLayout(off(this.sizes.TextureBindingLayout)),
storageTexture: this.StorageTextureBindingLayout(off(this.sizes.StorageTextureBindingLayout)),
binding: this.mem.loadU32(off(4)),
visibility: this.mem.loadU64(off(8)),
bindingArraySize: this.mem.loadU32(off(4)),
buffer: this.BufferBindingLayout(off(this.sizes.BufferBindingLayout)),
sampler: this.SamplerBindingLayout(off(this.sizes.SamplerBindingLayout)),
texture: this.TextureBindingLayout(off(this.sizes.TextureBindingLayout)),
storageTexture: this.StorageTextureBindingLayout(off(this.sizes.StorageTextureBindingLayout)),
};
if (!entry.buffer.type) {
entry.buffer = undefined;
@@ -696,9 +714,9 @@ class WebGPUInterface {
/**
* @param {number} start
* @returns {GPUProgrammableStage}
* @returns {GPUComputeState}
*/
ProgrammableStageDescriptor(start) {
ComputeState(start) {
const off = this.struct(start);
off(4);
@@ -749,7 +767,7 @@ class WebGPUInterface {
return {
label: label,
layout: layoutIdx > 0 ? this.pipelineLayouts.get(layoutIdx) : "auto",
compute: this.ProgrammableStageDescriptor(off(this.sizes.ProgrammableStageDescriptor)),
compute: this.ComputeState(off(this.sizes.ComputeState)),
};
}
@@ -793,11 +811,9 @@ class WebGPUInterface {
*/
VertexBufferLayout(start) {
const off = this.struct(start);
off(4); // nextInChain
const stepMode = this.enumeration("VertexStepMode", off(4));
if (stepMode == null) {
return null;
}
return {
arrayStride: this.mem.loadU64(off(8)),
@@ -817,6 +833,7 @@ class WebGPUInterface {
*/
VertexAttribute(start) {
const off = this.struct(start);
off(4); // nextInChain
return {
format: this.enumeration("VertexFormat", off(4)),
offset: this.mem.loadU64(off(8)),
@@ -1117,12 +1134,26 @@ class WebGPUInterface {
},
/**
* @param {number} capabilitiesPtr
* @returns {number}
* @param {number} featuresPtr
*/
wgpuGetInstanceCapabilities: (capabilitiesPtr) => {
wgpuGetInstanceFeatures: (featuresPtr) => {
// TODO: implement (futures).
return STATUS_ERROR;
},
/**
* @param {number} limitsPtr
*/
wgpuGetInstanceLimits: (limitsPtr) => {
// TODO: implement (futures).
},
/**
* @param {number} feature
* @returns {boolean}
*/
wgpuHasInstanceFeature: (feature) => {
// TODO: implement (futures).
return false;
},
/**
@@ -1389,6 +1420,30 @@ class WebGPUInterface {
return BigInt(0);
},
/**
* @param {number} bufferIdx
* @param {number|BigInt} offset
* @param {number} ptr
* @param {number|BigInt} size
* @return {number}
*/
wgpuBufferReadMappedRange: (bufferIdx, offset, ptr, size) => {
const buffer = this.buffers.get(bufferIdx);
offset = this.unwrapBigInt(offset);
size = this.unwrapBigInt(size);
this.assert(!buffer.mapping, "buffer already mapped");
const range = buffer.buffer.getMappedRange(offset, size);
const mapping = new Uint8Array(this.mem.memory.buffer, ptr, size);
mapping.set(new Uint8Array(range));
buffer.mapping = { range: range, ptr: ptr, size: range.byteLength };
return STATUS_SUCCESS;
},
/**
* @param {number} bufferIdx
* @param {number} labelPtr
@@ -1415,6 +1470,30 @@ class WebGPUInterface {
buffer.mapping = null;
},
/**
* @param {number} bufferIdx
* @param {number|BigInt} offset
* @param {number} ptr
* @param {number|BigInt} size
* @return {number}
*/
wgpuBufferWriteMappedRange: (bufferIdx, offset, ptr, size) => {
const buffer = this.buffers.get(bufferIdx);
offset = this.unwrapBigInt(offset);
size = this.unwrapBigInt(size);
this.assert(!buffer.mapping, "buffer already mapped");
const range = buffer.buffer.getMappedRange(offset, size);
const mapping = new Uint8Array(this.mem.memory.buffer, ptr, size);
(new Uint8Array(range)).set(mapping);
buffer.mapping = { range: range, ptr: ptr, size: range.byteLength };
return STATUS_SUCCESS;
},
...this.buffers.interface(),
/* ---------------------- CommandBuffer ---------------------- */
@@ -1438,7 +1517,7 @@ class WebGPUInterface {
off(4);
descriptor = {
label: this.StringView(off(this.sizes.StringView)),
timestampWrites: this.ComputePassTimestampWritesPtr(off(4)),
timestampWrites: this.PassTimestampWritesPtr(off(4)),
};
}
@@ -1478,7 +1557,7 @@ class WebGPUInterface {
),
depthStencilAttachment: this.RenderPassDepthStencilAttachmentPtr(off(4)),
occlusionQuerySet: this.QuerySet(off(4)),
timestampWrites: this.RenderPassTimestampWritesPtr(off(4)),
timestampWrites: this.PassTimestampWritesPtr(off(4)),
maxDrawCount: maxDrawCount,
};
@@ -1881,7 +1960,7 @@ class WebGPUInterface {
device.createComputePipelineAsync(this.ComputePipelineDescriptor(descriptorPtr))
.catch((e) => {
const messageAddr = this.makeMessageArg(e.message);
this.callCallback(callbackInfo, [ENUMS.CreatePipelineAsyncStatus.indexOf("Unknown"), 0, messageAddr]);
this.callCallback(callbackInfo, [ENUMS.CreatePipelineAsyncStatus.indexOf("ValidationError"), 0, messageAddr]);
this.mem.exports.wgpu_free(messageAddr);
})
.then((computePipeline) => {
@@ -2185,6 +2264,11 @@ class WebGPUInterface {
const callbackInfo = this.CallbackInfo(callbackInfoPtr);
device.popErrorScope()
.catch((e) => {
const messageAddr = this.makeMessageArg(e.message);
this.callCallback(callbackInfo, [ENUMS.PopErrorScopeStatus.indexOf("Error"), ENUMS.ErrorType.indexOf("unknown"), messageAddr]);
this.mem.exports.wgpu_free(messageAddr);
})
.then((error) => {
if (!error) {
this.callCallback(callbackInfo, [ENUMS.PopErrorScopeStatus.indexOf("Success"), ENUMS.ErrorType.indexOf("no-error"), this.zeroMessageArg()]);
@@ -2222,6 +2306,10 @@ class WebGPUInterface {
...this.devices.interface(true),
/* ---------------------- ExternalTexture ---------------------- */
...this.externalTextures.interface(true),
/* ---------------------- Instance ---------------------- */
/**
@@ -2261,7 +2349,6 @@ class WebGPUInterface {
/**
* @param {number} instanceIdx
* @param {number} featurePtr
* @returns {number}
*/
wgpuInstanceGetWGSLLanguageFeatures: (instanceIdx, featuresPtr) => {
this.assert(featuresPtr != 0);
@@ -2292,8 +2379,6 @@ class WebGPUInterface {
for (let i = 0; i < availableFeatures.length; i += 1) {
this.mem.storeI32(off(4), availableFeatures[i]);
}
return STATUS_SUCCESS;
},
/**
@@ -2325,11 +2410,22 @@ class WebGPUInterface {
let options;
if (optionsPtr != 0) {
const off = this.struct(optionsPtr);
off(4); // nextInChain
off(4); // featureLevel
let xrCompatible = undefined;
const nextInChain = this.mem.loadPtr(off(4));
if (nextInChain != 0) {
const nextInChainType = this.mem.loadI32(nextInChain + 4);
// RequestAdapterWebXROptions = 0x0000000B,
if (nextInChainType == 0x0000000B) {
xrCompatible = this.mem.loadB32(nextInChain + 8);
}
}
options = {
featureLevel: this.enumeration("FeatureLevel", off(4)),
powerPreference: this.enumeration("PowerPreference", off(4)),
forceFallbackAdapter: this.mem.loadB32(off(4)),
xrCompatible: xrCompatible,
};
}
@@ -2405,10 +2501,12 @@ class WebGPUInterface {
queue.onSubmittedWorkDone()
.catch((e) => {
console.warn(e);
this.callCallback(callbackInfo, [ENUMS.QueueWorkDoneStatus.indexOf("Error")]);
const messageAddr = this.makeMessageArg(e.message);
this.callCallback(callbackInfo, [ENUMS.QueueWorkDoneStatus.indexOf("Error"), messageAddr]);
this.mem.exports.wgpu_free(messageAddr);
})
.then(() => {
this.callCallback(callbackInfo, [ENUMS.QueueWorkDoneStatus.indexOf("Success")]);
this.callCallback(callbackInfo, [ENUMS.QueueWorkDoneStatus.indexOf("Success"), this.zeroMessageArg()]);
});
// TODO: futures?
@@ -2920,7 +3018,7 @@ class WebGPUInterface {
shaderModule.getCompilationInfo()
.catch((e) => {
console.warn(e);
this.callCallback(callbackInfo, [ENUMS.CompilationInfoRequestStatus.indexOf("Error"), null]);
this.callCallback(callbackInfo, [ENUMS.CompilationInfoRequestStatus.indexOf("CallbackCancelled"), null]);
})
.then((compilationInfo) => {
const ptrsToFree = [];
@@ -2975,6 +3073,11 @@ class WebGPUInterface {
this.mem.exports.wgpu_free(supportedFeaturesPtr);
},
/* ---------------------- SupportedInstanceFeatures ---------------------- */
wgpuSupportedInstanceFeaturesFreeMembers: (supportedFeaturesCount, supportedFeaturesPtr) => {
},
/* ---------------------- SupportedWGSLLanguageFeatures ---------------------- */
wgpuSupportedWGSLLanguageFeaturesFreeMembers: (supportedFeaturesCount, supportedFeaturesPtr) => {
@@ -2992,7 +3095,23 @@ class WebGPUInterface {
const context = surface.getContext("webgpu");
const off = this.struct(configPtr);
off(4);
let colorSpace = undefined;
let toneMapping = undefined;
const nextInChain = this.mem.loadPtr(off(4));
if (nextInChain != 0) {
const colorManagementOff = this.struct(nextInChain);
colorManagementOff(4); // next
const nextInChainType = this.mem.loadI32(colorManagementOff(4));
// SurfaceColorManagement = 0x0000000A,
if (nextInChainType == 0x0000000A) {
colorSpace = this.enumeration("PredefinedColorSpace", colorManagementOff(4)) ?? "srgb";
toneMapping = {
mode: this.enumeration("ToneMappingMode", colorManagementOff(4)) ?? "standard",
};
}
}
const device = this.devices.get(this.mem.loadPtr(off(4)));
const format = this.enumeration("TextureFormat", off(4));
const usage = this.mem.loadU64(off(8));
@@ -3019,6 +3138,8 @@ class WebGPUInterface {
viewFormats: viewFormats,
alphaMode: alphaMode,
presentMode: presentMode,
colorSpace: colorSpace,
toneMapping: toneMapping,
};
context.configure(config);
@@ -3071,7 +3192,7 @@ class WebGPUInterface {
const textureIdx = this.textures.create(texture);
this.mem.storeI32(texturePtr + 4, textureIdx);
// TODO: determine suboptimal and/or status.
// TODO: determine status somehow?
},
/**
@@ -3130,7 +3251,27 @@ class WebGPUInterface {
let descriptor;
if (descriptorPtr != 0) {
const off = this.struct(descriptorPtr);
off(4);
let swizzle = undefined;
const nextInChain = this.mem.loadPtr(off(4));
if (nextInChain != 0) {
const swizzleOff = this.struct(nextInChain);
swizzleOff(4); // next
const nextInChainType = this.mem.loadI32(swizzleOff(4));
// TextureComponentSwizzle = 0x00000016,
if (nextInChainType == 0x00000016) {
const r = this.enumeration("ComponentSwizzle", swizzleOff(4));
this.assert(r !== undefined);
const g = this.enumeration("ComponentSwizzle", swizzleOff(4));
this.assert(g !== undefined);
const b = this.enumeration("ComponentSwizzle", swizzleOff(4));
this.assert(b !== undefined);
const a = this.enumeration("ComponentSwizzle", swizzleOff(4));
this.assert(a !== undefined);
swizzle = r + g + b + a;
}
}
descriptor = {
label: this.StringView(off(this.sizes.StringView)),
format: this.enumeration("TextureFormat", off(4)),
@@ -3141,6 +3282,7 @@ class WebGPUInterface {
arrayLayerCount: this.mem.loadU32(off(4)),
aspect: this.enumeration("TextureAspect", off(4)),
usage: this.mem.loadU64(off(8)),
swizzle: swizzle,
};
if (descriptor.arrayLayerCount == 0xFFFFFFFF) {
descriptor.arrayLayerCount = undefined;
@@ -3216,6 +3358,15 @@ class WebGPUInterface {
return texture.sampleCount;
},
/**
* @param {number} textureIdx
* @returns {number}
*/
wgpuTextureGetTextureBindingViewDimension: (textureIdx) => {
const texture = this.textures.get(textureIdx);
return ENUMS.TextureViewDimension.indexOf(texture.textureBindingViewDimension);
},
/**
* @param {number} textureIdx
* @returns {number}

1232
vendor/wgpu/wgpu.odin vendored

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,8 @@ foreign libwgpu {
@(link_name="wgpuQueueSubmitForIndex")
RawQueueSubmitForIndex :: proc(queue: Queue, commandCount: uint, commands: [^]CommandBuffer) -> SubmissionIndex ---
QueueGetTimestampPeriod :: proc(queue: Queue) -> f32 ---
// Returns true if the queue is empty, or false if there are more queue submissions still in flight.
DevicePoll :: proc(device: Device, wait: b32, /* NULLABLE */ submissionIndex: /* const */ ^SubmissionIndex = nil) -> b32 ---
DeviceCreateShaderModuleSpirV :: proc(device: Device, descriptor: ^ShaderModuleDescriptorSpirV) -> ShaderModule ---
@@ -21,9 +23,13 @@ foreign libwgpu {
GetVersion :: proc() -> u32 ---
RenderPassEncoderSetPushConstants :: proc(encoder: RenderPassEncoder, stages: ShaderStageFlags, offset: u32, sizeBytes: u32, data: rawptr) ---
ComputePassEncoderSetPushConstants :: proc(encoder: ComputePassEncoder, offset: u32, sizeBytes: u32, data: rawptr) ---
RenderBundleEncoderSetPushConstants :: proc(encoder: RenderBundleEncoder, stages: ShaderStageFlags, offset: u32, sizeBytes: u32, data: rawptr) ---
DeviceGetNativeMetalDevice :: proc(device: Device) -> rawptr ---
DeviceGetNativeMetalCommandQueue :: proc(device: Device) -> rawptr ---
DeviceGetNativeMetalTexture :: proc(device: Device) -> rawptr ---
RenderPassEncoderSetImmediates :: proc(encoder: RenderPassEncoder, stages: ShaderStageFlags, offset: u32, sizeBytes: u32, data: rawptr) ---
ComputePassEncoderSetImmediates :: proc(encoder: ComputePassEncoder, offset: u32, sizeBytes: u32, data: rawptr) ---
RenderBundleEncoderSetImmediates :: proc(encoder: RenderBundleEncoder, stages: ShaderStageFlags, offset: u32, sizeBytes: u32, data: rawptr) ---
RenderPassEncoderMultiDrawIndirect :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count: u32) ---
RenderPassEncoderMultiDrawIndexedIndirect :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count: u32) ---
@@ -38,6 +44,9 @@ foreign libwgpu {
ComputePassEncoderWriteTimestamp :: proc(computePassEncoder: ComputePassEncoder, querySet: QuerySet, queryIndex: u32) ---
RenderPassEncoderWriteTimestamp :: proc(renderPassEncoder: RenderPassEncoder, querySet: QuerySet, queryIndex: u32) ---
DeviceStartGraphicsDebuggerCapture :: proc(device: Device) -> b32 ---
DeviceStopGraphicsDebuggerCapture :: proc(device: Device) ---
}
GenerateReport :: proc "c" (instance: Instance) -> (report: GlobalReport) {

View File

@@ -2,8 +2,8 @@ package wgpu
import "base:runtime"
BINDINGS_VERSION :: [4]u8{27, 0, 2, 0}
BINDINGS_VERSION_STRING :: "27.0.2.0"
BINDINGS_VERSION :: [4]u8{29, 0, 0, 0}
BINDINGS_VERSION_STRING :: "29.0.0.0"
LogLevel :: enum i32 {
Off,
@@ -19,21 +19,29 @@ InstanceBackend :: enum i32 {
GL,
Metal,
DX12,
DX11,
BrowserWebGPU,
// DX11,
BrowserWebGPU = 5,
}
InstanceBackendFlags :: bit_set[InstanceBackend; Flags]
InstanceBackendFlags_All :: InstanceBackendFlags{}
InstanceBackendFlags_Primary :: InstanceBackendFlags{ .Vulkan, .Metal, .DX12, .BrowserWebGPU }
InstanceBackendFlags_Secondary :: InstanceBackendFlags{ .GL, .DX11 }
InstanceBackendFlags_Secondary :: InstanceBackendFlags{ .GL }
InstanceFlag :: enum i32 {
Debug,
Validation,
DiscardHalLabels,
AllowUnderlyingNoncompliantAdapter,
GPUBasedValidation,
ValidationIndirectCall,
AutomaticTimestampNormalization,
Default = 24,
Debugging,
AdvancedDebugging,
WithEnv,
}
InstanceFlags :: bit_set[InstanceFlag; Flags]
InstanceFlags_Default :: InstanceFlags{}
InstanceFlags_Empty :: InstanceFlags{}
Dx12Compiler :: enum i32 {
Undefined,
@@ -72,6 +80,42 @@ GLFenceBehaviour :: enum i32 {
AutoFinish,
}
Dx12SwapchainKind :: enum i32 {
Undefined,
DxgiFromHwnd,
DxgiFromVisual,
}
NativeDisplayHandleType :: enum i32 {
None,
Xlib,
Xcb,
Wayland,
}
XlibDisplayHandle :: struct {
display: rawptr,
screen: i32,
}
XcbDisplayHandle :: struct {
connection: rawptr,
screen: i32,
}
WaylandDisplayHandle :: struct {
display: rawptr,
}
NativeDisplayHandle :: struct {
type: NativeDisplayHandleType,
using data: struct #raw_union {
xlib: XlibDisplayHandle,
xcb: XcbDisplayHandle,
wayland: WaylandDisplayHandle,
},
}
InstanceExtras :: struct {
using chain: ChainedStruct,
backends: InstanceBackendFlags,
@@ -81,8 +125,10 @@ InstanceExtras :: struct {
glFenceBehaviour: GLFenceBehaviour,
dxcPath: StringView,
dcxMaxShaderModel: DxcMaxShaderModel,
dx12PresentationSystem: Dx12SwapchainKind,
budgetForDeviceCreation: ^u8,
budgetForDeviceLoss: ^u8,
displayHandle: NativeDisplayHandle,
}
DeviceExtras :: struct {
@@ -91,21 +137,15 @@ DeviceExtras :: struct {
}
NativeLimits :: struct {
using chain: ChainedStructOut,
maxPushConstantSize: u32,
using chain: ChainedStruct,
maxImmediateSize: u32,
maxNonSamplerBindings: u32,
}
PushConstantRange :: struct {
stages: ShaderStageFlags,
start: u32,
end: u32,
maxBindingArrayElementsPerShaderStage: u32,
}
PipelineLayoutExtras :: struct {
using chain: ChainedStruct,
pushConstantRangeCount: uint,
pushConstantRanges: [^]PushConstantRange `fmt:"v,pushConstantRangeCount"`,
immediateDataSize: u32,
}
SubmissionIndex :: distinct u64

View File

@@ -222,7 +222,7 @@ foreign zlib {
@(default_calling_convention="c")
foreign zlib {
deflateInit_ :: proc(strm: z_streamp, level: c.int, version: cstring, stream_size: c.int) -> c.int ---
inflateInit_ :: proc(strm: z_streamp, level: c.int, version: cstring, stream_size: c.int) -> c.int ---
inflateInit_ :: proc(strm: z_streamp, version: cstring, stream_size: c.int) -> c.int ---
deflateInit2_ :: proc(strm: z_streamp, level, method, windowBits, memLevel, strategy: c.int, version: cstring, stream_size: c.int) -> c.int ---
inflateInit2_ :: proc(strm: z_streamp, windowBits: c.int, version: cstring, stream_size: c.int) -> c.int ---
inflateBackInit_ :: proc(strm: z_streamp, windowBits: c.int, window: [^]c.uchar, version: cstring, stream_size: c.int) -> c.int ---
@@ -236,8 +236,8 @@ deflateInit :: #force_inline proc "c" (strm: z_streamp, level: c.int) -> c.int {
return deflateInit_(strm, level, VERSION, c.int(size_of(z_stream)))
}
inflateInit :: #force_inline proc "c" (strm: z_streamp, level: c.int) -> c.int {
return inflateInit_(strm, level, VERSION, c.int(size_of(z_stream)))
inflateInit :: #force_inline proc "c" (strm: z_streamp) -> c.int {
return inflateInit_(strm, VERSION, c.int(size_of(z_stream)))
}
deflateInit2 :: #force_inline proc "c" (strm: z_streamp, level, method, windowBits, memLevel, strategy: c.int) -> c.int {