Skip to content

Latest commit

 

History

History
544 lines (429 loc) · 16.2 KB

File metadata and controls

544 lines (429 loc) · 16.2 KB

BiSecure Protocol Documentation

This document provides a detailed explanation of how messages are constructed and deconstructed in the BiSecure Gateway protocol.

Table of Contents

  1. Overview
  2. Message Structure
  3. Transport Container
  4. BiPackage (Inner Package)
  5. Payload Formats
  6. Checksum Calculation
  7. Encoding and Decoding
  8. Examples

Overview

The BiSecure Gateway protocol uses a layered message structure where:

  • The Transport Container is the outer layer containing sender/receiver addresses and a checksum
  • The BiPackage is the inner layer containing the command, session token, and payload
  • The Payload contains command-specific data

All messages are converted to hexadecimal string representation before being sent over TCP/IP.

Message Structure

Complete Message Layout

┌─────────────────── Transport Container ───────────────────┐
│                                                            │
│  Sender    Receiver      BiPackage          TC Checksum   │
│ (6 bytes) (6 bytes)   (variable length)      (1 byte)     │
│                                                            │
│            ┌────────── BiPackage ──────────┐              │
│            │                                │              │
│            │  Length  Tag  Token  Command  │              │
│            │ (2 bytes)(1) (4 bytes) (1)    │              │
│            │                                │              │
│            │  Payload        BP Checksum   │              │
│            │ (variable)        (1 byte)    │              │
│            └────────────────────────────────┘              │
└────────────────────────────────────────────────────────────┘

Transport Container

The Transport Container wraps the entire message and provides addressing information.

Structure

