File: protocol.rs

package info (click to toggle)
rust-gix-worktree-stream 0.18.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 132 kB
  • sloc: makefile: 4
file content (131 lines) | stat: -rw-r--r-- 4,289 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use crate::utils;
use gix_object::bstr::{BStr, BString};
use std::io::{self, ErrorKind, Read, Write};

// Format: [usize-LE][usize-LE][byte][byte][hash][relative_path_bytes][object_stream]
// Note that stream_len can be usize::MAX to indicate the stream size is unknown
pub(crate) fn read_entry_info(
    read: &mut utils::Read,
    path_buf: &mut BString,
) -> std::io::Result<(Option<usize>, gix_object::tree::EntryMode, gix_hash::ObjectId)> {
    let mut buf = [0; std::mem::size_of::<usize>() * 2 + 2];

    read.read_exact(&mut buf)?;
    let (path_len, rest) = buf.split_at(std::mem::size_of::<usize>());
    let (stream_len, bytes) = rest.split_at(std::mem::size_of::<usize>());
    let path_len = usize::from_le_bytes(path_len.try_into().expect("valid"));
    let stream_size = usize::from_le_bytes(stream_len.try_into().expect("valid"));
    let mode = byte_to_mode(bytes[0]);
    let hash_kind = byte_to_hash(bytes[1]);

    let mut hash = hash_kind.null();
    read.read_exact(hash.as_mut_slice())?;

    clear_and_set_len(path_buf, path_len)?;
    read.read_exact(path_buf)?;

    Ok(((stream_size != usize::MAX).then_some(stream_size), mode, hash))
}

/// This function must match the read-count of `read_entry_info` for max efficiency.
pub(crate) fn write_entry_header_and_path(
    path: &BStr,
    oid: &gix_hash::oid,
    mode: gix_object::tree::EntryMode,
    stream_len: Option<usize>,
    out: &mut gix_features::io::pipe::Writer,
) -> std::io::Result<()> {
    const HEADER_LEN: usize = std::mem::size_of::<usize>() * 2 + 2;
    let mut buf = [0u8; HEADER_LEN + gix_hash::Kind::longest().len_in_bytes()];
    let (path_len_buf, rest) = buf.split_at_mut(std::mem::size_of::<usize>());
    let (stream_len_buf, bytes) = rest.split_at_mut(std::mem::size_of::<usize>());

    path_len_buf.copy_from_slice(&path.len().to_le_bytes());
    stream_len_buf.copy_from_slice(&stream_len.unwrap_or(usize::MAX).to_le_bytes());
    bytes[0] = mode_to_byte(mode);
    bytes[1] = hash_to_byte(oid.kind());
    bytes[2..][..oid.kind().len_in_bytes()].copy_from_slice(oid.as_bytes());

    // We know how `out` works in a pipe writer, it's always writing everything.
    #[allow(clippy::unused_io_amount)]
    {
        out.write(&buf[..HEADER_LEN + oid.kind().len_in_bytes()])?;
        out.write(path)?;
    }
    Ok(())
}

/// This writes everything in `input` in such way that the receiver knows exactly how much to read.
/// The format is similar to the packetline format, but in binary.
pub(crate) fn write_stream(
    buf: &mut Vec<u8>,
    mut input: impl std::io::Read,
    out: &mut gix_features::io::pipe::Writer,
) -> std::io::Result<()> {
    const BUF_LEN: usize = u16::MAX as usize;
    clear_and_set_len(buf, BUF_LEN)?;

    // We know how `out` works in a pipe writer, it's always writing everything.
    #[allow(clippy::unused_io_amount)]
    loop {
        match input.read(buf) {
            Ok(0) => {
                // terminator
                out.write(&0_u16.to_le_bytes())?;
                break;
            }
            Ok(n) => {
                out.write(&(n as u16).to_le_bytes())?;
                out.write(&buf[..n])?;
            }
            Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
            Err(e) => return Err(e),
        }
    }

    Ok(())
}

fn byte_to_hash(b: u8) -> gix_hash::Kind {
    match b {
        0 => gix_hash::Kind::Sha1,
        _ => unreachable!("BUG: we control the protocol"),
    }
}

fn byte_to_mode(b: u8) -> gix_object::tree::EntryMode {
    use gix_object::tree::EntryKind::*;
    match b {
        0 => Tree,
        1 => Blob,
        2 => BlobExecutable,
        3 => Link,
        4 => Commit,
        _ => unreachable!("BUG: we control the protocol"),
    }
    .into()
}

fn hash_to_byte(h: gix_hash::Kind) -> u8 {
    match h {
        gix_hash::Kind::Sha1 => 0,
    }
}

fn mode_to_byte(m: gix_object::tree::EntryMode) -> u8 {
    use gix_object::tree::EntryKind::*;
    match m.kind() {
        Tree => 0,
        Blob => 1,
        BlobExecutable => 2,
        Link => 3,
        Commit => 4,
    }
}

fn clear_and_set_len(buf: &mut Vec<u8>, len: usize) -> io::Result<()> {
    buf.clear();
    buf.try_reserve(len).map_err(|_| ErrorKind::OutOfMemory)?;
    buf.resize(len, 0);
    Ok(())
}