Skip to content

Commit

Permalink
fix various issues in icmp channel; add icmp header rewrite and corre…
Browse files Browse the repository at this point in the history
…sponding tests
  • Loading branch information
johnnzhou committed Jan 28, 2024
1 parent 8dda735 commit 7a99b71
Show file tree
Hide file tree
Showing 9 changed files with 649 additions and 437 deletions.
115 changes: 73 additions & 42 deletions Sources/LCLPing/ICMP/ICMPChannelHandlers.swift

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Sources/LCLPing/Models/Errors+LCLPing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ public enum PingError: Error {
case httpUnableToEstablishTLSConnection
case httpMissingResult

case invalidHexFormat

case forTestingPurposeOnly
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/LCLPing/Models/PingSummary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ extension PingSummary {
return lhs.seqNum == rhs.seqNum
}

public let seqNum: UInt16
public let seqNum: UInt16?
public let reason: String
}
}
Expand Down
73 changes: 29 additions & 44 deletions Sources/LCLPing/TestUtils/Rewritable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,16 @@ import Foundation
import NIOCore

protocol Rewritable {
// var allKeyPaths: [PartialKeyPath<Self>] { get }
func rewrite(newValues: [PartialKeyPath<Self> : AnyObject]) -> Self
}

extension ICMPRequestPayload: Rewritable {
// var allKeyPaths: [PartialKeyPath<ICMPRequestPayload>] {
// return [\.identifier, \.timestamp]
// }

func rewrite(newValues: [PartialKeyPath<ICMPRequestPayload> : AnyObject]) -> ICMPRequestPayload {
return ICMPRequestPayload(timestamp: newValues[\.timestamp] as? TimeInterval ?? self.timestamp, identifier: newValues[\.identifier] as? UInt16 ?? self.identifier)
}
}

