//===----------------------------------------------------------------------===// // Copyright © 2025-2026 Apple Inc. and the Containerization project authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //===----------------------------------------------------------------------===// // swiftlint:disable force_try import Foundation import Testing @testable import ContainerizationEXT4 struct TestEXT4ExtendedAttribute { @Test func compressName() { struct TestCase { let input: String let expectedId: UInt8 let expectedStr: String init(_ input: String, _ expectedId: UInt8, _ expectedStr: String) { self.input = input self.expectedId = expectedId self.expectedStr = expectedStr } } let tests: [TestCase] = [ .init("my.test.xattr", 0, "my.test.xattr"), .init("user.fubar", 1, "fubar"), .init("system.posix_acl_access.denied_su", 2, ".denied_su"), .init("system.posix_acl_default_failed", 3, "_failed"), .init("trusted.user", 4, "user"), .init("trusted_user", 0, "trusted_user"), .init("security.auth", 6, "auth"), .init("system.admin", 7, "admin"), .init("system.richacl.denied", 8, ".denied"), ] for test in tests { let ret = EXT4.ExtendedAttribute.compressName(test.input) #expect(ret.0 == test.expectedId) #expect(ret.1 == test.expectedStr) } } @Test func lastXattrNotDroppedAtBufferBoundary() throws { let buffer: [UInt8] = [ 0, // nameLength 8, // nameIndex 0, 0, // valueOffset 0, 0, 0, 0, // valueInum 0, 0, 0, 0, // valueSize 0, 0, 0, 0, // hash ] let attrs = try EXT4.FileXattrsState.read(buffer: buffer, start: 0, offset: 0) try #require(attrs.count == 1) #expect(attrs[0].fullName == "system.richacl") } @Test func xattrOutOfBoundsValueDoesNotCrash() throws { let buffer: [UInt8] = [ 1, // nameLength 1, // nameIndex 17, 0, // valueOffset 0, 0, 0, 0, // valueInum 4, 0, 0, 0, // valueSize 0, 0, 0, 0, // hash UInt8(ascii: "a"), 0, 0, 0, // name ] let attrs = try EXT4.FileXattrsState.read(buffer: buffer, start: 0, offset: 0) #expect(attrs.isEmpty) } @Test func encodeDecodeAttributes() { let xattrs: [String: Data] = [ "foo.bar": Data([1, 2, 3]), "bar": Data([0, 0, 0]), "system.richacl.bar": Data([99, 1, 9, 1]), "foobar.user": Data([71, 2, 45]), "test.xattr.cap": Data([1, 32, 3]), "testing123": Data([12, 24, 45]), "sys.admin": Data([16, 23, 13]), "test.123": Data([15, 26, 54]), "extendedattribute.test": Data([15, 26, 54, 1, 2, 4, 6, 7, 7]), ] let blockSize = 4096 var state = EXT4.FileXattrsState( inode: 1, inodeXattrCapacity: EXT4.InodeExtraSize, blockCapacity: UInt32(blockSize)) for (s, d) in xattrs { let attribute = EXT4.ExtendedAttribute(name: s, value: [UInt8](d)) try! state.add(attribute) } var inlineAttrBuffer: [UInt8] = .init(repeating: 0, count: Int(EXT4.InodeExtraSize)) var blockAttrBuffer: [UInt8] = .init(repeating: 0, count: blockSize) try! state.writeInlineAttributes(buffer: &inlineAttrBuffer) try! state.writeBlockAttributes(buffer: &blockAttrBuffer) let gotInlineXattrs = try! EXT4.EXT4Reader.readInlineExtendedAttributes(from: inlineAttrBuffer) let gotBlockXattrs = try! EXT4.EXT4Reader.readBlockExtendedAttributes(from: blockAttrBuffer) var gotXattrs: [String: Data] = [:] for attr in gotBlockXattrs + gotInlineXattrs { gotXattrs[attr.fullName] = Data(attr.value) } #expect(gotXattrs == xattrs) } }