一种线性时间的字节模式查找器,用于发现两个字节序列几乎相同的情况。它们可能确实相同,但在其中一个的构造过程中,开发者可能意外地反转了序列,或者它们源自整数,可能错误地使用了错误的字节序,或者两者兼而有之。
以下所有示例都假设字节序列 LHS
和 RHS
具有相同的长度。
两个字节序列完全相同,即彼此相等。
let finder = BytePatternFinder()
finder.find(
lhs: try Data(hex: "dead beef 1234 5678 abba 0912 deed fade"),
rhs: try Data(hex: "dead beef 1234 5678 abba 0912 deed fade")
) // `.identical`
如果其中一个字节序列被完全反转,则这两个序列将完全相同。
finder.find(
lhs: "ab12 cd34",
rhs: "34cd 12ab"
) // `.sameIf([.reversed])`
finder.find(
lhs: "dead beef 1234 5678 abba 0912 deed fade",
rhs: "defa edde 1209 baab 7856 3412 efbe adde"
) // `.sameIf([.reversed])`
如果其中一个字节序列从一个在传递给字节序列初始化之前就被反转的十六进制字符串初始化,则这两个序列将完全相同。
或者换句话说,如果其中一个序列中的每个字节都通过 4 位进行了旋转(循环移位)。此运算符不是标准库的一部分,但通过以下方式实现:(input >> 4) | (input << 4)
。
finder.find(
lhs: "ab12 cd34",
rhs: "43dc 21ba"
) // `.sameIf([.reversedHex])`
finder.find(
lhs: "dead beef 1234 5678 abba 0912 deed fade",
rhs: "edaf deed 2190 abba 8765 4321 feeb daed"
) // `.sameIf([.reversedHex])`
如果其中一个字节序列被“分块”为长度为 2/4/8 的片段,则这两个序列将完全相同。例如,就像在每个片段的开头加载了 UInt16
/UInt32
/UInt64
,然后反转了此整数列表,对于 reverseOrderOfUInt16sFromBytes
/reverseOrderOfUInt32sFromBytes
/reverseOrderOfUInt64sFromBytes
来说,情况就是如此。
finder.find(
lhs: "dead beef 1234 5678",
rhs: "5678 1234 beef dead"
) // `.sameIf([.reverseOrderOfUInt16sFromBytes])`
finder.find(
lhs: "dead beef 1234",
rhs: "1234 beef dead"
) // `.sameIf([.reverseOrderOfUInt16sFromBytes])`
如果其中一个字节序列被“分块”为长度为 2/4/8 的片段,例如加载了 UInt16
/UInt32
/UInt64
,并且我们交换了此列表中每个整数的字节序,对于 swapEndianessOfUInt16sFromBytes
/swapEndianessOfUInt32sFromBytes
/swapEndianessOfUInt64sFromBytes
来说,情况就是如此。
finder.find(
lhs: "ab12 cd34",
rhs: "12ab 34cd"
) // `.sameIf([.swapEndianessOfUInt16sFromBytes])`
finder.find(
lhs: "dead beef 1234 5678 abba 0912 deed fade",
rhs: "adde efbe 3412 7856 baab 1209 edde defa"
) // `.sameIf([.swapEndianessOfUInt16sFromBytes])`
finder.find(
lhs: "deadbeef 12345678 abba0912 deedfade",
rhs: "efbeadde 78563412 1209baab defaedde"
) // `.sameIf([.swapEndianessOfUInt32sFromBytes])`
finder.find(
lhs: "deadbeef12345678 abba0912deedfade",
rhs: "78563412efbeadde defaedde1209baab"
) // `.sameIf([.swapEndianessOfUInt64sFromBytes])`
finder.find(
lhs: "dead beef 1234",
rhs: "edda ebfe 2143",
) // `sameIf([.swapEndianessOfUInt16sFromBytes, .reverseOrderOfUInt16sFromBytes, .reversedHex])`
一个小型的测试实用程序包,使您可以使用 BytePatternFinder
方便地比较字节序列。它包含一些类似 XCTAssert
的方法,但专门为字节序列比较量身定制。
// This test will fail.
// Assertion failure message is:
// "Expected bytes in LHS to equal RHS, but they are not, however, they resemble each other with according to byte pattern: sameIf([swapEndianessOfUInt16sFromBytes])."
func test_data_failing() throws {
try XCTAssertBytesEqual(
Data(hex: "ab12 cd34"),
Data(hex: "12ab 34cd"),
"An optional message goes here."
)
}
您可以更改测试的行为,以便在找到非 identical
模式时通过测试 - 但如果未找到任何模式,仍然会失败,方法是传递 passOnPatternNonIdentical: true
。
func test_data_passing() throws {
try XCTAssertBytesEqual(
Data(hex: "ab12 cd34"),
Data(hex: "12ab 34cd"),
"An optional message goes here.",
passOnPatternNonIdentical: true
)
}
您还可以选择在找到非 identical 模式时中断,方法是传递 haltOnPatternNonIdentical: true
func test_data_passing_halting() throws {
try XCTAssertBytesEqual(
Data(hex: "ab12 cd34"),
Data(hex: "12ab 34cd"),
"An optional message goes here.",
passOnPatternNonIdentical: true,
haltOnPatternNonIdentical: true
)
}
您还可以通过在全局类型 DefaultXCTAssertBytesEqualParameters
上设置这些属性来全局更改 passOnPatternNonIdentical
和 haltOnPatternNonIdentical
的默认值。执行此操作的一个好地方是在您的测试类的 setUp()
方法中。
override func setUp() {
super.setUp()
DefaultXCTAssertBytesEqualParameters.passOnPatternNonIdentical = true
DefaultXCTAssertBytesEqualParameters.haltOnPatternNonIdentical = true
}
func test_data_passing_halting_defaulParamsUsed() throws {
try XCTAssertBytesEqual(
Data(hex: "ab12 cd34"),
Data(hex: "12ab 34cd")
)
}
通过使用 XCTAssertBytesFromHexEqual
可以简化以上示例,如果您传入无效的十六进制字符串,则会失败并显示错误。
func test_nonIdentical_but_passing() {
XCTAssertBytesFromHexEqual(
"ab12 cd34",
"12ab 34cd",
passOnPatternNonIdentical: true
)
}
这个小包允许您对符合 ContiguousBytes
的任何字节序列执行与 BytePattern
的名称相对应的变异操作。
public extension ContiguousBytes {
func reversed() -> [UInt8]
func reversedHex() -> [UInt8]
func reverseOrderOfUInt16sFromBytes() -> [UInt8]
func reverseOrderOfUInt32sFromBytes() -> [UInt8]
func reverseOrderOfUInt64sFromBytes() -> [UInt8]
func swapEndianessOfUInt16sFromBytes() -> [UInt8]
func swapEndianessOfUInt32sFromBytes() -> [UInt8]
func swapEndianessOfUInt64sFromBytes() -> [UInt8]
}