//
//  IPPacket.swift
//  Tun
//
//  Created by 安礼成 on 2024/1/18.
//

import Foundation

enum IPVersion: UInt8 {
    case ipv4 = 4
    case ipv6 = 6
}

enum TransportProtocol: UInt8 {
    case icmp = 1
    case tcp  = 6
    case udp  = 17
}

// MARK: - IP Header

struct IPHeader {
    let version: UInt8
    let headerLength: UInt8
    let typeOfService: UInt8
    let totalLength: UInt16
    let id: UInt16
    let offset: UInt16
    let ttl: UInt8
    let proto: UInt8
    let checksum: UInt16
    let source: UInt32
    let destination: UInt32

    var headerBytes: Int {
        Int(headerLength)
    }
}

// MARK: - IP Packet

struct IPPacket {
    let header: IPHeader
    let data: Data
    let transportPacket: TransportPacket
    
    enum TransportPacket {
        case tcp(TCPPacket)
        case udp(UDPPacket)
        case icmp(ICMPPacket)
        case unsupported(UInt8)
        case malformed
    }
    
    init?(_ data: Data) {
        guard data.count >= 20 else {
            return nil
        }

        let version = data[0] >> 4
        let headerLen = (data[0] & 0x0F) * 4

        guard data.count >= headerLen else {
            return nil
        }

        self.header = IPHeader(
            version: version,
            headerLength: headerLen,
            typeOfService: data[1],
            totalLength: UInt16(bytes: (data[2], data[3])),
            id: UInt16(bytes: (data[4], data[5])),
            offset: UInt16(bytes: (data[6], data[7])),
            ttl: data[8],
            proto: data[9],
            checksum: UInt16(bytes: (data[10], data[11])),
            source: UInt32(bytes: (data[12], data[13], data[14], data[15])),
            destination: UInt32(bytes: (data[16], data[17], data[18], data[19]))
        )

        self.data = data
        self.transportPacket = Self.parseTransportPacket(proto: data[9], offset: Int(headerLen), data: data)
    }

    private static func parseTransportPacket(proto: UInt8, offset: Int, data: Data) -> TransportPacket {
        guard let proto = TransportProtocol(rawValue: proto) else {
            return .unsupported(proto)
        }
        
        switch proto {
        case .tcp:
            guard let tcp = TCPPacket(data, offset: offset) else {
                return .malformed
            }
            return .tcp(tcp)
            
        case .udp:
            guard let udp = UDPPacket(data, offset: offset) else {
                return .malformed
            }
            return .udp(udp)
            
        case .icmp:
            guard let icmp = ICMPPacket(data, offset: offset) else {
                return .malformed
            }
            return .icmp(icmp)
        }
    }
    
}

// MARK: - Lightweight IP Packet View

struct IPPacketView {
    let header: IPHeader
    let data: Data
    let transportPacket: TransportPacket

    enum TransportPacket {
        case tcp(srcPort: UInt16, dstPort: UInt16)
        case udp(srcPort: UInt16, dstPort: UInt16, payloadOffset: Int)
        case icmp
        case unsupported(UInt8)
        case malformed
    }

    init?(_ data: Data) {
        guard data.count >= 20 else {
            return nil
        }

        let firstByte = Self.byte(at: 0, in: data)
        let version = firstByte >> 4
        let headerLen = (firstByte & 0x0F) * 4

        guard headerLen >= 20, data.count >= headerLen else {
            return nil
        }

        let proto = Self.byte(at: 9, in: data)
        self.header = IPHeader(
            version: version,
            headerLength: headerLen,
            typeOfService: Self.byte(at: 1, in: data),
            totalLength: UInt16(bytes: (Self.byte(at: 2, in: data), Self.byte(at: 3, in: data))),
            id: UInt16(bytes: (Self.byte(at: 4, in: data), Self.byte(at: 5, in: data))),
            offset: UInt16(bytes: (Self.byte(at: 6, in: data), Self.byte(at: 7, in: data))),
            ttl: Self.byte(at: 8, in: data),
            proto: proto,
            checksum: UInt16(bytes: (Self.byte(at: 10, in: data), Self.byte(at: 11, in: data))),
            source: UInt32(bytes: (Self.byte(at: 12, in: data), Self.byte(at: 13, in: data), Self.byte(at: 14, in: data), Self.byte(at: 15, in: data))),
            destination: UInt32(bytes: (Self.byte(at: 16, in: data), Self.byte(at: 17, in: data), Self.byte(at: 18, in: data), Self.byte(at: 19, in: data)))
        )

        self.data = data
        self.transportPacket = Self.parseTransportPacket(proto: proto, offset: Int(headerLen), data: data)
    }

