Игра с небезопасным кодом

Прежде всего, я здесь, чтобы изучить небезопасные функции Rust. Если вы относитесь к тому типу парней, которые любят «безопасный» Rust и начнут ныть по поводу использования безопасных функций Rust, я все равно с удовольствием прочту ваш комментарий и узнаю о безопасных альтернативах. .

Тем не менее, я хочу расширить свои знания о небезопасный ржавчина. Документация очень скудная и не очень хорошо документирована. Постепенно, благодаря помощи пользователей Stack Overflow (особенно @kmdreko) и моей настойчивости, мне удалось заставить его работать. Моя программа может отправлять большие файлы (4Gb) по UDP.

Поскольку я всегда стараюсь улучшить качество своего кода, мне интересно, может ли кто-нибудь взглянуть на мой исходный код и дайте мне знать, как я могу его улучшить.


server.rs

const UDP_HEADER: usize = 8;
const IP_HEADER: usize = 20;
const AG_HEADER: usize = 4;
const MAX_CHUNK_SIZE: usize = (64 * 1024 - 1) - UDP_HEADER - IP_HEADER - AG_HEADER;

use std::net::UdpSocket;
use std::io;
use std::fs::File;
use std::io::prelude::*;
use std::alloc::{alloc, dealloc, Layout};
use std::mem;
use std::{mem::MaybeUninit};

// cmp -l 1.jpg 2.jpg

#[inline(always)]
fn memcpy(dst_ptr:*mut u8, src_ptr:*const u8, len:usize) {
    unsafe {
        std::ptr::copy_nonoverlapping(src_ptr, dst_ptr, len);
    }
}

#[inline(always)]
fn next_power_of_two(n:u32) -> u32 {
    return 32 - (n - 1).leading_zeros();
}

#[inline(always)]
fn write_chunks_to_file(filename: &str, bytes:&[u8]) -> Result<bool, io::Error> {
    let mut file = File::create(filename)?;
    file.write_all(bytes)?;
    Ok(true)
}

use std::thread;
fn main() {
    let socket = UdpSocket::bind("0.0.0.0:8888").expect("Could not bind socket");
    let filename = "2.jpg";
    let mut count = 0;
    let mut chunks_cnt:u32 = 0xffff;
    let mut total_size:usize = 0;
    let mut layout;
    unsafe { layout = MaybeUninit::<Layout>::uninit().assume_init(); };

    let mut bytes_buf;
    unsafe { bytes_buf = MaybeUninit::<*mut u8>::uninit().assume_init(); };

    loop {
        let mut buf = [0u8; MAX_CHUNK_SIZE + AG_HEADER];
        let sock = socket.try_clone().expect("Failed to clone socket");
        match socket.recv_from(&mut buf) {
            Ok((size, src)) => { // thanks https://doc.rust-lang.org/beta/std/net/struct.UdpSocket.html#method.recv_from
                total_size += size;
                let packet_index:usize = (buf[0] as usize) << 8 | buf[1] as usize;
                if count == 0 {
                    chunks_cnt = (buf[2] as u32) << 8 | buf[3] as u32;
                    let n:usize = 0x10000 << next_power_of_two(chunks_cnt);
                   // assert_eq!(n.count_ones(), 1); // can check with this function that n is aligned on power of 2
                    unsafe {
                         layout = Layout::from_size_align_unchecked(n, mem::align_of::<u8>());
                         bytes_buf = alloc(layout);
                    }
                }
                unsafe {
                    let dst_ptr = bytes_buf.offset((packet_index*MAX_CHUNK_SIZE) as isize);
                    memcpy(dst_ptr, &buf[AG_HEADER], size-AG_HEADER);
                };
                thread::spawn(move || {
                    //let s = String::from_utf8_lossy(&buf);
                    println!("receiving packet {} from: {}", packet_index, src);
                    sock.send_to(&buf, &src).expect("Failed to send a response");
                });
                println!("count: {}", count);
                count+=1;
            }
            Err(e) => {
                eprintln!("couldn't recieve a datagram: {}", e);
            }
        }
         if count == chunks_cnt { // all chunks have been collected, write bytes to file
            let bytes = unsafe { std::slice::from_raw_parts(bytes_buf, total_size) };
            let result = write_chunks_to_file(filename, &bytes);
            match result {
                Ok(true) => println!("Succesfully created file: {}", true),
                Ok(false) => println!("Could not create file: {}", false),
                Err(e) => println!("Error: {}", e),
            }
            count = 0;
            total_size = 0;
            unsafe { dealloc(bytes_buf, layout); }
        }
    }
}

