commit 4c0166bd31842cfeb0e79cc041cf01e5e0ed9b03 parent 35b67dc08d4b61bf98922cc94cffcb3037c77710 Author: William Casarin <jb55@jb55.com> Date: Sat, 26 Aug 2023 20:43:54 -0700 add swift flatbuffers Diffstat:
47 files changed, 3955 insertions(+), 0 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -104,6 +104,28 @@ 4C30AC7829A577AB00E2BD5A /* EventCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7729A577AB00E2BD5A /* EventCache.swift */; }; 4C30AC8029A6A53F00E2BD5A /* ProfilePicturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7F29A6A53F00E2BD5A /* ProfilePicturesView.swift */; }; 4C32B9332A99845B00DC3548 /* Ndb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C478E242A9932C100489948 /* Ndb.swift */; }; + 4C32B9342A9AD01A00DC3548 /* NdbProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C478E2C2A9935D300489948 /* NdbProfile.swift */; }; + 4C32B94C2A9AD44700DC3548 /* FbConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9372A9AD44700DC3548 /* FbConstants.swift */; }; + 4C32B94D2A9AD44700DC3548 /* Offset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9382A9AD44700DC3548 /* Offset.swift */; }; + 4C32B94E2A9AD44700DC3548 /* Mutable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9392A9AD44700DC3548 /* Mutable.swift */; }; + 4C32B94F2A9AD44700DC3548 /* Int+extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B93A2A9AD44700DC3548 /* Int+extension.swift */; }; + 4C32B9502A9AD44700DC3548 /* FlatBufferBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B93B2A9AD44700DC3548 /* FlatBufferBuilder.swift */; }; + 4C32B9512A9AD44700DC3548 /* FlatbuffersErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B93C2A9AD44700DC3548 /* FlatbuffersErrors.swift */; }; + 4C32B9522A9AD44700DC3548 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B93D2A9AD44700DC3548 /* Message.swift */; }; + 4C32B9532A9AD44700DC3548 /* Verifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B93E2A9AD44700DC3548 /* Verifier.swift */; }; + 4C32B9542A9AD44700DC3548 /* FlatBuffersUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B93F2A9AD44700DC3548 /* FlatBuffersUtils.swift */; }; + 4C32B9552A9AD44700DC3548 /* ByteBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9402A9AD44700DC3548 /* ByteBuffer.swift */; }; + 4C32B9562A9AD44700DC3548 /* TableVerifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9412A9AD44700DC3548 /* TableVerifier.swift */; }; + 4C32B9572A9AD44700DC3548 /* Root.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9422A9AD44700DC3548 /* Root.swift */; }; + 4C32B9582A9AD44700DC3548 /* VeriferOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9432A9AD44700DC3548 /* VeriferOptions.swift */; }; + 4C32B9592A9AD44700DC3548 /* Table.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9442A9AD44700DC3548 /* Table.swift */; }; + 4C32B95A2A9AD44700DC3548 /* Verifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9452A9AD44700DC3548 /* Verifiable.swift */; }; + 4C32B95B2A9AD44700DC3548 /* NativeObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9462A9AD44700DC3548 /* NativeObject.swift */; }; + 4C32B95C2A9AD44700DC3548 /* String+extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9472A9AD44700DC3548 /* String+extension.swift */; }; + 4C32B95D2A9AD44700DC3548 /* Documentation.docc in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9482A9AD44700DC3548 /* Documentation.docc */; }; + 4C32B95E2A9AD44700DC3548 /* FlatBufferObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9492A9AD44700DC3548 /* FlatBufferObject.swift */; }; + 4C32B95F2A9AD44700DC3548 /* Enum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B94A2A9AD44700DC3548 /* Enum.swift */; }; + 4C32B9602A9AD44700DC3548 /* Struct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B94B2A9AD44700DC3548 /* Struct.swift */; }; 4C363A8428233689006E126D /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8328233689006E126D /* Parser.swift */; }; 4C363A8828236948006E126D /* BlocksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8728236948006E126D /* BlocksView.swift */; }; 4C363A8C28236B92006E126D /* PubkeyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8B28236B92006E126D /* PubkeyView.swift */; }; @@ -617,6 +639,27 @@ 4C30AC7529A5770900E2BD5A /* NotificationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationItemView.swift; sourceTree = "<group>"; }; 4C30AC7729A577AB00E2BD5A /* EventCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventCache.swift; sourceTree = "<group>"; }; 4C30AC7F29A6A53F00E2BD5A /* ProfilePicturesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePicturesView.swift; sourceTree = "<group>"; }; + 4C32B9372A9AD44700DC3548 /* FbConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FbConstants.swift; sourceTree = "<group>"; }; + 4C32B9382A9AD44700DC3548 /* Offset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Offset.swift; sourceTree = "<group>"; }; + 4C32B9392A9AD44700DC3548 /* Mutable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Mutable.swift; sourceTree = "<group>"; }; + 4C32B93A2A9AD44700DC3548 /* Int+extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Int+extension.swift"; sourceTree = "<group>"; }; + 4C32B93B2A9AD44700DC3548 /* FlatBufferBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlatBufferBuilder.swift; sourceTree = "<group>"; }; + 4C32B93C2A9AD44700DC3548 /* FlatbuffersErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlatbuffersErrors.swift; sourceTree = "<group>"; }; + 4C32B93D2A9AD44700DC3548 /* Message.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = "<group>"; }; + 4C32B93E2A9AD44700DC3548 /* Verifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Verifier.swift; sourceTree = "<group>"; }; + 4C32B93F2A9AD44700DC3548 /* FlatBuffersUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlatBuffersUtils.swift; sourceTree = "<group>"; }; + 4C32B9402A9AD44700DC3548 /* ByteBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ByteBuffer.swift; sourceTree = "<group>"; }; + 4C32B9412A9AD44700DC3548 /* TableVerifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableVerifier.swift; sourceTree = "<group>"; }; + 4C32B9422A9AD44700DC3548 /* Root.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Root.swift; sourceTree = "<group>"; }; + 4C32B9432A9AD44700DC3548 /* VeriferOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VeriferOptions.swift; sourceTree = "<group>"; }; + 4C32B9442A9AD44700DC3548 /* Table.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Table.swift; sourceTree = "<group>"; }; + 4C32B9452A9AD44700DC3548 /* Verifiable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Verifiable.swift; sourceTree = "<group>"; }; + 4C32B9462A9AD44700DC3548 /* NativeObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NativeObject.swift; sourceTree = "<group>"; }; + 4C32B9472A9AD44700DC3548 /* String+extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+extension.swift"; sourceTree = "<group>"; }; + 4C32B9482A9AD44700DC3548 /* Documentation.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Documentation.docc; sourceTree = "<group>"; }; + 4C32B9492A9AD44700DC3548 /* FlatBufferObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlatBufferObject.swift; sourceTree = "<group>"; }; + 4C32B94A2A9AD44700DC3548 /* Enum.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Enum.swift; sourceTree = "<group>"; }; + 4C32B94B2A9AD44700DC3548 /* Struct.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Struct.swift; sourceTree = "<group>"; }; 4C363A8328233689006E126D /* Parser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; }; 4C363A8728236948006E126D /* BlocksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlocksView.swift; sourceTree = "<group>"; }; 4C363A8B28236B92006E126D /* PubkeyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubkeyView.swift; sourceTree = "<group>"; }; @@ -1329,6 +1372,34 @@ path = Notifications; sourceTree = "<group>"; }; + 4C32B9362A9AD44700DC3548 /* flatbuffers */ = { + isa = PBXGroup; + children = ( + 4C32B9372A9AD44700DC3548 /* FbConstants.swift */, + 4C32B9382A9AD44700DC3548 /* Offset.swift */, + 4C32B9392A9AD44700DC3548 /* Mutable.swift */, + 4C32B93A2A9AD44700DC3548 /* Int+extension.swift */, + 4C32B93B2A9AD44700DC3548 /* FlatBufferBuilder.swift */, + 4C32B93C2A9AD44700DC3548 /* FlatbuffersErrors.swift */, + 4C32B93D2A9AD44700DC3548 /* Message.swift */, + 4C32B93E2A9AD44700DC3548 /* Verifier.swift */, + 4C32B93F2A9AD44700DC3548 /* FlatBuffersUtils.swift */, + 4C32B9402A9AD44700DC3548 /* ByteBuffer.swift */, + 4C32B9412A9AD44700DC3548 /* TableVerifier.swift */, + 4C32B9422A9AD44700DC3548 /* Root.swift */, + 4C32B9432A9AD44700DC3548 /* VeriferOptions.swift */, + 4C32B9442A9AD44700DC3548 /* Table.swift */, + 4C32B9452A9AD44700DC3548 /* Verifiable.swift */, + 4C32B9462A9AD44700DC3548 /* NativeObject.swift */, + 4C32B9472A9AD44700DC3548 /* String+extension.swift */, + 4C32B9482A9AD44700DC3548 /* Documentation.docc */, + 4C32B9492A9AD44700DC3548 /* FlatBufferObject.swift */, + 4C32B94A2A9AD44700DC3548 /* Enum.swift */, + 4C32B94B2A9AD44700DC3548 /* Struct.swift */, + ); + path = flatbuffers; + sourceTree = "<group>"; + }; 4C478E2A2A9935D300489948 /* bindings */ = { isa = PBXGroup; children = ( @@ -1967,6 +2038,7 @@ 4CE6DEDA27F7A08100C66700 = { isa = PBXGroup; children = ( + 4C32B9362A9AD44700DC3548 /* flatbuffers */, 4C9054862A6AEB4500811EEC /* nostrdb */, 4C19AE4A2A5CEF7C00C90DB7 /* nostrscript */, 4C06670728FDE62900038D2A /* damus-c */, @@ -2414,16 +2486,19 @@ 4CE9FBBA2A6B3C63007E485C /* nostrdb.c in Sources */, 4C3AC79D2833036D00E1F516 /* FollowingView.swift in Sources */, 5CF72FC229B9142F00124A13 /* ShareAction.swift in Sources */, + 4C32B9522A9AD44700DC3548 /* Message.swift in Sources */, 4C8D1A6C29F1DFC200ACDF75 /* FriendIcon.swift in Sources */, 4C30AC7829A577AB00E2BD5A /* EventCache.swift in Sources */, 4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */, 4CDD1AE22A6B3074001CD4DF /* NdbTagsIterator.swift in Sources */, 4C216F34286F5ACD00040376 /* DMView.swift in Sources */, + 4C32B9572A9AD44700DC3548 /* Root.swift in Sources */, 4C3EA64428FF558100C48A62 /* sha256.c in Sources */, 4CCF9AAF2A1FDBDB00E03CFB /* VideoPlayer.swift in Sources */, 504323A72A34915F006AE6DC /* RelayModel.swift in Sources */, 4CA9276A2A290FC00098A105 /* ContextButton.swift in Sources */, 4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */, + 4C32B9542A9AD44700DC3548 /* FlatBuffersUtils.swift in Sources */, 4C363AA828297703006E126D /* InsertSort.swift in Sources */, 4C285C86283892E7008A31F1 /* CreateAccountModel.swift in Sources */, 4C64987C286D03E000EAE2B3 /* DirectMessagesView.swift in Sources */, @@ -2461,6 +2536,7 @@ 4CA5588329F33F5B00DC6A45 /* StringCodable.swift in Sources */, 4C75EFB92804A2740006080F /* EventView.swift in Sources */, 4C15C7152A55DE7A00D0A0DB /* ReactionsSettingsView.swift in Sources */, + 4C32B94C2A9AD44700DC3548 /* FbConstants.swift in Sources */, 4C8D00C829DF791C0036AF10 /* CompatibleAttribute.swift in Sources */, 4C7D09742A0AEF9000943473 /* AlbyGradient.swift in Sources */, 4C687C272A6039500092C550 /* TestData.swift in Sources */, @@ -2470,8 +2546,11 @@ 4CB883B6297730E400DC99E7 /* LNUrls.swift in Sources */, 4C7FF7D52823313F009601DB /* Mentions.swift in Sources */, BA4AB0AE2A63B9270070A32A /* AddEmojiView.swift in Sources */, + 4C32B94D2A9AD44700DC3548 /* Offset.swift in Sources */, 4C633350283D40E500B1C9C3 /* HomeModel.swift in Sources */, 4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */, + 4C32B9552A9AD44700DC3548 /* ByteBuffer.swift in Sources */, + 4C32B95B2A9AD44700DC3548 /* NativeObject.swift in Sources */, 3AB72AB9298ECF30004BB58C /* Translator.swift in Sources */, 4C363A9028247A1D006E126D /* NostrLink.swift in Sources */, 4C3D52B6298DB4E6001C5831 /* ZapEvent.swift in Sources */, @@ -2492,6 +2571,7 @@ 4C7D096D2A0AEA0400943473 /* CodeScanner.swift in Sources */, 4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */, 3A48E7B029DFBE9D006E787E /* MutedThreadsManager.swift in Sources */, + 4C32B94E2A9AD44700DC3548 /* Mutable.swift in Sources */, 4C285C8E28399BFE008A31F1 /* SaveKeysView.swift in Sources */, F7F0BA25297892BD009531F3 /* SwipeToDismiss.swift in Sources */, 4C8D00CA29DF80350036AF10 /* TruncatedText.swift in Sources */, @@ -2502,6 +2582,7 @@ 4CB8838F296F781C00DC99E7 /* ReactionsView.swift in Sources */, BA4AB0B02A63B94D0070A32A /* EmojiListItemView.swift in Sources */, 4C75EFB328049D640006080F /* NostrEvent.swift in Sources */, + 4C32B9582A9AD44700DC3548 /* VeriferOptions.swift in Sources */, 4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */, 4C30AC7629A5770900E2BD5A /* NotificationItemView.swift in Sources */, 4C86F7C42A76C44C00EC0817 /* ZappingNotify.swift in Sources */, @@ -2515,6 +2596,7 @@ 4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */, 4CB8838B296F6E1E00DC99E7 /* NIP05Badge.swift in Sources */, 4CA3FA1029F593D000FDB3C3 /* ZapTypePicker.swift in Sources */, + 4C32B95D2A9AD44700DC3548 /* Documentation.docc in Sources */, 4C3EA66828FF5F9900C48A62 /* hex.c in Sources */, E9E4ED0B295867B900DD7078 /* ThreadView.swift in Sources */, 4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */, @@ -2549,6 +2631,7 @@ 4C4E137B2A76D5FB00BDD832 /* MuteThreadNotify.swift in Sources */, 4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */, 4C12535A2A76C9960004F4B8 /* UnfollowNotify.swift in Sources */, + 4C32B95C2A9AD44700DC3548 /* String+extension.swift in Sources */, 4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */, 4CA352AA2A76BF3A003BB08B /* LocalNotificationNotify.swift in Sources */, 4C7D09682A0AE9B200943473 /* NWCScannerView.swift in Sources */, @@ -2560,6 +2643,7 @@ 4CCEB7B029B5415A0078AA28 /* SearchingProfileView.swift in Sources */, BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */, 3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */, + 4C32B9602A9AD44700DC3548 /* Struct.swift in Sources */, 4CC7AAF0297F11C700430951 /* SelectedEventView.swift in Sources */, 4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */, 4C687C242A5FA86D0092C550 /* SearchHeaderView.swift in Sources */, @@ -2633,6 +2717,7 @@ 5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */, 4CC14FF52A740BB7007AEB17 /* NoteId.swift in Sources */, 4C19AE512A5CEF7C00C90DB7 /* NostrScript.swift in Sources */, + 4C32B95E2A9AD44700DC3548 /* FlatBufferObject.swift in Sources */, 4C3EA64F28FF59F200C48A62 /* tal.c in Sources */, 4CB88393296F798300DC99E7 /* ReactionsModel.swift in Sources */, 5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */, @@ -2676,6 +2761,7 @@ 4C8D00CF29E38B950036AF10 /* nostr_bech32.c in Sources */, 4C1253562A76C8C60004F4B8 /* BroadcastNotify.swift in Sources */, 4C3BEFD42819DE8F00B3DE84 /* NostrKind.swift in Sources */, + 4C32B9532A9AD44700DC3548 /* Verifier.swift in Sources */, 4C3EA66028FF5E7700C48A62 /* node_id.c in Sources */, 4C687C212A5F7ED00092C550 /* DamusBackground.swift in Sources */, 4CA352A02A76AE80003BB08B /* Notify.swift in Sources */, @@ -2685,9 +2771,11 @@ 4C363A962827096D006E126D /* PostBlock.swift in Sources */, 4CA9275F2A2902B20098A105 /* LongformPreview.swift in Sources */, 4C5F9116283D855D0052CD1C /* EventsModel.swift in Sources */, + 4C32B94F2A9AD44700DC3548 /* Int+extension.swift in Sources */, 4CEE2AED2805B22500AB5EEF /* NostrRequest.swift in Sources */, 4C06670E28FDEAA000038D2A /* utf8.c in Sources */, 4C3EA66D28FF782800C48A62 /* amount.c in Sources */, + 4C32B9562A9AD44700DC3548 /* TableVerifier.swift in Sources */, 4C5E54062A9671F800FF6E60 /* UserStatusSheet.swift in Sources */, F71694F42A6732B7001F4053 /* GradientFollowButton.swift in Sources */, 4C3AC7A728369BA200E1F516 /* SearchHomeView.swift in Sources */, @@ -2695,8 +2783,10 @@ 4C363A922825FCF2006E126D /* ProfileUpdate.swift in Sources */, 4CB9D4A92992D2F400A9A7E4 /* FollowsYou.swift in Sources */, 4C3BEFDA281DCA1400B3DE84 /* LikeCounter.swift in Sources */, + 4C32B9502A9AD44700DC3548 /* FlatBufferBuilder.swift in Sources */, 50A60D142A28BEEE00186190 /* RelayLog.swift in Sources */, 4CB88389296AF99A00DC99E7 /* EventDetailBar.swift in Sources */, + 4C32B9512A9AD44700DC3548 /* FlatbuffersErrors.swift in Sources */, 4CE8794E2996B16A00F758CC /* RelayToggle.swift in Sources */, 4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */, 4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */, @@ -2708,6 +2798,7 @@ 4C12536C2A76D4B00004F4B8 /* RepostedNotify.swift in Sources */, 4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */, 4CE4F0F229D4FCFA005914DB /* DebouncedOnChange.swift in Sources */, + 4C32B9592A9AD44700DC3548 /* Table.swift in Sources */, 4C5D5C9D2A6B2CB40024563C /* AsciiCharacter.swift in Sources */, 4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */, 4C9146FE2A2A87C200DDEA40 /* nostrscript.c in Sources */, @@ -2746,6 +2837,7 @@ 4C75EFA427FA577B0006080F /* PostView.swift in Sources */, 4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */, 4C1A9A2129DDD3E100516EAC /* KeySettingsView.swift in Sources */, + 4C32B95A2A9AD44700DC3548 /* Verifiable.swift in Sources */, 4C73C5142A4437C10062CAC0 /* ZapUserView.swift in Sources */, 501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */, 4C1A9A1D29DDCF9B00516EAC /* NotificationSettingsView.swift in Sources */, @@ -2762,6 +2854,7 @@ 4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */, 4CE4F0F429D779B5005914DB /* PostBox.swift in Sources */, 4C9B0DF32A65C46800CBDA21 /* ProfileEditButton.swift in Sources */, + 4C32B95F2A9AD44700DC3548 /* Enum.swift in Sources */, 4C2859622A12A7F0004746F7 /* GoldSupportGradient.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/flatbuffers/ByteBuffer.swift b/flatbuffers/ByteBuffer.swift @@ -0,0 +1,462 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +import Foundation +#else +import SwiftOverlayShims +#endif + +/// `ByteBuffer` is the interface that stores the data for a `Flatbuffers` object +/// it allows users to write and read data directly from memory thus the use of its +/// functions should be used +@frozen +public struct ByteBuffer { + + /// Storage is a container that would hold the memory pointer to solve the issue of + /// deallocating the memory that was held by (memory: UnsafeMutableRawPointer) + @usableFromInline + final class Storage { + // This storage doesn't own the memory, therefore, we won't deallocate on deinit. + private let unowned: Bool + /// pointer to the start of the buffer object in memory + var memory: UnsafeMutableRawPointer + /// Capacity of UInt8 the buffer can hold + var capacity: Int + + @usableFromInline + init(count: Int, alignment: Int) { + memory = UnsafeMutableRawPointer.allocate( + byteCount: count, + alignment: alignment) + capacity = count + unowned = false + } + + @usableFromInline + init(memory: UnsafeMutableRawPointer, capacity: Int, unowned: Bool) { + self.memory = memory + self.capacity = capacity + self.unowned = unowned + } + + deinit { + if !unowned { + memory.deallocate() + } + } + + @usableFromInline + func copy(from ptr: UnsafeRawPointer, count: Int) { + assert( + !unowned, + "copy should NOT be called on a buffer that is built by assumingMemoryBound") + memory.copyMemory(from: ptr, byteCount: count) + } + + @usableFromInline + func initialize(for size: Int) { + assert( + !unowned, + "initalize should NOT be called on a buffer that is built by assumingMemoryBound") + memset(memory, 0, size) + } + + /// Reallocates the buffer incase the object to be written doesnt fit in the current buffer + /// - Parameter size: Size of the current object + @usableFromInline + func reallocate(_ size: Int, writerSize: Int, alignment: Int) { + let currentWritingIndex = capacity &- writerSize + while capacity <= writerSize &+ size { + capacity = capacity << 1 + } + + /// solution take from Apple-NIO + capacity = capacity.convertToPowerofTwo + + let newData = UnsafeMutableRawPointer.allocate( + byteCount: capacity, + alignment: alignment) + memset(newData, 0, capacity &- writerSize) + memcpy( + newData.advanced(by: capacity &- writerSize), + memory.advanced(by: currentWritingIndex), + writerSize) + memory.deallocate() + memory = newData + } + } + + @usableFromInline var _storage: Storage + + /// The size of the elements written to the buffer + their paddings + private var _writerSize: Int = 0 + /// Aliginment of the current memory being written to the buffer + var alignment = 1 + /// Current Index which is being used to write to the buffer, it is written from the end to the start of the buffer + var writerIndex: Int { _storage.capacity &- _writerSize } + + /// Reader is the position of the current Writer Index (capacity - size) + public var reader: Int { writerIndex } + /// Current size of the buffer + public var size: UOffset { UOffset(_writerSize) } + /// Public Pointer to the buffer object in memory. This should NOT be modified for any reason + public var memory: UnsafeMutableRawPointer { _storage.memory } + /// Current capacity for the buffer + public var capacity: Int { _storage.capacity } + + /// Constructor that creates a Flatbuffer object from a UInt8 + /// - Parameter bytes: Array of UInt8 + public init(bytes: [UInt8]) { + var b = bytes + _storage = Storage(count: bytes.count, alignment: alignment) + _writerSize = _storage.capacity + b.withUnsafeMutableBytes { bufferPointer in + self._storage.copy(from: bufferPointer.baseAddress!, count: bytes.count) + } + } + + #if !os(WASI) + /// Constructor that creates a Flatbuffer from the Swift Data type object + /// - Parameter data: Swift data Object + public init(data: Data) { + var b = data + _storage = Storage(count: data.count, alignment: alignment) + _writerSize = _storage.capacity + b.withUnsafeMutableBytes { bufferPointer in + self._storage.copy(from: bufferPointer.baseAddress!, count: data.count) + } + } + #endif + + /// Constructor that creates a Flatbuffer instance with a size + /// - Parameter size: Length of the buffer + init(initialSize size: Int) { + let size = size.convertToPowerofTwo + _storage = Storage(count: size, alignment: alignment) + _storage.initialize(for: size) + } + + #if swift(>=5.0) && !os(WASI) + /// Constructor that creates a Flatbuffer object from a ContiguousBytes + /// - Parameters: + /// - contiguousBytes: Binary stripe to use as the buffer + /// - count: amount of readable bytes + public init<Bytes: ContiguousBytes>( + contiguousBytes: Bytes, + count: Int) + { + _storage = Storage(count: count, alignment: alignment) + _writerSize = _storage.capacity + contiguousBytes.withUnsafeBytes { buf in + _storage.copy(from: buf.baseAddress!, count: buf.count) + } + } + #endif + + /// Constructor that creates a Flatbuffer from unsafe memory region without copying + /// - Parameter assumingMemoryBound: The unsafe memory region + /// - Parameter capacity: The size of the given memory region + public init( + assumingMemoryBound memory: UnsafeMutableRawPointer, + capacity: Int) + { + _storage = Storage(memory: memory, capacity: capacity, unowned: true) + _writerSize = capacity + } + + /// Creates a copy of the buffer that's being built by calling sizedBuffer + /// - Parameters: + /// - memory: Current memory of the buffer + /// - count: count of bytes + init(memory: UnsafeMutableRawPointer, count: Int) { + _storage = Storage(count: count, alignment: alignment) + _storage.copy(from: memory, count: count) + _writerSize = _storage.capacity + } + + /// Creates a copy of the existing flatbuffer, by copying it to a different memory. + /// - Parameters: + /// - memory: Current memory of the buffer + /// - count: count of bytes + /// - removeBytes: Removes a number of bytes from the current size + init( + memory: UnsafeMutableRawPointer, + count: Int, + removing removeBytes: Int) + { + _storage = Storage(count: count, alignment: alignment) + _storage.copy(from: memory, count: count) + _writerSize = removeBytes + } + + /// Fills the buffer with padding by adding to the writersize + /// - Parameter padding: Amount of padding between two to be serialized objects + @inline(__always) + @usableFromInline + mutating func fill(padding: Int) { + assert(padding >= 0, "Fill should be larger than or equal to zero") + ensureSpace(size: padding) + _writerSize = _writerSize &+ (MemoryLayout<UInt8>.size &* padding) + } + + /// Adds an array of type Scalar to the buffer memory + /// - Parameter elements: An array of Scalars + @inline(__always) + @usableFromInline + mutating func push<T: Scalar>(elements: [T]) { + let size = elements.count &* MemoryLayout<T>.size + ensureSpace(size: size) + elements.reversed().forEach { s in + push(value: s, len: MemoryLayout.size(ofValue: s)) + } + } + + /// Adds an object of type NativeStruct into the buffer + /// - Parameters: + /// - value: Object that will be written to the buffer + /// - size: size to subtract from the WriterIndex + @usableFromInline + @inline(__always) + mutating func push<T: NativeStruct>(struct value: T, size: Int) { + ensureSpace(size: size) + var v = value + memcpy(_storage.memory.advanced(by: writerIndex &- size), &v, size) + _writerSize = _writerSize &+ size + } + + /// Adds an object of type Scalar into the buffer + /// - Parameters: + /// - value: Object that will be written to the buffer + /// - len: Offset to subtract from the WriterIndex + @inline(__always) + @usableFromInline + mutating func push<T: Scalar>(value: T, len: Int) { + ensureSpace(size: len) + var v = value + memcpy(_storage.memory.advanced(by: writerIndex &- len), &v, len) + _writerSize = _writerSize &+ len + } + + /// Adds a string to the buffer using swift.utf8 object + /// - Parameter str: String that will be added to the buffer + /// - Parameter len: length of the string + @inline(__always) + @usableFromInline + mutating func push(string str: String, len: Int) { + ensureSpace(size: len) + if str.utf8 + .withContiguousStorageIfAvailable({ self.push(bytes: $0, len: len) }) != + nil + { + } else { + let utf8View = str.utf8 + for c in utf8View.reversed() { + push(value: c, len: 1) + } + } + } + + /// Writes a string to Bytebuffer using UTF8View + /// - Parameters: + /// - bytes: Pointer to the view + /// - len: Size of string + @usableFromInline + @inline(__always) + mutating func push( + bytes: UnsafeBufferPointer<String.UTF8View.Element>, + len: Int) -> Bool + { + memcpy( + _storage.memory.advanced(by: writerIndex &- len), + UnsafeRawPointer(bytes.baseAddress!), + len) + _writerSize = _writerSize &+ len + return true + } + + /// Write stores an object into the buffer directly or indirectly. + /// + /// Direct: ignores the capacity of buffer which would mean we are referring to the direct point in memory + /// indirect: takes into respect the current capacity of the buffer (capacity - index), writing to the buffer from the end + /// - Parameters: + /// - value: Value that needs to be written to the buffer + /// - index: index to write to + /// - direct: Should take into consideration the capacity of the buffer + @inline(__always) + func write<T>(value: T, index: Int, direct: Bool = false) { + var index = index + if !direct { + index = _storage.capacity &- index + } + assert(index < _storage.capacity, "Write index is out of writing bound") + assert(index >= 0, "Writer index should be above zero") + _storage.memory.storeBytes(of: value, toByteOffset: index, as: T.self) + } + + /// Makes sure that buffer has enouch space for each of the objects that will be written into it + /// - Parameter size: size of object + @discardableResult + @usableFromInline + @inline(__always) + mutating func ensureSpace(size: Int) -> Int { + if size &+ _writerSize > _storage.capacity { + _storage.reallocate(size, writerSize: _writerSize, alignment: alignment) + } + assert(size < FlatBufferMaxSize, "Buffer can't grow beyond 2 Gigabytes") + return size + } + + /// pops the written VTable if it's already written into the buffer + /// - Parameter size: size of the `VTable` + @usableFromInline + @inline(__always) + mutating func pop(_ size: Int) { + assert( + (_writerSize &- size) > 0, + "New size should NOT be a negative number") + memset(_storage.memory.advanced(by: writerIndex), 0, _writerSize &- size) + _writerSize = size + } + + /// Clears the current size of the buffer + @inline(__always) + mutating public func clearSize() { + _writerSize = 0 + } + + /// Clears the current instance of the buffer, replacing it with new memory + @inline(__always) + mutating public func clear() { + _writerSize = 0 + alignment = 1 + _storage.initialize(for: _storage.capacity) + } + + /// Reads an object from the buffer + /// - Parameters: + /// - def: Type of the object + /// - position: the index of the object in the buffer + @inline(__always) + public func read<T>(def: T.Type, position: Int) -> T { + _storage.memory.advanced(by: position).load(as: T.self) + } + + /// Reads a slice from the memory assuming a type of T + /// - Parameters: + /// - index: index of the object to be read from the buffer + /// - count: count of bytes in memory + @inline(__always) + public func readSlice<T>( + index: Int, + count: Int) -> [T] + { + assert( + index + count <= _storage.capacity, + "Reading out of bounds is illegal") + let start = _storage.memory.advanced(by: index) + .assumingMemoryBound(to: T.self) + let array = UnsafeBufferPointer(start: start, count: count) + return Array(array) + } + + #if !os(WASI) + /// Reads a string from the buffer and encodes it to a swift string + /// - Parameters: + /// - index: index of the string in the buffer + /// - count: length of the string + /// - type: Encoding of the string + @inline(__always) + public func readString( + at index: Int, + count: Int, + type: String.Encoding = .utf8) -> String? + { + assert( + index + count <= _storage.capacity, + "Reading out of bounds is illegal") + let start = _storage.memory.advanced(by: index) + .assumingMemoryBound(to: UInt8.self) + let bufprt = UnsafeBufferPointer(start: start, count: count) + return String(bytes: Array(bufprt), encoding: type) + } + #else + /// Reads a string from the buffer and encodes it to a swift string + /// - Parameters: + /// - index: index of the string in the buffer + /// - count: length of the string + /// - type: Encoding of the string + @inline(__always) + public func readString( + at index: Int, + count: Int) -> String? + { + assert( + index + count <= _storage.capacity, + "Reading out of bounds is illegal") + let start = _storage.memory.advanced(by: index) + .assumingMemoryBound(to: UInt8.self) + let bufprt = UnsafeBufferPointer(start: start, count: count) + return String(cString: bufprt.baseAddress!) + } + #endif + + /// Creates a new Flatbuffer object that's duplicated from the current one + /// - Parameter removeBytes: the amount of bytes to remove from the current Size + @inline(__always) + public func duplicate(removing removeBytes: Int = 0) -> ByteBuffer { + assert(removeBytes > 0, "Can NOT remove negative bytes") + assert( + removeBytes < _storage.capacity, + "Can NOT remove more bytes than the ones allocated") + return ByteBuffer( + memory: _storage.memory, + count: _storage.capacity, + removing: _writerSize &- removeBytes) + } + + /// Returns the written bytes into the ``ByteBuffer`` + public var underlyingBytes: [UInt8] { + let cp = capacity &- writerIndex + let start = memory.advanced(by: writerIndex) + .bindMemory(to: UInt8.self, capacity: cp) + + let ptr = UnsafeBufferPointer<UInt8>(start: start, count: cp) + return Array(ptr) + } + + /// SkipPrefix Skips the first 4 bytes in case one of the following + /// functions are called `getPrefixedSizeCheckedRoot` & `getPrefixedSizeRoot` + /// which allows us to skip the first 4 bytes instead of recreating the buffer + @discardableResult + @usableFromInline + @inline(__always) + mutating func skipPrefix() -> Int32 { + _writerSize = _writerSize &- MemoryLayout<Int32>.size + return read(def: Int32.self, position: 0) + } + +} + +extension ByteBuffer: CustomDebugStringConvertible { + + public var debugDescription: String { + """ + buffer located at: \(_storage.memory), with capacity of \(_storage.capacity) + { writerSize: \(_writerSize), readerSize: \(reader), writerIndex: \(writerIndex) } + """ + } +} diff --git a/flatbuffers/Documentation.docc/Documentation.md b/flatbuffers/Documentation.docc/Documentation.md @@ -0,0 +1,22 @@ +# ``FlatBuffers`` + +FlatBuffers: Memory Efficient Serialization Library + +## Overview + +- Access to serialized data without parsing/unpacking - What sets FlatBuffers apart is that it represents hierarchical data in a flat binary buffer in such a way that it can still be accessed directly without parsing/unpacking, while also still supporting data structure evolution (forwards/backwards compatibility). +- Memory efficiency and speed - The only memory needed to access your data is that of the buffer. It requires 0 additional allocations (in C++, other languages may vary). FlatBuffers is also very suitable for use with mmap (or streaming), requiring only part of the buffer to be in memory. Access is close to the speed of raw struct access with only one extra indirection (a kind of vtable) to allow for format evolution and optional fields. It is aimed at projects where spending time and space (many memory allocations) to be able to access or construct serialized data is undesirable, such as in games or any other performance sensitive applications. See the benchmarks for details. +- Flexible - Optional fields means not only do you get great forwards and backwards compatibility (increasingly important for long-lived games: don't have to update all data with each new version!). It also means you have a lot of choice in what data you write and what data you don't, and how you design data structures. +- Tiny code footprint - Small amounts of generated code, and just a single small header as the minimum dependency, which is very easy to integrate. Again, see the benchmark section for details. +- Strongly typed - Errors happen at compile time rather than manually having to write repetitive and error prone run-time checks. Useful code can be generated for you. + +## Topics + +### Read this first + +- <doc:Tutorial_Table_of_Contents> + +### Where to start + +- ``FlatBufferBuilder`` +- ``ByteBuffer`` diff --git a/flatbuffers/Documentation.docc/Resources/code/fbs/monster_step_1.fbs b/flatbuffers/Documentation.docc/Resources/code/fbs/monster_step_1.fbs @@ -0,0 +1 @@ + diff --git a/flatbuffers/Documentation.docc/Resources/code/fbs/monster_step_2.fbs b/flatbuffers/Documentation.docc/Resources/code/fbs/monster_step_2.fbs @@ -0,0 +1 @@ +enum Color:byte { red, green, blue } diff --git a/flatbuffers/Documentation.docc/Resources/code/fbs/monster_step_3.fbs b/flatbuffers/Documentation.docc/Resources/code/fbs/monster_step_3.fbs @@ -0,0 +1,6 @@ +enum Color:byte { red, green, blue } + +struct Vec3 { + x:float; + y:float; +} diff --git a/flatbuffers/Documentation.docc/Resources/code/fbs/monster_step_4.fbs b/flatbuffers/Documentation.docc/Resources/code/fbs/monster_step_4.fbs @@ -0,0 +1,12 @@ +enum Color:byte { red, green, blue } + +struct Vec3 { + x:float; + y:float; +} + +table Monster { + pos:Vec3; + color:Color = Blue; +} + diff --git a/flatbuffers/Documentation.docc/Resources/code/fbs/monster_step_5.fbs b/flatbuffers/Documentation.docc/Resources/code/fbs/monster_step_5.fbs @@ -0,0 +1,18 @@ +enum Color:byte { red, green, blue } + +struct Vec3 { + x:float; + y:float; +} + +table Monster { + pos:Vec3; + color:Color = Blue; + + mana:short = 150; + hp:short = 100; + name:string; + equipped:Equipment; + weapons:[Weapon]; + path:[Vec3]; +} diff --git a/flatbuffers/Documentation.docc/Resources/code/fbs/monster_step_6.fbs b/flatbuffers/Documentation.docc/Resources/code/fbs/monster_step_6.fbs @@ -0,0 +1,25 @@ +enum Color:byte { red, green, blue } + +union Equipment { Weapon } // Optionally add more tables. + +struct Vec3 { + x:float; + y:float; +} + +table Monster { + pos:Vec3; + color:Color = Blue; + + mana:short = 150; + hp:short = 100; + name:string; + equipped:Equipment; + weapons:[Weapon]; + path:[Vec3]; +} + +table Weapon { + name:string; + damage:short; +} diff --git a/flatbuffers/Documentation.docc/Resources/code/fbs/monster_step_7.fbs b/flatbuffers/Documentation.docc/Resources/code/fbs/monster_step_7.fbs @@ -0,0 +1,27 @@ +enum Color:byte { red, green, blue } + +union Equipment { Weapon } // Optionally add more tables. + +struct Vec3 { + x:float; + y:float; +} + +table Monster { + pos:Vec3; + color:Color = Blue; + + mana:short = 150; + hp:short = 100; + name:string; + equipped:Equipment; + weapons:[Weapon]; + path:[Vec3]; +} + +table Weapon { + name:string; + damage:short; +} + +root_type Monster; // flatc --swift monster.fbs diff --git a/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_1.swift b/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_1.swift @@ -0,0 +1 @@ +import Foundation diff --git a/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_10.swift b/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_10.swift @@ -0,0 +1,71 @@ +import FlatBuffers +import Foundation + +func run() { + // create a `FlatBufferBuilder`, which will be used to serialize objects + let builder = FlatBufferBuilder(initialSize: 1024) + + let weapon1Name = builder.create(string: "Sword") + let weapon2Name = builder.create(string: "Axe") + + // start creating the weapon by calling startWeapon + let weapon1Start = Weapon.startWeapon(&builder) + Weapon.add(name: weapon1Name, &builder) + Weapon.add(damage: 3, &builder) + // end the object by passing the start point for the weapon 1 + let sword = Weapon.endWeapon(&builder, start: weapon1Start) + + let weapon2Start = Weapon.startWeapon(&builder) + Weapon.add(name: weapon2Name, &builder) + Weapon.add(damage: 5, &builder) + let axe = Weapon.endWeapon(&builder, start: weapon2Start) + + // Create a FlatBuffer `vector` that contains offsets to the sword and axe + // we created above. + let weaponsOffset = builder.createVector(ofOffsets: [sword, axe]) + + // Name of the Monster. + let name = builder.create(string: "Orc") + + let pathOffset = fbb.createVector(ofStructs: [ + Vec3(x: 0, y: 0), + Vec3(x: 5, y: 5), + ]) + + // startVector(len, elementSize: MemoryLayout<Offset>.size) + // for o in offsets.reversed() { + // push(element: o) + // } + // endVector(len: len) + + let orc = Monster.createMonster( + &builder, + pos: Vec3(x: 1, y: 2), + hp: 300, + nameOffset: name, + color: .red, + weaponsVectorOffset: weaponsOffset, + equippedType: .weapon, + equippedOffset: axe, + pathOffset: pathOffset) + + // let start = Monster.startMonster(&builder) + // Monster.add(pos: Vec3(x: 1, y: 2), &builder) + // Monster.add(hp: 300, &builder) + // Monster.add(name: name, &builder) + // Monster.add(color: .red, &builder) + // Monster.addVectorOf(weapons: weaponsOffset, &builder) + // Monster.add(equippedType: .weapon, &builder) + // Monster.addVectorOf(paths: weaponsOffset, &builder) + // Monster.add(equipped: axe, &builder) + // var orc = Monster.endMonster(&builder, start: start) + + // Call `finish(offset:)` to instruct the builder that this monster is complete. + builder.finish(offset: orc) + // This must be called after `finish()`. + // `sizedByteArray` returns the finished buf of type [UInt8]. + let buf = builder.sizedByteArray + + // or you can use to get an object of type Data + let bufData = ByteBuffer(data: builder.sizedBuffer) +} diff --git a/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_11.swift b/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_11.swift @@ -0,0 +1,11 @@ +import FlatBuffers +import Foundation + +func run() { + // create a ByteBuffer(:) from an [UInt8] or Data() + let buf = [] // Get your data + var byteBuffer = ByteBuffer(bytes: buf) + // Get an accessor to the root object inside the buffer. + let monster: Monster = try! getCheckedRoot(byteBuffer: &byteBuffer) + // let monster: Monster = getRoot(byteBuffer: &byteBuffer) +} diff --git a/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_12.swift b/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_12.swift @@ -0,0 +1,19 @@ +import FlatBuffers +import Foundation + +func run() { + // create a ByteBuffer(:) from an [UInt8] or Data() + let buf = [] // Get your data + var byteBuffer = ByteBuffer(bytes: buf) + // Get an accessor to the root object inside the buffer. + let monster: Monster = try! getCheckedRoot(byteBuffer: &byteBuffer) + // let monster: Monster = getRoot(byteBuffer: &byteBuffer) + + let hp = monster.hp + let mana = monster.mana + let name = monster.name // returns an optional string + + let pos = monster.pos + let x = pos.x + let y = pos.y +} diff --git a/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_13.swift b/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_13.swift @@ -0,0 +1,26 @@ +import FlatBuffers +import Foundation + +func run() { + // create a ByteBuffer(:) from an [UInt8] or Data() + let buf = [] // Get your data + var byteBuffer = ByteBuffer(bytes: buf) + // Get an accessor to the root object inside the buffer. + let monster: Monster = try! getCheckedRoot(byteBuffer: &byteBuffer) + // let monster: Monster = getRoot(byteBuffer: &byteBuffer) + + let hp = monster.hp + let mana = monster.mana + let name = monster.name // returns an optional string + + let pos = monster.pos + let x = pos.x + let y = pos.y + + // Get and check if the monster has an equipped item + if monster.equippedType == .weapon { + let _weapon = monster.equipped(type: Weapon.self) + let name = _weapon.name // should return "Axe" + let dmg = _weapon.damage // should return 5 + } +} diff --git a/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_2.swift b/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_2.swift @@ -0,0 +1,2 @@ +import FlatBuffers +import Foundation diff --git a/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_3.swift b/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_3.swift @@ -0,0 +1,7 @@ +import FlatBuffers +import Foundation + +func run() { + // create a `FlatBufferBuilder`, which will be used to serialize objects + let builder = FlatBufferBuilder(initialSize: 1024) +} diff --git a/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_4.swift b/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_4.swift @@ -0,0 +1,10 @@ +import FlatBuffers +import Foundation + +func run() { + // create a `FlatBufferBuilder`, which will be used to serialize objects + let builder = FlatBufferBuilder(initialSize: 1024) + + let weapon1Name = builder.create(string: "Sword") + let weapon2Name = builder.create(string: "Axe") +} diff --git a/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_5.swift b/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_5.swift @@ -0,0 +1,22 @@ +import FlatBuffers +import Foundation + +func run() { + // create a `FlatBufferBuilder`, which will be used to serialize objects + let builder = FlatBufferBuilder(initialSize: 1024) + + let weapon1Name = builder.create(string: "Sword") + let weapon2Name = builder.create(string: "Axe") + + // start creating the weapon by calling startWeapon + let weapon1Start = Weapon.startWeapon(&builder) + Weapon.add(name: weapon1Name, &builder) + Weapon.add(damage: 3, &builder) + // end the object by passing the start point for the weapon 1 + let sword = Weapon.endWeapon(&builder, start: weapon1Start) + + let weapon2Start = Weapon.startWeapon(&builder) + Weapon.add(name: weapon2Name, &builder) + Weapon.add(damage: 5, &builder) + let axe = Weapon.endWeapon(&builder, start: weapon2Start) +} diff --git a/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_6.swift b/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_6.swift @@ -0,0 +1,26 @@ +import FlatBuffers +import Foundation + +func run() { + // create a `FlatBufferBuilder`, which will be used to serialize objects + let builder = FlatBufferBuilder(initialSize: 1024) + + let weapon1Name = builder.create(string: "Sword") + let weapon2Name = builder.create(string: "Axe") + + // start creating the weapon by calling startWeapon + let weapon1Start = Weapon.startWeapon(&builder) + Weapon.add(name: weapon1Name, &builder) + Weapon.add(damage: 3, &builder) + // end the object by passing the start point for the weapon 1 + let sword = Weapon.endWeapon(&builder, start: weapon1Start) + + let weapon2Start = Weapon.startWeapon(&builder) + Weapon.add(name: weapon2Name, &builder) + Weapon.add(damage: 5, &builder) + let axe = Weapon.endWeapon(&builder, start: weapon2Start) + + // Create a FlatBuffer `vector` that contains offsets to the sword and axe + // we created above. + let weaponsOffset = builder.createVector(ofOffsets: [sword, axe]) +} diff --git a/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_7.swift b/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_7.swift @@ -0,0 +1,29 @@ +import FlatBuffers +import Foundation + +func run() { + // create a `FlatBufferBuilder`, which will be used to serialize objects + let builder = FlatBufferBuilder(initialSize: 1024) + + let weapon1Name = builder.create(string: "Sword") + let weapon2Name = builder.create(string: "Axe") + + // start creating the weapon by calling startWeapon + let weapon1Start = Weapon.startWeapon(&builder) + Weapon.add(name: weapon1Name, &builder) + Weapon.add(damage: 3, &builder) + // end the object by passing the start point for the weapon 1 + let sword = Weapon.endWeapon(&builder, start: weapon1Start) + + let weapon2Start = Weapon.startWeapon(&builder) + Weapon.add(name: weapon2Name, &builder) + Weapon.add(damage: 5, &builder) + let axe = Weapon.endWeapon(&builder, start: weapon2Start) + + // Create a FlatBuffer `vector` that contains offsets to the sword and axe + // we created above. + let weaponsOffset = builder.createVector(ofOffsets: [sword, axe]) + + // Name of the Monster. + let name = builder.create(string: "Orc") +} diff --git a/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_8.swift b/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_8.swift @@ -0,0 +1,40 @@ +import FlatBuffers +import Foundation + +func run() { + // create a `FlatBufferBuilder`, which will be used to serialize objects + let builder = FlatBufferBuilder(initialSize: 1024) + + let weapon1Name = builder.create(string: "Sword") + let weapon2Name = builder.create(string: "Axe") + + // start creating the weapon by calling startWeapon + let weapon1Start = Weapon.startWeapon(&builder) + Weapon.add(name: weapon1Name, &builder) + Weapon.add(damage: 3, &builder) + // end the object by passing the start point for the weapon 1 + let sword = Weapon.endWeapon(&builder, start: weapon1Start) + + let weapon2Start = Weapon.startWeapon(&builder) + Weapon.add(name: weapon2Name, &builder) + Weapon.add(damage: 5, &builder) + let axe = Weapon.endWeapon(&builder, start: weapon2Start) + + // Create a FlatBuffer `vector` that contains offsets to the sword and axe + // we created above. + let weaponsOffset = builder.createVector(ofOffsets: [sword, axe]) + + // Name of the Monster. + let name = builder.create(string: "Orc") + + let pathOffset = fbb.createVector(ofStructs: [ + Vec3(x: 0, y: 0), + Vec3(x: 5, y: 5), + ]) + + // startVector(len, elementSize: MemoryLayout<Offset>.size) + // for o in offsets.reversed() { + // push(element: o) + // } + // endVector(len: len) +} diff --git a/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_9.swift b/flatbuffers/Documentation.docc/Resources/code/swift/swift_code_9.swift @@ -0,0 +1,62 @@ +import FlatBuffers +import Foundation + +func run() { + // create a `FlatBufferBuilder`, which will be used to serialize objects + let builder = FlatBufferBuilder(initialSize: 1024) + + let weapon1Name = builder.create(string: "Sword") + let weapon2Name = builder.create(string: "Axe") + + // start creating the weapon by calling startWeapon + let weapon1Start = Weapon.startWeapon(&builder) + Weapon.add(name: weapon1Name, &builder) + Weapon.add(damage: 3, &builder) + // end the object by passing the start point for the weapon 1 + let sword = Weapon.endWeapon(&builder, start: weapon1Start) + + let weapon2Start = Weapon.startWeapon(&builder) + Weapon.add(name: weapon2Name, &builder) + Weapon.add(damage: 5, &builder) + let axe = Weapon.endWeapon(&builder, start: weapon2Start) + + // Create a FlatBuffer `vector` that contains offsets to the sword and axe + // we created above. + let weaponsOffset = builder.createVector(ofOffsets: [sword, axe]) + + // Name of the Monster. + let name = builder.create(string: "Orc") + + let pathOffset = fbb.createVector(ofStructs: [ + Vec3(x: 0, y: 0), + Vec3(x: 5, y: 5), + ]) + + // startVector(len, elementSize: MemoryLayout<Offset>.size) + // for o in offsets.reversed() { + // push(element: o) + // } + // endVector(len: len) + + let orc = Monster.createMonster( + &builder, + pos: Vec3(x: 1, y: 2), + hp: 300, + nameOffset: name, + color: .red, + weaponsVectorOffset: weaponsOffset, + equippedType: .weapon, + equippedOffset: axe, + pathOffset: pathOffset) + + // let start = Monster.startMonster(&builder) + // Monster.add(pos: Vec3(x: 1, y: 2), &builder) + // Monster.add(hp: 300, &builder) + // Monster.add(name: name, &builder) + // Monster.add(color: .red, &builder) + // Monster.addVectorOf(weapons: weaponsOffset, &builder) + // Monster.add(equippedType: .weapon, &builder) + // Monster.addVectorOf(paths: weaponsOffset, &builder) + // Monster.add(equipped: axe, &builder) + // var orc = Monster.endMonster(&builder, start: start) +} diff --git a/flatbuffers/Documentation.docc/Resources/images/tutorial_cover_image_1.png b/flatbuffers/Documentation.docc/Resources/images/tutorial_cover_image_1.png Binary files differ. diff --git a/flatbuffers/Documentation.docc/Tutorials/Tutorial_Table_of_Contents.tutorial b/flatbuffers/Documentation.docc/Tutorials/Tutorial_Table_of_Contents.tutorial @@ -0,0 +1,14 @@ +@Tutorials(name: "Starting with FlatBuffers") { + @Intro(title: "Starting with FlatBuffers") { + FlatBuffers is an efficient cross platform serialization library for C++, + C#, C, Go, Java, Kotlin, JavaScript, Lobster, Lua, TypeScript, PHP, Python, Rust and Swift. + It was originally created at Google for game development and other performance-critical applications. + } + @Chapter(name: "Generating your code") { + Start by generating your first FlatBuffers objects. + @Image(source: tutorial_cover_image_1.png, alt: "A code structure for a base struct in flatbuffers") + @TutorialReference(tutorial: "doc:creating_flatbuffer_schema") + @TutorialReference(tutorial: "doc:create_your_first_buffer") + @TutorialReference(tutorial: "doc:reading_bytebuffer") + } +} diff --git a/flatbuffers/Documentation.docc/Tutorials/create_your_first_buffer.tutorial b/flatbuffers/Documentation.docc/Tutorials/create_your_first_buffer.tutorial @@ -0,0 +1,72 @@ +@Tutorial(time: 5) { + @Intro(title: "After having our code generated") { + After generating the code from the previous section, we will know start creating our monster object. + We will create a monster object called orc. + } + + @Section(title: "Building your first buffer") { + @ContentAndMedia {} + @Steps { + @Step { + Starting with a new file, we will create our very first Flatbuffer. + @Code(name: "ViewController.swift", file: "swift_code_1.swift") + } + @Step { + First, we need to import ``FlatBuffers`` + @Code(name: "ViewController.swift", file: "swift_code_2.swift") + } + @Step { + We need to create an instance of the `FlatBufferBuilder`, which will contain the buffer as it grows. + You can pass an initial size of the buffer (here 1024 bytes), which will grow automatically if needed. + @Code(name: "ViewController.swift", file: "swift_code_3.swift") + } + @Step { + After creating the builder, we can start serializing our data. Before we make our orc Monster, + let's create some Weapons: a Sword and an Axe. However we will start by naming our weapons as `Sword` and `Axe` + @Code(name: "ViewController.swift", file: "swift_code_4.swift") + } + @Step { + After naming the weapons, we will create two weapon objects with the damage that the weapon is going to deal. + That's done by calling the `start` Method on each table you will be creating, in this case its called `startWeapon` + and finished by calling `end`. + @Code(name: "ViewController.swift", file: "swift_code_5.swift") + } + @Step { + We will take our (Sword and Axe) serialized data and serialize their offsets as a vector of tables into our `ByteBuffer`. + So we can reference them later on from our Monster Object + @Code(name: "ViewController.swift", file: "swift_code_6.swift") + } + @Step { + We will add our Monster name as a string value just like we did with the weapons. + @Code(name: "ViewController.swift", file: "swift_code_7.swift") + } + + @Step { + We will create a path that our monster should be using while roaming in its den. To create a vector of paths we would us + `createVector(ofStructs: [])` which will take a Native `Swift` struct that has been padded to fit the `FlatBuffers` standards. + + There are usually two ways of creating vectors in `FlatBuffers` which you can see in commented out code. + And thus there are multiple convenience methods that will cover all the bases + when trying to create a vector so that you dont have to create it with `start` and `end` + @Code(name: "ViewController.swift", file: "swift_code_8.swift") + } + + @Step { + Now to serialize our data into our `Monster` object. Which again there are two ways of doing, by calling the `create` method or + by serializing the objects yourself. What we added to our Monster were the `Equipped Type` and the `Equipped` union itself, which + allows the Monster to have the `Axe` as his equipped weapon. + + Important: Unlike structs, you should not nest tables or other objects, + which is why we created all the `strings/vectors/tables` that this monster refers to before start. + If you try to create any of them between start and end, you will get an `assert`. + @Code(name: "ViewController.swift", file: "swift_code_9.swift") + } + + @Step { + Finally you can just finalize the buffer by calling `builder.finish` and get the Byte array from the buffer. + @Code(name: "ViewController.swift", file: "swift_code_10.swift") + } + + } + } + } diff --git a/flatbuffers/Documentation.docc/Tutorials/creating_flatbuffer_schema.tutorial b/flatbuffers/Documentation.docc/Tutorials/creating_flatbuffer_schema.tutorial @@ -0,0 +1,47 @@ +@Tutorial(time: 2) { + @Intro(title: "Creating a schema") { + You will need to have the FlatBuffer compiler to be installed on your device + } + + @Section(title: "Creating a schema") { + @ContentAndMedia {} + @Steps { + @Step { + Start by creating a new empty folder called `monster.fbs`. We want to create a Monster table, that contains + position, color, and basic information about the monster. + @Code(name: "monster.fbs", file: "monster_step_1.fbs") + } + @Step { + We will start by adding our Color object. We will be using an enumerate, to represent this object + @Code(name: "monster.fbs", file: "monster_step_2.fbs") + } + @Step { + We will add a position object and will use a struct to represent that type of data. Where we will need the monsters + x and y positions. + @Code(name: "monster.fbs", file: "monster_step_3.fbs") + } + @Step { + Then we will be creating our Monster object of type table. This will contain the current position of our + monster and its color + @Code(name: "monster.fbs", file: "monster_step_4.fbs") + } + @Step { + Our Monster is missing a name, mana, hp, name, equipped Weapon, weapons, and path. We will be adding these + fields to our table with a proper data type for each. Example; weapons, and path would be a vector of data. + @Code(name: "monster.fbs", file: "monster_step_5.fbs") + } + @Step { + Now we are missing two data types here, `Weapon` and `Equipment`. And since Equipment can be a weapon, we will be using + a `Union` enumerate that can contain all the equipment that you would want your monster to have. And the weapon can simply + have a name and amount of damage + @Code(name: "monster.fbs", file: "monster_step_6.fbs") + } + @Step { + And to finalize our monster table, we can add a root type of type Monster. + Then run the command `flatc --swift monster.fbs` + Note: Make sure to import the file to your xcode project. + @Code(name: "monster.fbs", file: "monster_step_7.fbs") + } + } + } + } diff --git a/flatbuffers/Documentation.docc/Tutorials/reading_bytebuffer.tutorial b/flatbuffers/Documentation.docc/Tutorials/reading_bytebuffer.tutorial @@ -0,0 +1,27 @@ +@Tutorial(time: 2) { + @Intro(title: "Reading ByteBuffers") { + After getting our ByteBuffer created, we can now read it. + } + + @Section(title: "Reading your first buffer") { + @ContentAndMedia {} + @Steps { + @Step { + After fetching the data from disk or network you need to access that data, and that can be done. + By simply calling `getCheckedRoot`, which checks if the data is valid before enabling you to read from a corrupt buffer. + however, if you are sure that the data is 100% correct you can simply call `getRoot` + @Code(name: "ViewController.swift", file: "swift_code_11.swift") + } + @Step { + Now since we have a Monster object, all the fields can be accessed by simply fetching the data. Note, Deprecated fields will not + show up + @Code(name: "ViewController.swift", file: "swift_code_12.swift") + } + @Step { + And you can access union types as easy as this + @Code(name: "ViewController.swift", file: "swift_code_13.swift") + } + } + } + } + diff --git a/flatbuffers/Enum.swift b/flatbuffers/Enum.swift @@ -0,0 +1,59 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +import Foundation +#else +import SwiftOverlayShims +#endif + +/// Enum is a protocol that all flatbuffers enums should conform to +/// Since it allows us to get the actual `ByteSize` and `Value` from +/// a swift enum. +public protocol Enum { + /// associatedtype that the type of the enum should conform to + associatedtype T: Scalar & Verifiable + /// Size of the current associatedtype in the enum + static var byteSize: Int { get } + /// The current value the enum hosts + var value: T { get } +} + +extension Enum where Self: Verifiable { + + /// Verifies that the current value is which the bounds of the buffer, and if + /// the current `Value` is aligned properly + /// - Parameters: + /// - verifier: Verifier that hosts the buffer + /// - position: Current position within the buffer + /// - type: The type of the object to be verified + /// - Throws: Errors coming from `inBuffer` function + public static func verify<T>( + _ verifier: inout Verifier, + at position: Int, + of type: T.Type) throws where T: Verifiable + { + try verifier.inBuffer(position: position, of: type.self) + } + +} + +/// UnionEnum is a Protocol that allows us to create Union type of enums +/// and their value initializers. Since an `init` was required by +/// the verifier +public protocol UnionEnum: Enum { + init?(value: T) throws +} diff --git a/flatbuffers/FbConstants.swift b/flatbuffers/FbConstants.swift @@ -0,0 +1,122 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +#if os(Linux) +import CoreFoundation +#else +import Foundation +#endif +#else +import SwiftOverlayShims +#endif + +/// A boolean to see if the system is littleEndian +let isLitteEndian: Bool = { + let number: UInt32 = 0x12345678 + return number == number.littleEndian +}() +/// Constant for the file id length +let FileIdLength = 4 +/// Type aliases +public typealias Byte = UInt8 +public typealias UOffset = UInt32 +public typealias SOffset = Int32 +public typealias VOffset = UInt16 +/// Maximum size for a buffer +public let FlatBufferMaxSize = UInt32 + .max << ((MemoryLayout<SOffset>.size * 8 - 1) - 1) + +/// Protocol that All Scalars should conform to +/// +/// Scalar is used to conform all the numbers that can be represented in a FlatBuffer. It's used to write/read from the buffer. +public protocol Scalar: Equatable { + associatedtype NumericValue + var convertedEndian: NumericValue { get } +} + +extension Scalar where Self: Verifiable {} + +extension Scalar where Self: FixedWidthInteger { + /// Converts the value from BigEndian to LittleEndian + /// + /// Converts values to little endian on machines that work with BigEndian, however this is NOT TESTED yet. + public var convertedEndian: NumericValue { + self as! Self.NumericValue + } +} + +extension Double: Scalar, Verifiable { + public typealias NumericValue = UInt64 + + public var convertedEndian: UInt64 { + bitPattern.littleEndian + } +} + +extension Float32: Scalar, Verifiable { + public typealias NumericValue = UInt32 + + public var convertedEndian: UInt32 { + bitPattern.littleEndian + } +} + +extension Bool: Scalar, Verifiable { + public var convertedEndian: UInt8 { + self == true ? 1 : 0 + } + + public typealias NumericValue = UInt8 +} + +extension Int: Scalar, Verifiable { + public typealias NumericValue = Int +} + +extension Int8: Scalar, Verifiable { + public typealias NumericValue = Int8 +} + +extension Int16: Scalar, Verifiable { + public typealias NumericValue = Int16 +} + +extension Int32: Scalar, Verifiable { + public typealias NumericValue = Int32 +} + +extension Int64: Scalar, Verifiable { + public typealias NumericValue = Int64 +} + +extension UInt8: Scalar, Verifiable { + public typealias NumericValue = UInt8 +} + +extension UInt16: Scalar, Verifiable { + public typealias NumericValue = UInt16 +} + +extension UInt32: Scalar, Verifiable { + public typealias NumericValue = UInt32 +} + +extension UInt64: Scalar, Verifiable { + public typealias NumericValue = UInt64 +} + +public func FlatBuffersVersion_23_5_26() {} diff --git a/flatbuffers/FlatBufferBuilder.swift b/flatbuffers/FlatBufferBuilder.swift @@ -0,0 +1,920 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +import Foundation +#else +import SwiftOverlayShims +#endif + +/// ``FlatBufferBuilder`` builds a `FlatBuffer` through manipulating its internal state. +/// +/// This is done by creating a ``ByteBuffer`` that hosts the incoming data and +/// has a hardcoded growth limit of `2GiB` which is set by the Flatbuffers standards. +/// +/// ```swift +/// var builder = FlatBufferBuilder() +/// ``` +/// The builder should be always created as a variable, since it would be passed into the writers +/// +@frozen +public struct FlatBufferBuilder { + + /// Storage for the Vtables used in the buffer are stored in here, so they would be written later in EndTable + @usableFromInline internal var _vtableStorage = VTableStorage() + /// Flatbuffer data will be written into + @usableFromInline internal var _bb: ByteBuffer + + /// Reference Vtables that were already written to the buffer + private var _vtables: [UOffset] = [] + /// A check if the buffer is being written into by a different table + private var isNested = false + /// Dictonary that stores a map of all the strings that were written to the buffer + private var stringOffsetMap: [String: Offset] = [:] + /// A check to see if finish(::) was ever called to retreive data object + private var finished = false + /// A check to see if the buffer should serialize Default values + private var serializeDefaults: Bool + + /// Current alignment for the buffer + var _minAlignment: Int = 0 { + didSet { + _bb.alignment = _minAlignment + } + } + + /// Gives a read access to the buffer's size + public var size: UOffset { _bb.size } + + #if !os(WASI) + /// Data representation of the buffer + /// + /// Should only be used after ``finish(offset:addPrefix:)`` is called + public var data: Data { + assert(finished, "Data shouldn't be called before finish()") + return Data( + bytes: _bb.memory.advanced(by: _bb.writerIndex), + count: _bb.capacity &- _bb.writerIndex) + } + #endif + + /// Returns the underlying bytes in the ``ByteBuffer`` + /// + /// Note: This should be used with caution. + public var fullSizedByteArray: [UInt8] { + let ptr = UnsafeBufferPointer( + start: _bb.memory.assumingMemoryBound(to: UInt8.self), + count: _bb.capacity) + return Array(ptr) + } + + /// Returns the written bytes into the ``ByteBuffer`` + /// + /// Should only be used after ``finish(offset:addPrefix:)`` is called + public var sizedByteArray: [UInt8] { + assert(finished, "Data shouldn't be called before finish()") + return _bb.underlyingBytes + } + + /// Returns the original ``ByteBuffer`` + /// + /// Returns the current buffer that was just created + /// with the offsets, and data written to it. + public var buffer: ByteBuffer { _bb } + + /// Returns a newly created sized ``ByteBuffer`` + /// + /// returns a new buffer that is sized to the data written + /// to the main buffer + public var sizedBuffer: ByteBuffer { + assert(finished, "Data shouldn't be called before finish()") + return ByteBuffer( + memory: _bb.memory.advanced(by: _bb.reader), + count: Int(_bb.size)) + } + + // MARK: - Init + + /// Initialize the buffer with a size + /// - Parameters: + /// - initialSize: Initial size for the buffer + /// - force: Allows default to be serialized into the buffer + /// + /// This initializes a new builder with an initialSize that would initialize + /// a new ``ByteBuffer``. ``FlatBufferBuilder`` by default doesnt serialize defaults + /// however the builder can be force by passing true for `serializeDefaults` + public init( + initialSize: Int32 = 1024, + serializeDefaults force: Bool = false) + { + assert(initialSize > 0, "Size should be greater than zero!") + guard isLitteEndian else { + fatalError( + "Reading/Writing a buffer in big endian machine is not supported on swift") + } + serializeDefaults = force + _bb = ByteBuffer(initialSize: Int(initialSize)) + } + + /// Clears the builder and the buffer from the written data. + mutating public func clear() { + _minAlignment = 0 + isNested = false + stringOffsetMap.removeAll(keepingCapacity: true) + _vtables.removeAll(keepingCapacity: true) + _vtableStorage.clear() + _bb.clear() + } + + // MARK: - Create Tables + + /// Checks if the required fields were serialized into the buffer + /// - Parameters: + /// - table: offset for the table + /// - fields: Array of all the important fields to be serialized + /// + /// *NOTE: Never call this function, this is only supposed to be called + /// by the generated code* + @inline(__always) + mutating public func require(table: Offset, fields: [Int32]) { + for field in fields { + let start = _bb.capacity &- Int(table.o) + let startTable = start &- Int(_bb.read(def: Int32.self, position: start)) + let isOkay = _bb.read( + def: VOffset.self, + position: startTable &+ Int(field)) != 0 + assert(isOkay, "Flatbuffers requires the following field") + } + } + + /// Finished the buffer by adding the file id and then calling finish + /// - Parameters: + /// - offset: Offset of the table + /// - fileId: Takes the fileId + /// - prefix: if false it wont add the size of the buffer + /// + /// ``finish(offset:fileId:addPrefix:)`` should be called at the end of creating + /// a table + /// ```swift + /// var root = SomeObject + /// .createObject(&builder, + /// name: nameOffset) + /// builder.finish( + /// offset: root, + /// fileId: "ax1a", + /// addPrefix: true) + /// ``` + /// File id would append a file id name at the end of the written bytes before, + /// finishing the buffer. + /// + /// Whereas, if `addPrefix` is true, the written bytes would + /// include the size of the current buffer. + mutating public func finish( + offset: Offset, + fileId: String, + addPrefix prefix: Bool = false) + { + let size = MemoryLayout<UOffset>.size + preAlign( + len: size &+ (prefix ? size : 0) &+ FileIdLength, + alignment: _minAlignment) + assert(fileId.count == FileIdLength, "Flatbuffers requires file id to be 4") + _bb.push(string: fileId, len: 4) + finish(offset: offset, addPrefix: prefix) + } + + /// Finished the buffer by adding the file id, offset, and prefix to it. + /// - Parameters: + /// - offset: Offset of the table + /// - prefix: if false it wont add the size of the buffer + /// + /// ``finish(offset:addPrefix:)`` should be called at the end of creating + /// a table + /// ```swift + /// var root = SomeObject + /// .createObject(&builder, + /// name: nameOffset) + /// builder.finish( + /// offset: root, + /// addPrefix: true) + /// ``` + /// If `addPrefix` is true, the written bytes would + /// include the size of the current buffer. + mutating public func finish( + offset: Offset, + addPrefix prefix: Bool = false) + { + notNested() + let size = MemoryLayout<UOffset>.size + preAlign(len: size &+ (prefix ? size : 0), alignment: _minAlignment) + push(element: refer(to: offset.o)) + if prefix { push(element: _bb.size) } + _vtableStorage.clear() + finished = true + } + + /// ``startTable(with:)`` will let the builder know, that a new object is being serialized. + /// + /// The function will fatalerror if called while there is another object being serialized. + /// ```swift + /// let start = Monster + /// .startMonster(&fbb) + /// ``` + /// - Parameter numOfFields: Number of elements to be written to the buffer + /// - Returns: Offset of the newly started table + @inline(__always) + mutating public func startTable(with numOfFields: Int) -> UOffset { + notNested() + isNested = true + _vtableStorage.start(count: numOfFields) + return _bb.size + } + + /// ``endTable(at:)`` will let the ``FlatBufferBuilder`` know that the + /// object that's written to it is completed + /// + /// This would be called after all the elements are serialized, + /// it will add the current vtable into the ``ByteBuffer``. + /// The functions will `fatalError` in case the object is called + /// without ``startTable(with:)``, or the object has exceeded the limit of 2GB. + /// + /// - Parameter startOffset:Start point of the object written + /// - returns: The root of the table + mutating public func endTable(at startOffset: UOffset) -> UOffset { + assert(isNested, "Calling endtable without calling starttable") + let sizeofVoffset = MemoryLayout<VOffset>.size + let vTableOffset = push(element: SOffset(0)) + + let tableObjectSize = vTableOffset &- startOffset + assert(tableObjectSize < 0x10000, "Buffer can't grow beyond 2 Gigabytes") + let _max = Int(_vtableStorage.maxOffset) &+ sizeofVoffset + + _bb.fill(padding: _max) + _bb.write( + value: VOffset(tableObjectSize), + index: _bb.writerIndex &+ sizeofVoffset, + direct: true) + _bb.write(value: VOffset(_max), index: _bb.writerIndex, direct: true) + + var itr = 0 + while itr < _vtableStorage.writtenIndex { + let loaded = _vtableStorage.load(at: itr) + itr = itr &+ _vtableStorage.size + guard loaded.offset != 0 else { continue } + let _index = (_bb.writerIndex &+ Int(loaded.position)) + _bb.write( + value: VOffset(vTableOffset &- loaded.offset), + index: _index, + direct: true) + } + + _vtableStorage.clear() + let vt_use = _bb.size + + var isAlreadyAdded: Int? + + let vt2 = _bb.memory.advanced(by: _bb.writerIndex) + let len2 = vt2.load(fromByteOffset: 0, as: Int16.self) + + for table in _vtables { + let position = _bb.capacity &- Int(table) + let vt1 = _bb.memory.advanced(by: position) + let len1 = _bb.read(def: Int16.self, position: position) + if len2 != len1 || 0 != memcmp(vt1, vt2, Int(len2)) { continue } + + isAlreadyAdded = Int(table) + break + } + + if let offset = isAlreadyAdded { + let vTableOff = Int(vTableOffset) + let space = _bb.capacity &- vTableOff + _bb.write(value: Int32(offset &- vTableOff), index: space, direct: true) + _bb.pop(_bb.capacity &- space) + } else { + _bb.write(value: Int32(vt_use &- vTableOffset), index: Int(vTableOffset)) + _vtables.append(_bb.size) + } + isNested = false + return vTableOffset + } + + // MARK: - Builds Buffer + + /// Asserts to see if the object is not nested + @inline(__always) + @usableFromInline + mutating internal func notNested() { + assert(!isNested, "Object serialization must not be nested") + } + + /// Changes the minimuim alignment of the buffer + /// - Parameter size: size of the current alignment + @inline(__always) + @usableFromInline + mutating internal func minAlignment(size: Int) { + if size > _minAlignment { + _minAlignment = size + } + } + + /// Gets the padding for the current element + /// - Parameters: + /// - bufSize: Current size of the buffer + the offset of the object to be written + /// - elementSize: Element size + @inline(__always) + @usableFromInline + mutating internal func padding( + bufSize: UInt32, + elementSize: UInt32) -> UInt32 + { + ((~bufSize) &+ 1) & (elementSize - 1) + } + + /// Prealigns the buffer before writting a new object into the buffer + /// - Parameters: + /// - len:Length of the object + /// - alignment: Alignment type + @inline(__always) + @usableFromInline + mutating internal func preAlign(len: Int, alignment: Int) { + minAlignment(size: alignment) + _bb.fill(padding: Int(padding( + bufSize: _bb.size &+ UOffset(len), + elementSize: UOffset(alignment)))) + } + + /// Prealigns the buffer before writting a new object into the buffer + /// - Parameters: + /// - len: Length of the object + /// - type: Type of the object to be written + @inline(__always) + @usableFromInline + mutating internal func preAlign<T: Scalar>(len: Int, type: T.Type) { + preAlign(len: len, alignment: MemoryLayout<T>.size) + } + + /// Refers to an object that's written in the buffer + /// - Parameter off: the objects index value + @inline(__always) + @usableFromInline + mutating internal func refer(to off: UOffset) -> UOffset { + let size = MemoryLayout<UOffset>.size + preAlign(len: size, alignment: size) + return _bb.size &- off &+ UInt32(size) + } + + /// Tracks the elements written into the buffer + /// - Parameters: + /// - offset: The offset of the element witten + /// - position: The position of the element + @inline(__always) + @usableFromInline + mutating internal func track(offset: UOffset, at position: VOffset) { + _vtableStorage.add(loc: FieldLoc(offset: offset, position: position)) + } + + // MARK: - Inserting Vectors + + /// ``startVector(_:elementSize:)`` creates a new vector within buffer + /// + /// The function checks if there is a current object being written, if + /// the check passes it creates a buffer alignment of `length * elementSize` + /// ```swift + /// builder.startVector( + /// int32Values.count, elementSize: 4) + /// ``` + /// + /// - Parameters: + /// - len: Length of vector to be created + /// - elementSize: Size of object type to be written + @inline(__always) + mutating public func startVector(_ len: Int, elementSize: Int) { + notNested() + isNested = true + preAlign(len: len &* elementSize, type: UOffset.self) + preAlign(len: len &* elementSize, alignment: elementSize) + } + + /// ``endVector(len:)`` ends the currently created vector + /// + /// Calling ``endVector(len:)`` requires the length, of the current + /// vector. The length would be pushed to indicate the count of numbers + /// within the vector. If ``endVector(len:)`` is called without + /// ``startVector(_:elementSize:)`` it asserts. + /// + /// ```swift + /// let vectorOffset = builder. + /// endVector(len: int32Values.count) + /// ``` + /// + /// - Parameter len: Length of the buffer + /// - Returns: Returns the current ``Offset`` in the ``ByteBuffer`` + @inline(__always) + mutating public func endVector(len: Int) -> Offset { + assert(isNested, "Calling endVector without calling startVector") + isNested = false + return Offset(offset: push(element: Int32(len))) + } + + /// Creates a vector of type ``Scalar`` into the ``ByteBuffer`` + /// + /// ``createVector(_:)-4swl0`` writes a vector of type Scalars into + /// ``ByteBuffer``. This is a convenient method instead of calling, + /// ``startVector(_:elementSize:)`` and then ``endVector(len:)`` + /// ```swift + /// let vectorOffset = builder. + /// createVector([1, 2, 3, 4]) + /// ``` + /// + /// The underlying implementation simply calls ``createVector(_:size:)-4lhrv`` + /// + /// - Parameter elements: elements to be written into the buffer + /// - returns: ``Offset`` of the vector + @inline(__always) + mutating public func createVector<T: Scalar>(_ elements: [T]) -> Offset { + createVector(elements, size: elements.count) + } + + /// Creates a vector of type Scalar in the buffer + /// + /// ``createVector(_:)-4swl0`` writes a vector of type Scalars into + /// ``ByteBuffer``. This is a convenient method instead of calling, + /// ``startVector(_:elementSize:)`` and then ``endVector(len:)`` + /// ```swift + /// let vectorOffset = builder. + /// createVector([1, 2, 3, 4], size: 4) + /// ``` + /// + /// - Parameter elements: Elements to be written into the buffer + /// - Parameter size: Count of elements + /// - returns: ``Offset`` of the vector + @inline(__always) + mutating public func createVector<T: Scalar>( + _ elements: [T], + size: Int) -> Offset + { + let size = size + startVector(size, elementSize: MemoryLayout<T>.size) + _bb.push(elements: elements) + return endVector(len: size) + } + + /// Creates a vector of type ``Enum`` into the ``ByteBuffer`` + /// + /// ``createVector(_:)-9h189`` writes a vector of type ``Enum`` into + /// ``ByteBuffer``. This is a convenient method instead of calling, + /// ``startVector(_:elementSize:)`` and then ``endVector(len:)`` + /// ```swift + /// let vectorOffset = builder. + /// createVector([.swift, .cpp]) + /// ``` + /// + /// The underlying implementation simply calls ``createVector(_:size:)-7cx6z`` + /// + /// - Parameter elements: elements to be written into the buffer + /// - returns: ``Offset`` of the vector + @inline(__always) + mutating public func createVector<T: Enum>(_ elements: [T]) -> Offset { + createVector(elements, size: elements.count) + } + + /// Creates a vector of type ``Enum`` into the ``ByteBuffer`` + /// + /// ``createVector(_:)-9h189`` writes a vector of type ``Enum`` into + /// ``ByteBuffer``. This is a convenient method instead of calling, + /// ``startVector(_:elementSize:)`` and then ``endVector(len:)`` + /// ```swift + /// let vectorOffset = builder. + /// createVector([.swift, .cpp]) + /// ``` + /// + /// - Parameter elements: Elements to be written into the buffer + /// - Parameter size: Count of elements + /// - returns: ``Offset`` of the vector + @inline(__always) + mutating public func createVector<T: Enum>( + _ elements: [T], + size: Int) -> Offset + { + let size = size + startVector(size, elementSize: T.byteSize) + for e in elements.reversed() { + _bb.push(value: e.value, len: T.byteSize) + } + return endVector(len: size) + } + + /// Creates a vector of already written offsets + /// + /// ``createVector(ofOffsets:)`` creates a vector of ``Offset`` into + /// ``ByteBuffer``. This is a convenient method instead of calling, + /// ``startVector(_:elementSize:)`` and then ``endVector(len:)``. + /// + /// The underlying implementation simply calls ``createVector(ofOffsets:len:)`` + /// + /// ```swift + /// let namesOffsets = builder. + /// createVector(ofOffsets: [name1, name2]) + /// ``` + /// - Parameter offsets: Array of offsets of type ``Offset`` + /// - returns: ``Offset`` of the vector + @inline(__always) + mutating public func createVector(ofOffsets offsets: [Offset]) -> Offset { + createVector(ofOffsets: offsets, len: offsets.count) + } + + /// Creates a vector of already written offsets + /// + /// ``createVector(ofOffsets:)`` creates a vector of ``Offset`` into + /// ``ByteBuffer``. This is a convenient method instead of calling, + /// ``startVector(_:elementSize:)`` and then ``endVector(len:)`` + /// + /// ```swift + /// let namesOffsets = builder. + /// createVector(ofOffsets: [name1, name2]) + /// ``` + /// + /// - Parameter offsets: Array of offsets of type ``Offset`` + /// - Parameter size: Count of elements + /// - returns: ``Offset`` of the vector + @inline(__always) + mutating public func createVector( + ofOffsets offsets: [Offset], + len: Int) -> Offset + { + startVector(len, elementSize: MemoryLayout<Offset>.size) + for o in offsets.reversed() { + push(element: o) + } + return endVector(len: len) + } + + /// Creates a vector of strings + /// + /// ``createVector(ofStrings:)`` creates a vector of `String` into + /// ``ByteBuffer``. This is a convenient method instead of manually + /// creating the string offsets, you simply pass it to this function + /// and it would write the strings into the ``ByteBuffer``. + /// After that it calls ``createVector(ofOffsets:)`` + /// + /// ```swift + /// let namesOffsets = builder. + /// createVector(ofStrings: ["Name", "surname"]) + /// ``` + /// + /// - Parameter str: Array of string + /// - returns: ``Offset`` of the vector + @inline(__always) + mutating public func createVector(ofStrings str: [String]) -> Offset { + var offsets: [Offset] = [] + for s in str { + offsets.append(create(string: s)) + } + return createVector(ofOffsets: offsets) + } + + /// Creates a vector of type ``NativeStruct``. + /// + /// Any swift struct in the generated code, should confirm to + /// ``NativeStruct``. Since the generated swift structs are padded + /// to the `FlatBuffers` standards. + /// + /// ```swift + /// let offsets = builder. + /// createVector(ofStructs: [NativeStr(num: 1), NativeStr(num: 2)]) + /// ``` + /// + /// - Parameter structs: A vector of ``NativeStruct`` + /// - Returns: ``Offset`` of the vector + @inline(__always) + mutating public func createVector<T: NativeStruct>(ofStructs structs: [T]) + -> Offset + { + startVector( + structs.count * MemoryLayout<T>.size, + elementSize: MemoryLayout<T>.alignment) + for i in structs.reversed() { + _ = create(struct: i) + } + return endVector(len: structs.count) + } + + // MARK: - Inserting Structs + + /// Writes a ``NativeStruct`` into the ``ByteBuffer`` + /// + /// Adds a native struct that's build and padded according + /// to `FlatBuffers` standards. with a predefined position. + /// + /// ```swift + /// let offset = builder.create( + /// struct: NativeStr(num: 1), + /// position: 10) + /// ``` + /// + /// - Parameters: + /// - s: ``NativeStruct`` to be inserted into the ``ByteBuffer`` + /// - position: The predefined position of the object + /// - Returns: ``Offset`` of written struct + @inline(__always) + @discardableResult + mutating public func create<T: NativeStruct>( + struct s: T, position: VOffset) -> Offset + { + let offset = create(struct: s) + _vtableStorage.add(loc: FieldLoc( + offset: _bb.size, + position: VOffset(position))) + return offset + } + + /// Writes a ``NativeStruct`` into the ``ByteBuffer`` + /// + /// Adds a native struct that's build and padded according + /// to `FlatBuffers` standards, directly into the buffer without + /// a predefined position. + /// + /// ```swift + /// let offset = builder.create( + /// struct: NativeStr(num: 1)) + /// ``` + /// + /// - Parameters: + /// - s: ``NativeStruct`` to be inserted into the ``ByteBuffer`` + /// - Returns: ``Offset`` of written struct + @inline(__always) + @discardableResult + mutating public func create<T: NativeStruct>( + struct s: T) -> Offset + { + let size = MemoryLayout<T>.size + preAlign(len: size, alignment: MemoryLayout<T>.alignment) + _bb.push(struct: s, size: size) + return Offset(offset: _bb.size) + } + + // MARK: - Inserting Strings + + /// Insets a string into the buffer of type `UTF8` + /// + /// Adds a swift string into ``ByteBuffer`` by encoding it + /// using `UTF8` + /// + /// ```swift + /// let nameOffset = builder + /// .create(string: "welcome") + /// ``` + /// + /// - Parameter str: String to be serialized + /// - returns: ``Offset`` of inserted string + @inline(__always) + mutating public func create(string str: String?) -> Offset { + guard let str = str else { return Offset() } + let len = str.utf8.count + notNested() + preAlign(len: len &+ 1, type: UOffset.self) + _bb.fill(padding: 1) + _bb.push(string: str, len: len) + push(element: UOffset(len)) + return Offset(offset: _bb.size) + } + + /// Insets a shared string into the buffer of type `UTF8` + /// + /// Adds a swift string into ``ByteBuffer`` by encoding it + /// using `UTF8`. The function will check if the string, + /// is already written to the ``ByteBuffer`` + /// + /// ```swift + /// let nameOffset = builder + /// .createShared(string: "welcome") + /// + /// + /// let secondOffset = builder + /// .createShared(string: "welcome") + /// + /// assert(nameOffset.o == secondOffset.o) + /// ``` + /// + /// - Parameter str: String to be serialized + /// - returns: ``Offset`` of inserted string + @inline(__always) + mutating public func createShared(string str: String?) -> Offset { + guard let str = str else { return Offset() } + if let offset = stringOffsetMap[str] { + return offset + } + let offset = create(string: str) + stringOffsetMap[str] = offset + return offset + } + + // MARK: - Inseting offsets + + /// Writes the ``Offset`` of an already written table + /// + /// Writes the ``Offset`` of a table if not empty into the + /// ``ByteBuffer`` + /// + /// - Parameters: + /// - offset: ``Offset`` of another object to be written + /// - position: The predefined position of the object + @inline(__always) + mutating public func add(offset: Offset, at position: VOffset) { + if offset.isEmpty { return } + add(element: refer(to: offset.o), def: 0, at: position) + } + + /// Pushes a value of type ``Offset`` into the ``ByteBuffer`` + /// - Parameter o: ``Offset`` + /// - returns: Current position of the ``Offset`` + @inline(__always) + @discardableResult + mutating public func push(element o: Offset) -> UOffset { + push(element: refer(to: o.o)) + } + + // MARK: - Inserting Scalars to Buffer + + /// Writes a ``Scalar`` value into ``ByteBuffer`` + /// + /// ``add(element:def:at:)`` takes in a default value, and current value + /// and the position within the `VTable`. The default value would not + /// be serialized if the value is the same as the current value or + /// `serializeDefaults` is equal to false. + /// + /// If serializing defaults is important ``init(initialSize:serializeDefaults:)``, + /// passing true for `serializeDefaults` would do the job. + /// + /// ```swift + /// // Adds 10 to the buffer + /// builder.add(element: Int(10), def: 1, position 12) + /// ``` + /// + /// *NOTE: Never call this manually* + /// + /// - Parameters: + /// - element: Element to insert + /// - def: Default value for that element + /// - position: The predefined position of the element + @inline(__always) + mutating public func add<T: Scalar>( + element: T, + def: T, + at position: VOffset) + { + if element == def && !serializeDefaults { return } + track(offset: push(element: element), at: position) + } + + /// Writes a optional ``Scalar`` value into ``ByteBuffer`` + /// + /// Takes an optional value to be written into the ``ByteBuffer`` + /// + /// *NOTE: Never call this manually* + /// + /// - Parameters: + /// - element: Optional element of type scalar + /// - position: The predefined position of the element + @inline(__always) + mutating public func add<T: Scalar>(element: T?, at position: VOffset) { + guard let element = element else { return } + track(offset: push(element: element), at: position) + } + + /// Pushes a values of type ``Scalar`` into the ``ByteBuffer`` + /// + /// *NOTE: Never call this manually* + /// + /// - Parameter element: Element to insert + /// - returns: Postion of the Element + @inline(__always) + @discardableResult + mutating public func push<T: Scalar>(element: T) -> UOffset { + let size = MemoryLayout<T>.size + preAlign( + len: size, + alignment: size) + _bb.push(value: element, len: size) + return _bb.size + } + +} + +extension FlatBufferBuilder: CustomDebugStringConvertible { + + public var debugDescription: String { + """ + buffer debug: + \(_bb) + builder debug: + { finished: \(finished), serializeDefaults: \(serializeDefaults), isNested: \(isNested) } + """ + } + + /// VTableStorage is a class to contain the VTable buffer that would be serialized into buffer + @usableFromInline + internal class VTableStorage { + /// Memory check since deallocating each time we want to clear would be expensive + /// and memory leaks would happen if we dont deallocate the first allocated memory. + /// memory is promised to be available before adding `FieldLoc` + private var memoryInUse = false + /// Size of FieldLoc in memory + let size = MemoryLayout<FieldLoc>.stride + /// Memeory buffer + var memory: UnsafeMutableRawBufferPointer! + /// Capacity of the current buffer + var capacity: Int = 0 + /// Maximuim offset written to the class + var maxOffset: VOffset = 0 + /// number of fields written into the buffer + var numOfFields: Int = 0 + /// Last written Index + var writtenIndex: Int = 0 + + /// Creates the memory to store the buffer in + @usableFromInline + @inline(__always) + init() { + memory = UnsafeMutableRawBufferPointer.allocate( + byteCount: 0, + alignment: 0) + } + + @inline(__always) + deinit { + memory.deallocate() + } + + /// Builds a buffer with byte count of fieldloc.size * count of field numbers + /// - Parameter count: number of fields to be written + @inline(__always) + func start(count: Int) { + assert(count >= 0, "number of fields should NOT be negative") + let capacity = count &* size + ensure(space: capacity) + } + + /// Adds a FieldLoc into the buffer, which would track how many have been written, + /// and max offset + /// - Parameter loc: Location of encoded element + @inline(__always) + func add(loc: FieldLoc) { + memory.baseAddress?.advanced(by: writtenIndex).storeBytes( + of: loc, + as: FieldLoc.self) + writtenIndex = writtenIndex &+ size + numOfFields = numOfFields &+ 1 + maxOffset = max(loc.position, maxOffset) + } + + /// Clears the data stored related to the encoded buffer + @inline(__always) + func clear() { + maxOffset = 0 + numOfFields = 0 + writtenIndex = 0 + } + + /// Ensure that the buffer has enough space instead of recreating the buffer each time. + /// - Parameter space: space required for the new vtable + @inline(__always) + func ensure(space: Int) { + guard space &+ writtenIndex > capacity else { return } + memory.deallocate() + memory = UnsafeMutableRawBufferPointer.allocate( + byteCount: space, + alignment: size) + capacity = space + } + + /// Loads an object of type `FieldLoc` from buffer memory + /// - Parameter index: index of element + /// - Returns: a FieldLoc at index + @inline(__always) + func load(at index: Int) -> FieldLoc { + memory.load(fromByteOffset: index, as: FieldLoc.self) + } + + } + + internal struct FieldLoc { + var offset: UOffset + var position: VOffset + } + +} diff --git a/flatbuffers/FlatBufferObject.swift b/flatbuffers/FlatBufferObject.swift @@ -0,0 +1,68 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +import Foundation +#else +import SwiftOverlayShims +#endif + +/// NativeStruct is a protocol that indicates if the struct is a native `swift` struct +/// since now we will be serializing native structs into the buffer. +public protocol NativeStruct {} + +/// FlatbuffersInitializable is a protocol that allows any object to be +/// Initialized from a ByteBuffer +public protocol FlatbuffersInitializable { + /// Any flatbuffers object that confirms to this protocol is going to be + /// initializable through this initializer + init(_ bb: ByteBuffer, o: Int32) +} + +/// FlatbufferObject structures all the Flatbuffers objects +public protocol FlatBufferObject: FlatbuffersInitializable { + var __buffer: ByteBuffer! { get } +} + +/// ``ObjectAPIPacker`` is a protocol that allows object to pack and unpack from a +/// ``NativeObject`` to a flatbuffers Object and vice versa. +public protocol ObjectAPIPacker { + /// associatedtype to the object that should be unpacked. + associatedtype T + + /// ``pack(_:obj:)-3ptws`` tries to pacs the variables of a native Object into the `ByteBuffer` by using + /// a FlatBufferBuilder + /// - Parameters: + /// - builder: FlatBufferBuilder that will host incoming data + /// - obj: Object of associatedtype to the current implementer + /// + /// ``pack(_:obj:)-3ptws`` can be called by passing through an already initialized ``FlatBufferBuilder`` + /// or it can be called by using the public API that will create a new ``FlatBufferBuilder`` + static func pack(_ builder: inout FlatBufferBuilder, obj: inout T?) -> Offset + + /// ``pack(_:obj:)-20ipk`` packs the variables of a native Object into the `ByteBuffer` by using + /// the FlatBufferBuilder + /// - Parameters: + /// - builder: FlatBufferBuilder that will host incoming data + /// - obj: Object of associatedtype to the current implementer + /// + /// ``pack(_:obj:)-20ipk`` can be called by passing through an already initialized ``FlatBufferBuilder`` + /// or it can be called by using the public API that will create a new ``FlatBufferBuilder`` + static func pack(_ builder: inout FlatBufferBuilder, obj: inout T) -> Offset + + /// ``unpack()`` unpacks a ``FlatBuffers`` object into a Native swift object. + mutating func unpack() -> T +} diff --git a/flatbuffers/FlatBuffersUtils.swift b/flatbuffers/FlatBuffersUtils.swift @@ -0,0 +1,41 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +import Foundation +#else +import SwiftOverlayShims +#endif + +/// FlatBuffersUtils hosts some utility functions that might be useful +public enum FlatBuffersUtils { + + /// Gets the size of the prefix + /// - Parameter bb: Flatbuffer object + public static func getSizePrefix(bb: ByteBuffer) -> Int32 { + bb.read(def: Int32.self, position: bb.reader) + } + + /// Removes the prefix by duplicating the Flatbuffer this call is expensive since its + /// creates a new buffer use `readPrefixedSizeCheckedRoot` instead + /// unless a completely new buffer is required + /// - Parameter bb: Flatbuffer object + /// + /// + public static func removeSizePrefix(bb: ByteBuffer) -> ByteBuffer { + bb.duplicate(removing: MemoryLayout<Int32>.size) + } +} diff --git a/flatbuffers/FlatbuffersErrors.swift b/flatbuffers/FlatbuffersErrors.swift @@ -0,0 +1,77 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +import Foundation +#else +import SwiftOverlayShims +#endif + +/// Collection of thrown from the Flatbuffer verifier +public enum FlatbuffersErrors: Error, Equatable { + + /// Thrown when verifying a file id that doesnt match buffer id + case bufferIdDidntMatchPassedId + /// Prefixed size doesnt match the current (readable) buffer size + case prefixedSizeNotEqualToBufferSize + /// Thrown when buffer is bigger than the allowed 2GiB + case exceedsMaxSizeAllowed + /// Thrown when there is an missaligned pointer at position + /// of type + case missAlignedPointer(position: Int, type: String) + /// Thrown when trying to read a value that goes out of the + /// current buffer bounds + case outOfBounds(position: UInt, end: Int) + /// Thrown when the signed offset is out of the bounds of the + /// current buffer + case signedOffsetOutOfBounds(offset: Int, position: Int) + /// Thrown when a required field doesnt exist within the buffer + case requiredFieldDoesntExist(position: VOffset, name: String) + /// Thrown when a string is missing its NULL Terminator `\0`, + /// this can be disabled in the `VerifierOptions` + case missingNullTerminator(position: Int, str: String?) + /// Thrown when the verifier has reached the maximum tables allowed, + /// this can be disabled in the `VerifierOptions` + case maximumTables + /// Thrown when the verifier has reached the maximum depth allowed, + /// this can be disabled in the `VerifierOptions` + case maximumDepth + /// Thrown when the verifier is presented with an unknown union case + case unknownUnionCase + /// thrown when a value for a union is not found within the buffer + case valueNotFound(key: Int?, keyName: String, field: Int?, fieldName: String) + /// thrown when the size of the keys vector doesnt match fields vector + case unionVectorSize( + keyVectorSize: Int, + fieldVectorSize: Int, + unionKeyName: String, + fieldName: String) + case apparentSizeTooLarge + +} + +#if !os(WASI) + +extension FlatbuffersErrors { + public static func == ( + lhs: FlatbuffersErrors, + rhs: FlatbuffersErrors) -> Bool + { + lhs.localizedDescription == rhs.localizedDescription + } +} + +#endif diff --git a/flatbuffers/Int+extension.swift b/flatbuffers/Int+extension.swift @@ -0,0 +1,51 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +import Foundation +#else +import SwiftOverlayShims +#endif + +extension Int { + + /// Moves the current int into the nearest power of two + /// + /// This is used since the UnsafeMutableRawPointer will face issues when writing/reading + /// if the buffer alignment exceeds that actual size of the buffer + var convertToPowerofTwo: Int { + guard self > 0 else { return 1 } + var n = UOffset(self) + + #if arch(arm) || arch(i386) + let max = UInt32(Int.max) + #else + let max = UInt32.max + #endif + + n -= 1 + n |= n >> 1 + n |= n >> 2 + n |= n >> 4 + n |= n >> 8 + n |= n >> 16 + if n != max { + n += 1 + } + + return Int(n) + } +} diff --git a/flatbuffers/Message.swift b/flatbuffers/Message.swift @@ -0,0 +1,69 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +import Foundation +#else +import SwiftOverlayShims +#endif + +/// FlatBufferGRPCMessage protocol that should allow us to invoke +/// initializers directly from the GRPC generated code +public protocol FlatBufferGRPCMessage { + + /// Raw pointer which would be pointing to the beginning of the readable bytes + var rawPointer: UnsafeMutableRawPointer { get } + + /// Size of readable bytes in the buffer + var size: Int { get } + + init(byteBuffer: ByteBuffer) +} + +/// Message is a wrapper around Buffers to to able to send Flatbuffers `Buffers` through the +/// GRPC library +public struct Message<T: FlatBufferObject>: FlatBufferGRPCMessage { + internal var buffer: ByteBuffer + + /// Returns the an object of type T that would be read from the buffer + public var object: T { + T.init( + buffer, + o: Int32(buffer.read(def: UOffset.self, position: buffer.reader)) + + Int32(buffer.reader)) + } + + public var rawPointer: UnsafeMutableRawPointer { + buffer.memory.advanced(by: buffer.reader) } + + public var size: Int { Int(buffer.size) } + + /// Initializes the message with the type Flatbuffer.Bytebuffer that is transmitted over + /// GRPC + /// - Parameter byteBuffer: Flatbuffer ByteBuffer object + public init(byteBuffer: ByteBuffer) { + buffer = byteBuffer + } + + /// Initializes the message by copying the buffer to the message to be sent. + /// from the builder + /// - Parameter builder: FlatbufferBuilder that has the bytes created in + /// - Note: Use `builder.finish(offset)` before passing the builder without prefixing anything to it + public init(builder: inout FlatBufferBuilder) { + buffer = builder.sizedBuffer + builder.clear() + } +} diff --git a/flatbuffers/Mutable.swift b/flatbuffers/Mutable.swift @@ -0,0 +1,88 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +import Foundation +#else +import SwiftOverlayShims +#endif + +/// Mutable is a protocol that allows us to mutate Scalar values within a ``ByteBuffer`` +public protocol Mutable { + /// makes Flatbuffer accessed within the Protocol + var bb: ByteBuffer { get } + /// makes position of the ``Table``/``struct`` accessed within the Protocol + var postion: Int32 { get } +} + +extension Mutable { + + /// Mutates the memory in the buffer, this is only called from the access function of ``Table`` and ``struct`` + /// - Parameters: + /// - value: New value to be inserted to the buffer + /// - index: index of the Element + func mutate<T: Scalar>(value: T, o: Int32) -> Bool { + guard o != 0 else { return false } + bb.write(value: value, index: Int(o), direct: true) + return true + } +} + +extension Mutable where Self == Table { + + /// Mutates a value by calling mutate with respect to the position in a ``Table`` + /// - Parameters: + /// - value: New value to be inserted to the buffer + /// - index: index of the Element + public func mutate<T: Scalar>(_ value: T, index: Int32) -> Bool { + guard index != 0 else { return false } + return mutate(value: value, o: index + postion) + } + + /// Directly mutates the element by calling mutate + /// + /// Mutates the Element at index ignoring the current position by calling mutate + /// - Parameters: + /// - value: New value to be inserted to the buffer + /// - index: index of the Element + public func directMutate<T: Scalar>(_ value: T, index: Int32) -> Bool { + mutate(value: value, o: index) + } +} + +extension Mutable where Self == Struct { + + /// Mutates a value by calling mutate with respect to the position in the struct + /// - Parameters: + /// - value: New value to be inserted to the buffer + /// - index: index of the Element + public func mutate<T: Scalar>(_ value: T, index: Int32) -> Bool { + mutate(value: value, o: index + postion) + } + + /// Directly mutates the element by calling mutate + /// + /// Mutates the Element at index ignoring the current position by calling mutate + /// - Parameters: + /// - value: New value to be inserted to the buffer + /// - index: index of the Element + public func directMutate<T: Scalar>(_ value: T, index: Int32) -> Bool { + mutate(value: value, o: index) + } +} + +extension Struct: Mutable {} +extension Table: Mutable {} diff --git a/flatbuffers/NativeObject.swift b/flatbuffers/NativeObject.swift @@ -0,0 +1,57 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +import Foundation +#else +import SwiftOverlayShims +#endif + +/// NativeObject is a protocol that all of the `Object-API` generated code should be +/// conforming to since it allows developers the ease of use to pack and unpack their +/// Flatbuffers objects +public protocol NativeObject {} + +extension NativeObject { + + /// Serialize is a helper function that serailizes the data from the Object API to a bytebuffer directly th + /// - Parameter type: Type of the Flatbuffer object + /// - Returns: returns the encoded sized ByteBuffer + public func serialize<T: ObjectAPIPacker>(type: T.Type) -> ByteBuffer + where T.T == Self + { + var builder = FlatBufferBuilder(initialSize: 1024) + return serialize(builder: &builder, type: type.self) + } + + /// Serialize is a helper function that serailizes the data from the Object API to a bytebuffer directly. + /// + /// - Parameters: + /// - builder: A FlatBufferBuilder + /// - type: Type of the Flatbuffer object + /// - Returns: returns the encoded sized ByteBuffer + /// - Note: The `serialize(builder:type)` can be considered as a function that allows you to create smaller builder instead of the default `1024`. + /// It can be considered less expensive in terms of memory allocation + public func serialize<T: ObjectAPIPacker>( + builder: inout FlatBufferBuilder, + type: T.Type) -> ByteBuffer where T.T == Self + { + var s = self + let root = type.pack(&builder, obj: &s) + builder.finish(offset: root) + return builder.sizedBuffer + } +} diff --git a/flatbuffers/Offset.swift b/flatbuffers/Offset.swift @@ -0,0 +1,32 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +import Foundation +#else +import SwiftOverlayShims +#endif + +/// Offset object for all the Objects that are written into the buffer +public struct Offset { + /// Offset of the object in the buffer + public var o: UOffset + /// Returns false if the offset is equal to zero + public var isEmpty: Bool { o == 0 } + + public init(offset: UOffset) { o = offset } + public init() { o = 0 } +} diff --git a/flatbuffers/Root.swift b/flatbuffers/Root.swift @@ -0,0 +1,119 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +import Foundation +#else +import SwiftOverlayShims +#endif + +/// Takes in a prefixed sized buffer, where the prefixed size would be skipped. +/// And would verify that the buffer passed is a valid `Flatbuffers` Object. +/// - Parameters: +/// - byteBuffer: Buffer that needs to be checked and read +/// - options: Verifier options +/// - Throws: FlatbuffersErrors +/// - Returns: Returns a valid, checked Flatbuffers object +/// +/// ``getPrefixedSizeCheckedRoot(byteBuffer:options:)`` would skip the first Bytes in +/// the ``ByteBuffer`` and verifies the buffer by calling ``getCheckedRoot(byteBuffer:options:)`` +public func getPrefixedSizeCheckedRoot<T: FlatBufferObject & Verifiable>( + byteBuffer: inout ByteBuffer, + fileId: String? = nil, + options: VerifierOptions = .init()) throws -> T +{ + byteBuffer.skipPrefix() + return try getCheckedRoot( + byteBuffer: &byteBuffer, + fileId: fileId, + options: options) +} + +/// Takes in a prefixed sized buffer, where we check if the sized buffer is equal to prefix size. +/// And would verify that the buffer passed is a valid `Flatbuffers` Object. +/// - Parameters: +/// - byteBuffer: Buffer that needs to be checked and read +/// - options: Verifier options +/// - Throws: FlatbuffersErrors +/// - Returns: Returns a valid, checked Flatbuffers object +/// +/// ``getPrefixedSizeCheckedRoot(byteBuffer:options:)`` would skip the first Bytes in +/// the ``ByteBuffer`` and verifies the buffer by calling ``getCheckedRoot(byteBuffer:options:)`` +public func getCheckedPrefixedSizeRoot<T: FlatBufferObject & Verifiable>( + byteBuffer: inout ByteBuffer, + fileId: String? = nil, + options: VerifierOptions = .init()) throws -> T +{ + let prefix = byteBuffer.skipPrefix() + if prefix != byteBuffer.size { + throw FlatbuffersErrors.prefixedSizeNotEqualToBufferSize + } + return try getCheckedRoot( + byteBuffer: &byteBuffer, + fileId: fileId, + options: options) +} + +/// Takes in a prefixed sized buffer, where the prefixed size would be skipped. +/// Returns a `NON-Checked` flatbuffers object +/// - Parameter byteBuffer: Buffer that contains data +/// - Returns: Returns a Flatbuffers object +/// +/// ``getPrefixedSizeCheckedRoot(byteBuffer:options:)`` would skip the first Bytes in +/// the ``ByteBuffer`` and then calls ``getRoot(byteBuffer:)`` +public func getPrefixedSizeRoot<T: FlatBufferObject>(byteBuffer: inout ByteBuffer) + -> T +{ + byteBuffer.skipPrefix() + return getRoot(byteBuffer: &byteBuffer) + +} + +/// Verifies that the buffer passed is a valid `Flatbuffers` Object. +/// - Parameters: +/// - byteBuffer: Buffer that needs to be checked and read +/// - options: Verifier options +/// - Throws: FlatbuffersErrors +/// - Returns: Returns a valid, checked Flatbuffers object +/// +/// ``getCheckedRoot(byteBuffer:options:)`` Takes in a ``ByteBuffer`` and verifies +/// that by creating a ``Verifier`` and checkes if all the `Bytes` and correctly aligned +/// and within the ``ByteBuffer`` range. +public func getCheckedRoot<T: FlatBufferObject & Verifiable>( + byteBuffer: inout ByteBuffer, + fileId: String? = nil, + options: VerifierOptions = .init()) throws -> T +{ + var verifier = try Verifier(buffer: &byteBuffer, options: options) + if let fileId = fileId { + try verifier.verify(id: fileId) + } + try ForwardOffset<T>.verify(&verifier, at: 0, of: T.self) + return T.init( + byteBuffer, + o: Int32(byteBuffer.read(def: UOffset.self, position: byteBuffer.reader)) + + Int32(byteBuffer.reader)) +} + +/// Returns a `NON-Checked` flatbuffers object +/// - Parameter byteBuffer: Buffer that contains data +/// - Returns: Returns a Flatbuffers object +public func getRoot<T: FlatBufferObject>(byteBuffer: inout ByteBuffer) -> T { + T.init( + byteBuffer, + o: Int32(byteBuffer.read(def: UOffset.self, position: byteBuffer.reader)) + + Int32(byteBuffer.reader)) +} diff --git a/flatbuffers/String+extension.swift b/flatbuffers/String+extension.swift @@ -0,0 +1,113 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +import Foundation +#else +import SwiftOverlayShims +#endif + +extension String: Verifiable { + + /// Verifies that the current value is which the bounds of the buffer, and if + /// the current `Value` is aligned properly + /// - Parameters: + /// - verifier: Verifier that hosts the buffer + /// - position: Current position within the buffer + /// - type: The type of the object to be verified + /// - Throws: Errors coming from `inBuffer`, `missingNullTerminator` and `outOfBounds` + public static func verify<T>( + _ verifier: inout Verifier, + at position: Int, + of type: T.Type) throws where T: Verifiable + { + + let range = try String.verifyRange(&verifier, at: position, of: UInt8.self) + /// Safe &+ since we already check for overflow in verify range + let stringLen = range.start &+ range.count + + if stringLen >= verifier.capacity { + throw FlatbuffersErrors.outOfBounds( + position: UInt(clamping: stringLen.magnitude), + end: verifier.capacity) + } + + let isNullTerminated = verifier._buffer.read( + def: UInt8.self, + position: stringLen) == 0 + + if !verifier._options._ignoreMissingNullTerminators && !isNullTerminated { + let str = verifier._buffer.readString(at: range.start, count: range.count) + throw FlatbuffersErrors.missingNullTerminator( + position: position, + str: str) + } + } +} + +extension String: FlatbuffersInitializable { + + /// Initailizes a string from a Flatbuffers ByteBuffer + /// - Parameters: + /// - bb: ByteBuffer containing the readable string + /// - o: Current position + public init(_ bb: ByteBuffer, o: Int32) { + let v = Int(o) + let count = bb.read(def: Int32.self, position: v) + self = bb.readString( + at: MemoryLayout<Int32>.size + v, + count: Int(count)) ?? "" + } +} + +extension String: ObjectAPIPacker { + + public static func pack( + _ builder: inout FlatBufferBuilder, + obj: inout String?) -> Offset + { + guard var obj = obj else { return Offset() } + return pack(&builder, obj: &obj) + } + + public static func pack( + _ builder: inout FlatBufferBuilder, + obj: inout String) -> Offset + { + builder.create(string: obj) + } + + public mutating func unpack() -> String { + self + } + +} + +extension String: NativeObject { + + public func serialize<T: ObjectAPIPacker>(type: T.Type) -> ByteBuffer + where T.T == Self + { + fatalError("serialize should never be called from string directly") + } + + public func serialize<T: ObjectAPIPacker>( + builder: inout FlatBufferBuilder, + type: T.Type) -> ByteBuffer where T.T == Self + { + fatalError("serialize should never be called from string directly") + } +} diff --git a/flatbuffers/Struct.swift b/flatbuffers/Struct.swift @@ -0,0 +1,51 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +import Foundation +#else +import SwiftOverlayShims +#endif + +/// Struct is a representation of a mutable `Flatbuffers` struct +/// since native structs are value types and cant be mutated +@frozen +public struct Struct { + + /// Hosting Bytebuffer + public private(set) var bb: ByteBuffer + /// Current position of the struct + public private(set) var postion: Int32 + + /// Initializer for a mutable flatbuffers struct + /// - Parameters: + /// - bb: Current hosting Bytebuffer + /// - position: Current position for the struct in the ByteBuffer + public init(bb: ByteBuffer, position: Int32 = 0) { + self.bb = bb + postion = position + } + + /// Reads data from the buffer directly at offset O + /// - Parameters: + /// - type: Type of data to be read + /// - o: Current offset of the data + /// - Returns: Data of Type T that conforms to type Scalar + public func readBuffer<T: Scalar>(of type: T.Type, at o: Int32) -> T { + let r = bb.read(def: T.self, position: Int(o + postion)) + return r + } +} diff --git a/flatbuffers/Table.swift b/flatbuffers/Table.swift @@ -0,0 +1,240 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +import Foundation +#else +import SwiftOverlayShims +#endif + +/// `Table` is a Flatbuffers object that can read, +/// mutate scalar fields within a valid flatbuffers buffer +@frozen +public struct Table { + + /// Hosting Bytebuffer + public private(set) var bb: ByteBuffer + /// Current position of the table within the buffer + public private(set) var postion: Int32 + + /// Initializer for the table interface to allow generated code to read + /// data from memory + /// - Parameters: + /// - bb: ByteBuffer that stores data + /// - position: Current table position + /// - Note: This will `CRASH` if read on a big endian machine + public init(bb: ByteBuffer, position: Int32 = 0) { + guard isLitteEndian else { + fatalError( + "Reading/Writing a buffer in big endian machine is not supported on swift") + } + self.bb = bb + postion = position + } + + /// Gets the offset of the current field within the buffer by reading + /// the vtable + /// - Parameter o: current offset + /// - Returns: offset of field within buffer + public func offset(_ o: Int32) -> Int32 { + let vtable = postion - bb.read(def: Int32.self, position: Int(postion)) + return o < bb + .read(def: VOffset.self, position: Int(vtable)) ? Int32(bb.read( + def: Int16.self, + position: Int(vtable + o))) : 0 + } + + /// Gets the indirect offset of the current stored object + /// (applicable only for object arrays) + /// - Parameter o: current offset + /// - Returns: offset of field within buffer + public func indirect(_ o: Int32) -> Int32 { + o + bb.read(def: Int32.self, position: Int(o)) + } + + /// String reads from the buffer with respect to position of the current table. + /// - Parameter offset: Offset of the string + public func string(at offset: Int32) -> String? { + directString(at: offset + postion) + } + + /// Direct string reads from the buffer disregarding the position of the table. + /// It would be preferable to use string unless the current position of the table + /// is not needed + /// - Parameter offset: Offset of the string + public func directString(at offset: Int32) -> String? { + var offset = offset + offset += bb.read(def: Int32.self, position: Int(offset)) + let count = bb.read(def: Int32.self, position: Int(offset)) + let position = Int(offset) + MemoryLayout<Int32>.size + return bb.readString(at: position, count: Int(count)) + } + + /// Reads from the buffer with respect to the position in the table. + /// - Parameters: + /// - type: Type of Element that needs to be read from the buffer + /// - o: Offset of the Element + public func readBuffer<T>(of type: T.Type, at o: Int32) -> T { + directRead(of: T.self, offset: o + postion) + } + + /// Reads from the buffer disregarding the position of the table. + /// It would be used when reading from an + /// ``` + /// let offset = __t.offset(10) + /// //Only used when the we already know what is the + /// // position in the table since __t.vector(at:) + /// // returns the index with respect to the position + /// __t.directRead(of: Byte.self, + /// offset: __t.vector(at: offset) + index * 1) + /// ``` + /// - Parameters: + /// - type: Type of Element that needs to be read from the buffer + /// - o: Offset of the Element + public func directRead<T>(of type: T.Type, offset o: Int32) -> T { + let r = bb.read(def: T.self, position: Int(o)) + return r + } + + /// Returns that current `Union` object at a specific offset + /// by adding offset to the current position of table + /// - Parameter o: offset + /// - Returns: A flatbuffers object + public func union<T: FlatbuffersInitializable>(_ o: Int32) -> T { + let o = o + postion + return directUnion(o) + } + + /// Returns a direct `Union` object at a specific offset + /// - Parameter o: offset + /// - Returns: A flatbuffers object + public func directUnion<T: FlatbuffersInitializable>(_ o: Int32) -> T { + T.init(bb, o: o + bb.read(def: Int32.self, position: Int(o))) + } + + /// Returns a vector of type T at a specific offset + /// This should only be used by `Scalars` + /// - Parameter off: Readable offset + /// - Returns: Returns a vector of type [T] + public func getVector<T>(at off: Int32) -> [T]? { + let o = offset(off) + guard o != 0 else { return nil } + return bb.readSlice(index: Int(vector(at: o)), count: Int(vector(count: o))) + } + + /// Vector count gets the count of Elements within the array + /// - Parameter o: start offset of the vector + /// - returns: Count of elements + public func vector(count o: Int32) -> Int32 { + var o = o + o += postion + o += bb.read(def: Int32.self, position: Int(o)) + return bb.read(def: Int32.self, position: Int(o)) + } + + /// Vector start index in the buffer + /// - Parameter o:start offset of the vector + /// - returns: the start index of the vector + public func vector(at o: Int32) -> Int32 { + var o = o + o += postion + return o + bb.read(def: Int32.self, position: Int(o)) + 4 + } + + /// Reading an indirect offset of a table. + /// - Parameters: + /// - o: position within the buffer + /// - fbb: ByteBuffer + /// - Returns: table offset + static public func indirect(_ o: Int32, _ fbb: ByteBuffer) -> Int32 { + o + fbb.read(def: Int32.self, position: Int(o)) + } + + /// Gets a vtable value according to an table Offset and a field offset + /// - Parameters: + /// - o: offset relative to entire buffer + /// - vOffset: Field offset within a vtable + /// - fbb: ByteBuffer + /// - Returns: an position of a field + static public func offset( + _ o: Int32, + vOffset: Int32, + fbb: ByteBuffer) -> Int32 + { + let vTable = Int32(fbb.capacity) - o + return vTable + Int32(fbb.read( + def: Int16.self, + position: Int(vTable + vOffset - fbb.read( + def: Int32.self, + position: Int(vTable))))) + } + + /// Compares two objects at offset A and offset B within a ByteBuffer + /// - Parameters: + /// - off1: first offset to compare + /// - off2: second offset to compare + /// - fbb: Bytebuffer + /// - Returns: returns the difference between + static public func compare( + _ off1: Int32, + _ off2: Int32, + fbb: ByteBuffer) -> Int32 + { + let memorySize = Int32(MemoryLayout<Int32>.size) + let _off1 = off1 + fbb.read(def: Int32.self, position: Int(off1)) + let _off2 = off2 + fbb.read(def: Int32.self, position: Int(off2)) + let len1 = fbb.read(def: Int32.self, position: Int(_off1)) + let len2 = fbb.read(def: Int32.self, position: Int(_off2)) + let startPos1 = _off1 + memorySize + let startPos2 = _off2 + memorySize + let minValue = min(len1, len2) + for i in 0...minValue { + let b1 = fbb.read(def: Int8.self, position: Int(i + startPos1)) + let b2 = fbb.read(def: Int8.self, position: Int(i + startPos2)) + if b1 != b2 { + return Int32(b2 - b1) + } + } + return len1 - len2 + } + + /// Compares two objects at offset A and array of `Bytes` within a ByteBuffer + /// - Parameters: + /// - off1: Offset to compare to + /// - key: bytes array to compare to + /// - fbb: Bytebuffer + /// - Returns: returns the difference between + static public func compare( + _ off1: Int32, + _ key: [Byte], + fbb: ByteBuffer) -> Int32 + { + let memorySize = Int32(MemoryLayout<Int32>.size) + let _off1 = off1 + fbb.read(def: Int32.self, position: Int(off1)) + let len1 = fbb.read(def: Int32.self, position: Int(_off1)) + let len2 = Int32(key.count) + let startPos1 = _off1 + memorySize + let minValue = min(len1, len2) + for i in 0..<minValue { + let b = fbb.read(def: Int8.self, position: Int(i + startPos1)) + let byte = key[Int(i)] + if b != byte { + return Int32(b - Int8(byte)) + } + } + return len1 - len2 + } +} diff --git a/flatbuffers/TableVerifier.swift b/flatbuffers/TableVerifier.swift @@ -0,0 +1,207 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +import Foundation +#else +import SwiftOverlayShims +#endif + +/// `TableVerifier` verifies a table object is within a provided memory. +/// It checks if all the objects for a specific generated table, are within +/// the bounds of the buffer, aligned. +public struct TableVerifier { + + /// position of current table in `ByteBuffer` + fileprivate var _position: Int + + /// Current VTable position + fileprivate var _vtable: Int + + /// Length of current VTable + fileprivate var _vtableLength: Int + + /// `Verifier` object created in the base verifable call. + fileprivate var _verifier: Verifier + + /// Creates a `TableVerifier` verifier that allows the Flatbuffer object + /// to verify the buffer before accessing any of the data. + /// + /// - Parameters: + /// - position: Current table Position + /// - vtable: Current `VTable` position + /// - vtableLength: Current `VTable` length + /// - verifier: `Verifier` Object that caches the data of the verifiable object + internal init( + position: Int, + vtable: Int, + vtableLength: Int, + verifier: inout Verifier) + { + _position = position + _vtable = vtable + _vtableLength = vtableLength + _verifier = verifier + } + + /// Dereference the current object position from the `VTable` + /// - Parameter field: Current VTable refrence to position. + /// - Throws: A `FlatbuffersErrors` incase the voffset is not aligned/outOfBounds/apparentSizeTooLarge + /// - Returns: An optional position for current field + internal mutating func dereference(_ field: VOffset) throws -> Int? { + if field >= _vtableLength { + return nil + } + + /// Reading the offset for the field needs to be read. + let offset: VOffset = try _verifier.getValue( + at: Int(clamping: _vtable &+ Int(field))) + + if offset > 0 { + return Int(clamping: _position &+ Int(offset)) + } + return nil + } + + /// Visits all the fields within the table to validate the integrity + /// of the data + /// - Parameters: + /// - field: voffset of the current field to be read + /// - fieldName: fieldname to report data Errors. + /// - required: If the field has to be available in the buffer + /// - type: Type of field to be read + /// - Throws: A `FlatbuffersErrors` where the field is corrupt + public mutating func visit<T>( + field: VOffset, + fieldName: String, + required: Bool, + type: T.Type) throws where T: Verifiable + { + let derefValue = try dereference(field) + + if let value = derefValue { + try T.verify(&_verifier, at: value, of: T.self) + return + } + if required { + throw FlatbuffersErrors.requiredFieldDoesntExist( + position: field, + name: fieldName) + } + } + + /// Visits all the fields for a union object within the table to + /// validate the integrity of the data + /// - Parameters: + /// - key: Current Key Voffset + /// - field: Current field Voffset + /// - unionKeyName: Union key name + /// - fieldName: Field key name + /// - required: indicates if an object is required to be present + /// - completion: Completion is a handler that WILL be called in the generated + /// - Throws: A `FlatbuffersErrors` where the field is corrupt + public mutating func visit<T>( + unionKey key: VOffset, + unionField field: VOffset, + unionKeyName: String, + fieldName: String, + required: Bool, + completion: @escaping (inout Verifier, T, Int) throws -> Void) throws + where T: UnionEnum + { + let keyPos = try dereference(key) + let valPos = try dereference(field) + + if keyPos == nil && valPos == nil { + if required { + throw FlatbuffersErrors.requiredFieldDoesntExist( + position: key, + name: unionKeyName) + } + return + } + + if let _key = keyPos, + let _val = valPos + { + /// verifiying that the key is within the buffer + try T.T.verify(&_verifier, at: _key, of: T.T.self) + guard let _enum = try T.init(value: _verifier._buffer.read( + def: T.T.self, + position: _key)) else + { + throw FlatbuffersErrors.unknownUnionCase + } + /// we are assuming that Unions will always be of type Uint8 + try completion( + &_verifier, + _enum, + _val) + return + } + throw FlatbuffersErrors.valueNotFound( + key: keyPos, + keyName: unionKeyName, + field: valPos, + fieldName: fieldName) + } + + /// Visits and validates all the objects within a union vector + /// - Parameters: + /// - key: Current Key Voffset + /// - field: Current field Voffset + /// - unionKeyName: Union key name + /// - fieldName: Field key name + /// - required: indicates if an object is required to be present + /// - completion: Completion is a handler that WILL be called in the generated + /// - Throws: A `FlatbuffersErrors` where the field is corrupt + public mutating func visitUnionVector<T>( + unionKey key: VOffset, + unionField field: VOffset, + unionKeyName: String, + fieldName: String, + required: Bool, + completion: @escaping (inout Verifier, T, Int) throws -> Void) throws + where T: UnionEnum + { + let keyVectorPosition = try dereference(key) + let offsetVectorPosition = try dereference(field) + + if let keyPos = keyVectorPosition, + let valPos = offsetVectorPosition + { + try UnionVector<T>.verify( + &_verifier, + keyPosition: keyPos, + fieldPosition: valPos, + unionKeyName: unionKeyName, + fieldName: fieldName, + completion: completion) + return + } + if required { + throw FlatbuffersErrors.requiredFieldDoesntExist( + position: field, + name: fieldName) + } + } + + /// Finishs the current Table verifier, and subtracts the current + /// table from the incremented depth. + public mutating func finish() { + _verifier.finish() + } +} diff --git a/flatbuffers/VeriferOptions.swift b/flatbuffers/VeriferOptions.swift @@ -0,0 +1,56 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +import Foundation +#else +import SwiftOverlayShims +#endif + +/// `VerifierOptions` is a set of options to verify a flatbuffer +public struct VerifierOptions { + + /// Maximum `Apparent` size if the buffer can be expanded into a DAG tree + internal var _maxApparentSize: UOffset + + /// Maximum table count allowed in a buffer + internal var _maxTableCount: UOffset + + /// Maximum depth allowed in a buffer + internal var _maxDepth: UOffset + + /// Ignoring missing null terminals in strings + internal var _ignoreMissingNullTerminators: Bool + + /// initializes the set of options for the verifier + /// - Parameters: + /// - maxDepth: Maximum depth allowed in a buffer + /// - maxTableCount: Maximum table count allowed in a buffer + /// - maxApparentSize: Maximum `Apparent` size if the buffer can be expanded into a DAG tree + /// - ignoreMissingNullTerminators: Ignoring missing null terminals in strings *Currently not supported in swift* + public init( + maxDepth: UOffset = 64, + maxTableCount: UOffset = 1000000, + maxApparentSize: UOffset = 1 << 31, + ignoreMissingNullTerminators: Bool = false) + { + _maxDepth = maxDepth + _maxTableCount = maxTableCount + _maxApparentSize = maxApparentSize + _ignoreMissingNullTerminators = ignoreMissingNullTerminators + } + +} diff --git a/flatbuffers/Verifiable.swift b/flatbuffers/Verifiable.swift @@ -0,0 +1,215 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +import Foundation +#else +import SwiftOverlayShims +#endif + +/// Verifiable is a protocol all swift flatbuffers object should conform to, +/// since swift is similar to `cpp` and `rust` where the data is read directly +/// from `unsafeMemory` thus the need to verify if the buffer received is a valid one +public protocol Verifiable { + + /// Verifies that the current value is which the bounds of the buffer, and if + /// the current `Value` is aligned properly + /// - Parameters: + /// - verifier: Verifier that hosts the buffer + /// - position: Current position within the buffer + /// - type: The type of the object to be verified + /// - Throws: Errors coming from `inBuffer` function + static func verify<T>( + _ verifier: inout Verifier, + at position: Int, + of type: T.Type) throws where T: Verifiable +} + +extension Verifiable { + + /// Verifies if the current range to be read is within the bounds of the buffer, + /// and if the range is properly aligned + /// - Parameters: + /// - verifier: Verifier that hosts the buffer + /// - position: Current position within the buffer + /// - type: The type of the object to be verified + /// - Throws: Erros thrown from `isAligned` & `rangeInBuffer` + /// - Returns: a tuple of the start position and the count of objects within the range + @discardableResult + public static func verifyRange<T>( + _ verifier: inout Verifier, + at position: Int, of type: T.Type) throws -> (start: Int, count: Int) + { + let len: UOffset = try verifier.getValue(at: position) + let intLen = Int(len) + let start = Int(clamping: (position &+ MemoryLayout<Int32>.size).magnitude) + try verifier.isAligned(position: start, type: type.self) + try verifier.rangeInBuffer(position: start, size: intLen) + return (start, intLen) + } +} + +extension Verifiable where Self: Scalar { + + /// Verifies that the current value is which the bounds of the buffer, and if + /// the current `Value` is aligned properly + /// - Parameters: + /// - verifier: Verifier that hosts the buffer + /// - position: Current position within the buffer + /// - type: The type of the object to be verified + /// - Throws: Errors coming from `inBuffer` function + public static func verify<T>( + _ verifier: inout Verifier, + at position: Int, + of type: T.Type) throws where T: Verifiable + { + try verifier.inBuffer(position: position, of: type.self) + } +} + +// MARK: - ForwardOffset + +/// ForwardOffset is a container to wrap around the Generic type to be verified +/// from the flatbuffers object. +public enum ForwardOffset<U>: Verifiable where U: Verifiable { + + /// Verifies that the current value is which the bounds of the buffer, and if + /// the current `Value` is aligned properly + /// - Parameters: + /// - verifier: Verifier that hosts the buffer + /// - position: Current position within the buffer + /// - type: The type of the object to be verified + /// - Throws: Errors coming from `inBuffer` function + public static func verify<T>( + _ verifier: inout Verifier, + at position: Int, + of type: T.Type) throws where T: Verifiable + { + let offset: UOffset = try verifier.getValue(at: position) + let nextOffset = Int(clamping: (Int(offset) &+ position).magnitude) + try U.verify(&verifier, at: nextOffset, of: U.self) + } +} + +// MARK: - Vector + +/// Vector is a container to wrap around the Generic type to be verified +/// from the flatbuffers object. +public enum Vector<U, S>: Verifiable where U: Verifiable, S: Verifiable { + + /// Verifies that the current value is which the bounds of the buffer, and if + /// the current `Value` is aligned properly + /// - Parameters: + /// - verifier: Verifier that hosts the buffer + /// - position: Current position within the buffer + /// - type: The type of the object to be verified + /// - Throws: Errors coming from `inBuffer` function + public static func verify<T>( + _ verifier: inout Verifier, + at position: Int, + of type: T.Type) throws where T: Verifiable + { + /// checks if the next verification type S is equal to U of type forwardOffset + /// This had to be done since I couldnt find a solution for duplicate call functions + /// A fix will be appreciated + if U.self is ForwardOffset<S>.Type { + let range = try verifyRange(&verifier, at: position, of: UOffset.self) + for index in stride( + from: range.start, + to: Int(clamping: range.start &+ range.count), + by: MemoryLayout<UOffset>.size) + { + try U.verify(&verifier, at: index, of: U.self) + } + } else { + try S.verifyRange(&verifier, at: position, of: S.self) + } + } +} + +// MARK: - UnionVector + +/// UnionVector is a container to wrap around the Generic type to be verified +/// from the flatbuffers object. +public enum UnionVector<S> where S: UnionEnum { + + /// Completion handler for the function Verify, that passes the verifier + /// enum type and position of union field + public typealias Completion = (inout Verifier, S, Int) throws -> Void + + /// Verifies if the current range to be read is within the bounds of the buffer, + /// and if the range is properly aligned. It also verifies if the union type is a + /// *valid/supported* union type. + /// - Parameters: + /// - verifier: Verifier that hosts the buffer + /// - keyPosition: Current union key position within the buffer + /// - fieldPosition: Current union field position within the buffer + /// - unionKeyName: Name of key to written if error is presented + /// - fieldName: Name of field to written if error is presented + /// - completion: Completion is a handler that WILL be called in the generated + /// code to verify the actual objects + /// - Throws: FlatbuffersErrors + public static func verify( + _ verifier: inout Verifier, + keyPosition: Int, + fieldPosition: Int, + unionKeyName: String, + fieldName: String, + completion: @escaping Completion) throws + { + /// Get offset for union key vectors and offset vectors + let keyOffset: UOffset = try verifier.getValue(at: keyPosition) + let fieldOffset: UOffset = try verifier.getValue(at: fieldPosition) + + /// Check if values are within the buffer, returns the start position of vectors, and vector counts + /// Using &+ is safe since we already verified that the value is within the buffer, where the max is + /// going to be 2Gib and swift supports Int64 by default + let keysRange = try S.T.verifyRange( + &verifier, + at: Int(keyOffset) &+ keyPosition, + of: S.T.self) + let offsetsRange = try UOffset.verifyRange( + &verifier, + at: Int(fieldOffset) &+ fieldPosition, + of: UOffset.self) + + guard keysRange.count == offsetsRange.count else { + throw FlatbuffersErrors.unionVectorSize( + keyVectorSize: keysRange.count, + fieldVectorSize: offsetsRange.count, + unionKeyName: unionKeyName, + fieldName: fieldName) + } + + var count = 0 + /// Iterate over the vector of keys and offsets. + while count < keysRange.count { + + /// index of readable enum value in array + let keysIndex = MemoryLayout<S.T>.size * count + guard let _enum = try S.init(value: verifier._buffer.read( + def: S.T.self, + position: keysRange.start + keysIndex)) else + { + throw FlatbuffersErrors.unknownUnionCase + } + /// index of readable offset value in array + let fieldIndex = MemoryLayout<UOffset>.size * count + try completion(&verifier, _enum, offsetsRange.start + fieldIndex) + count += 1 + } + } +} diff --git a/flatbuffers/Verifier.swift b/flatbuffers/Verifier.swift @@ -0,0 +1,217 @@ +/* + * Copyright 2023 Google Inc. All rights reserved. + * + * 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 + * + * http://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. + */ + +#if !os(WASI) +import Foundation +#else +import SwiftOverlayShims +#endif + +/// Verifier that check if the buffer passed into it is a valid, +/// safe, aligned Flatbuffers object since swift read from `unsafeMemory` +public struct Verifier { + + /// Flag to check for alignment if true + fileprivate let _checkAlignment: Bool + /// Capacity of the current buffer + fileprivate var _capacity: Int + /// Current ApparentSize + fileprivate var _apparentSize: UOffset = 0 + /// Amount of tables present within a buffer + fileprivate var _tableCount = 0 + + /// Capacity of the buffer + internal var capacity: Int { _capacity } + /// Current reached depth within the buffer + internal var _depth = 0 + /// Current verifiable ByteBuffer + internal var _buffer: ByteBuffer + /// Options for verification + internal let _options: VerifierOptions + + /// Initializer for the verifier + /// - Parameters: + /// - buffer: Bytebuffer that is required to be verified + /// - options: `VerifierOptions` that set the rule for some of the verification done + /// - checkAlignment: If alignment check is required to be preformed + /// - Throws: `exceedsMaxSizeAllowed` if capacity of the buffer is more than 2GiB + public init( + buffer: inout ByteBuffer, + options: VerifierOptions = .init(), + checkAlignment: Bool = true) throws + { + guard buffer.capacity < FlatBufferMaxSize else { + throw FlatbuffersErrors.exceedsMaxSizeAllowed + } + + _buffer = buffer + _capacity = buffer.capacity + _checkAlignment = checkAlignment + _options = options + } + + /// Resets the verifier to initial state + public mutating func reset() { + _depth = 0 + _tableCount = 0 + } + + /// Checks if the value of type `T` is aligned properly in the buffer + /// - Parameters: + /// - position: Current position + /// - type: Type of value to check + /// - Throws: `missAlignedPointer` if the pointer is not aligned properly + public mutating func isAligned<T>(position: Int, type: T.Type) throws { + + /// If check alignment is false this mutating function doesnt continue + if !_checkAlignment { return } + + /// advance pointer to position X + let ptr = _buffer._storage.memory.advanced(by: position) + /// Check if the pointer is aligned + if Int(bitPattern: ptr) & (MemoryLayout<T>.alignment &- 1) == 0 { + return + } + + throw FlatbuffersErrors.missAlignedPointer( + position: position, + type: String(describing: T.self)) + } + + /// Checks if the value of Size "X" is within the range of the buffer + /// - Parameters: + /// - position: Current postion to be read + /// - size: `Byte` Size of readable object within the buffer + /// - Throws: `outOfBounds` if the value is out of the bounds of the buffer + /// and `apparentSizeTooLarge` if the apparent size is bigger than the one specified + /// in `VerifierOptions` + public mutating func rangeInBuffer(position: Int, size: Int) throws { + let end = UInt(clamping: (position &+ size).magnitude) + if end > _buffer.capacity { + throw FlatbuffersErrors.outOfBounds(position: end, end: capacity) + } + _apparentSize = _apparentSize &+ UInt32(size) + if _apparentSize > _options._maxApparentSize { + throw FlatbuffersErrors.apparentSizeTooLarge + } + } + + /// Validates if a value of type `T` is aligned and within the bounds of + /// the buffer + /// - Parameters: + /// - position: Current readable position + /// - type: Type of value to check + /// - Throws: FlatbuffersErrors + public mutating func inBuffer<T>(position: Int, of type: T.Type) throws { + try isAligned(position: position, type: type) + try rangeInBuffer(position: position, size: MemoryLayout<T>.size) + } + + /// Visits a table at the current position and validates if the table meets + /// the rules specified in the `VerifierOptions` + /// - Parameter position: Current position to be read + /// - Throws: FlatbuffersErrors + /// - Returns: A `TableVerifier` at the current readable table + public mutating func visitTable(at position: Int) throws -> TableVerifier { + let vtablePosition = try derefOffset(position: position) + let vtableLength: VOffset = try getValue(at: vtablePosition) + + let length = Int(vtableLength) + try isAligned( + position: Int(clamping: (vtablePosition + length).magnitude), + type: VOffset.self) + try rangeInBuffer(position: vtablePosition, size: length) + + _tableCount += 1 + + if _tableCount > _options._maxTableCount { + throw FlatbuffersErrors.maximumTables + } + + _depth += 1 + + if _depth > _options._maxDepth { + throw FlatbuffersErrors.maximumDepth + } + + return TableVerifier( + position: position, + vtable: vtablePosition, + vtableLength: length, + verifier: &self) + } + + /// Validates if a value of type `T` is within the buffer and returns it + /// - Parameter position: Current position to be read + /// - Throws: `inBuffer` errors + /// - Returns: a value of type `T` usually a `VTable` or a table offset + internal mutating func getValue<T>(at position: Int) throws -> T { + try inBuffer(position: position, of: T.self) + return _buffer.read(def: T.self, position: position) + } + + /// derefrences an offset within a vtable to get the position of the field + /// in the bytebuffer + /// - Parameter position: Current readable position + /// - Throws: `inBuffer` errors & `signedOffsetOutOfBounds` + /// - Returns: Current readable position for a field + @inline(__always) + internal mutating func derefOffset(position: Int) throws -> Int { + try inBuffer(position: position, of: Int32.self) + + let offset = _buffer.read(def: Int32.self, position: position) + // switching to int32 since swift's default Int is int64 + // this should be safe since we already checked if its within + // the buffer + let _int32Position = UInt32(position) + + let reportedOverflow: (partialValue: UInt32, overflow: Bool) + if offset > 0 { + reportedOverflow = _int32Position + .subtractingReportingOverflow(offset.magnitude) + } else { + reportedOverflow = _int32Position + .addingReportingOverflow(offset.magnitude) + } + + /// since `subtractingReportingOverflow` & `addingReportingOverflow` returns true, + /// if there is overflow we return failure + if reportedOverflow.overflow || reportedOverflow.partialValue > _buffer + .capacity + { + throw FlatbuffersErrors.signedOffsetOutOfBounds( + offset: Int(offset), + position: position) + } + + return Int(reportedOverflow.partialValue) + } + + /// finishes the current iteration of verification on an object + internal mutating func finish() { + _depth -= 1 + } + + mutating func verify(id: String) throws { + let size = MemoryLayout<Int32>.size + let str = _buffer.readString(at: size, count: size) + if id == str { + return + } + throw FlatbuffersErrors.bufferIdDidntMatchPassedId + } + +}