    private static func parseTransportPacket(proto: UInt8, offset: Int, data: Data) -> TransportPacket {
        guard let proto = TransportProtocol(rawValue: proto) else {
            return .unsupported(proto)
        }

        switch proto {
        case .tcp:
            guard data.count >= offset + 20 else {
                return .malformed
            }
            return .tcp(
                srcPort: UInt16(bytes: (Self.byte(at: offset, in: data), Self.byte(at: offset + 1, in: data))),
                dstPort: UInt16(bytes: (Self.byte(at: offset + 2, in: data), Self.byte(at: offset + 3, in: data)))
            )

        case .udp:
            guard data.count >= offset + 8 else {
                return .malformed
            }
            return .udp(
                srcPort: UInt16(bytes: (Self.byte(at: offset, in: data), Self.byte(at: offset + 1, in: data))),
                dstPort: UInt16(bytes: (Self.byte(at: offset + 2, in: data), Self.byte(at: offset + 3, in: data))),
                payloadOffset: offset + 8
            )

        case .icmp:
            guard data.count >= offset + 4 else {
                return .malformed
            }
            return .icmp
        }
    }

    private static func byte(at offset: Int, in data: Data) -> UInt8 {
        data[data.index(data.startIndex, offsetBy: offset)]
    }
}

// MARK: - TCP Flags

struct TCPFlags: OptionSet {
    let rawValue: UInt16

    static let fin = TCPFlags(rawValue: 1 << 0)
    static let syn = TCPFlags(rawValue: 1 << 1)
    static let rst = TCPFlags(rawValue: 1 << 2)
    static let psh = TCPFlags(rawValue: 1 << 3)
    static let ack = TCPFlags(rawValue: 1 << 4)
    static let urg = TCPFlags(rawValue: 1 << 5)
    static let ece = TCPFlags(rawValue: 1 << 6)
    static let cwr = TCPFlags(rawValue: 1 << 7)
}

// MARK: - TCP Header

struct TCPHeader {
    let srcPort: UInt16
    let dstPort: UInt16

    let seq: UInt32
    let ack: UInt32

    let dataOffset: UInt8
    let flags: TCPFlags

    let window: UInt16
    let checksum: UInt16
    let urgentPointer: UInt16

    var headerLength: Int {
        Int(dataOffset) * 4
    }
}

// MARK: - TCP Packet

struct TCPPacket {
    let header: TCPHeader

    init?(_ data: Data, offset: Int) {
        guard data.count >= offset + 20 else {
            return nil
        }

        let srcPort = UInt16(bytes: (data[offset], data[offset + 1]))
        let dstPort = UInt16(bytes: (data[offset + 2], data[offset + 3]))

        let seq = UInt32(bytes: (data[offset + 4], data[offset + 5], data[offset + 6], data[offset + 7]))
        let ack = UInt32(bytes: (data[offset + 8], data[offset + 9], data[offset + 10], data[offset + 11]))

        let offsetAndFlags = UInt16(bytes: (data[offset + 12], data[offset + 13]))

        let dataOffset = UInt8(offsetAndFlags >> 12)
        let flags = TCPFlags(rawValue: offsetAndFlags & 0x01FF)

        let window = UInt16(bytes: (data[offset + 14], data[offset + 15]))
        let checksum = UInt16(bytes: (data[offset + 16], data[offset + 17]))
        let urgent = UInt16(bytes: (data[offset + 18], data[offset + 19]))

        let header = TCPHeader(
            srcPort: srcPort,
            dstPort: dstPort,
            seq: seq,
            ack: ack,
            dataOffset: dataOffset,
            flags: flags,
            window: window,
            checksum: checksum,
            urgentPointer: urgent
        )

        let headerLen = header.headerLength
        guard data.count >= offset + headerLen else {
            return nil
        }
        
        self.header = header
    }
    
}

// MARK: - UDP Packet

struct UDPPacket {
    let srcPort: UInt16
    let dstPort: UInt16
    let length: UInt16
    let checksum: UInt16
    let payloadOffset: Int
    
    init?(_ data: Data, offset: Int) {
        guard data.count >= offset + 8 else {
            return nil
        }
        
        self.srcPort = UInt16(bytes: (data[offset], data[offset + 1]))
        self.dstPort = UInt16(bytes: (data[offset + 2], data[offset + 3]))
        self.length  = UInt16(bytes: (data[offset + 4], data[offset + 5]))
        self.checksum = UInt16(bytes: (data[offset + 6], data[offset + 7]))
        self.payloadOffset = offset + 8
    }
    
}

// MARK: - ICMP Packet

struct ICMPPacket {
    let type: UInt8
    let code: UInt8
    let checksum: UInt16

    init?(_ data: Data, offset: Int) {
        guard data.count >= offset + 4 else {
            return nil
        }

        self.type = data[offset]
        self.code = data[offset + 1]
        self.checksum = UInt16(bytes: (data[offset + 2], data[offset + 3]))
    }
    
}
