adjacentlink / emane

Distributed wireless network emulation framework
Other
127 stars 37 forks source link

Integrating own MAC layer model into the EMANE stack question #242

Open vivianchiong opened 11 months ago

vivianchiong commented 11 months ago

Hello,

I have a question that I could not quite find the answer to on other issues.

I have a MAC layer model (C++ code) that I would like to test in the EMANE environment for the same reasons as others – to benefit from the framework’s scalability and flexibility of large-scale network emulation and test our design’s performance in different scenarios before needing to buy real radio hardware. This model is based on TDMA, and I’ve modified the open-source TDMA radio model slightly and configured the FrameworkPHY layer to match our own physical layer model’s behavior (thanks to your help!). But beyond this, we want to plugin in our MAC layer model into something that can run in EMANE nodes without conflating our MAC layer model’s API with EMANE’s radio model plugin API.

I would like to be able to treat our MAC layer model of code like a black box that does, indeed, generally do what EMANE radio models typically do (e.g. queuing, fragmentation/assembly, TDMA scheduling, etc.) but come time for the model to interact with higher layers (e.g. sending received packets up to the Virtual Transport to reach the user) or lower layers (e.g. sending packets down to the FrameworkPHY for OTA transmission), our model would use a defined API to speak to those surrounding layers for upstream/downstream data/control functionalities.

The reason for us is because we don’t want to over-rely on the EMANE framework or couple our software with EMANE-only constructs resulting in breaking our implementation when we put the same black box MAC layer model onto real radios. However, we also don’t want to have to re-solve challenges that you and other developers at Adjacent Link have already solved when writing the open-source TDMA radio model.

How can we integrate our model with EMANE? (I imagine we can’t just write the name of our MAC layer model's lib file into an EMANE XML, since it would need to adhere to EMANE’s APIs.) I have thought of a few potential solution angles and listed them below. Any insights or recommendations on what solution(s) are possible/feasible/ideal would be greatly appreciated!

  1. Wrap our MAC layer model code into a ShimLayer that sits, for example, between the Virtual Transport and a custom, lightweight radio model (if a radio model is even necessary), all of which sits on top of the FrameworkPHY. This ShimLayer would be quite heavy though, and from what I've seen, ShimLayers tend to be more lightweight.
  2. Integrate our MAC layer model into a custom radio model that derives from MACLayerImplementor, while architecting it in a way that keeps EMANE interfaces/constructs separated from the rest to avoid the coupling concern previously mentioned.
  3. Run our MAC layer model as a separate black box application, and have it interface with and drive the FrameworkPHY layer and EMANE somehow.

Thank you so much!

vivianchiong commented 10 months ago

Does my problem statement fall under "Shared Code Emulation" / "Shared Code Waveform Stack" capability mentioned in the Wiki?

Thanks again!

patelkb commented 10 months ago

Yes.Kaushik B PatelAdjacent Link LLCCEO, Director of EngineeringOn Oct 12, 2023, at 2:39 PM, Vivian Chiong @.***> wrote: Does my problem statement fall under "Shared Code Emulation" / "Shared Code Waveform Stack" capability mentioned in the Wiki? Thanks again!

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you are subscribed to this thread.Message ID: @.***>

vivianchiong commented 10 months ago

Thanks for your response, I really appreciate it. So it seems that we need to put our MAC layer model inside a Shared Code Waveform instance, and implement the "Modem Hardware Abstraction Layer" and "Transport API"? in the left diagram. Then, whatever requirements we need from EMANE that is not encapsulated in our MAC layer model would go into some custom Radio Model and/or the PHY layer. Is my understanding correct?

image

I see there is https://github.com/adjacentlink/emane-model-lte and https://github.com/adjacentlink/srsRAN-emane mentioned in the Wiki, but I also found the repo https://github.com/adjacentlink/srsLTE-emane. What is the difference between srsRAN-emane and srsLTE-emane, and what do you think is the better reference in my case?

Specifically, I did not find too many materials on the "Modem Hardware Abstraction Layer" in EMANE so I'm not sure where to start.

sgalgano commented 10 months ago

Take a look at the emane-emedded-example. The emane-emedded-example is a simple example for embedding the emulator within your waveform stack, which is how we do shared code modeling. It is not represented in the diagram you referenced but it is the better option.

You can use any mechanism you like to communicate with your radio model, just keep in mind that emane API callbacks (functor queue) happen on the emulator's radio model thread -- so depending on your approach you may need to use synchronization objects. Consider using the FileDescriptorService as a message passing based alternative.

vivianchiong commented 10 months ago

To use FileDescriptorService, I need to be able to call setFileDescriptorServiceProvider(FileDescriptorServiceProvider::instance()) on pPlatformService_ in the Embedded::RadioModel, but since pPlatformService_ is type PlatformServiceProvider, it doesn't have the setFileDescriptorServiceProvider method in its interface.

I tried to dynamic_cast pPlatformService_ to EMANE::PlatformService but I had no success. Would I have to modify and re-compile libemane buildMACLayer_i to force an instantiation of FileDescriptorService in my Embedded::RadioModel? Or is it possible to set the FileDescriptorService directly within the emane-emedded-example?

Thank you for your help!

sgalgano commented 10 months ago

Use pPlatformService_->fileDescriptorService().addFileDescriptor().

vivianchiong commented 10 months ago

Thank you! That worked for me and I was able to register a socket/file descriptor at the end of the Embedded::RadioModel::postStart() method, and get my callback testFileDescriptor (which accepts no parameters, just prints a single log message out) triggered like so

  pPlatformService_->fileDescriptorService()
    .addFileDescriptor(fd_, 
                       EMANE::FileDescriptorServiceProvider::DescriptorType::READ,
                       std::bind(&Embedded::RadioModel::testFileDescriptor,
                       this));

However, my testFileDescriptor is printing out an extreme number of times, even though I only send a single message over the registered socket/file descriptor in the main emane-embedded-example.cc file here:

      std::string str = "Hello World!";
      if (sock.send(str) != ssize_t(str.length())) {
        std::cerr << "Error writing to the UDP socket: " << sock.last_error_str() << std::endl;
      } else {
        std::cout << "Sent message!" << std::endl;
      }

image

Am I using the FileDescriptorService incorrectly? I have read up on the NEMQueuedLayer code and the basics of epoll, but I couldn't quite diagnose why the callback is being triggered so repeatedly. Could it be the length/content of the "Hello World!" message?

Please let me know if I should move my question to the emane-embedded-example repo instead. Again, thank you so much for your help thus far, I truly appreciate it!

sgalgano commented 10 months ago

Are you reading the data from fd_ in testFileDescriptor()?

vivianchiong commented 10 months ago

Ah, I am not. This is my testFileDescriptor() method body:

image

It sounds to me that I should be reading from fd_ inside the method body here, to clear the Hello World! message from the socket right? It would make sense for it to constantly trigger if the socket always has data that the FileDescriptorService wants to read!

vivianchiong commented 10 months ago

Would you recommend using a TCP or UDP socket to "register a socket with your radio model to exchange data and control with upper stack component(s)" #227 ? In the emane-embedded-example main function, I have been using a UDP socket from sockpp to send the "Hello World!" message across, which seems to work since my testFileDescriptor() method triggers as I previously showed. But I have not been able to read the received message bytes from fd_ in testFileDescriptor() without getting a ECONNREFUSED error.

void Embedded::RadioModel::testFileDescriptor()
    char buf[20];
    ssize_t bytes = read(fd_, buf, sizeof(buf));
    if (bytes == -1) {
        perror("Error occurred while opening file.\n"); // Connection refused error

Should I be creating another socket instance, or passing a clone of the socket to my Embedded::RadioModel ? I checked /proc/PID/fd for the file descriptor, and it is there, so I'm not sure why Embedded::RadioModel can't read from it.

image

sgalgano commented 10 months ago

Typically when you are making a shared code model there is already waveform interfaces that exist between components. The goal would be to use the same protocol/mechanism that is used to communicate with the waveform modem subsystem. It does not have to be socket based, since you are embedding the radio model but then you have to be aware of the threading model used in emane. When using message based systems, the recommendation is to integrate with the FileDescriptorService to avoid having to synchronize data access between threads -- the ones you may need to directly create or that are indirectly created by the communication library you are using.

Every radio model has a dedicated thread that works off an API queue, where each API method (virtual function you provide implementation for in your radio model along with timer and file descriptor callbacks) is executed.

The FileDescriptorService provides the ability to register a file descriptor, in this case a socket, with the API queue. There are many tutorials and guides on socket programming with information that would be directly applicable to their use with the FileDescriptorService.