Field Size (bytes) Description
Sender Address 6 MAC address of sender (app uses 000000000000)
Receiver Address 6 MAC address of receiver (gateway's MAC)
BiPackage Variable The inner package (see below)
Checksum 1 XOR checksum of all bytes in the container

Construction (Pseudo-code)

fun constructTransportContainer(
    sender: String,      // e.g., "000000000000"
    receiver: String,    // e.g., "5410EC036150" (gateway MAC)
    biPackage: BiPackage
): TransportContainer {
    val checksum = calculateTransportChecksum(sender, receiver, biPackage)
    return TransportContainer(sender, receiver, biPackage, checksum)
}

Deconstruction Steps

  1. Extract Sender (bytes 0-5): Convert to hex string
  2. Extract Receiver (bytes 6-11): Convert to hex string
  3. Extract BiPackage (bytes 12 to length-1): Parse as BiPackage (see below)
  4. Extract Checksum (last byte): Verify against calculated checksum

Example

Request (GET_NAME):

0000000000005410EC03615000090000000000262F4A
│────────││────────││─────────────────││──│
Sender(6) Receiver(6)   BiPackage(9)    CS(1)

Where:

  • Sender: 000000000000 (app)
  • Receiver: 5410EC036150 (gateway)
  • BiPackage: 00090000000000262F (see BiPackage section)
  • Checksum: 4A

BiPackage (Inner Package)

The BiPackage contains the actual command and data.

Structure

Field Size (bytes) Description
Length 2 Total length of BiPackage (including length field itself)
Tag 1 Message tag/identifier (usually 0 for requests)
Token 4 Session token (from login response, 00000000 before login)
Command 1 Command code (bit 7 set for responses)
Payload Variable Command-specific data
Checksum 1 Calculated checksum of the BiPackage

Command Codes

Command Code (hex) Description Auth Required
PING 0x00 Ping gateway No
ERROR 0x01 Error response No
GET_MAC 0x02 Get MAC address No
SET_VALUE 0x03 Set a value Yes
JMCP 0x06 JSON over MCP Yes
LOGIN 0x10 Login with credentials No
LOGOUT 0x11 Logout Yes
GET_NAME 0x26 Get gateway name No
SET_STATE 0x33 Set device state (open/close door) Yes
HM_GET_TRANSITION 0x70 Get device transition/state Yes

Note: Response messages have bit 7 set in the command byte. For example:

  • GET_NAME request: 0x26
  • GET_NAME response: 0xA6 (0x26 | 0x80)

Construction (Pseudo-code)

fun constructBiPackage(
    command: Command,
    token: String = "00000000",
    payload: Payload,
    tag: Int = 0
): BiPackage {
    val length = calculateLength(payload)  // Frame size + payload size
    val commandByte = command.code.toByte()
    val checksum = calculatePackageChecksum(length, tag, token, commandByte, payload)
    
    return BiPackage(
        command = command,
        tag = tag,
        token = token,
        payload = payload,
        checksum = checksum
    )
}

fun toByteArray(biPackage: BiPackage): ByteArray {
    return length.toByteArray(2) +
           tag.toByte() +
           token.toHexByteArray() +
           commandCode.toByte() +
           payload.toByteArray() +
           checksum.toByte()
}

Deconstruction Steps

  1. Extract Length (bytes 0-1): Convert to integer (big-endian)
  2. Extract Tag (byte 2): Convert to integer
  3. Extract Token (bytes 3-6): Convert to hex string (4 bytes)
  4. Extract Command (byte 7):
    • Check bit 7 to determine if it's a response
    • Mask out bit 7 to get command code
  5. Extract Payload (bytes 8 to length-2): Variable length data
  6. Extract Checksum (byte at position length-1): Verify checksum

Length Calculation

Length = Frame Size + Payload Size
Frame Size = 2 (length) + 1 (tag) + 4 (token) + 1 (command) + 1 (checksum) = 9 bytes
Total Length = 9 + payload.size

Example

GET_NAME Request:

00090000000000262F
││││││││││││││││││
│││││││││││││││││└─ Checksum: 0x2F
││││││││││││││││└── Command: 0x26 (GET_NAME)
│││││││││││└─────── Token: 0x00000000
│││└────────────── Tag: 0x00
└──────────────── Length: 0x0009 (9 bytes)

Payload Formats

Payloads vary by command. Here are the common formats:

LOGIN

Structure:

┌──────────────┬──────────────┬──────────────┐
│ Username Len │  Username    │  Password    │
│   (1 byte)   │ (variable)   │ (variable)   │
└──────────────┴──────────────┴──────────────┘

Example: Username="thomas", Password="aaabbbccc"

0674686F6D6173616161626262636363
││└────────┘└──────────────────┘
│└─ Username: "thomas" (ASCII hex)
└── Length: 0x06 (6 characters)
   Password: "aaabbbccc" (ASCII hex, no length prefix)

GET_NAME, PING, LOGOUT

No payload (empty byte array)

JMCP (JSON over MCP)

Structure: JSON string encoded as ASCII bytes

Example: {"cmd":"GET_VALUES"}

7B22636D64223A224745545F56414C554553227D
└────────────────────────────────────────┘
  JSON string as ASCII hex bytes

HM_GET_TRANSITION

Structure:

┌──────────┐
│ Port ID  │
│ (1 byte) │
└──────────┘

Example: Port ID = 0

00
└─ Port ID: 0x00

SET_STATE

Structure:

┌──────────┬────────┐
│ Port ID  │ State  │
│ (1 byte) │(1 byte)│
└──────────┴────────┘

Example: Port ID = 0, State = 0xFF (toggle)

00FF
││└─ State: 0xFF (toggle)
└──── Port ID: 0x00

Checksum Calculation

BiPackage Checksum

The BiPackage checksum is calculated by summing all bytes in the package (except the checksum itself) and taking the result modulo 256.

Algorithm:

fun calculatePackageChecksum(biPackage: BiPackage): Int {
    var value = biPackage.length
    value += biPackage.tag
    value += (token.toHexInt() and 0xFF)        // Token byte 0
    value += (token.toHexInt() shr 8 and 0xFF)  // Token byte 1
    value += (token.toHexInt() shr 16 and 0xFF) // Token byte 2
    value += (token.toHexInt() shr 24 and 0xFF) // Token byte 3
    value += commandCode
    biPackage.payload.toByteArray().forEach { value += it }
    return value and 0xFF  // Modulo 256
}

Example (GET_NAME):

Length: 0x0009 → 9
Tag:    0x00   → 0
Token:  0x00000000 → 0 + 0 + 0 + 0
Command: 0x26  → 38
Payload: (empty) → 0
Sum: 9 + 0 + 0 + 38 = 47 = 0x2F

Transport Container Checksum

The Transport Container checksum is calculated by summing the ASCII values of all characters in the hex string representation (except the checksum itself) and taking the result modulo 256.

Algorithm:

fun calculateTransportChecksum(sender: String, receiver: String, pack: BiPackage): Byte {
    val hexString = sender + receiver + pack.toHexString()
    var value = 0
    hexString.forEach { char ->
        value += char.toByte()  // Add ASCII value of each hex character
    }
    return (value and 0xFF).toByte()  // Modulo 256
}

Example (GET_NAME):

Hex string: "0000000000005410EC03615000090000000000262F"
Sum of ASCII values of each character:
  '0'(48) + '0'(48) + ... + '2'(50) + 'F'(70) = calculated sum
Result & 0xFF = 0x4A (checksum)

Encoding and Decoding

Hex String Representation

All byte arrays are converted to hex strings for transmission over TCP/IP.

Byte Array to Hex String:

fun ByteArray.toHexString(): String {
    return joinToString("") { 
        it.toUByte().toString(16).padStart(2, '0').toUpperCase()
    }
}

Example:

Bytes: [0x00, 0x09, 0xAB, 0xCD]
Hex String: "0009ABCD"

Hex String to Byte Array:

fun String.toHexByteArray(): ByteArray {
    return chunked(2).map { it.toInt(16).toByte() }.toByteArray()
}

Example:

Hex String: "0009ABCD"
Bytes: [0x00, 0x09, 0xAB, 0xCD]

Gateway Encoding

The gateway uses a special encoding for certain data types where each hex digit is converted to its ASCII representation.

Example:

  • Internal representation: 0x00 (one byte)
  • Gateway encoding: 0x30 0x30 (two bytes: ASCII '0' and '0')

Encoding to Gateway Format:

fun ByteArray.encodeToGW(): ByteArray {
    return toHexString().toByteArray()  // Convert to hex string, then to ASCII bytes
}

Decoding from Gateway Format:

fun ByteArray.decodeFromGW(): ByteArray {
    return joinToString("") { 
        it.toByte(16).toChar()
    }.toHexByteArray()
}

Examples

Example 1: Login Request

Goal: Login with username "thomas" and password "aaabbbccc"

Step 1: Construct Payload

username = "thomas" (6 characters)
password = "aaabbbccc"

payload = [0x06] + "thomas".toByteArray() + "aaabbbccc".toByteArray()
        = [0x06, 0x74, 0x68, 0x6F, 0x6D, 0x61, 0x73, 
           0x61, 0x61, 0x61, 0x62, 0x62, 0x62, 0x63, 0x63, 0x63]
hex = "0674686F6D61736161616262626363636"

Step 2: Construct BiPackage

command = LOGIN (0x10)
token = "00000000" (no token yet)
tag = 0
length = 9 + 16 = 25 = 0x0019

checksum calculation:
  length + tag + token_bytes + command + payload_bytes
  = 25 + 0 + (0+0+0+0) + 16 + (6+116+104+111+109+97+115+97+97+97+98+98+98+99+99+99)
  = 25 + 0 + 0 + 16 + 1540
  = 1581
  = 1581 mod 256 = 45 = 0x2D

BiPackage hex: "00190000000000100674686F6D61736161616262626363632D"

Step 3: Construct Transport Container

sender = "000000000000" (app)
receiver = "5410EC036150" (gateway MAC)

checksum = sum of ASCII values of hex string characters
  hexString = "0000000000005410EC03615000190000000000100674686F6D61736161616262626363632D"
  Sum ASCII values of each character, then & 0xFF
  = 0xF0 (from test example)

Final hex: "0000000000005410EC03615000190000000000100674686F6D61736161616262626363632DF0"

Example 2: GET_NAME Request

Goal: Get the gateway name

Step 1: Construct Payload

payload = [] (empty)

Step 2: Construct BiPackage

command = GET_NAME (0x26 = 38)
token = "00000000"
tag = 0
length = 9 + 0 = 9 = 0x0009

checksum = 9 + 0 + 0 + 38 = 47 = 0x2F

BiPackage hex: "00090000000000262F"

Step 3: Construct Transport Container

sender = "000000000000"
receiver = "5410EC036150"

checksum = sum of ASCII values of hex string characters
  hexString = "0000000000005410EC03615000090000000000262F"
  Sum ASCII values of each character, then & 0xFF
  = 0x4A (from test example)

Final hex: "0000000000005410EC03615000090000000000262F4A"

Example 3: GET_NAME Response

Goal: Parse a GET_NAME response from gateway

Raw message:

5410EC03615000000000000600180100000000A64269536563757220476174657761795E97

Step 1: Parse Transport Container

sender = "5410EC036150" (gateway)
receiver = "000000000006" (app)
checksum = 0x97

Step 2: Parse BiPackage

Length: 0x0018 = 24 bytes
Tag: 0x01
Token: 0x00000000
Command: 0xA6 → bit 7 is set (response), command = 0x26 (GET_NAME)
Payload length: 24 - 9 = 15 bytes
Payload: "4269536563757220476174657761795E"
Checksum: 0x5E (last byte of payload section)

Step 3: Decode Payload

payload hex: "426953656375722047617465776179"
ASCII: "BiSecur Gateway"

Example 4: JMCP Request (GET_VALUES)

Goal: Send JMCP command to get values

Step 1: Construct Payload

json = "{\"cmd\":\"GET_VALUES\"}"
payload = json.toByteArray()  // ASCII encoding
hex = "7B22636D64223A224745545F56414C554553227D"

Step 2: Construct BiPackage

command = JMCP (0x06)
token = "EC25B186" (example session token from login)
tag = 0
length = 9 + 20 = 29 = 0x001D

checksum = 29 + 0 + (token bytes) + 6 + (payload sum)
  = 29 + 0 + 0xEC + 0x25 + 0xB1 + 0x86 + 6 + payload_sum
  = ... = 0x20 (example)

BiPackage hex: "001D00EC25B1860067B22636D64223A224745545F56414C554553227D20"

Response Message Format

Response messages follow the same structure as requests with the following differences:

  1. Sender/Receiver are swapped: Gateway is sender, app is receiver
  2. Command byte has bit 7 set: For example, GET_NAME (0x26) becomes 0xA6
  3. Tag is incremented: Request tag 0 → Response tag 1
  4. Token remains the same: Session token from login

Common Pitfalls

  1. Checksum Calculation Order: Always calculate BiPackage checksum before Transport Container checksum
  2. Response Command Code: Don't forget to set bit 7 for responses
  3. Byte Order: All multi-byte integers use big-endian (network byte order)
  4. Hex String Case: Convention is uppercase, but comparison should be case-insensitive
  5. Token Padding: Always pad token to 8 hex characters (4 bytes)
  6. Gateway Encoding: Only certain payloads use the special gateway encoding (mainly JMCP responses)

Protocol Flow

A typical session follows this flow:

  1. Discovery (UDP): Find gateway on network
  2. Connect (TCP): Establish TCP connection to gateway:4000
  3. GET_NAME: First request to verify connection
  4. LOGIN: Authenticate and receive session token
  5. Commands: Send authenticated commands (GET_GROUPS, HM_GET_TRANSITION, SET_STATE, etc.)
  6. LOGOUT: End session gracefully

Each request-response pair increments the tag counter and maintains the session token throughout.