Skip to content
This repository has been archived by the owner on Jan 9, 2019. It is now read-only.

Understanding the Terminal Protocol

Evan edited this page Jan 3, 2017 · 7 revisions

First, connect with the "terminal" path and perform your AES handshake just as you would with the normal server (this socket does indeed require its own handshake).

Packets for this server (including your initial handshake) have a different structure than the others.

Here is the function used by the web client to build a packet:

send(type: string, restOfPacket: any) {
    let packet = _.assign({}, {type}, restOfPacket)
    this.promiseToSendPacket(packet, "Terminal") //serialize and send
}

And the handshake is sent as follows (in a callback for the very first message that the server sends on connection):

let {key, iv, encKey, encIV} = generateKey(msg.publicKey)
t.send("AesHandShakeRequest", {
    encryptedKey: encKey,
    encryptedIv: encIV
})

Here are the structures of all the different packets:

declare namespace TerminalInfo {
    interface Message {
        correlationId?: number,
        type: string
    }
    interface Terminal {
        terminalType: string,
        id: string,
        currentPath: string
    }
    interface SessionState extends Message {
        userId: string,
        aesShook?: boolean,
        publicKey?: string,
        terminals: Terminal[]
    }
    interface Created extends Message {
        terminalType: string,
        currentPath: string,
        terminalId: string
    }
    interface Closed extends Message {
        terminalId: string
    }
    interface Output {
        output: string,
        endOfCommand?: boolean,
        sensitive?: boolean,
        terminalId: string,
        type?: string,
        correlationId?: number,
        currentPath?: string
    }
    interface Line {
        output: string,
        correlationId: number,
        sensitive: boolean
    }
    interface Input extends Message {
        input: string,
        terminalId: string
    }
}

Every terminal packet has a type and a correlationId. They work just as endpoint and synckey work on the main server. The type is PascalCase, however.

Here are a few of the types:

export function isOutputEvent(message: any): message is Ti.Output {
    return message.type == "TerminalOutputEvent"
}

export function isSessionStateEvent(message: Ti.Message): message is Ti.SessionState {
    return message.type == "SessionStateEvent"
}

export function isCreatedEvent(message: Ti.Message): message is Ti.Created {
    return message.type == "CreatedTerminalEvent"
}

export function isClosedEvent(message: Ti.Message): message is Ti.Closed {
    return message.type == "ClosedTerminalEvent"
}

As soon as you shake, you'll receive a SessionStateEvent with your handshake status, any open terminals and your user ID. For the others, I assume that you can likely guess when they'll show up.

Then, you can create terminals, send input to them and close them via the following api:

export let terminalApi = {
    send(input: string, terminalId: string) {
        tC.send("TerminalInputRequest", {
            input,
            terminalId,
            correlationId: tC.nextCorrelationId()
        })
    },
    create(terminalType: string) {
        tC.send("CreateTerminalRequest", {
            terminalType, 
            correlationId: tC.nextCorrelationId()
        })
    },
    close(terminalId: string) {
        tC.send("CloseTerminalRequest", {
            terminalId, correlationId: tC.nextCorrelationId()
        })
    }
}

The nextCorrelationId function is just a closure around an incrementing integer. As with the synckey, you can use it to pair requests and responses.

Clone this wiki locally