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으로 구체적인 구현을 빈틈없이 진행할 수 있으리라 예상한다.