Skip to main content

File transfer

File transfer lets a master retrieve files from an outstation, such as logs and disturbance records, and push files to it, such as configuration. The master API supports these operations:

  • Get file info - Retrieve information on an individual file or directory
  • Read file - Read a remote file from the outstation asynchronously
  • Read directory listing - Read the contents of a directory from the outstation
  • Write file - Write a file to the outstation, one block at a time

Get file info​

Information about a single file or directory may be retrieved from outstation using a single method invocation.

let info = self.association.get_file_info(".").await?;
print_file_info(info);

Read a file​

File data may be asynchronously read from the outstation using a callback interface. Reading a file consists of multiple DNP3 requests. Obtaining an authentication key from the outstation is optional, depending on outstation requirements.

The FileReader interface is used to receive data from the outstation as it is read. Your code is responsible for doing something with this data, e.g. writing it to a local file.

struct FileLogger;

impl FileReader for FileLogger {
fn opened(&mut self, size: u32) -> FileAction {
tracing::info!("File opened - size: {size}");
FileAction::Continue
}

fn block_received(&mut self, block_num: u32, data: &[u8]) -> MaybeAsync<FileAction> {
tracing::info!("Received block {block_num} with size: {}", data.len());
MaybeAsync::ready(FileAction::Continue)
}

fn aborted(&mut self, err: FileError) {
println!("rf: aborted: {err}");
}

fn completed(&mut self) {
println!("rf: completed");
}
}

// asynchronously start the transfer
self.association
.read_file(
".", // this reads the root "directory" file but doesn't parse it
FileReadConfig::default(),
Box::new(FileLogger),
None,
)
.await?;
note

The master closes the file when a read completes, or when your FileReader aborts the transfer in opened. If the transfer stops for any other reason, such as an abort in block_received, a timeout or an error, the file is left for the outstation to clean up.

Read a directory​

A directory listing in DNP3 is just a special kind of file with a special encoding. We implement this feature by starting a background file transfer, accumulating the file data over possibly multiple blocks, and then parsing it into a collection of FileInfo instances.

warning

The master limits the size of a directory listing it accumulates. Raise the maximum size in DirReadConfig if listings are large.

let items = self
.association
.read_directory(".", DirReadConfig::default(), None)
.await?;

for info in items {
print_file_info(info);
}

Write a file​

Writing a file opens it for writing, writes its data one block at a time, and closes it. The last block is marked as the last one, so that the outstation knows the file is complete. The examples write two lines of text to hello_world.txt.

let line = "hello world\n".as_bytes();

let file = self
.association
.open_file(
"hello_world.txt",
AuthKey::none(),
Permissions::default(),
0xFFFFFFFF, // indicate that we don't know the size
FileMode::Write,
512,
)
.await?;

// write the 'hello world' line to the file twice
let mut block = BlockNumber::default();
self.association
.write_file_block(file.file_handle, block, line.to_vec())
.await?;
block.increment()?;
block.set_last();
self.association
.write_file_block(file.file_handle, block, line.to_vec())
.await?;
self.association.close_file(file.file_handle).await?;