Skip to content

[ironrdp-dvc] Support multiple DVC channel instances with the same name #1135

@CBenoit

Description

MS-RDPEUSB (and potentially other protocols) requires opening multiple DVC channels that share the same channel name but have distinct channel IDs. The current DynamicChannelSet in ironrdp-dvc uses channel name as its primary key, which makes this impossible.

Depends on: nothing

Tasks

  • Add a DvcChannelListener trait (factory) to lib.rs that produces a new DvcClientProcessor instance per DYNVC_CREATE_REQ:
pub trait DvcChannelListener: Send {
  fn channel_name(&self) -> &str;

  /// Called for each new DYNVC_CREATE_REQ for this name.
  fn on_channel_creation(&mut self, channel_id: u32) -> Box<dyn DvcClientProcessor + Send>;
}
  • Refactor DynamicChannelSet to store entries keyed by DynamicChannelId rather than DynamicChannelName; maintain a name → listener map for creation
  • Update DrdynvcClient::process() to call the listener factory on DYNVC_CREATE_REQ instead of looking up a pre-registered processor by name
  • Add DrdynvcClient::with_listener() / attach_listener() alongside the existing with_dynamic_channel() / attach_dynamic_channel() (keep the old API for single-instance channels)
  • Add a take_new_channels() drain hook to DvcServerProcessor in server.rs; update DrdynvcServer::process() to drain it after each process() call and emit the corresponding DYNVC_CREATE_REQ PDUs
// Add a post-process drain hook on DvcServerProcessor:

pub trait DvcServerProcessor: DvcProcessor {
    /// Called by DrdynvcServer after each process().
    /// Return new processor instances to be opened as new DVC channels.
    fn take_new_channels(&mut self) -> Vec<Box<dyn DvcServerProcessor + Send>> {
        Vec::new()
    }
}

// Then in DrdynvcServer::process(), after calling c.processor.process(...):

// Drain any new channels the processor wants to open.
for new_proc in c.processor.take_new_channels() {
    let id = self.dynamic_channels.insert(DynamicChannel::new_boxed(new_proc));
    let channel_id = u32::try_from(id).expect("...");
    let channel = &self.dynamic_channels[id];
    let req = DrdynvcServerPdu::Create(CreateRequestPdu::new(
        channel_id,
        channel.processor.channel_name().into(),
    ));
    self.dynamic_channels[id].state = ChannelState::Creation;
    resp.push(as_svc_msg_with_flag(req)?);
}

// The URBDRC control processor would then:

fn process(&mut self, channel_id: u32, payload: &[u8]) -> PduResult<Vec<DvcMessage>> {
    // ...
    self.pending_new_channels.push(Box::new(UsbDeviceChannel::new()));
    Ok(vec![])
}

fn take_new_channels(&mut self) -> Vec<Box<dyn DvcServerProcessor + Send>> {
    self.pending_new_channels.drain(..).collect()
}

EDIT: See this comment: #1135 (comment)

Acceptance criteria

  • A channel name can have multiple simultaneous open instances, each with its own independent processor and fragmentation buffer
  • Existing single-instance channels (displaycontrol, cliprdr, etc.) continue to work unchanged via with_dynamic_channel()
  • Server can open new DVC channels mid-session in response to processor requests
  • Unit tests covering multi-instance creation, data routing by channel ID, and close handling

Metadata

Metadata

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions