|
| 1 | +import { |
| 2 | + computeIpChecksum, |
| 3 | + IpAddress, |
| 4 | + IpPayload, |
| 5 | + TCP_PROTOCOL_NUMBER, |
| 6 | +} from "./ip"; |
| 7 | + |
| 8 | +export class Flags { |
| 9 | + // Urgent Pointer field significant |
| 10 | + public urg = false; |
| 11 | + // Acknowledgment field significant |
| 12 | + public ack = false; |
| 13 | + // Push function |
| 14 | + public psh = false; |
| 15 | + // Reset the connection |
| 16 | + public rst = false; |
| 17 | + // Synchronize sequence numbers |
| 18 | + public syn = false; |
| 19 | + // No more data from sender |
| 20 | + public fin = false; |
| 21 | + |
| 22 | + // 6 bits |
| 23 | + toByte(): number { |
| 24 | + return [this.urg, this.ack, this.psh, this.rst, this.syn, this.fin].reduce( |
| 25 | + (acc, flag, index) => { |
| 26 | + return acc | bitSet(flag, 5 - index); |
| 27 | + }, |
| 28 | + 0, |
| 29 | + ); |
| 30 | + } |
| 31 | + |
| 32 | + withUrg(urg = true): Flags { |
| 33 | + this.urg = urg; |
| 34 | + return this; |
| 35 | + } |
| 36 | + |
| 37 | + withAck(ack = true): Flags { |
| 38 | + this.ack = ack; |
| 39 | + return this; |
| 40 | + } |
| 41 | + |
| 42 | + withPsh(psh = true): Flags { |
| 43 | + this.psh = psh; |
| 44 | + return this; |
| 45 | + } |
| 46 | + |
| 47 | + withRst(rst = true): Flags { |
| 48 | + this.rst = rst; |
| 49 | + return this; |
| 50 | + } |
| 51 | + |
| 52 | + withSyn(syn = true): Flags { |
| 53 | + this.syn = syn; |
| 54 | + return this; |
| 55 | + } |
| 56 | + |
| 57 | + withFin(fin = true): Flags { |
| 58 | + this.fin = fin; |
| 59 | + return this; |
| 60 | + } |
| 61 | +} |
| 62 | + |
| 63 | +function bitSet(value: boolean, bit: number): number { |
| 64 | + return value ? 1 << bit : 0; |
| 65 | +} |
| 66 | + |
| 67 | +export class TcpSegment implements IpPayload { |
| 68 | + // Info taken from the original RFC: https://www.ietf.org/rfc/rfc793.txt |
| 69 | + // 0 1 2 3 |
| 70 | + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
| 71 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 72 | + // | Source Port | Destination Port | |
| 73 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 74 | + // | Sequence Number | |
| 75 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 76 | + // | Acknowledgment Number | |
| 77 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 78 | + // | Data | |U|A|P|R|S|F| | |
| 79 | + // | Offset| Reserved |R|C|S|S|Y|I| Window | |
| 80 | + // | | |G|K|H|T|N|N| | |
| 81 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 82 | + // | Checksum | Urgent Pointer | |
| 83 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 84 | + // | Options | Padding | |
| 85 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 86 | + // | data | |
| 87 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 88 | + // |
| 89 | + // 2 bytes |
| 90 | + // The source port number. |
| 91 | + sourcePort: number; |
| 92 | + // 2 bytes |
| 93 | + // The destination port number. |
| 94 | + destinationPort: number; |
| 95 | + // 4 bytes |
| 96 | + // The sequence number of the first data octet in this segment (except |
| 97 | + // when SYN is present). If SYN is present the sequence number is the |
| 98 | + // initial sequence number (ISN) and the first data octet is ISN+1. |
| 99 | + sequenceNumber: number; |
| 100 | + // 4 bytes |
| 101 | + // If the ACK control bit is set this field contains the value of the |
| 102 | + // next sequence number the sender of the segment is expecting to |
| 103 | + // receive. Once a connection is established this is always sent. |
| 104 | + acknowledgementNumber: number; |
| 105 | + |
| 106 | + // 4 bits |
| 107 | + // 4-byte offset from the start of the TCP segment to the start of the data |
| 108 | + readonly dataOffset: number = 5; |
| 109 | + |
| 110 | + // 6 bits |
| 111 | + // Reserved for future use. Must be zero. |
| 112 | + readonly reserved: number = 0; |
| 113 | + |
| 114 | + // Control bits |
| 115 | + // 6 bits |
| 116 | + flags: Flags; |
| 117 | + |
| 118 | + // 2 bytes |
| 119 | + // The number of data octets beginning with the one indicated in the |
| 120 | + // acknowledgment field which the sender of this segment is willing to |
| 121 | + // accept. |
| 122 | + public window = 0xffff; |
| 123 | + |
| 124 | + // 2 bytes |
| 125 | + get checksum(): number { |
| 126 | + return this.computeChecksum(); |
| 127 | + } |
| 128 | + |
| 129 | + // 2 bytes |
| 130 | + readonly urgentPointer = 0; |
| 131 | + |
| 132 | + // 0-40 bytes |
| 133 | + // TODO: implement options |
| 134 | + // options: Option[]; |
| 135 | + |
| 136 | + // Variable size |
| 137 | + data: Uint8Array; |
| 138 | + |
| 139 | + // Used for the checksum calculation |
| 140 | + srcIpAddress: IpAddress; |
| 141 | + dstIpAddress: IpAddress; |
| 142 | + |
| 143 | + constructor( |
| 144 | + srcPort: number, |
| 145 | + dstPort: number, |
| 146 | + seqNum: number, |
| 147 | + ackNum: number, |
| 148 | + flags: Flags, |
| 149 | + data: Uint8Array, |
| 150 | + ) { |
| 151 | + checkUint(srcPort, 16); |
| 152 | + checkUint(dstPort, 16); |
| 153 | + checkUint(seqNum, 32); |
| 154 | + checkUint(ackNum, 32); |
| 155 | + |
| 156 | + this.sourcePort = srcPort; |
| 157 | + this.destinationPort = dstPort; |
| 158 | + this.sequenceNumber = seqNum; |
| 159 | + this.acknowledgementNumber = ackNum; |
| 160 | + this.flags = flags; |
| 161 | + this.data = data; |
| 162 | + } |
| 163 | + |
| 164 | + computeChecksum(): number { |
| 165 | + const segmentBytes = this.toBytes({ withChecksum: false }); |
| 166 | + |
| 167 | + const pseudoHeaderBytes = Uint8Array.from([ |
| 168 | + ...this.srcIpAddress.octets, |
| 169 | + ...this.dstIpAddress.octets, |
| 170 | + 0, |
| 171 | + TCP_PROTOCOL_NUMBER, |
| 172 | + ...uintToBytes(segmentBytes.length, 2), |
| 173 | + ]); |
| 174 | + const totalBytes = Uint8Array.from([...pseudoHeaderBytes, ...segmentBytes]); |
| 175 | + return computeIpChecksum(totalBytes); |
| 176 | + } |
| 177 | + |
| 178 | + // ### IpPayload ### |
| 179 | + toBytes({ |
| 180 | + withChecksum = true, |
| 181 | + }: { withChecksum?: boolean } = {}): Uint8Array { |
| 182 | + const checksum = withChecksum ? this.checksum : 0; |
| 183 | + return Uint8Array.from([ |
| 184 | + ...uintToBytes(this.sourcePort, 2), |
| 185 | + ...uintToBytes(this.destinationPort, 2), |
| 186 | + ...uintToBytes(this.sequenceNumber, 4), |
| 187 | + ...uintToBytes(this.acknowledgementNumber, 4), |
| 188 | + (this.dataOffset << 4) | this.reserved, |
| 189 | + ((this.reserved & 0b11) << 6) | this.flags.toByte(), |
| 190 | + ...uintToBytes(this.window, 2), |
| 191 | + ...uintToBytes(checksum, 2), |
| 192 | + ...uintToBytes(this.urgentPointer, 2), |
| 193 | + ...this.data, |
| 194 | + ]); |
| 195 | + } |
| 196 | + |
| 197 | + protocol(): number { |
| 198 | + return TCP_PROTOCOL_NUMBER; |
| 199 | + } |
| 200 | + // ### IpPayload ### |
| 201 | +} |
| 202 | + |
| 203 | +function checkUint(n: number, numBits: number): void { |
| 204 | + if (numBits > 32) { |
| 205 | + throw new Error("Bitwidth more than 32 not supported"); |
| 206 | + } |
| 207 | + // >>> 0 is to turn this into an unsigned integer again |
| 208 | + // https://stackoverflow.com/a/34897012 |
| 209 | + const max = numBits === 32 ? 0xffffffff : ((1 << numBits) >>> 0) - 1; |
| 210 | + if (n < 0 || n > max) { |
| 211 | + throw new Error("Invalid value for uint" + numBits + ": " + n); |
| 212 | + } |
| 213 | +} |
| 214 | + |
| 215 | +function uintToBytes(n: number, numBytes: number): Uint8Array { |
| 216 | + const original = n; |
| 217 | + const bytes = new Uint8Array(numBytes); |
| 218 | + for (let i = 0; i < numBytes; i++) { |
| 219 | + bytes[numBytes - i - 1] = n & 0xff; |
| 220 | + n >>>= 8; |
| 221 | + } |
| 222 | + if (n !== 0) { |
| 223 | + throw new Error("Value too large for " + numBytes + " bytes: " + original); |
| 224 | + } |
| 225 | + return bytes; |
| 226 | +} |
0 commit comments