Skip to content

Commit 90c35bb

Browse files
committed
feat: refactor block_read to return the offset from the end
Previously, it would return the amount of data read. Unfortunately, this made it impossible to tell if we were at the end of the block. Now, we return the offset (negative or positive) from the end of the block, to the end of the user provided buffer.
1 parent bf60fc3 commit 90c35bb

File tree

8 files changed

+54
-55
lines changed

8 files changed

+54
-55
lines changed

fvm/src/kernel/default.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -363,23 +363,23 @@ where
363363
Ok(k)
364364
}
365365

366-
fn block_read(&mut self, id: BlockId, offset: u32, buf: &mut [u8]) -> Result<u32> {
367-
let data = self.blocks.get(id).or_illegal_argument()?.data();
366+
fn block_read(&mut self, id: BlockId, offset: u32, buf: &mut [u8]) -> Result<i32> {
367+
let block = self.blocks.get(id)?;
368+
let data = block.data();
368369

369-
let len = if offset as usize >= data.len() {
370-
0
371-
} else {
372-
buf.len().min(data.len())
373-
};
370+
let start = offset as usize;
374371

372+
let to_read = std::cmp::min(data.len().saturating_sub(start), buf.len());
375373
self.call_manager
376-
.charge_gas(self.call_manager.price_list().on_block_read(len))?;
374+
.charge_gas(self.call_manager.price_list().on_block_read(to_read))?;
377375

378-
if len != 0 {
379-
buf.copy_from_slice(&data[offset as usize..][..len]);
376+
let end = start + to_read;
377+
if to_read != 0 {
378+
buf[..to_read].copy_from_slice(&data[start..end]);
380379
}
381380

382-
Ok(len as u32)
381+
// Returns the difference between the end of the block, and the end of the data we've read.
382+
Ok((data.len() as i32) - (end as i32))
383383
}
384384

385385
fn block_stat(&mut self, id: BlockId) -> Result<BlockStat> {

fvm/src/kernel/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ pub trait BlockOps {
116116
/// Read data from a block.
117117
///
118118
/// This method will fail if the block handle is invalid.
119-
fn block_read(&mut self, id: BlockId, offset: u32, buf: &mut [u8]) -> Result<u32>;
119+
fn block_read(&mut self, id: BlockId, offset: u32, buf: &mut [u8]) -> Result<i32>;
120120

121121
/// Returns the blocks codec & size.
122122
///
@@ -130,8 +130,8 @@ pub trait BlockOps {
130130
let stat = self.block_stat(id)?;
131131
let mut ret = vec![0; stat.size as usize];
132132
// TODO error handling.
133-
let read = self.block_read(id, 0, &mut ret)?;
134-
debug_assert_eq!(stat.size, read, "didn't read expected bytes");
133+
let remaining = self.block_read(id, 0, &mut ret)?;
134+
debug_assert_eq!(remaining, 0, "didn't read expected bytes");
135135
Ok((stat.codec, ret))
136136
}
137137

fvm/src/syscalls/ipld.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ pub fn read(
4848
offset: u32,
4949
obuf_off: u32,
5050
obuf_len: u32,
51-
) -> Result<u32> {
51+
) -> Result<i32> {
5252
let data = context.memory.try_slice_mut(obuf_off, obuf_len)?;
5353
context.kernel.block_read(id, offset, data)
5454
}

sdk/src/ipld.rs

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,24 +32,38 @@ pub fn get(cid: &Cid) -> SyscallResult<Vec<u8>> {
3232
.expect("CID encoding should not fail");
3333
let fvm_shared::sys::out::ipld::IpldOpen { id, size, .. } =
3434
sys::ipld::open(cid_buf.as_mut_ptr())?;
35-
get_block(id, Some(size))
35+
let mut block = Vec::with_capacity(size as usize);
36+
let remaining = sys::ipld::read(id, 0, block.as_mut_ptr(), size)?;
37+
debug_assert_eq!(remaining, 0, "expected to read the block exactly");
38+
Ok(block)
3639
}
3740
}
3841

39-
/// Gets the data of the block referenced by BlockId. If the caller knows the
40-
/// size, this function will avoid statting the block.
41-
pub fn get_block(id: fvm_shared::sys::BlockId, size: Option<u32>) -> SyscallResult<Vec<u8>> {
42-
let size = match size {
43-
Some(size) => size,
44-
None => unsafe { sys::ipld::stat(id).map(|out| out.size)? },
45-
};
46-
let mut block = Vec::with_capacity(size as usize);
42+
/// Gets the data of the block referenced by BlockId. If the caller knows the size, this function
43+
/// will read the block in a single syscall. Otherwise, any block over 1KiB will take two syscalls.
44+
pub fn get_block(id: fvm_shared::sys::BlockId, size_hint: Option<u32>) -> SyscallResult<Vec<u8>> {
45+
// Check for the "empty" block first.
46+
if id == UNIT {
47+
return Ok(Vec::new());
48+
}
49+
50+
let mut buf = Vec::with_capacity(size_hint.unwrap_or(1024) as usize);
4751
unsafe {
48-
let bytes_read = sys::ipld::read(id, 0, block.as_mut_ptr(), size)?;
49-
debug_assert!(bytes_read == size, "read an unexpected number of bytes");
50-
block.set_len(size as usize);
52+
let mut remaining = sys::ipld::read(id, 0, buf.as_mut_ptr(), buf.capacity() as u32)?;
53+
if remaining > 0 {
54+
buf.set_len(buf.capacity());
55+
buf.reserve_exact(remaining as usize);
56+
remaining = sys::ipld::read(
57+
id,
58+
buf.len() as u32,
59+
buf.as_mut_ptr_range().end,
60+
(buf.capacity() - buf.len()) as u32,
61+
)?;
62+
debug_assert!(remaining <= 0, "should have read whole block");
63+
}
64+
buf.set_len(buf.capacity() + (remaining as usize));
5165
}
52-
Ok(block)
66+
Ok(buf)
5367
}
5468

5569
/// Writes the supplied block and returns the BlockId.

sdk/src/message.rs

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,22 +42,6 @@ pub fn params_raw(id: BlockId) -> SyscallResult<(Codec, Vec<u8>)> {
4242
}
4343
unsafe {
4444
let fvm_shared::sys::out::ipld::IpldStat { codec, size } = sys::ipld::stat(id)?;
45-
log::trace!(
46-
"params_raw -> ipld stat: size={:?}; codec={:?}",
47-
size,
48-
codec
49-
);
50-
51-
let mut buf: Vec<u8> = Vec::with_capacity(size as usize);
52-
let ptr = buf.as_mut_ptr();
53-
let bytes_read = sys::ipld::read(id, 0, ptr, size)?;
54-
buf.set_len(bytes_read as usize);
55-
log::trace!(
56-
"params_raw -> ipld read: bytes_read={:?}, data: {:x?}",
57-
bytes_read,
58-
&buf
59-
);
60-
debug_assert!(bytes_read == size, "read an unexpected number of bytes");
61-
Ok((codec, buf))
45+
Ok((codec, crate::ipld::get_block(id, Some(size))?))
6246
}
6347
}

sdk/src/send.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,8 @@ pub fn send(
4848
let exit_code = ExitCode::new(exit_code);
4949
let return_data = match exit_code {
5050
ExitCode::OK if return_id != NO_DATA_BLOCK_ID => {
51-
// Allocate a buffer to read the return data.
52-
let fvm_shared::sys::out::ipld::IpldStat { size, .. } = sys::ipld::stat(return_id)?;
53-
let mut bytes = vec![0; size as usize];
54-
5551
// Now read the return data.
56-
let read = sys::ipld::read(return_id, 0, bytes.as_mut_ptr(), size)?;
57-
assert_eq!(read, size);
52+
let bytes = crate::ipld::get_block(return_id, None)?;
5853
RawBytes::from(bytes)
5954
}
6055
_ => Default::default(),

sdk/src/sys/ipld.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,12 @@ super::fvm_syscalls! {
5454
/// Reads the block identified by `id` into `obuf`, starting at `offset`, reading _at most_
5555
/// `max_len` bytes.
5656
///
57-
/// Returns the number of bytes read.
57+
/// Returns the difference between the length of the block and `offset + max_len`. This can be
58+
/// used to find the end of the block relative to the buffer the block is being read into:
59+
///
60+
/// - A zero return value means that the block was read into the output buffer exactly.
61+
/// - A positive return value means that that many more bytes need to be read.
62+
/// - A negative return value means that the buffer should be truncated by the return value.
5863
///
5964
/// # Arguments
6065
///
@@ -64,15 +69,16 @@ super::fvm_syscalls! {
6469
/// - `max_len` is the maximum amount of block data to read.
6570
///
6671
/// Passing a length/offset that exceed the length of the block will not result in an error, but
67-
/// will result in no data being read.
72+
/// will result in no data being read and a negative return value indicating where the block
73+
/// actually ended (relative to `offset + max_len`).
6874
///
6975
/// # Errors
7076
///
7177
/// | Error | Reason |
7278
/// |---------------------|---------------------------------------------------|
7379
/// | [`InvalidHandle`] | if the handle isn't known. |
7480
/// | [`IllegalArgument`] | if the passed buffer isn't valid, in memory, etc. |
75-
pub fn read(id: u32, offset: u32, obuf: *mut u8, max_len: u32) -> Result<u32>;
81+
pub fn read(id: u32, offset: u32, obuf: *mut u8, max_len: u32) -> Result<i32>;
7682

7783
/// Returns the codec and size of the specified block.
7884
///

testing/conformance/src/vm.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ where
362362
self.0.block_link(id, hash_fun, hash_len)
363363
}
364364

365-
fn block_read(&mut self, id: BlockId, offset: u32, buf: &mut [u8]) -> Result<u32> {
365+
fn block_read(&mut self, id: BlockId, offset: u32, buf: &mut [u8]) -> Result<i32> {
366366
self.0.block_read(id, offset, buf)
367367
}
368368

0 commit comments

Comments
 (0)