extension IPv4Header: Rewritable {
// var allKeyPaths: [PartialKeyPath<IPv4Header>] {
// return [
// \.versionAndHeaderLength,
// \.differentiatedServicesAndECN,
// \.totalLength,
// \.identification,
// \.flagsAndFragmentOffset,
// \.timeToLive,
// \.protocol,
// \.headerChecksum,
// \.sourceAddress,
// \.destinationAddress
// ]
// }

func rewrite(newValues: [PartialKeyPath<IPv4Header> : AnyObject]) -> IPv4Header {
return IPv4Header(
versionAndHeaderLength: newValues[\.versionAndHeaderLength] as? UInt8 ?? self.versionAndHeaderLength,
Expand All @@ -61,17 +41,6 @@ extension IPv4Header: Rewritable {
}

extension ICMPHeader: Rewritable {
// var allKeyPaths: [PartialKeyPath<ICMPHeader>] {
// return [
// \.type,
// \.code,
// \.checkSum,
// \.idenifier,
// \.sequenceNum,
// \.payload
// ]
// }

func rewrite(newValues: [PartialKeyPath<ICMPHeader> : AnyObject]) -> ICMPHeader {
var newHeader = ICMPHeader(
type: newValues[\.type] as? UInt8 ?? self.type,
Expand All @@ -85,25 +54,41 @@ extension ICMPHeader: Rewritable {
}
}

extension AddressedEnvelope: Rewritable where DataType: Rewritable {
// var allKeyPaths: [PartialKeyPath<NIOCore.AddressedEnvelope<DataType>>] {
// return [
// \.remoteAddress,
// \.data
// ]
// }

extension AddressedEnvelope: Rewritable where DataType == ByteBuffer {
func rewrite(newValues: [PartialKeyPath<NIOCore.AddressedEnvelope<DataType>> : AnyObject]) -> NIOCore.AddressedEnvelope<DataType> {
return AddressedEnvelope(
remoteAddress: newValues[\.remoteAddress] as? SocketAddress ?? self.remoteAddress,
data: data.rewrite(newValues: newValues[\.data] as! [PartialKeyPath<DataType> : AnyObject])
data: data.rewrite(newValues: newValues[\AddressedEnvelope.data] as? [RewriteData] ?? [RewriteData(index: 0, byte: 0x55)])
)
}
}

// TODO: implement Bytebuffer
extension ByteBuffer: Rewritable {
func rewrite(newValues: [PartialKeyPath<NIOCore.ByteBuffer> : AnyObject]) -> NIOCore.ByteBuffer {
return ByteBuffer(buffer: self)

extension ByteBuffer {
func rewrite(newValues: ByteBuffer) -> NIOCore.ByteBuffer {
print("[ByteBuffer Rewrite]: received new value: \(newValues.readableBytesView)")
return ByteBuffer(buffer: newValues)
}

func rewrite(newValues: [RewriteData]) -> ByteBuffer {
print("[ByteBuffer Rewrite]: received new value: \(newValues)")
var newBuffer = ByteBuffer(buffer: self)
for newValue in newValues {
newBuffer.setBytes(newValue.byte.data, at: newValue.index)
}
print("ByteBuffer Rewrite: rewritten as \(newBuffer.readableBytesView)")
return newBuffer
}
}

extension Int8 {
var data: Data {
var int = self
return Data(bytes: &int, count: MemoryLayout<Int8>.size)
}
}

struct RewriteData {
let index: Int
let byte: Int8
}
53 changes: 53 additions & 0 deletions Sources/LCLPing/Utilities/Hex.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// This source file is part of the LCLPing open source project
//
// Copyright (c) 2021-2023 Local Connectivity Lab and the project authors
// Licensed under Apache License v2.0
//
// See LICENSE for license information
// See CONTRIBUTORS for the list of project authors
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation


let charA = UInt8(UnicodeScalar("a").value)
let char0 = UInt8(UnicodeScalar("0").value)

private func itoh(_ value: UInt8) -> UInt8 {
return (value > 9) ? (charA + value - 10) : (char0 + value)
}

private func htoi(_ value: UInt8) throws -> UInt8 {
switch value {
case char0...char0 + 9:
return value - char0
case charA...charA + 5:
return value - charA + 10
default:
throw PingError.invalidHexFormat
}
}

extension Data {

/// Initialize the `Data` from the hex string value
init(hexString: String) throws {
self.init()

if hexString.count % 2 != 0 || hexString.count == 0 {
throw PingError.invalidHexFormat
}

let stringBytes: [UInt8] = Array(hexString.lowercased().data(using: String.Encoding.utf8)!)

for i in stride(from: stringBytes.startIndex, to: stringBytes.endIndex - 1, by: 2) {
let char1 = stringBytes[i]
let char2 = stringBytes[i + 1]

try self.append(htoi(char1) << 4 + htoi(char2))
}
}
}
2 changes: 1 addition & 1 deletion Sources/LCLPing/Utilities/Utilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ internal func summarizePingResponse(_ pingResponses: [PingResponse], host: Socke
timeout.insert(sequenceNum)
case .error(let seqNum, let error):
errorCount += 1
if let seqNum = seqNum, let error = error {
if let error = error {
errors.insert(PingSummary.PingErrorSummary(seqNum: seqNum, reason: error.localizedDescription))
}
}
Expand Down
12 changes: 8 additions & 4 deletions Tests/ICMPChannelTests/ICMPDuplexerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,6 @@ final class ICMPDuplexerTests: XCTestCase {
}

func testReadFireCorrectError() {
XCTAssertNoThrow(try channel.pipeline.addHandler(ICMPDuplexer(configuration: self.icmpConfiguration)).wait())
channel.pipeline.fireChannelActive()

let inputs = [
(3, 0, PingError.icmpDestinationNetworkUnreachable),
(3, 1, PingError.icmpDestinationHostUnreachable),
Expand Down Expand Up @@ -123,10 +120,17 @@ final class ICMPDuplexerTests: XCTestCase {
for input in inputs {
let (type, code, expectedError) = input
var inboundInData: ICMPHeader = ICMPHeader(type: UInt8(type), code: UInt8(code), idenifier: 0xbeef, sequenceNum: 2)
let outboundInData: ICMPOutboundIn = (UInt16(0xbeef), UInt16(2))
inboundInData.setChecksum()
do {
let channel = EmbeddedChannel()
let loop = channel.embeddedEventLoop
XCTAssertNoThrow(try channel.pipeline.addHandler(ICMPDuplexer(configuration: self.icmpConfiguration)).wait())
channel.pipeline.fireChannelActive()

try channel.writeOutbound(outboundInData)
try channel.writeInbound(inboundInData)
self.loop.run()
loop.run()
let inboundInResult = try channel.readInbound(as: PingResponse.self)!
switch inboundInResult {
case .error(.some(let seqNum), .some(let error)):
Expand Down
Loading

0 comments on commit 7a99b71

Please sign in to comment.