Private
Public Access
1
0
Files
Kordophone/kordophone/Bridge/MBIMPingPongWebSocket.m

217 lines
7.6 KiB
Mathematica
Raw Normal View History

//
// MBIMPingPongWebSocket.m
// kordophoned
//
// Created by James Magahern on 6/13/25.
// Copyright © 2025 James Magahern. All rights reserved.
//
#import "MBIMPingPongWebSocket.h"
#import "GCDAsyncSocket.h"
// WebSocket opcodes (from the base class)
#define TIMEOUT_NONE -1
#define TIMEOUT_REQUEST_BODY 10
#define TAG_HTTP_REQUEST_BODY 100
#define TAG_HTTP_RESPONSE_HEADERS 200
#define TAG_HTTP_RESPONSE_BODY 201
#define TAG_PREFIX 300
#define TAG_MSG_PLUS_SUFFIX 301
#define TAG_MSG_WITH_LENGTH 302
#define TAG_MSG_MASKING_KEY 303
#define TAG_PAYLOAD_PREFIX 304
#define TAG_PAYLOAD_LENGTH 305
#define TAG_PAYLOAD_LENGTH16 306
#define TAG_PAYLOAD_LENGTH64 307
#define WS_OP_CONTINUATION_FRAME 0
#define WS_OP_TEXT_FRAME 1
#define WS_OP_BINARY_FRAME 2
#define WS_OP_CONNECTION_CLOSE 8
#define WS_OP_PING 9
#define WS_OP_PONG 10
// Additional tags for ping/pong handling
#define TAG_PING_PAYLOAD 400
#define TAG_PONG_SENT 401
@interface WebSocket ()
- (void)socket:(GCDAsyncSocket *)asyncSocket didReadData:(NSData *)data withTag:(long)tag;
@end
@implementation MBIMPingPongWebSocket
{
2025-06-13 14:54:51 -07:00
NSUInteger currentPayloadLength;
}
#pragma mark - Ping/Pong Frame Construction
- (NSData *)createPongFrameWithPayload:(NSData *)pingPayload {
NSMutableData *frame = [NSMutableData data];
// First byte: FIN (1) + RSV (000) + Opcode (1010 = 0xA for Pong)
uint8_t firstByte = 0x8A; // 10001010 in binary
[frame appendBytes:&firstByte length:1];
// Second byte: MASK (0) + Payload Length
NSUInteger payloadLength = [pingPayload length];
if (payloadLength <= 125) {
// Short payload length (0-125 bytes)
uint8_t secondByte = (uint8_t)payloadLength; // MASK bit is 0 for server-to-client
[frame appendBytes:&secondByte length:1];
}
else if (payloadLength <= 65535) {
// Medium payload length (126-65535 bytes)
uint8_t secondByte = 126;
[frame appendBytes:&secondByte length:1];
// Add 16-bit length in network byte order (big-endian)
uint16_t extendedLength = htons((uint16_t)payloadLength);
[frame appendBytes:&extendedLength length:2];
}
else {
// Large payload length (65536+ bytes) - rarely needed for pings
uint8_t secondByte = 127;
[frame appendBytes:&secondByte length:1];
// Add 64-bit length in network byte order (big-endian)
uint64_t extendedLength = OSSwapHostToBigInt64(payloadLength);
[frame appendBytes:&extendedLength length:8];
}
// Append the payload (copied exactly from ping)
if (payloadLength > 0) {
[frame appendData:pingPayload];
}
return [frame copy];
}
2025-06-13 14:54:51 -07:00
- (void)sendPongWithPayload:(NSData *)payload {
NSData *pongFrame = [self createPongFrameWithPayload:payload];
// Send the pong frame directly through the socket
[asyncSocket writeData:pongFrame withTimeout:TIMEOUT_NONE tag:TAG_PONG_SENT];
}
#pragma mark - Override AsyncSocket Delegate
2025-06-13 14:54:51 -07:00
- (void)socket:(GCDAsyncSocket *)asyncSocket didReadData:(NSData *)data withTag:(long)tag {
2025-06-13 14:47:47 -07:00
// xxx: sheesh: really need to get off of CococaHTTPServer.
NSData *maskingKey = [self valueForKey:@"maskingKey"];
BOOL nextFrameMasked = [[self valueForKey:@"nextFrameMasked"] boolValue];
2025-06-13 14:47:47 -07:00
NSUInteger nextOpCode = [[self valueForKey:@"nextOpCode"] unsignedIntValue];
2025-06-13 14:39:24 -07:00
// Handle our custom ping payload tag
if (tag == TAG_PING_PAYLOAD) {
// We've read the ping payload, now send the pong response
NSData *payload = data;
// If the frame was masked, we need to unmask it
2025-06-13 14:47:47 -07:00
if (nextFrameMasked && maskingKey) {
NSMutableData *unmaskedPayload = [data mutableCopy];
UInt8 *payloadBytes = [unmaskedPayload mutableBytes];
2025-06-13 14:47:47 -07:00
UInt8 *maskBytes = (UInt8 *)[maskingKey bytes];
for (NSUInteger i = 0; i < [data length]; i++) {
payloadBytes[i] ^= maskBytes[i % 4];
}
payload = [unmaskedPayload copy];
}
2025-06-13 14:54:51 -07:00
[self sendPongWithPayload:payload];
// Continue reading the next frame
[asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_PREFIX];
return;
}
// Let parent handle TAG_PAYLOAD_PREFIX first to set nextOpCode
if (tag == TAG_PAYLOAD_PREFIX) {
2025-06-13 14:54:51 -07:00
[super socket:asyncSocket didReadData:data withTag:tag];
2025-06-13 14:47:47 -07:00
return;
}
// Now intercept ping/pong handling after nextOpCode is set
if (nextOpCode == WS_OP_PING || nextOpCode == WS_OP_PONG) {
if (tag == TAG_PAYLOAD_LENGTH) {
UInt8 frame = *(UInt8 *)[data bytes];
BOOL masked = (frame & 0x80) != 0;
NSUInteger length = frame & 0x7F;
nextFrameMasked = masked;
2025-06-13 14:54:51 -07:00
currentPayloadLength = length;
if (length <= 125) {
if (nextFrameMasked) {
[asyncSocket readDataToLength:4 withTimeout:TIMEOUT_NONE tag:TAG_MSG_MASKING_KEY];
2025-06-13 14:54:51 -07:00
} else if (length > 0) {
[asyncSocket readDataToLength:length withTimeout:TIMEOUT_NONE tag:TAG_PING_PAYLOAD];
} else {
// Empty payload, no masking - handle immediately
[self sendPongWithPayload:[NSData data]];
[asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_PREFIX];
}
2025-06-13 14:47:47 -07:00
}
else if (length == 126) {
[asyncSocket readDataToLength:2 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH16];
}
else {
[asyncSocket readDataToLength:8 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH64];
}
return;
2025-06-13 14:47:47 -07:00
}
if (tag == TAG_MSG_MASKING_KEY) {
// Store the masking key
maskingKey = [data copy];
2025-06-13 14:54:51 -07:00
// Now read the payload (or handle empty payload)
if (currentPayloadLength > 0) {
[asyncSocket readDataToLength:currentPayloadLength withTimeout:TIMEOUT_NONE tag:TAG_PING_PAYLOAD];
} else {
// Empty payload - send pong immediately
[self sendPongWithPayload:[NSData data]];
[asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_PREFIX];
}
return;
}
2025-06-13 14:47:47 -07:00
if (tag == TAG_PAYLOAD_LENGTH16) {
UInt8 *pFrame = (UInt8 *)[data bytes];
NSUInteger length = ((NSUInteger)pFrame[0] << 8) | (NSUInteger)pFrame[1];
if (nextFrameMasked) {
[asyncSocket readDataToLength:4 withTimeout:TIMEOUT_NONE tag:TAG_MSG_MASKING_KEY];
}
[asyncSocket readDataToLength:length withTimeout:TIMEOUT_NONE tag:TAG_PING_PAYLOAD];
return;
}
2025-06-13 14:47:47 -07:00
if (tag == TAG_PAYLOAD_LENGTH64) {
// For 64-bit length, this is too complex for ping frames - just close
[self didClose];
return;
2025-06-13 14:47:47 -07:00
}
}
2025-06-13 14:47:47 -07:00
// For all other cases, call the parent implementation
2025-06-13 14:54:51 -07:00
[super socket:asyncSocket didReadData:data withTag:tag];
}
#pragma mark - Helper Methods
// Expose the isValidWebSocketFrame method since it's private in the parent
- (BOOL)isValidWebSocketFrame:(UInt8)frame {
NSUInteger rsv = frame & 0x70;
NSUInteger opcode = frame & 0x0F;
if (rsv || (3 <= opcode && opcode <= 7) || (0xB <= opcode && opcode <= 0xF)) {
return NO;
}
return YES;
}
@end