client.rs

const UDP_HEADER: usize = 8;
const IP_HEADER: usize = 20;
const AG_HEADER: usize = 4;
const MAX_DATA_LENGTH: usize = (64 * 1024 - 1) - UDP_HEADER - IP_HEADER;

use std::io::Read;
use std::net::UdpSocket;
use std::io;

pub fn get_chunks_from_file(mut filename: String,total_size: &mut usize) -> Result<Vec<Vec<u8>>, io::Error> {
    filename.pop(); // get read of the trailing 'n' in user input.
    let mut file = std::fs::File::open(filename)?;
    let mut list_of_chunks = Vec::new();
    let chunk_size = MAX_DATA_LENGTH - AG_HEADER;

    loop {
        let mut chunk = Vec::with_capacity(chunk_size);
        let n = file
            .by_ref()
            .take(chunk_size as u64)
            .read_to_end(&mut chunk)?;
        *total_size += n;
        if n == 0 {
            break;
        }
        list_of_chunks.push(chunk);
        if n < chunk_size {
            break;
        }
    }
    Ok(list_of_chunks)
}

fn main() {
    let socket = UdpSocket::bind("127.0.0.1:8000").expect("Could not bind client socket");
    let mut buffer = [0u8; MAX_DATA_LENGTH];

    socket.connect("127.0.0.1:8888").expect("Could not connect to server");
    loop {
        let mut input = String::new();

        io::stdin()
            .read_line(&mut input)
            .expect("Failed to read from stdin");
        println!("{}", input);
        let mut total_size: usize = 0;
        let result: Result<Vec<Vec<u8>>, io::Error> = get_chunks_from_file(input, &mut total_size); // : Result<u8:u8>
        match result {
            Ok(chunks) => {
                let nb: u16 = chunks.len() as u16;
                let mut index: u16 = 0;
                let header: &mut[u8;4] = &mut[
                    (index >> 8) as u8,
                    (index & 0xff) as u8,
                    (nb >> 8) as u8,
                    (nb & 0xff) as u8,

                ]; //input.as_bytes();

                for chunk in chunks.iter() {
                    header[1] = (index & 0xff) as u8;
                    header[0] = (index >> 8) as u8;
                    let data:Vec<u8> = [header.as_ref(), chunk].concat();
                    //println!("FILE {} BYTESn {:?}", index, chunk);
                    println!(
                        "size: {} FILE {:?} of {} BYTESn {:?}",
                        total_size,
                        (header[0] as u16) << 8 | header[1] as u16,
                        nb - 1,
                        [0]
                    );
                    println!("{}", index);
                    socket.send(&data).expect("Failed to write to server");
                    socket.recv_from(&mut buffer).expect("Could not read into buffer");
                    index += 1;
                }
            }
            Err(e) => println!("Error: {}", e),
        }
        //print!( "{}",str::from_utf8(&buffer).expect("Could not write buffer as string"));
      //  println!( "Chunk received by server {:?}", &buffer);
    }
}

Те, о которых я знаю

Есть несколько вещей, которые меня особенно беспокоят и которые я уже знаю, которые можно улучшить с помощью этой функции:

fn write_chunks_to_file(filename: &str, bytes:&[u8]) -> Result<bool, io::Error> {
    let mut file = File::create(filename)?;
    file.write_all(bytes)?;
    Ok(true)
}

Я написал это так, чтобы компиляция работала, но даже я, создатель, могу признать, что в этом нет никакого смысла! Что было бы хорошим результатом для возврата?

А также следующее предупреждение при компиляции файла сервера:

warning: the type `std::alloc::Layout` does not permit being left uninitialized
  --> udp-server.rs:43:23
   |
43 |     unsafe { layout = MaybeUninit::<Layout>::uninit().assume_init(); };
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |                       |
   |                       this code causes undefined behavior when executed
   |                       help: use `MaybeUninit<T>` instead, and only call `assume_init` after initialization is done
   |
   = note: `#[warn(invalid_value)]` on by default
note: `std::num::NonZeroUsize` must be non-null (in this struct field)

Будем признательны за любую экспертную оценку.

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *