Heimdall with Bifrost

While Heimdall’s codebase can support any TCP-based transport and any serializer choice, like Bifrost, the initial libraries will be focused on providing support for Windows Sockets and Google Protobuf.

The Heimdall.Bifrost project integrates Heimdall with Bifrost out of the box. This project is either provided with the Heimdall project or standalone.

You must integrate Bifrost with your project before following the next steps. Check out the Bifrost documentation for more information.

C++ Side

Step 1: Define your types

In addition to the types defined in the Heimdall Integration section, you must define what transport and serializer protocol you wish to use with Bifrost.

The following is an example of a connection that uses the windows transport protocol and protobuf serializer protocol:

#include "Bifrost/BifrostConnection.hpp"
#include "HookHandlerProtobuf.h"
#include "Win_TcpTransport.h"

// Select the protobuf hook handler/serializer
using HookHandler = Microsoft::XboxStudios::Bifrost::Serializer::HookHandlerProtobuf;

// Select the transport protocol based on platform
using Transport = Microsoft::XboxStudios::Bifrost::Transport::Win_TcpTransport;

In the Heimdall Integration section we used a sample callback for demonstration purposes. The Heimdall.Bifrost project WinsockConnection implementation for the callback method for the Windows and GameCore platforms.

Define the callback:

using SenderConnector = Microsoft::XboxStudios::Heimdall::WinsockConnection;

Finally, define the factory that will be used to create the Bifrost connection via Heimdall.

Factory:

#include "BifrostConnectionFactory.hpp"

using ConnectionFactory = BifrostConnectionFactory<Poller, SenderConnector, HookHandler, Transport>;

Note that Poller is the type defined in the Heimdall Integration section.

Step 2: Create the connection

The factory create method expects 3 parameters.

  • HeimdallBifrostConfiguration: Used to configure the Bifrost connection and Heimdall.

  • Bifrost HookHandler: Stores your custom game hooks for Bifrost to invoke.

  • Bifrost Transport: The transport protocol Bifrost uses to establish connections.

auto bifrostConnection = ConnectionFactory::CreateConnection(
    configuration,
    HookHandler(),  // Pass in your game hooks here.
    Transport(configuration.listenerPort, SocketMode::Dual));

if (!bifrostConnection.has_value())
{
    std::cout << "Could not create a Bifrost connection via Heimdall." << std::endl;
    std::cout << bifrostConnection.error().ToString() << std::endl;

    // Handle error here
}

Note that the factory would fail to create a connection if incorrect configuration was passed.

Once the connection is created, you can now use it the same way as you would use a Bifrost connection.

C# side

The C# setup is very similar to the steps explained in the Bifrost documentation.

Instead of TransportByTcp you need to take a reference to the Bifrost.Transport.Heimdall NuGet package and call:

services
    .AddBifrostHookChannel(
        builder => builder
            .SerializeWithProtobuf()
            .TransportByHeimdall<HeimdallClient>(listener, senderOptions =>
            {
                // If null, the listner will connect to the public IP address of the sender.
                senderOptions.IPAddress = null;

                // If 0, the sender will open an available port on the OS for the listener to connect to.
                senderOptions.Port = 0;
            }))
    .AddTransient<BifrostProtoChannel>()
    .AddSingleton(new HeimdallClientOptions())
    .AddSingleton<IHttpClientWrapper, HttpClientWrapper>();

Where

  • listner: Contains the listener name and key of the game instance to connect to.

  • HeimdallClientOptions: Contains the endpoint of the Heimdall service and MS authentication endpoint. This parameter is optional, while in the future it may be used to target separate instances.

When user calls connect on the Bifrost channel:

  • The transport protocol creates a sender and passes the listener information to it.

  • The sender starts a TcpListener at a random available or configured port.

  • The sender sends a callback request to the Heimdall service for a listener to establish connection with the sender.

  • Once a connection is established, the sender creates a TcpClient stream and passes it back to the transport protocol for processing.

If you wish to attempt to connect to the listener directly using Bifrost before attempting a callback, use TransportByHeimdallAndBifrost extension method where you also pass the endpoint of the listener in TCPOptions.

Multiple Bifrost connections

A tool like Phoenix may have many senders. Each sender can only establish one connection with a listener while a listener can establish connections with many senders. This is similar to how our tools work currently (multiple clients on the tool side connecting with one server).

Note that each sender starts listening on a different port for incoming listener connections. There may be an edge case where too many senders are created and there are no more ports available.