| Joel Galenson | 4be0c6d | 2020-07-07 13:20:14 -0700 | [diff] [blame] | 1 | // Copyright 2018 Developers of the Rand project. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| 4 | // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 5 | // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your |
| 6 | // option. This file may not be copied, modified, or distributed |
| 7 | // except according to those terms. |
| 8 | |
| 9 | //! Implementation for WASM via stdweb |
| 10 | extern crate std; |
| 11 | |
| 12 | use core::mem; |
| 13 | |
| 14 | use stdweb::js; |
| 15 | use stdweb::unstable::TryInto; |
| 16 | use stdweb::web::error::Error as WebError; |
| 17 | |
| 18 | use crate::error::{STDWEB_NO_RNG, STDWEB_RNG_FAILED}; |
| 19 | use crate::Error; |
| 20 | use std::sync::Once; |
| 21 | |
| 22 | #[derive(Clone, Copy, Debug)] |
| 23 | enum RngSource { |
| 24 | Browser, |
| 25 | Node, |
| 26 | } |
| 27 | |
| 28 | pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { |
| 29 | assert_eq!(mem::size_of::<usize>(), 4); |
| 30 | static ONCE: Once = Once::new(); |
| 31 | static mut RNG_SOURCE: Result<RngSource, Error> = Ok(RngSource::Node); |
| 32 | |
| 33 | // SAFETY: RNG_SOURCE is only written once, before being read. |
| 34 | ONCE.call_once(|| unsafe { |
| 35 | RNG_SOURCE = getrandom_init(); |
| 36 | }); |
| 37 | getrandom_fill(unsafe { RNG_SOURCE }?, dest) |
| 38 | } |
| 39 | |
| 40 | fn getrandom_init() -> Result<RngSource, Error> { |
| 41 | let result = js! { |
| 42 | try { |
| 43 | if ( |
| 44 | typeof self === "object" && |
| 45 | typeof self.crypto === "object" && |
| 46 | typeof self.crypto.getRandomValues === "function" |
| 47 | ) { |
| 48 | return { success: true, ty: 1 }; |
| 49 | } |
| 50 | |
| 51 | if (typeof require("crypto").randomBytes === "function") { |
| 52 | return { success: true, ty: 2 }; |
| 53 | } |
| 54 | |
| 55 | return { success: false, error: new Error("not supported") }; |
| 56 | } catch(err) { |
| 57 | return { success: false, error: err }; |
| 58 | } |
| 59 | }; |
| 60 | |
| 61 | if js! { return @{ result.as_ref() }.success } == true { |
| 62 | let ty = js! { return @{ result }.ty }; |
| 63 | |
| 64 | if ty == 1 { |
| 65 | Ok(RngSource::Browser) |
| 66 | } else if ty == 2 { |
| 67 | Ok(RngSource::Node) |
| 68 | } else { |
| 69 | unreachable!() |
| 70 | } |
| 71 | } else { |
| 72 | let _err: WebError = js! { return @{ result }.error }.try_into().unwrap(); |
| 73 | error!("getrandom unavailable: {}", _err); |
| 74 | Err(STDWEB_NO_RNG) |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | fn getrandom_fill(source: RngSource, dest: &mut [u8]) -> Result<(), Error> { |
| 79 | for chunk in dest.chunks_mut(65536) { |
| 80 | let len = chunk.len() as u32; |
| 81 | let ptr = chunk.as_mut_ptr() as i32; |
| 82 | |
| 83 | let result = match source { |
| 84 | RngSource::Browser => js! { |
| 85 | try { |
| 86 | let array = new Uint8Array(@{ len }); |
| 87 | self.crypto.getRandomValues(array); |
| 88 | HEAPU8.set(array, @{ ptr }); |
| 89 | |
| 90 | return { success: true }; |
| 91 | } catch(err) { |
| 92 | return { success: false, error: err }; |
| 93 | } |
| 94 | }, |
| 95 | RngSource::Node => js! { |
| 96 | try { |
| 97 | let bytes = require("crypto").randomBytes(@{ len }); |
| 98 | HEAPU8.set(new Uint8Array(bytes), @{ ptr }); |
| 99 | |
| 100 | return { success: true }; |
| 101 | } catch(err) { |
| 102 | return { success: false, error: err }; |
| 103 | } |
| 104 | }, |
| 105 | }; |
| 106 | |
| 107 | if js! { return @{ result.as_ref() }.success } != true { |
| 108 | let _err: WebError = js! { return @{ result }.error }.try_into().unwrap(); |
| 109 | error!("getrandom failed: {}", _err); |
| 110 | return Err(STDWEB_RNG_FAILED); |
| 111 | } |
| 112 | } |
| 113 | Ok(()) |
| 114 | } |