Files
SideImpactor/wasm/openssl/rust-src/lib.rs
2026-04-10 20:34:14 +08:00

1239 lines
35 KiB
Rust

use core::ffi::{c_char, c_int, c_long, c_ulong, c_void};
use std::alloc::{alloc, alloc_zeroed, dealloc, realloc as rust_realloc, Layout};
use std::io::{ErrorKind, Read, Write};
use std::mem;
use std::ptr;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Once, OnceLock};
use openssl::error::ErrorStack;
use openssl::hash::MessageDigest;
use openssl::nid::Nid;
use openssl::pkey::{HasPublic, PKey, Private};
use openssl::provider::Provider;
use openssl::rsa::Rsa;
use openssl::ssl::{
HandshakeError, MidHandshakeSslStream, Ssl, SslContextBuilder, SslMethod, SslStream,
SslVerifyMode, SslVersion,
};
use openssl::x509::extension::{BasicConstraints, KeyUsage, SubjectKeyIdentifier};
use openssl_sys as ffi;
use openssl::x509::{X509NameBuilder, X509};
use openssl::{asn1::{Asn1Integer, Asn1Time}, bn::BigNum};
use serde::Serialize;
use wasm_bindgen::prelude::*;
use web_sys::window;
static OPENSSL_PROVIDER_INIT: Once = Once::new();
static OPENSSL_PROVIDER_STATUS: OnceLock<String> = OnceLock::new();
static COUNT_QSORT: AtomicUsize = AtomicUsize::new(0);
static COUNT_VSNPRINTF: AtomicUsize = AtomicUsize::new(0);
static COUNT_SSCANF: AtomicUsize = AtomicUsize::new(0);
static COUNT_STRTOL: AtomicUsize = AtomicUsize::new(0);
static COUNT_STRTOUL: AtomicUsize = AtomicUsize::new(0);
static COUNT_GETENV: AtomicUsize = AtomicUsize::new(0);
static COUNT_OPEN: AtomicUsize = AtomicUsize::new(0);
static COUNT_READ: AtomicUsize = AtomicUsize::new(0);
static COUNT_STAT: AtomicUsize = AtomicUsize::new(0);
static COUNT_OPENDIR: AtomicUsize = AtomicUsize::new(0);
static ARC4_FALLBACK_SEED: AtomicUsize = AtomicUsize::new(0x9E37_79B9);
#[no_mangle]
pub static mut errno: c_int = 0;
#[no_mangle]
pub static CLOCK_REALTIME: c_int = 0;
#[no_mangle]
pub static _CLOCK_REALTIME: c_int = 0;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct PairRecordWasmOut {
host_id: String,
system_buid: String,
host_certificate_pem: String,
host_private_key_pem: String,
root_certificate_pem: String,
root_private_key_pem: String,
device_certificate_pem: String,
}
#[derive(Copy, Clone)]
enum PairCertProfile {
Root,
Host,
Device,
}
fn bump(counter: &AtomicUsize) {
counter.fetch_add(1, Ordering::Relaxed);
}
fn ensure_openssl_providers() {
OPENSSL_PROVIDER_INIT.call_once(|| {
let mut status = Vec::new();
for name in ["default", "base", "legacy"] {
match Provider::load(None, name) {
Ok(provider) => {
// Keep providers loaded for the whole process lifetime.
mem::forget(provider);
status.push(format!("{name}=ok"));
}
Err(error) => {
status.push(format!("{name}=err({error})"));
}
}
}
let _ = OPENSSL_PROVIDER_STATUS.set(status.join(","));
});
}
fn openssl_provider_status() -> &'static str {
OPENSSL_PROVIDER_STATUS
.get()
.map(String::as_str)
.unwrap_or("<uninitialized>")
}
#[wasm_bindgen]
pub fn libimobiledevice_generate_pair_record(
device_public_key: Vec<u8>,
host_id: String,
system_buid: String,
) -> Result<String, JsValue> {
openssl::init();
ensure_openssl_providers();
let device_pubkey = parse_device_public_key(&device_public_key)
.map_err(|error| js_err("parse device public key failed", error))?;
let root_key = PKey::from_rsa(Rsa::generate(2048).map_err(|error| js_err("generate root RSA failed", error))?)
.map_err(|error| js_err("create root pkey failed", error))?;
let host_key = PKey::from_rsa(Rsa::generate(2048).map_err(|error| js_err("generate host RSA failed", error))?)
.map_err(|error| js_err("create host pkey failed", error))?;
let root_cert = build_pair_certificate(&root_key, &root_key, None, PairCertProfile::Root)
.map_err(|error| js_err("build root cert failed", error))?;
let host_cert =
build_pair_certificate(&host_key, &root_key, Some(&root_cert), PairCertProfile::Host)
.map_err(|error| js_err("build host cert failed", error))?;
let device_cert = build_pair_certificate(
&device_pubkey,
&root_key,
Some(&root_cert),
PairCertProfile::Device,
)
.map_err(|error| js_err("build device cert failed", error))?;
let root_rsa = root_key
.rsa()
.map_err(|error| js_err("extract root rsa failed", error))?;
let host_rsa = host_key
.rsa()
.map_err(|error| js_err("extract host rsa failed", error))?;
let output = PairRecordWasmOut {
host_id,
system_buid,
host_certificate_pem: pem_bytes_to_string(
host_cert
.to_pem()
.map_err(|error| js_err("export host cert failed", error))?,
)?,
host_private_key_pem: pem_bytes_to_string(
host_rsa
.private_key_to_pem()
.map_err(|error| js_err("export host key failed", error))?,
)?,
root_certificate_pem: pem_bytes_to_string(
root_cert
.to_pem()
.map_err(|error| js_err("export root cert failed", error))?,
)?,
root_private_key_pem: pem_bytes_to_string(
root_rsa
.private_key_to_pem()
.map_err(|error| js_err("export root key failed", error))?,
)?,
device_certificate_pem: pem_bytes_to_string(
device_cert
.to_pem()
.map_err(|error| js_err("export device cert failed", error))?,
)?,
};
serde_json::to_string(&output)
.map_err(|error| JsValue::from_str(&format!("serialize pair record failed: {error}")))
}
fn parse_device_public_key(device_public_key: &[u8]) -> Result<PKey<openssl::pkey::Public>, ErrorStack> {
if let Ok(key) = PKey::public_key_from_pem(device_public_key) {
return Ok(key);
}
PKey::public_key_from_der(device_public_key)
}
fn build_pair_certificate<T: HasPublic>(
subject_key: &PKey<T>,
issuer_key: &PKey<Private>,
issuer_cert: Option<&X509>,
profile: PairCertProfile,
) -> Result<X509, ErrorStack> {
let mut builder = X509::builder()?;
builder.set_version(2)?;
let serial_bn = BigNum::from_u32(0)?;
let serial = Asn1Integer::from_bn(&serial_bn)?;
builder.set_serial_number(&serial)?;
// Avoid runtime time dependencies in wasm libc shims.
let not_before = Asn1Time::from_str_x509("20240101000000Z")?;
builder.set_not_before(not_before.as_ref())?;
let not_after = Asn1Time::from_str_x509("20340101000000Z")?;
builder.set_not_after(not_after.as_ref())?;
let subject_name = X509NameBuilder::new()?.build();
builder.set_subject_name(&subject_name)?;
if let Some(cert) = issuer_cert {
builder.set_issuer_name(cert.subject_name())?;
} else {
builder.set_issuer_name(&subject_name)?;
}
builder.set_pubkey(subject_key)?;
match profile {
PairCertProfile::Root => {
let ext = BasicConstraints::new().critical().ca().build()?;
builder.append_extension(ext)?;
}
PairCertProfile::Host => {
let basic = BasicConstraints::new().critical().build()?;
builder.append_extension(basic)?;
let usage = KeyUsage::new()
.critical()
.digital_signature()
.key_encipherment()
.build()?;
builder.append_extension(usage)?;
}
PairCertProfile::Device => {
let basic = BasicConstraints::new().critical().build()?;
builder.append_extension(basic)?;
let subject_key_id =
SubjectKeyIdentifier::new().build(&builder.x509v3_context(issuer_cert.map(|v| v.as_ref()), None))?;
builder.append_extension(subject_key_id)?;
let usage = KeyUsage::new()
.critical()
.digital_signature()
.key_encipherment()
.build()?;
builder.append_extension(usage)?;
}
}
builder.sign(issuer_key, MessageDigest::sha256())?;
Ok(builder.build())
}
fn pem_bytes_to_string(value: Vec<u8>) -> Result<String, JsValue> {
let text = String::from_utf8(value)
.map_err(|error| JsValue::from_str(&format!("invalid utf8 pem: {error}")))?;
Ok(text.replace('\0', ""))
}
#[wasm_bindgen]
pub struct OpensslClient {
state: Option<TlsState>,
tls_out: Vec<u8>,
plain_out: Vec<u8>,
}
enum TlsState {
Handshaking(MidHandshakeSslStream<PumpIo>),
Ready(SslStream<PumpIo>),
}
#[wasm_bindgen]
impl OpensslClient {
#[wasm_bindgen(constructor)]
pub fn new(
_server_name: String,
_ca_cert_pem: String,
client_cert_pem: String,
client_key_pem: String,
) -> Result<OpensslClient, JsValue> {
openssl::init();
ensure_openssl_providers();
let cert = X509::from_pem(client_cert_pem.as_bytes())
.map_err(|error| js_err("X509::from_pem failed", error))?;
let key = PKey::private_key_from_pem(client_key_pem.as_bytes())
.map_err(|error| js_err("PKey::private_key_from_pem failed", error))?;
let cert_pubkey = cert.public_key().map_err(|error| {
JsValue::from_str(&format!(
"cert.public_key failed: {error}; providers={} cert_len={} cert_head={} cert_tail={}",
openssl_provider_status(),
client_cert_pem.len(),
pem_head_line(&client_cert_pem),
pem_tail_line(&client_cert_pem),
))
})?;
let cert_cn = cert_common_name(&cert);
let cert_pub_alg = format!("{:?}", cert_pubkey.id());
let key_alg = format!("{:?}", key.id());
let key_matches_cert = cert_pubkey.public_eq(&key);
let mut builder =
SslContextBuilder::new(SslMethod::tls()).map_err(|error| js_err("SslContextBuilder::new failed", error))?;
unsafe {
// Match libimobiledevice OpenSSL behavior for newer OpenSSL branches.
let options = ffi::SSL_OP_LEGACY_SERVER_CONNECT as u64;
let _ = ffi::SSL_CTX_set_options(builder.as_ptr(), options);
}
builder.set_security_level(0);
builder.set_verify(SslVerifyMode::NONE);
let _ = builder.set_min_proto_version(Some(SslVersion::TLS1));
builder.set_certificate(&cert).map_err(|error| {
JsValue::from_str(&format!(
"set_certificate failed: {error}; providers={} cert_cn={cert_cn} cert_pub_alg={cert_pub_alg} key_alg={key_alg} key_matches_cert={key_matches_cert} cert_len={} cert_head={} cert_tail={}",
openssl_provider_status(),
client_cert_pem.len(),
pem_head_line(&client_cert_pem),
pem_tail_line(&client_cert_pem),
))
})?;
builder
.set_private_key(&key)
.map_err(|error| js_err("set_private_key failed", error))?;
builder
.check_private_key()
.map_err(|error| js_err("check_private_key failed", error))?;
let context = builder.build();
let mut ssl = Ssl::new(&context).map_err(|error| js_err("Ssl::new failed", error))?;
ssl.set_connect_state();
ssl.set_verify(SslVerifyMode::NONE);
let io = PumpIo::new();
let state = match ssl.connect(io) {
Ok(stream) => TlsState::Ready(stream),
Err(HandshakeError::WouldBlock(mid)) => TlsState::Handshaking(mid),
Err(HandshakeError::Failure(mid)) => {
return Err(JsValue::from_str(&format!(
"initial handshake failure: {}",
mid.error()
)))
}
Err(HandshakeError::SetupFailure(error)) => {
return Err(js_err("initial handshake setup failure", error))
}
};
let mut client = OpensslClient {
state: Some(state),
tls_out: Vec::new(),
plain_out: Vec::new(),
};
client.collect_tls_out();
Ok(client)
}
pub fn is_handshaking(&self) -> bool {
matches!(self.state, Some(TlsState::Handshaking(_)))
}
pub fn write_plaintext(&mut self, data: &[u8]) -> Result<(), JsValue> {
let mut state = self.take_state()?;
match &mut state {
TlsState::Ready(stream) => {
stream
.write_all(data)
.map_err(|error| JsValue::from_str(&format!("SSL write failed: {error}")))?;
}
TlsState::Handshaking(_) => {
self.state = Some(state);
return Err(JsValue::from_str("TLS handshake is not completed"));
}
}
self.state = Some(state);
self.collect_tls_out();
Ok(())
}
pub fn feed_tls(&mut self, data: &[u8]) -> Result<(), JsValue> {
let mut state = self.take_state()?;
state.io_mut().push_inbound(data);
state = advance_handshake(state)?;
if let TlsState::Ready(stream) = &mut state {
let mut buf = [0u8; 4096];
loop {
match stream.read(&mut buf) {
Ok(0) => break,
Ok(read) => self.plain_out.extend_from_slice(&buf[..read]),
Err(error) if error.kind() == ErrorKind::WouldBlock => break,
Err(error) => {
self.state = Some(state);
return Err(JsValue::from_str(&format!("SSL read failed: {error}")));
}
}
}
}
self.state = Some(state);
self.collect_tls_out();
Ok(())
}
pub fn take_tls_out(&mut self) -> Vec<u8> {
mem::take(&mut self.tls_out)
}
pub fn take_plain_out(&mut self) -> Vec<u8> {
mem::take(&mut self.plain_out)
}
}
#[wasm_bindgen]
pub fn debug_runtime_stats() -> String {
format!(
"providers={}; qsort={} vsnprintf={} sscanf={} strtol={} strtoul={} getenv={} open={} read={} stat={} opendir={}",
openssl_provider_status(),
COUNT_QSORT.load(Ordering::Relaxed),
COUNT_VSNPRINTF.load(Ordering::Relaxed),
COUNT_SSCANF.load(Ordering::Relaxed),
COUNT_STRTOL.load(Ordering::Relaxed),
COUNT_STRTOUL.load(Ordering::Relaxed),
COUNT_GETENV.load(Ordering::Relaxed),
COUNT_OPEN.load(Ordering::Relaxed),
COUNT_READ.load(Ordering::Relaxed),
COUNT_STAT.load(Ordering::Relaxed),
COUNT_OPENDIR.load(Ordering::Relaxed),
)
}
impl OpensslClient {
fn take_state(&mut self) -> Result<TlsState, JsValue> {
self.state
.take()
.ok_or_else(|| JsValue::from_str("TLS state is unavailable"))
}
fn collect_tls_out(&mut self) {
if let Some(state) = &mut self.state {
let bytes = state.io_mut().take_outbound();
if !bytes.is_empty() {
self.tls_out.extend_from_slice(&bytes);
}
}
}
}
impl TlsState {
fn io_mut(&mut self) -> &mut PumpIo {
match self {
TlsState::Handshaking(mid) => mid.get_mut(),
TlsState::Ready(stream) => stream.get_mut(),
}
}
}
fn advance_handshake(mut state: TlsState) -> Result<TlsState, JsValue> {
loop {
match state {
TlsState::Handshaking(mid) => match mid.handshake() {
Ok(stream) => {
state = TlsState::Ready(stream);
}
Err(HandshakeError::WouldBlock(next_mid)) => {
return Ok(TlsState::Handshaking(next_mid));
}
Err(HandshakeError::Failure(failed_mid)) => {
return Err(JsValue::from_str(&format!(
"handshake failure: {}",
failed_mid.error()
)))
}
Err(HandshakeError::SetupFailure(error)) => {
return Err(js_err("handshake setup failure", error))
}
},
TlsState::Ready(_) => return Ok(state),
}
}
}
fn js_err(prefix: &str, error: ErrorStack) -> JsValue {
JsValue::from_str(&format!("{prefix}: {error}"))
}
fn pem_head_line(pem: &str) -> &str {
pem.lines()
.find(|line| !line.trim().is_empty())
.unwrap_or("<empty>")
}
fn pem_tail_line(pem: &str) -> &str {
pem.lines()
.rev()
.find(|line| !line.trim().is_empty())
.unwrap_or("<empty>")
}
fn cert_common_name(cert: &X509) -> String {
cert.subject_name()
.entries_by_nid(Nid::COMMONNAME)
.next()
.and_then(|entry| entry.data().as_utf8().ok().map(|v| v.to_string()))
.unwrap_or_else(|| "<none>".to_string())
}
struct PumpIo {
inbound: Vec<u8>,
outbound: Vec<u8>,
}
impl PumpIo {
fn new() -> Self {
Self {
inbound: Vec::new(),
outbound: Vec::new(),
}
}
fn push_inbound(&mut self, data: &[u8]) {
if !data.is_empty() {
self.inbound.extend_from_slice(data);
}
}
fn take_outbound(&mut self) -> Vec<u8> {
mem::take(&mut self.outbound)
}
}
impl Read for PumpIo {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.inbound.is_empty() {
return Err(std::io::Error::from(ErrorKind::WouldBlock));
}
let read_len = buf.len().min(self.inbound.len());
buf[..read_len].copy_from_slice(&self.inbound[..read_len]);
self.inbound.drain(..read_len);
Ok(read_len)
}
}
impl Write for PumpIo {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
if !buf.is_empty() {
self.outbound.extend_from_slice(buf);
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[repr(C)]
struct AllocHeader {
payload_size: usize,
align: usize,
}
fn alloc_payload(size: usize, align: usize, zeroed: bool) -> *mut c_void {
let safe_align = align.max(mem::align_of::<AllocHeader>()).max(8);
let total_size = match size.checked_add(mem::size_of::<AllocHeader>()) {
Some(value) => value,
None => return ptr::null_mut(),
};
let layout = match Layout::from_size_align(total_size, safe_align) {
Ok(value) => value,
Err(_) => return ptr::null_mut(),
};
let raw = unsafe {
if zeroed {
alloc_zeroed(layout)
} else {
alloc(layout)
}
};
if raw.is_null() {
return ptr::null_mut();
}
unsafe {
let header = raw as *mut AllocHeader;
(*header).payload_size = size;
(*header).align = safe_align;
raw.add(mem::size_of::<AllocHeader>()) as *mut c_void
}
}
unsafe fn header_from_payload(payload: *mut c_void) -> *mut AllocHeader {
(payload as *mut u8).sub(mem::size_of::<AllocHeader>()) as *mut AllocHeader
}
unsafe fn c_strlen(ptr_in: *const c_char) -> usize {
if ptr_in.is_null() {
return 0;
}
let mut len = 0usize;
let mut cursor = ptr_in;
while *cursor != 0 {
len += 1;
cursor = cursor.add(1);
}
len
}
unsafe fn c_strcmp(a: *const c_char, b: *const c_char, limit: Option<usize>) -> c_int {
let mut index = 0usize;
loop {
if let Some(max_len) = limit {
if index >= max_len {
return 0;
}
}
let av = if a.is_null() { 0 } else { *a.add(index) as i32 };
let bv = if b.is_null() { 0 } else { *b.add(index) as i32 };
if av != bv {
return av - bv;
}
if av == 0 {
return 0;
}
index += 1;
}
}
unsafe fn c_find_char(haystack: *const c_char, needle: c_int, from_end: bool) -> *mut c_char {
if haystack.is_null() {
return ptr::null_mut();
}
let n = (needle & 0xff) as u8;
if from_end {
let len = c_strlen(haystack);
let mut index = len;
loop {
let value = *haystack.add(index) as u8;
if value == n {
return haystack.add(index) as *mut c_char;
}
if index == 0 {
break;
}
index -= 1;
}
return ptr::null_mut();
}
let mut index = 0usize;
loop {
let value = *haystack.add(index) as u8;
if value == n {
return haystack.add(index) as *mut c_char;
}
if value == 0 {
return ptr::null_mut();
}
index += 1;
}
}
#[no_mangle]
pub extern "C" fn malloc(size: usize) -> *mut c_void {
alloc_payload(size, 16, false)
}
#[no_mangle]
pub extern "C" fn calloc(count: usize, size: usize) -> *mut c_void {
let total = match count.checked_mul(size) {
Some(value) => value,
None => return ptr::null_mut(),
};
alloc_payload(total, 16, true)
}
#[no_mangle]
pub extern "C" fn realloc(ptr_in: *mut c_void, size: usize) -> *mut c_void {
if ptr_in.is_null() {
return malloc(size);
}
unsafe {
let old_header = header_from_payload(ptr_in);
let old_payload_size = (*old_header).payload_size;
let align = (*old_header).align;
let old_total = old_payload_size + mem::size_of::<AllocHeader>();
let new_total = match size.checked_add(mem::size_of::<AllocHeader>()) {
Some(value) => value,
None => return ptr::null_mut(),
};
let layout = match Layout::from_size_align(old_total, align) {
Ok(value) => value,
Err(_) => return ptr::null_mut(),
};
let new_raw = rust_realloc(old_header as *mut u8, layout, new_total);
if new_raw.is_null() {
return ptr::null_mut();
}
let new_header = new_raw as *mut AllocHeader;
(*new_header).payload_size = size;
(*new_header).align = align;
new_raw.add(mem::size_of::<AllocHeader>()) as *mut c_void
}
}
#[no_mangle]
pub extern "C" fn free(ptr_in: *mut c_void) {
if ptr_in.is_null() {
return;
}
unsafe {
let header = header_from_payload(ptr_in);
let payload_size = (*header).payload_size;
let align = (*header).align;
let total = payload_size + mem::size_of::<AllocHeader>();
if let Ok(layout) = Layout::from_size_align(total, align) {
dealloc(header as *mut u8, layout);
}
}
}
#[no_mangle]
pub extern "C" fn posix_memalign(out: *mut *mut c_void, align: usize, size: usize) -> c_int {
if out.is_null() {
return 22;
}
let ptr_out = alloc_payload(size, align.max(16), false);
if ptr_out.is_null() {
return 12;
}
unsafe {
*out = ptr_out;
}
0
}
#[no_mangle]
pub extern "C" fn memchr(ptr_in: *const c_void, value: c_int, len: usize) -> *mut c_void {
if ptr_in.is_null() {
return ptr::null_mut();
}
let needle = (value & 0xff) as u8;
unsafe {
let bytes = std::slice::from_raw_parts(ptr_in as *const u8, len);
for (index, byte) in bytes.iter().enumerate() {
if *byte == needle {
return (ptr_in as *const u8).add(index) as *mut c_void;
}
}
}
ptr::null_mut()
}
#[no_mangle]
pub extern "C" fn strcmp(a: *const c_char, b: *const c_char) -> c_int {
unsafe { c_strcmp(a, b, None) }
}
#[no_mangle]
pub extern "C" fn strncmp(a: *const c_char, b: *const c_char, n: usize) -> c_int {
unsafe { c_strcmp(a, b, Some(n)) }
}
#[no_mangle]
pub extern "C" fn strchr(haystack: *const c_char, needle: c_int) -> *mut c_char {
unsafe { c_find_char(haystack, needle, false) }
}
#[no_mangle]
pub extern "C" fn strrchr(haystack: *const c_char, needle: c_int) -> *mut c_char {
unsafe { c_find_char(haystack, needle, true) }
}
#[no_mangle]
pub extern "C" fn strcpy(dst: *mut c_char, src: *const c_char) -> *mut c_char {
if dst.is_null() || src.is_null() {
return dst;
}
unsafe {
let mut index = 0usize;
loop {
let byte = *src.add(index);
*dst.add(index) = byte;
if byte == 0 {
break;
}
index += 1;
}
}
dst
}
#[no_mangle]
pub extern "C" fn strncpy(dst: *mut c_char, src: *const c_char, n: usize) -> *mut c_char {
if dst.is_null() || src.is_null() {
return dst;
}
unsafe {
let mut index = 0usize;
while index < n {
let byte = *src.add(index);
*dst.add(index) = byte;
index += 1;
if byte == 0 {
break;
}
}
while index < n {
*dst.add(index) = 0;
index += 1;
}
}
dst
}
#[no_mangle]
pub extern "C" fn strstr(haystack: *const c_char, needle: *const c_char) -> *mut c_char {
if haystack.is_null() || needle.is_null() {
return ptr::null_mut();
}
unsafe {
let needle_len = c_strlen(needle);
if needle_len == 0 {
return haystack as *mut c_char;
}
let hay_len = c_strlen(haystack);
if hay_len < needle_len {
return ptr::null_mut();
}
for start in 0..=(hay_len - needle_len) {
if c_strcmp(haystack.add(start), needle, Some(needle_len)) == 0 {
return haystack.add(start) as *mut c_char;
}
}
}
ptr::null_mut()
}
#[no_mangle]
pub extern "C" fn strpbrk(haystack: *const c_char, accept: *const c_char) -> *mut c_char {
if haystack.is_null() || accept.is_null() {
return ptr::null_mut();
}
unsafe {
let mut index = 0usize;
loop {
let ch = *haystack.add(index);
if ch == 0 {
break;
}
if !c_find_char(accept, ch as c_int, false).is_null() {
return haystack.add(index) as *mut c_char;
}
index += 1;
}
}
ptr::null_mut()
}
#[no_mangle]
pub extern "C" fn strspn(s: *const c_char, accept: *const c_char) -> usize {
if s.is_null() || accept.is_null() {
return 0;
}
unsafe {
let mut count = 0usize;
loop {
let ch = *s.add(count);
if ch == 0 {
return count;
}
if c_find_char(accept, ch as c_int, false).is_null() {
return count;
}
count += 1;
}
}
}
#[no_mangle]
pub extern "C" fn strcspn(s: *const c_char, reject: *const c_char) -> usize {
if s.is_null() || reject.is_null() {
return 0;
}
unsafe {
let mut count = 0usize;
loop {
let ch = *s.add(count);
if ch == 0 {
return count;
}
if !c_find_char(reject, ch as c_int, false).is_null() {
return count;
}
count += 1;
}
}
}
#[no_mangle]
pub extern "C" fn atoi(s: *const c_char) -> c_int {
strtol(s, ptr::null_mut(), 10) as c_int
}
#[no_mangle]
pub extern "C" fn strtol(s: *const c_char, endptr: *mut *mut c_char, base: c_int) -> c_long {
bump(&COUNT_STRTOL);
let (signed, value, parsed, end) = unsafe { parse_c_integer(s, base) };
if !endptr.is_null() {
unsafe {
*endptr = if parsed { end as *mut c_char } else { s as *mut c_char };
}
}
if !parsed {
return 0;
}
if signed {
let neg = value as i128;
let v = -neg;
if v < c_long::MIN as i128 {
return c_long::MIN;
}
return v as c_long;
}
if value > c_long::MAX as u128 {
return c_long::MAX;
}
value as c_long
}
#[no_mangle]
pub extern "C" fn strtoul(s: *const c_char, endptr: *mut *mut c_char, base: c_int) -> c_ulong {
bump(&COUNT_STRTOUL);
let (signed, value, parsed, end) = unsafe { parse_c_integer(s, base) };
if !endptr.is_null() {
unsafe {
*endptr = if parsed { end as *mut c_char } else { s as *mut c_char };
}
}
if !parsed {
return 0;
}
if signed {
let wrapped = (0u128).wrapping_sub(value) as u128;
return wrapped as c_ulong;
}
if value > c_ulong::MAX as u128 {
return c_ulong::MAX;
}
value as c_ulong
}
unsafe fn parse_c_integer(
s: *const c_char,
base_in: c_int,
) -> (bool, u128, bool, *const c_char) {
if s.is_null() {
return (false, 0, false, s);
}
let mut p = s as *const u8;
while !p.is_null() {
let ch = *p;
if ch == b' ' || (b'\t'..=b'\r').contains(&ch) {
p = p.add(1);
continue;
}
break;
}
let mut negative = false;
if !p.is_null() {
match *p {
b'-' => {
negative = true;
p = p.add(1);
}
b'+' => {
p = p.add(1);
}
_ => {}
}
}
let mut base = base_in as i32;
if base == 0 {
if *p == b'0' {
let next = *p.add(1);
if next == b'x' || next == b'X' {
base = 16;
p = p.add(2);
} else {
base = 8;
p = p.add(1);
}
} else {
base = 10;
}
} else if base == 16 {
if *p == b'0' {
let next = *p.add(1);
if next == b'x' || next == b'X' {
p = p.add(2);
}
}
}
if !(2..=36).contains(&base) {
return (negative, 0, false, s);
}
let base_u = base as u128;
let mut value: u128 = 0;
let mut parsed = false;
let mut cursor = p;
loop {
let ch = *cursor;
if ch == 0 {
break;
}
let digit = match ch {
b'0'..=b'9' => (ch - b'0') as u32,
b'a'..=b'z' => (ch - b'a' + 10) as u32,
b'A'..=b'Z' => (ch - b'A' + 10) as u32,
_ => break,
};
if digit >= base as u32 {
break;
}
parsed = true;
value = value
.saturating_mul(base_u)
.saturating_add(digit as u128);
cursor = cursor.add(1);
}
if !parsed {
return (negative, 0, false, s);
}
(negative, value, true, cursor as *const c_char)
}
#[no_mangle]
pub extern "C" fn tolower(value: c_int) -> c_int {
if (65..=90).contains(&value) {
value + 32
} else {
value
}
}
#[no_mangle]
pub extern "C" fn qsort(
base: *mut c_void,
nmemb: usize,
size: usize,
compar: extern "C" fn(*const c_void, *const c_void) -> c_int,
) {
bump(&COUNT_QSORT);
if base.is_null() || size == 0 || nmemb < 2 {
return;
}
unsafe {
let base_ptr = base as *mut u8;
let mut tmp = vec![0u8; size];
for i in 1..nmemb {
let mut j = i;
while j > 0 {
let left = base_ptr.add((j - 1) * size) as *const c_void;
let right = base_ptr.add(j * size) as *const c_void;
if compar(left, right) <= 0 {
break;
}
ptr::copy_nonoverlapping(base_ptr.add((j - 1) * size), tmp.as_mut_ptr(), size);
ptr::copy(
base_ptr.add(j * size),
base_ptr.add((j - 1) * size),
size,
);
ptr::copy_nonoverlapping(tmp.as_ptr(), base_ptr.add(j * size), size);
j -= 1;
}
}
}
}
#[no_mangle]
pub extern "C" fn sscanf(_src: *const c_char, _format: *const c_char, _arg: *mut c_void) -> c_int {
bump(&COUNT_SSCANF);
0
}
#[no_mangle]
pub extern "C" fn getenv(_name: *const c_char) -> *mut c_char {
bump(&COUNT_GETENV);
ptr::null_mut()
}
static ERRNO_TEXT: &[u8] = b"error\0";
#[no_mangle]
pub extern "C" fn strerror(_errnum: c_int) -> *mut c_char {
ERRNO_TEXT.as_ptr() as *mut c_char
}
#[no_mangle]
pub extern "C" fn getpid() -> c_int {
1
}
#[no_mangle]
pub extern "C" fn abort() {
panic!("abort called")
}
#[no_mangle]
pub extern "C" fn __cxa_atexit(
_func: extern "C" fn(*mut c_void),
_arg: *mut c_void,
_dso: *mut c_void,
) -> c_int {
0
}
#[no_mangle]
pub extern "C" fn time(out: *mut i64) -> i64 {
let now = 0i64;
if !out.is_null() {
unsafe {
*out = now;
}
}
now
}
#[no_mangle]
pub extern "C" fn gettimeofday(tv: *mut c_void, _tz: *mut c_void) -> c_int {
if tv.is_null() {
return -1;
}
unsafe {
let seconds = tv as *mut i64;
let micros = seconds.add(1);
*seconds = 0;
*micros = 0;
}
0
}
#[no_mangle]
pub extern "C" fn clock_gettime(_clock_id: c_int, tp: *mut c_void) -> c_int {
if tp.is_null() {
return -1;
}
unsafe {
let sec = tp as *mut i64;
let nsec = sec.add(1);
*sec = 0;
*nsec = 0;
}
0
}
#[no_mangle]
pub extern "C" fn arc4random_buf(buf: *mut c_void, len: usize) {
if buf.is_null() {
return;
}
unsafe {
let out = std::slice::from_raw_parts_mut(buf as *mut u8, len);
if fill_random_with_webcrypto(out) {
return;
}
// Fallback for non-browser runtime paths.
let mut state = (ARC4_FALLBACK_SEED.fetch_add(0x7f4a7c15, Ordering::Relaxed) as u64)
^ ((out.as_ptr() as usize as u64) << 1)
^ (len as u64).wrapping_mul(0x9e3779b97f4a7c15);
for byte in out.iter_mut() {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
*byte = (state & 0xff) as u8;
}
}
}
fn fill_random_with_webcrypto(out: &mut [u8]) -> bool {
if out.is_empty() {
return true;
}
if let Some(win) = window() {
if let Ok(crypto) = win.crypto() {
// Browser RNG source, equivalent to native secure random for this runtime.
return crypto.get_random_values_with_u8_array(out).is_ok();
}
}
false
}
#[no_mangle]
pub extern "C" fn getentropy(buf: *mut c_void, len: usize) -> c_int {
arc4random_buf(buf, len);
0
}
#[no_mangle]
pub extern "C" fn open(_path: *const c_char, _flags: c_int, _mode: c_int) -> c_int {
bump(&COUNT_OPEN);
-1
}
#[no_mangle]
pub extern "C" fn read(_fd: c_int, _buf: *mut c_void, _count: usize) -> c_int {
bump(&COUNT_READ);
-1
}
#[no_mangle]
pub extern "C" fn close(_fd: c_int) -> c_int {
0
}
#[no_mangle]
pub extern "C" fn stat(_path: *const c_char, _out: *mut c_void) -> c_int {
bump(&COUNT_STAT);
-1
}
#[no_mangle]
pub extern "C" fn fstat(_fd: c_int, _out: *mut c_void) -> c_int {
-1
}
#[no_mangle]
pub extern "C" fn opendir(_name: *const c_char) -> *mut c_void {
bump(&COUNT_OPENDIR);
ptr::null_mut()
}
#[no_mangle]
pub extern "C" fn readdir(_dir: *mut c_void) -> *mut c_void {
ptr::null_mut()
}
#[no_mangle]
pub extern "C" fn closedir(_dir: *mut c_void) -> c_int {
0
}
#[no_mangle]
pub extern "C" fn SSL_add_dir_cert_subjects_to_stack(_stack: *mut c_void, _dir: *const c_char) -> c_int {
1
}