nvmf <8> - introduction (feat. Github Copilot)
ChatGPT의 가능성을 보고, 이번에는 Github Copilot을 사용해보았다. 간략히 느낌을 얻기 위해 nvmf <1> 포스팅의 예제를 Copilot에게 주고 테스트를 작성시켜 보았다:
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Read, Write};
use std::net::TcpStream;
use std::thread;
use std::time::Duration;
#[test]
fn test_create_connection() {
// Start the server in a separate thread
thread::spawn(|| {
main().unwrap();
});
// Wait for the server to start
thread::sleep(Duration::from_secs(1));
// Connect to the server
let mut stream = TcpStream::connect("127.0.0.1:8420").unwrap();
// Send a Create Connection command. Refactor with constant names
stream.write(&[0]); // opcode
stream.write(&[0]); // flags
stream.write(&[0, 0, 0, 0]); // reserved
stream.write(&[0, 0, 0, 0]); // cid
stream.write(&[0, 0, 0, 0]); // nsid
stream.write(&[0, 0]); // reserved
stream.write(&[0, 0]); // qid
stream.write(&[0, 0]); // sqsize
stream.write(&[0, 0]); // cqsize
stream.write(&[0, 0, 0, 0]); // maxdata
stream.write(&[0, 0]); // assocqid
stream.write(&[0, 0]); // reserved
// Read the response
let mut response = [0u8; 8];
stream.read_exact(&mut response).unwrap();
// The response should be a success
assert_eq!(response, [0, 0, 0, 0, 0, 0, 0, 0]);
}
}
직접 main() 을 호출하여 TCP connection을 맺고, response를 리턴해주는 코드인데 군더더기 없이 깔끔하게 잘 짜주었다. 내가 한 일은, 약간의 주석을 추가하고 자동 완성 탭을 누른 것 뿐이었다.
이번에는 nvmf <1> 예제에서 빠져있는 opcode
들을 match case에 추가해보고 싶었다.
순전히 주석과 자동 완성만으로 아래와 같이 약 900줄의 코드를 앉은 자리에서 1시간 내에
졸면서도 작성할 수 있었고 (심지어 Copilot은 오늘 처음 써봄),
단순 반복 작업에 가까운 부분도 있었지만, 어쨌든 NVMe-oF 스펙을 전혀
알지 못하는 상태에서도 아무 참고자료 없이 이정도는 짜도록 도와줄 수 있다는 점이 놀라웠다:
use std::collections::HashMap;
use std::convert::TryInto;
use std::error::Error;
use std::io::{Read, Write};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
// Define a struct to represent an I/O queue
struct IoQueue {
nsid: u32,
cqid: u32,
qsize: u32,
}
impl IoQueue {
fn new(nsid: u32, cqid: u32, qsize: u32) -> Self {
Self { nsid, cqid, qsize }
}
}
async fn handle_client(mut stream: TcpStream, active_queues: &mut HashMap<u16, IoQueue>) -> Result<(), Box<dyn Error>> {
// Read the command header
let mut header = [0u8; 28];
stream.read_exact(&mut header).await?;
// Parse the command header
let opcode = header[0];
let flags = header[1];
let cid = u16::from_le_bytes(header[4..8].try_into().unwrap());
let nsid = u32::from_le_bytes(header[8..12].try_into().unwrap());
let qid = u16::from_le_bytes(header[14..16].try_into().unwrap());
let sqsize = u16::from_le_bytes(header[16..18].try_into().unwrap());
let cqsize = u16::from_le_bytes(header[18..20].try_into().unwrap());
let maxdata = u32::from_le_bytes(header[20..24].try_into().unwrap());
let assocqid = u16::from_le_bytes(header[24..26].try_into().unwrap());
let reserved = u16::from_le_bytes(header[26..28].try_into().unwrap());
// Handle the command based on its opcode. Add more opcodes as needed
match opcode {
0x00 => {
// Create Connection command
if active_queues.contains_key(&qid) {
// Queue already exists
let response = [0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00];
stream.write_all(&response);
} else {
// Create new I/O queue
let queue = IoQueue::new(nsid, cid as u32, sqsize as u32);
active_queues.insert(qid, queue);
let response = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
stream.write_all(&response);
}
}
0x01 => {
// Destroy Connection command
if active_queues.contains_key(&qid) {
// Remove the I/O queue
active_queues.remove(&qid);
}
// Send a success response
let response = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
stream.write_all(&response);
}
0x02 => {
// Identify command.
let mut response = [0u8; 4096];
response[0] = 0x00;
response[1] = 0x00;
response[2] = 0x00;
response[3] = 0x00;
response[4] = 0x00;
response[5] = 0x00;
response[6] = 0x00;
response[7] = 0x00;
stream.write_all(&response);
}
0x03 => {
// Abort command
let resp = [0u8; 4096];
stream.write_all(&resp);
}
0x04 => {
// Set Features command
}
0x05 => {
// Get Features command
}
0x06 => {
// Asynchronous Event Request command
}
0x07 => {
// Namespace Management command
}
0x08 => {
// Firmware Commit command
}
0x09 => {
// Firmware Image Download command
}
0x0A => {
// Namespace Attachment command
let n = NamespaceAttachment::new(stream, active_queues, qid, cid);
n.handle().await?;
}
0x0B => {
// Keep Alive command
let k = KeepAlive::new(stream, active_queues, qid, cid);
k.handle().await?;
}
0x0C => {
// Directive Send command
let d = DirectiveSend::new(stream, active_queues, qid, cid);
d.handle().await?;
}
0x0D => {
// Directive Receive command
let d = DirectiveReceive::new(stream, active_queues, qid, cid);
d.handle().await?;
}
0x0E => {
// Virtualization Management command
let v = VirtualizationManagement::new(stream, active_queues, qid, cid);
v.handle().await?;
}
0x0F => {
// NVMe-MI Send command
let n = NvmeMiSend::new(stream, active_queues, qid, cid);
n.handle().await?;
}
0x10 => {
// NVMe-MI Receive command
let n = NvmeMiReceive::new(stream, active_queues, qid, cid);
n.handle().await?;
}
0x11 => {
// Doorbell Buffer Config command
let d = DoorbellBufferConfig::new(stream, active_queues, qid, cid);
d.handle().await?;
}
0x7F => {
// Format NVM command
let f = FormatNvm::new(stream, active_queues, qid, cid);
f.handle().await?;
}
0x80 => {
// Security Send command
let s = SecuritySend::new(stream, active_queues, qid, cid);
s.handle().await?;
}
0x81 => {
// Security Receive command
let s = SecurityReceive::new(stream, active_queues, qid, cid);
s.handle().await?;
}
0x82 => {
// Sanitize command
let s = Sanitize::new(stream, active_queues, qid, cid);
s.handle().await?;
}
0x83 => {
// Get Log Page command
let g = GetLogPage::new(stream, active_queues, qid, cid);
g.handle().await?;
}
0x84 => {
// Get LBA Status command
let g = GetLbaStatus::new(stream, active_queues, qid, cid);
g.handle().await?;
}
0x85 => {
// Read command
let r = ReadCommand::new(stream, active_queues, qid, cid);
r.handle().await?;
}
0x86 => {
// Write command
let w = WriteCommand::new(stream, active_queues, qid, cid);
w.handle().await?;
}
0x87 => {
// Write Uncorrectable command
let w = WriteUncorrectable::new(stream, active_queues, qid, cid);
w.handle().await?;
}
0x88 => {
// Compare command
let c = Compare::new(stream, active_queues, qid, cid);
c.handle().await?;
}
0x89 => {
// Write Zeroes command
let w = WriteZeroes::new(stream, active_queues, qid, cid);
w.handle().await?;
}
0x8A => {
// Dataset Management command
let d = DatasetManagement::new(stream, active_queues, qid, cid);
d.handle().await?;
}
0x8B => {
// Reservation Register command
let r = ReservationRegister::new(stream, active_queues, qid, cid);
r.handle().await?;
}
0x8C => {
// Reservation Report command
let r = ReservationReport::new(stream, active_queues, qid, cid);
r.handle().await?;
}
0x8D => {
// Reservation Acquire command
let r = ReservationAcquire::new(stream, active_queues, qid, cid);
r.handle().await?;
}
0x8E => {
// Reservation Release command
let r = ReservationRelease::new(stream, active_queues, qid, cid);
r.handle().await?;
}
0x90 => {
// Timestamp command
let t = Timestamp::new(stream, active_queues, qid, cid);
t.handle().await?;
}
0x91 | 0x92 | 0x93 | 0x94 | 0x95 | 0x96 | 0x97 | 0x98 | 0x99 | 0x9A | 0x9B | 0x9C | 0x9D | 0x9E | 0x9F => {
// I/O command
if let Some(queue) = active_queues.get_mut(&qid) {
// Check if the queue is full
if queue.qsize == 0 {
// Queue is full
let response = [0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00];
stream.write_all(&response);
} else {
// Queue is not full
queue.qsize -= 1;
}
}
}
0xA0 | 0xA1 | 0xA2 | 0xA3 | 0xA4 | 0xA5 | 0xA6 | 0xA7 | 0xA8 | 0xA9 | 0xAA | 0xAB | 0xAC | 0xAD | 0xAE | 0xAF => {
// Admin command
if let Some(queue) = active_queues.get_mut(&qid) {
// Check if the queue is full
if queue.qsize == 0 {
// Queue is full
let response = [0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00];
stream.write_all(&response);
} else {
// Queue is not full
queue.qsize -= 1;
}
}
}
0xB0 | 0xB1 | 0xB2 | 0xB3 | 0xB4 | 0xB5 | 0xB6 | 0xB7 | 0xB8 | 0xB9 | 0xBA | 0xBB | 0xBC | 0xBD | 0xBE | 0xBF => {
// Vendor specific command
}
_ => {
// Unsupported command
let response = [0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
stream.write_all(&response);
}
}
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Listen for incoming connections
let listener = TcpListener::bind("127.0.0.1:8420").await?;
println!("Listening on {}", listener.local_addr()?);
// Keep track of active I/O queues
let mut active_queues = HashMap::new();
// Accept and handle incoming connections
loop {
let (stream, _) = listener.accept().await?;
println!("Accepted connection from {}", stream.peer_addr()?);
if let Err(e) = handle_client(stream, &mut active_queues).await {
println!("Error: {:?}", e);
}
}
}
struct CreateConnection {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl CreateConnection {
fn new(stream: TcpStream, active_queues: HashMap<u16, IoQueue>, qid: u16, cid: u16) -> CreateConnection {
CreateConnection {
stream,
active_queues,
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct CreateConnectionResponse {
qid: u16,
qsize: u16,
sqsize: u16,
cqsize: u16,
}
impl CreateConnectionCommandResponse {
fn new(qid: u16, qsize: u16, sqsize: u16, cqsize: u16) -> CreateConnectionCommandResponse {
CreateConnectionCommandResponse {
qid,
qsize,
sqsize,
cqsize,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct CreateConnectionCommand {
qid: u16,
qsize: u16,
sqsize: u16,
cqsize: u16,
}
impl CreateConnectionCommand {
fn new(qid: u16, qsize: u16, sqsize: u16, cqsize: u16) -> CreateConnectionCommand {
CreateConnectionCommand {
qid,
qsize,
sqsize,
cqsize,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct CreateConnectionCommandResponse {
qid: u16,
qsize: u16,
sqsize: u16,
cqsize: u16,
}
struct NamespaceManagement {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl NamespaceManagement {
fn new(stream: TcpStream, active_queues: HashMap<u16, IoQueue>, qid: u16, cid: u16) -> NamespaceManagement {
NamespaceManagement {
stream,
active_queues,
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct DatasetManagement {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl DatasetManagement {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> DatasetManagement {
DatasetManagement {
stream,
qid,
cid,
active_queues: Default::default(),
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct ReservationRegister {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl ReservationRegister {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> ReservationRegister {
ReservationRegister {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct ReservationReport {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl ReservationReport {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> ReservationReport {
ReservationReport {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct ReservationAcquire {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl ReservationAcquire {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> ReservationAcquire {
ReservationAcquire {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct ReservationRelease {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl ReservationRelease {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> ReservationRelease {
ReservationRelease {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct Timestamp {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl Timestamp {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> Timestamp {
Timestamp {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct Identify {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl Identify {
fn new(stream: TcpStream, active_queues: HashMap<u16, IoQueue>, qid: u16, cid: u16) -> Identify {
Identify {
stream,
active_queues,
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct Abort {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl Abort {
fn new(stream: TcpStream, active_queues: HashMap<u16, IoQueue>, qid: u16, cid: u16) -> Abort {
Abort {
stream,
active_queues,
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct SetFeatures {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl SetFeatures {
fn new(stream: TcpStream, active_queues: HashMap<u16, IoQueue>, qid: u16, cid: u16) -> SetFeatures {
SetFeatures {
stream,
active_queues,
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct GetFeatures {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl GetFeatures {
fn new(stream: TcpStream, active_queues: HashMap<u16, IoQueue>, qid: u16, cid: u16) -> GetFeatures {
GetFeatures {
stream,
active_queues,
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct AsynchronousEventRequest {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl AsynchronousEventRequest {
fn new(stream: TcpStream, active_queues: HashMap<u16, IoQueue>, qid: u16, cid: u16) -> AsynchronousEventRequest {
AsynchronousEventRequest {
stream,
active_queues,
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct AsynchronousEvent {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl AsynchronousEvent {
fn new(stream: TcpStream, active_queues: HashMap<u16, IoQueue>, qid: u16, cid: u16) -> AsynchronousEvent {
AsynchronousEvent {
stream,
active_queues,
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct NamespaceAttachment {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl NamespaceAttachment {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> NamespaceAttachment {
NamespaceAttachment {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct KeepAlive {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl KeepAlive {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> KeepAlive {
KeepAlive {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct DirectiveSend {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl DirectiveSend {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> DirectiveSend {
DirectiveSend {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct DirectiveReceive {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl DirectiveReceive {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> DirectiveReceive {
DirectiveReceive {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct VirtualizationManagement {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl VirtualizationManagement {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> VirtualizationManagement {
VirtualizationManagement {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct NvmeMiSend {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl NvmeMiSend {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> NvmeMiSend {
NvmeMiSend {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct NvmeMiReceive {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl NvmeMiReceive {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> NvmeMiReceive {
NvmeMiReceive {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct DoorbellBufferConfig {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl DoorbellBufferConfig {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> DoorbellBufferConfig {
DoorbellBufferConfig {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct FormatNvm {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl FormatNvm {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> FormatNvm {
FormatNvm {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct SecuritySend {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl SecuritySend {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> SecuritySend {
SecuritySend {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct SecurityReceive {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl SecurityReceive {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> SecurityReceive {
SecurityReceive {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct Sanitize {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl Sanitize {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> Sanitize {
Sanitize {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct GetLogPage {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl GetLogPage {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> GetLogPage {
GetLogPage {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct GetLbaStatus {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl GetLbaStatus {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> GetLbaStatus {
GetLbaStatus {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct FirmwareCommit {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl FirmwareCommit {
fn new(stream: TcpStream, active_queues: HashMap<u16, IoQueue>, qid: u16, cid: u16) -> FirmwareCommit {
FirmwareCommit {
stream,
active_queues,
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct FirmwareImageDownload {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl FirmwareImageDownload {
fn new(stream: TcpStream, active_queues: HashMap<u16, IoQueue>, qid: u16, cid: u16) -> FirmwareImageDownload {
FirmwareImageDownload {
stream,
active_queues,
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct WriteUncorrectable {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl WriteUncorrectable {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> WriteUncorrectable {
WriteUncorrectable {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct Compare {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl Compare {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> Compare {
Compare {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct WriteZeroes {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl WriteZeroes {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> WriteZeroes {
WriteZeroes {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
pub async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct ReadCommand {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl ReadCommand {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> ReadCommand {
ReadCommand {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
struct WriteCommand {
stream: TcpStream,
active_queues: HashMap<u16, IoQueue>,
qid: u16,
cid: u16,
}
impl WriteCommand {
fn new(stream: TcpStream, _active_queues: &mut HashMap<u16, IoQueue>, qid: u16, cid: u16) -> WriteCommand {
WriteCommand {
stream,
active_queues: Default::default(),
qid,
cid,
}
}
async fn handle(&self) -> Result<(), Box<dyn Error>> {
todo!()
}
}
// let's write a test case that connects to the TCP port in the main function
// and sends a Create Connection command
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Read, Write};
use std::net::TcpStream;
use std::thread;
use std::time::Duration;
#[test]
fn test_create_connection() {
// Start the server in a separate thread
thread::spawn(|| {
main().unwrap();
});
// Wait for the server to start
thread::sleep(Duration::from_secs(1));
// Connect to the server
let mut stream = TcpStream::connect("127.0.0.1:8420").unwrap();
// Send a Create Connection command. Refactor with constant names
stream.write(&[0]); // opcode
stream.write(&[0]); // flags
stream.write(&[0, 0, 0, 0]); // reserved
stream.write(&[0, 0, 0, 0]); // cid
stream.write(&[0, 0, 0, 0]); // nsid
stream.write(&[0, 0]); // reserved
stream.write(&[0, 0]); // qid
stream.write(&[0, 0]); // sqsize
stream.write(&[0, 0]); // cqsize
stream.write(&[0, 0, 0, 0]); // maxdata
stream.write(&[0, 0]); // assocqid
stream.write(&[0, 0]); // reserved
// Read the response
let mut response = [0u8; 8];
stream.read_exact(&mut response).unwrap();
// The response should be a success
assert_eq!(response, [0, 0, 0, 0, 0, 0, 0, 0]);
}
}
async/await 관련해서는, ChatGPT와 마찬가지로 Copilot도 옛날에 학습된 Rust 지식 때문인지
컴파일 에러가 발생하는 상황이지만, 손쉽게 수정이 가능하므로 추후로 미뤄두겠다.
위 예제를 Copilot으로 작성하면서 한 가지 놀랐던 점은, 개발자의 코드 작성 패턴을 곧바로
학습해서 이를 추천해주는 점이었다 (느낌이었을지도…). 가령, struct FirmwareCommit
부분을 자동 완성으로 생성하면, 그 다음에 있는 struct FirmwareImageDownload
는
아예 자동 완성도 가기 전에, 커서 위치에 곧바로 후보를 띄워주는 식이다. 이 덕분에,
리듬감있게 Copilot과 핑퐁을 주고 받으며 pair로 공동 개발하는 느낌을 받을 수 있었다.
ChatGPT보다는 약간의 미드/로우 레벨로 개발자의 의도를 표현할 경우, 아주 효과적으로 코드를 자동 생성해주는 것 같았다. 아무런 코드 베이스가 없을 때, 개발자가 하이 레벨로 질문을 던져 ChatGPT가 초반 코드를 작성해 주면, Copilot으로 구체적인 구현을 빈틈없이 진행할 수 있으리라 예상한다.