mirror of
https://github.com/ichmagmaus111/ghostgram.git
synced 2026-11-03 22:27:13 +01:00
Ghostgram for iOS, rebuilt from scratch on Telegram iOS 13.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
136 lines
5.3 KiB
Swift
136 lines
5.3 KiB
Swift
import Foundation
|
|
|
|
public enum WebProxyProtocol {
|
|
public static let headerSize = 8
|
|
public static let initialWindow = 4 * 1024 * 1024
|
|
public static let maximumPayload = 1024 * 1024
|
|
public static let maximumDataPayload = 64 * 1024
|
|
public static let defaultBatchSize = 2 * 1024 * 1024
|
|
public static let maximumBatchFrames = 4096
|
|
public static let maximumQueuedBytes = 32 * 1024 * 1024
|
|
public static let maximumQueuedItems = 16384
|
|
public static let tombstoneCount = 4096
|
|
public static let maximumStreamId: UInt32 = 0x00ff_ffff
|
|
}
|
|
|
|
public enum WebProxyFrameType: UInt8, CaseIterable {
|
|
case open = 0x01
|
|
case data = 0x02
|
|
case close = 0x03
|
|
case window = 0x04
|
|
case ping = 0x05
|
|
case pong = 0x06
|
|
case hello = 0x10
|
|
case welcome = 0x11
|
|
case bye = 0x1f
|
|
}
|
|
|
|
public struct WebProxyFrame: Equatable {
|
|
public let type: WebProxyFrameType
|
|
public let streamId: UInt32
|
|
public let payload: Data
|
|
|
|
public init(type: WebProxyFrameType, streamId: UInt32, payload: Data = Data()) {
|
|
self.type = type
|
|
self.streamId = streamId
|
|
self.payload = payload
|
|
}
|
|
|
|
public func validated() throws -> WebProxyFrame {
|
|
guard self.streamId <= WebProxyProtocol.maximumStreamId,
|
|
self.payload.count <= WebProxyProtocol.maximumPayload else {
|
|
throw WebProxyFrameError.invalidFrame
|
|
}
|
|
switch self.type {
|
|
case .open, .close:
|
|
guard self.streamId != 0 && self.payload.isEmpty else { throw WebProxyFrameError.invalidFrame }
|
|
case .data:
|
|
guard self.streamId != 0 && !self.payload.isEmpty else { throw WebProxyFrameError.invalidFrame }
|
|
case .window:
|
|
guard self.streamId != 0, self.payload.count == 4, self.windowDelta != 0 else { throw WebProxyFrameError.invalidFrame }
|
|
case .ping, .pong:
|
|
guard self.streamId == 0 else { throw WebProxyFrameError.invalidFrame }
|
|
case .hello:
|
|
guard self.streamId == 0 && self.payload == Data([1]) else { throw WebProxyFrameError.invalidFrame }
|
|
case .welcome:
|
|
guard self.streamId == 0 && self.payload.isEmpty else { throw WebProxyFrameError.invalidFrame }
|
|
case .bye:
|
|
guard self.streamId == 0 else { throw WebProxyFrameError.invalidFrame }
|
|
}
|
|
return self
|
|
}
|
|
|
|
public var windowDelta: UInt32? {
|
|
guard self.payload.count == 4 else { return nil }
|
|
return self.payload.reduce(UInt32(0)) { ($0 << 8) | UInt32($1) }
|
|
}
|
|
|
|
public static func window(streamId: UInt32, delta: UInt32) -> WebProxyFrame {
|
|
return WebProxyFrame(type: .window, streamId: streamId, payload: Data([
|
|
UInt8((delta >> 24) & 0xff), UInt8((delta >> 16) & 0xff),
|
|
UInt8((delta >> 8) & 0xff), UInt8(delta & 0xff)
|
|
]))
|
|
}
|
|
}
|
|
|
|
public enum WebProxyFrameError: Error, Equatable {
|
|
case invalidFrame
|
|
case unknownType
|
|
case bufferLimitExceeded
|
|
}
|
|
|
|
public enum WebProxyFrameEncoder {
|
|
public static func encode(_ frame: WebProxyFrame) throws -> Data {
|
|
let frame = try frame.validated()
|
|
let length = UInt32(frame.payload.count)
|
|
var data = Data(capacity: WebProxyProtocol.headerSize + frame.payload.count)
|
|
data.append(frame.type.rawValue)
|
|
data.append(UInt8((frame.streamId >> 16) & 0xff))
|
|
data.append(UInt8((frame.streamId >> 8) & 0xff))
|
|
data.append(UInt8(frame.streamId & 0xff))
|
|
data.append(UInt8((length >> 24) & 0xff))
|
|
data.append(UInt8((length >> 16) & 0xff))
|
|
data.append(UInt8((length >> 8) & 0xff))
|
|
data.append(UInt8(length & 0xff))
|
|
data.append(frame.payload)
|
|
return data
|
|
}
|
|
}
|
|
|
|
public final class WebProxyFrameDecoder {
|
|
private var buffer = Data()
|
|
|
|
public init() {
|
|
}
|
|
|
|
public func append(_ data: Data) throws -> [WebProxyFrame] {
|
|
let maximumBufferedSize = WebProxyProtocol.maximumQueuedBytes
|
|
guard self.buffer.count <= maximumBufferedSize,
|
|
data.count <= maximumBufferedSize - self.buffer.count else {
|
|
throw WebProxyFrameError.bufferLimitExceeded
|
|
}
|
|
self.buffer.append(data)
|
|
var result: [WebProxyFrame] = []
|
|
while self.buffer.count >= WebProxyProtocol.headerSize {
|
|
guard let type = WebProxyFrameType(rawValue: self.buffer[0]) else {
|
|
throw WebProxyFrameError.unknownType
|
|
}
|
|
let streamId = (UInt32(self.buffer[1]) << 16) | (UInt32(self.buffer[2]) << 8) | UInt32(self.buffer[3])
|
|
let length = (UInt32(self.buffer[4]) << 24) | (UInt32(self.buffer[5]) << 16) | (UInt32(self.buffer[6]) << 8) | UInt32(self.buffer[7])
|
|
guard length <= UInt32(WebProxyProtocol.maximumPayload) else {
|
|
throw WebProxyFrameError.invalidFrame
|
|
}
|
|
let frameLength = WebProxyProtocol.headerSize + Int(length)
|
|
guard self.buffer.count >= frameLength else { break }
|
|
let payload = self.buffer.subdata(in: WebProxyProtocol.headerSize ..< frameLength)
|
|
let frame = try WebProxyFrame(type: type, streamId: streamId, payload: payload).validated()
|
|
result.append(frame)
|
|
guard result.count <= WebProxyProtocol.maximumBatchFrames else {
|
|
throw WebProxyFrameError.bufferLimitExceeded
|
|
}
|
|
self.buffer.removeSubrange(0 ..< frameLength)
|
|
}
|
|
return result
|
|
}
|
|
}
|